|
Packit |
116408 |
/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- */
|
|
Packit |
116408 |
/*
|
|
Packit |
116408 |
* Copyright (C) 2002 CodeFactory AB
|
|
Packit |
116408 |
* Copyright (C) 2002 Mikael Hallendal <micke@imendio.com>
|
|
Packit |
116408 |
* Copyright (C) 2008 Imendio AB
|
|
Packit |
116408 |
* Copyright (C) 2010 Lanedo GmbH
|
|
Packit |
116408 |
* Copyright (C) 2015-2018 Sébastien Wilmet <swilmet@gnome.org>
|
|
Packit |
116408 |
*
|
|
Packit |
116408 |
* This program is free software; you can redistribute it and/or
|
|
Packit |
116408 |
* modify it under the terms of the GNU General Public License as
|
|
Packit |
116408 |
* published by the Free Software Foundation; either version 2 of the
|
|
Packit |
116408 |
* License, or (at your option) any later version.
|
|
Packit |
116408 |
*
|
|
Packit |
116408 |
* This program is distributed in the hope that it will be useful,
|
|
Packit |
116408 |
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
Packit |
116408 |
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
Packit |
116408 |
* General Public License for more details.
|
|
Packit |
116408 |
*
|
|
Packit |
116408 |
* You should have received a copy of the GNU General Public License
|
|
Packit |
116408 |
* along with this program; if not, see <http://www.gnu.org/licenses/>.
|
|
Packit |
116408 |
*/
|
|
Packit |
116408 |
|
|
Packit |
116408 |
#include "dh-keyword-model.h"
|
|
Packit |
116408 |
#include <gtk/gtk.h>
|
|
Packit |
116408 |
#include "dh-book.h"
|
|
Packit |
116408 |
#include "dh-book-manager.h"
|
|
Packit |
116408 |
#include "dh-search-context.h"
|
|
Packit |
116408 |
#include "dh-util.h"
|
|
Packit |
116408 |
|
|
Packit |
116408 |
/**
|
|
Packit |
116408 |
* SECTION:dh-keyword-model
|
|
Packit |
116408 |
* @Title: DhKeywordModel
|
|
Packit |
116408 |
* @Short_description: A custom #GtkTreeModel implementation for searching
|
|
Packit |
116408 |
* #DhLink's
|
|
Packit |
116408 |
*
|
|
Packit |
116408 |
* #DhKeywordModel is a custom #GtkTreeModel implementation (as a list, not a
|
|
Packit |
116408 |
* tree) for searching #DhLink's.
|
|
Packit |
116408 |
*
|
|
Packit |
116408 |
* The dh_keyword_model_filter() function is used to set the search criteria. It
|
|
Packit |
116408 |
* fills the #GtkTreeModel with the list of #DhLink's that match the search
|
|
Packit |
116408 |
* criteria (up to a certain maximum number of matches).
|
|
Packit |
116408 |
*
|
|
Packit |
116408 |
* How the search works (for end users) is explained in the user documentation
|
|
Packit |
116408 |
* of the Devhelp application.
|
|
Packit |
116408 |
*
|
|
Packit |
116408 |
* # Filter by book and page
|
|
Packit |
116408 |
*
|
|
Packit |
116408 |
* As a kind of API for integrating Devhelp with other applications, the search
|
|
Packit |
116408 |
* string supports additional features. Those features are not intended to be
|
|
Packit |
116408 |
* used directly by end users when typing the search string in the GUI, because
|
|
Packit |
116408 |
* it's not really convenient. It is intended to be used with the
|
|
Packit |
116408 |
* `devhelp --search "search-string"` command line, so that another application
|
|
Packit |
116408 |
* can launch Devhelp and set a specific search string.
|
|
Packit |
116408 |
*
|
|
Packit |
116408 |
* It is possible to filter by book by prefixing the search string with
|
|
Packit |
116408 |
* “book:the-book-ID”. For example “book:gtk3”. If there are no other search
|
|
Packit |
116408 |
* terms, it shows the top-level page of that book. If there are other search
|
|
Packit |
116408 |
* terms, it limits the search to the specified book. See also the
|
|
Packit |
116408 |
* dh_book_get_id() function (in the `*.devhelp2` index file format it's called
|
|
Packit |
116408 |
* the book “name”, not ID, but ID is clearer).
|
|
Packit |
116408 |
*
|
|
Packit |
116408 |
* Similarly, it is possible to filter by page, by prefixing the search string
|
|
Packit |
116408 |
* with “page:the-page-ID”. For example “page:GtkWindow”. If there are no other
|
|
Packit |
116408 |
* search terms, the top of the page is shown and the search matches all the
|
|
Packit |
116408 |
* symbols part of that page. If there are other search terms, it limits the
|
|
Packit |
116408 |
* search to the specified page. To know what is the “page ID”, see the
|
|
Packit |
116408 |
* dh_link_belongs_to_page() function.
|
|
Packit |
116408 |
*
|
|
Packit |
116408 |
* “book:” and “page:” can be combined. Normal search terms must be
|
|
Packit |
116408 |
* <emphasis>after</emphasis> “book:” and “page:”.
|
|
Packit |
116408 |
*
|
|
Packit |
116408 |
* The book and page IDs – even if they contain an uppercase letter – don't
|
|
Packit |
116408 |
* affect the case sensitivity for the other search terms.
|
|
Packit |
116408 |
*/
|
|
Packit |
116408 |
|
|
Packit |
116408 |
typedef struct {
|
|
Packit |
116408 |
gchar *current_book_id;
|
|
Packit |
116408 |
|
|
Packit |
116408 |
/* List of owned DhLink*.
|
|
Packit |
116408 |
*
|
|
Packit |
116408 |
* Note: GQueue, not GQueue* so we are sure that it always exists, we
|
|
Packit |
116408 |
* don't need to check if priv->links == NULL.
|
|
Packit |
116408 |
*/
|
|
Packit |
116408 |
GQueue links;
|
|
Packit |
116408 |
|
|
Packit |
116408 |
gint stamp;
|
|
Packit |
116408 |
} DhKeywordModelPrivate;
|
|
Packit |
116408 |
|
|
Packit |
116408 |
typedef struct {
|
|
Packit |
116408 |
DhSearchContext *search_context;
|
|
Packit |
116408 |
const gchar *book_id;
|
|
Packit |
116408 |
const gchar *skip_book_id;
|
|
Packit |
116408 |
guint prefix : 1;
|
|
Packit |
116408 |
} SearchSettings;
|
|
Packit |
116408 |
|
|
Packit |
116408 |
#define MAX_HITS 1000
|
|
Packit |
116408 |
|
|
Packit |
116408 |
static void dh_keyword_model_tree_model_init (GtkTreeModelIface *iface);
|
|
Packit |
116408 |
|
|
Packit |
116408 |
G_DEFINE_TYPE_WITH_CODE (DhKeywordModel, dh_keyword_model, G_TYPE_OBJECT,
|
|
Packit |
116408 |
G_ADD_PRIVATE (DhKeywordModel)
|
|
Packit |
116408 |
G_IMPLEMENT_INTERFACE (GTK_TYPE_TREE_MODEL,
|
|
Packit |
116408 |
dh_keyword_model_tree_model_init));
|
|
Packit |
116408 |
|
|
Packit |
116408 |
static void
|
|
Packit |
116408 |
clear_links (DhKeywordModel *model)
|
|
Packit |
116408 |
{
|
|
Packit |
116408 |
DhKeywordModelPrivate *priv = dh_keyword_model_get_instance_private (model);
|
|
Packit |
116408 |
GList *l;
|
|
Packit |
116408 |
|
|
Packit |
116408 |
for (l = priv->links.head; l != NULL; l = l->next) {
|
|
Packit |
116408 |
DhLink *cur_link = l->data;
|
|
Packit |
116408 |
dh_link_unref (cur_link);
|
|
Packit |
116408 |
}
|
|
Packit |
116408 |
|
|
Packit |
116408 |
g_queue_clear (&priv->links);
|
|
Packit |
116408 |
}
|
|
Packit |
116408 |
|
|
Packit |
116408 |
static void
|
|
Packit |
116408 |
dh_keyword_model_finalize (GObject *object)
|
|
Packit |
116408 |
{
|
|
Packit |
116408 |
DhKeywordModel *model = DH_KEYWORD_MODEL (object);
|
|
Packit |
116408 |
DhKeywordModelPrivate *priv = dh_keyword_model_get_instance_private (model);
|
|
Packit |
116408 |
|
|
Packit |
116408 |
g_free (priv->current_book_id);
|
|
Packit |
116408 |
clear_links (model);
|
|
Packit |
116408 |
|
|
Packit |
116408 |
G_OBJECT_CLASS (dh_keyword_model_parent_class)->finalize (object);
|
|
Packit |
116408 |
}
|
|
Packit |
116408 |
|
|
Packit |
116408 |
static void
|
|
Packit |
116408 |
dh_keyword_model_class_init (DhKeywordModelClass *klass)
|
|
Packit |
116408 |
{
|
|
Packit |
116408 |
GObjectClass *object_class = G_OBJECT_CLASS (klass);
|
|
Packit |
116408 |
|
|
Packit |
116408 |
object_class->finalize = dh_keyword_model_finalize;
|
|
Packit |
116408 |
}
|
|
Packit |
116408 |
|
|
Packit |
116408 |
static void
|
|
Packit |
116408 |
dh_keyword_model_init (DhKeywordModel *model)
|
|
Packit |
116408 |
{
|
|
Packit |
116408 |
DhKeywordModelPrivate *priv = dh_keyword_model_get_instance_private (model);
|
|
Packit |
116408 |
|
|
Packit |
116408 |
priv->stamp = g_random_int_range (1, G_MAXINT32);
|
|
Packit |
116408 |
}
|
|
Packit |
116408 |
|
|
Packit |
116408 |
static GtkTreeModelFlags
|
|
Packit |
116408 |
dh_keyword_model_get_flags (GtkTreeModel *tree_model)
|
|
Packit |
116408 |
{
|
|
Packit |
116408 |
return GTK_TREE_MODEL_LIST_ONLY;
|
|
Packit |
116408 |
}
|
|
Packit |
116408 |
|
|
Packit |
116408 |
static gint
|
|
Packit |
116408 |
dh_keyword_model_get_n_columns (GtkTreeModel *tree_model)
|
|
Packit |
116408 |
{
|
|
Packit |
116408 |
return DH_KEYWORD_MODEL_NUM_COLS;
|
|
Packit |
116408 |
}
|
|
Packit |
116408 |
|
|
Packit |
116408 |
static GType
|
|
Packit |
116408 |
dh_keyword_model_get_column_type (GtkTreeModel *tree_model,
|
|
Packit |
116408 |
gint column)
|
|
Packit |
116408 |
{
|
|
Packit |
116408 |
switch (column) {
|
|
Packit |
116408 |
case DH_KEYWORD_MODEL_COL_NAME:
|
|
Packit |
116408 |
return G_TYPE_STRING;
|
|
Packit |
116408 |
|
|
Packit |
116408 |
case DH_KEYWORD_MODEL_COL_LINK:
|
|
Packit |
116408 |
return DH_TYPE_LINK;
|
|
Packit |
116408 |
|
|
Packit |
116408 |
case DH_KEYWORD_MODEL_COL_CURRENT_BOOK_FLAG:
|
|
Packit |
116408 |
return G_TYPE_BOOLEAN;
|
|
Packit |
116408 |
|
|
Packit |
116408 |
default:
|
|
Packit |
116408 |
return G_TYPE_INVALID;
|
|
Packit |
116408 |
}
|
|
Packit |
116408 |
}
|
|
Packit |
116408 |
|
|
Packit |
116408 |
static gboolean
|
|
Packit |
116408 |
dh_keyword_model_get_iter (GtkTreeModel *tree_model,
|
|
Packit |
116408 |
GtkTreeIter *iter,
|
|
Packit |
116408 |
GtkTreePath *path)
|
|
Packit |
116408 |
{
|
|
Packit |
116408 |
DhKeywordModelPrivate *priv;
|
|
Packit |
116408 |
const gint *indices;
|
|
Packit |
116408 |
GList *node;
|
|
Packit |
116408 |
|
|
Packit |
116408 |
priv = dh_keyword_model_get_instance_private (DH_KEYWORD_MODEL (tree_model));
|
|
Packit |
116408 |
|
|
Packit |
116408 |
if (gtk_tree_path_get_depth (path) > 1) {
|
|
Packit |
116408 |
return FALSE;
|
|
Packit |
116408 |
}
|
|
Packit |
116408 |
|
|
Packit |
116408 |
indices = gtk_tree_path_get_indices (path);
|
|
Packit |
116408 |
|
|
Packit |
116408 |
if (indices == NULL) {
|
|
Packit |
116408 |
return FALSE;
|
|
Packit |
116408 |
}
|
|
Packit |
116408 |
|
|
Packit |
116408 |
node = g_queue_peek_nth_link (&priv->links, indices[0]);
|
|
Packit |
116408 |
|
|
Packit |
116408 |
if (node != NULL) {
|
|
Packit |
116408 |
iter->stamp = priv->stamp;
|
|
Packit |
116408 |
iter->user_data = node;
|
|
Packit |
116408 |
return TRUE;
|
|
Packit |
116408 |
}
|
|
Packit |
116408 |
|
|
Packit |
116408 |
return FALSE;
|
|
Packit |
116408 |
}
|
|
Packit |
116408 |
|
|
Packit |
116408 |
static GtkTreePath *
|
|
Packit |
116408 |
dh_keyword_model_get_path (GtkTreeModel *tree_model,
|
|
Packit |
116408 |
GtkTreeIter *iter)
|
|
Packit |
116408 |
{
|
|
Packit |
116408 |
DhKeywordModelPrivate *priv;
|
|
Packit |
116408 |
GList *node;
|
|
Packit |
116408 |
GtkTreePath *path;
|
|
Packit |
116408 |
gint pos;
|
|
Packit |
116408 |
|
|
Packit |
116408 |
priv = dh_keyword_model_get_instance_private (DH_KEYWORD_MODEL (tree_model));
|
|
Packit |
116408 |
|
|
Packit |
116408 |
g_return_val_if_fail (iter->stamp == priv->stamp, NULL);
|
|
Packit |
116408 |
|
|
Packit |
116408 |
node = iter->user_data;
|
|
Packit |
116408 |
pos = g_queue_link_index (&priv->links, node);
|
|
Packit |
116408 |
|
|
Packit |
116408 |
if (pos < 0) {
|
|
Packit |
116408 |
return NULL;
|
|
Packit |
116408 |
}
|
|
Packit |
116408 |
|
|
Packit |
116408 |
path = gtk_tree_path_new ();
|
|
Packit |
116408 |
gtk_tree_path_append_index (path, pos);
|
|
Packit |
116408 |
|
|
Packit |
116408 |
return path;
|
|
Packit |
116408 |
}
|
|
Packit |
116408 |
|
|
Packit |
116408 |
static void
|
|
Packit |
116408 |
dh_keyword_model_get_value (GtkTreeModel *tree_model,
|
|
Packit |
116408 |
GtkTreeIter *iter,
|
|
Packit |
116408 |
gint column,
|
|
Packit |
116408 |
GValue *value)
|
|
Packit |
116408 |
{
|
|
Packit |
116408 |
DhKeywordModelPrivate *priv;
|
|
Packit |
116408 |
GList *node;
|
|
Packit |
116408 |
DhLink *link;
|
|
Packit |
116408 |
gboolean in_current_book;
|
|
Packit |
116408 |
|
|
Packit |
116408 |
priv = dh_keyword_model_get_instance_private (DH_KEYWORD_MODEL (tree_model));
|
|
Packit |
116408 |
|
|
Packit |
116408 |
g_return_if_fail (iter->stamp == priv->stamp);
|
|
Packit |
116408 |
|
|
Packit |
116408 |
node = iter->user_data;
|
|
Packit |
116408 |
link = node->data;
|
|
Packit |
116408 |
|
|
Packit |
116408 |
switch (column) {
|
|
Packit |
116408 |
case DH_KEYWORD_MODEL_COL_NAME:
|
|
Packit |
116408 |
g_value_init (value, G_TYPE_STRING);
|
|
Packit |
116408 |
g_value_set_string (value, dh_link_get_name (link));
|
|
Packit |
116408 |
break;
|
|
Packit |
116408 |
|
|
Packit |
116408 |
case DH_KEYWORD_MODEL_COL_LINK:
|
|
Packit |
116408 |
g_value_init (value, DH_TYPE_LINK);
|
|
Packit |
116408 |
g_value_set_boxed (value, link);
|
|
Packit |
116408 |
break;
|
|
Packit |
116408 |
|
|
Packit |
116408 |
case DH_KEYWORD_MODEL_COL_CURRENT_BOOK_FLAG:
|
|
Packit |
116408 |
in_current_book = g_strcmp0 (dh_link_get_book_id (link), priv->current_book_id) == 0;
|
|
Packit |
116408 |
g_value_init (value, G_TYPE_BOOLEAN);
|
|
Packit |
116408 |
g_value_set_boolean (value, in_current_book);
|
|
Packit |
116408 |
break;
|
|
Packit |
116408 |
|
|
Packit |
116408 |
default:
|
|
Packit |
116408 |
g_warning ("Bad column %d requested", column);
|
|
Packit |
116408 |
}
|
|
Packit |
116408 |
}
|
|
Packit |
116408 |
|
|
Packit |
116408 |
static gboolean
|
|
Packit |
116408 |
dh_keyword_model_iter_next (GtkTreeModel *tree_model,
|
|
Packit |
116408 |
GtkTreeIter *iter)
|
|
Packit |
116408 |
{
|
|
Packit |
116408 |
DhKeywordModelPrivate *priv;
|
|
Packit |
116408 |
GList *node;
|
|
Packit |
116408 |
|
|
Packit |
116408 |
priv = dh_keyword_model_get_instance_private (DH_KEYWORD_MODEL (tree_model));
|
|
Packit |
116408 |
|
|
Packit |
116408 |
g_return_val_if_fail (priv->stamp == iter->stamp, FALSE);
|
|
Packit |
116408 |
|
|
Packit |
116408 |
node = iter->user_data;
|
|
Packit |
116408 |
iter->user_data = node->next;
|
|
Packit |
116408 |
|
|
Packit |
116408 |
return iter->user_data != NULL;
|
|
Packit |
116408 |
}
|
|
Packit |
116408 |
|
|
Packit |
116408 |
static gboolean
|
|
Packit |
116408 |
dh_keyword_model_iter_children (GtkTreeModel *tree_model,
|
|
Packit |
116408 |
GtkTreeIter *iter,
|
|
Packit |
116408 |
GtkTreeIter *parent)
|
|
Packit |
116408 |
{
|
|
Packit |
116408 |
DhKeywordModelPrivate *priv;
|
|
Packit |
116408 |
|
|
Packit |
116408 |
priv = dh_keyword_model_get_instance_private (DH_KEYWORD_MODEL (tree_model));
|
|
Packit |
116408 |
|
|
Packit |
116408 |
/* This is a list, nodes have no children. */
|
|
Packit |
116408 |
if (parent != NULL) {
|
|
Packit |
116408 |
return FALSE;
|
|
Packit |
116408 |
}
|
|
Packit |
116408 |
|
|
Packit |
116408 |
/* But if parent == NULL we return the list itself as children of
|
|
Packit |
116408 |
* the "root".
|
|
Packit |
116408 |
*/
|
|
Packit |
116408 |
if (priv->links.head != NULL) {
|
|
Packit |
116408 |
iter->stamp = priv->stamp;
|
|
Packit |
116408 |
iter->user_data = priv->links.head;
|
|
Packit |
116408 |
return TRUE;
|
|
Packit |
116408 |
}
|
|
Packit |
116408 |
|
|
Packit |
116408 |
return FALSE;
|
|
Packit |
116408 |
}
|
|
Packit |
116408 |
|
|
Packit |
116408 |
static gboolean
|
|
Packit |
116408 |
dh_keyword_model_iter_has_child (GtkTreeModel *tree_model,
|
|
Packit |
116408 |
GtkTreeIter *iter)
|
|
Packit |
116408 |
{
|
|
Packit |
116408 |
return FALSE;
|
|
Packit |
116408 |
}
|
|
Packit |
116408 |
|
|
Packit |
116408 |
static gint
|
|
Packit |
116408 |
dh_keyword_model_iter_n_children (GtkTreeModel *tree_model,
|
|
Packit |
116408 |
GtkTreeIter *iter)
|
|
Packit |
116408 |
{
|
|
Packit |
116408 |
DhKeywordModelPrivate *priv;
|
|
Packit |
116408 |
|
|
Packit |
116408 |
priv = dh_keyword_model_get_instance_private (DH_KEYWORD_MODEL (tree_model));
|
|
Packit |
116408 |
|
|
Packit |
116408 |
if (iter == NULL) {
|
|
Packit |
116408 |
return priv->links.length;
|
|
Packit |
116408 |
}
|
|
Packit |
116408 |
|
|
Packit |
116408 |
g_return_val_if_fail (priv->stamp == iter->stamp, -1);
|
|
Packit |
116408 |
|
|
Packit |
116408 |
return 0;
|
|
Packit |
116408 |
}
|
|
Packit |
116408 |
|
|
Packit |
116408 |
static gboolean
|
|
Packit |
116408 |
dh_keyword_model_iter_nth_child (GtkTreeModel *tree_model,
|
|
Packit |
116408 |
GtkTreeIter *iter,
|
|
Packit |
116408 |
GtkTreeIter *parent,
|
|
Packit |
116408 |
gint n)
|
|
Packit |
116408 |
{
|
|
Packit |
116408 |
DhKeywordModelPrivate *priv;
|
|
Packit |
116408 |
GList *child;
|
|
Packit |
116408 |
|
|
Packit |
116408 |
priv = dh_keyword_model_get_instance_private (DH_KEYWORD_MODEL (tree_model));
|
|
Packit |
116408 |
|
|
Packit |
116408 |
if (parent != NULL) {
|
|
Packit |
116408 |
return FALSE;
|
|
Packit |
116408 |
}
|
|
Packit |
116408 |
|
|
Packit |
116408 |
child = g_queue_peek_nth_link (&priv->links, n);
|
|
Packit |
116408 |
|
|
Packit |
116408 |
if (child != NULL) {
|
|
Packit |
116408 |
iter->stamp = priv->stamp;
|
|
Packit |
116408 |
iter->user_data = child;
|
|
Packit |
116408 |
return TRUE;
|
|
Packit |
116408 |
}
|
|
Packit |
116408 |
|
|
Packit |
116408 |
return FALSE;
|
|
Packit |
116408 |
}
|
|
Packit |
116408 |
|
|
Packit |
116408 |
static gboolean
|
|
Packit |
116408 |
dh_keyword_model_iter_parent (GtkTreeModel *tree_model,
|
|
Packit |
116408 |
GtkTreeIter *iter,
|
|
Packit |
116408 |
GtkTreeIter *child)
|
|
Packit |
116408 |
{
|
|
Packit |
116408 |
return FALSE;
|
|
Packit |
116408 |
}
|
|
Packit |
116408 |
|
|
Packit |
116408 |
static void
|
|
Packit |
116408 |
dh_keyword_model_tree_model_init (GtkTreeModelIface *iface)
|
|
Packit |
116408 |
{
|
|
Packit |
116408 |
iface->get_flags = dh_keyword_model_get_flags;
|
|
Packit |
116408 |
iface->get_n_columns = dh_keyword_model_get_n_columns;
|
|
Packit |
116408 |
iface->get_column_type = dh_keyword_model_get_column_type;
|
|
Packit |
116408 |
iface->get_iter = dh_keyword_model_get_iter;
|
|
Packit |
116408 |
iface->get_path = dh_keyword_model_get_path;
|
|
Packit |
116408 |
iface->get_value = dh_keyword_model_get_value;
|
|
Packit |
116408 |
iface->iter_next = dh_keyword_model_iter_next;
|
|
Packit |
116408 |
iface->iter_children = dh_keyword_model_iter_children;
|
|
Packit |
116408 |
iface->iter_has_child = dh_keyword_model_iter_has_child;
|
|
Packit |
116408 |
iface->iter_n_children = dh_keyword_model_iter_n_children;
|
|
Packit |
116408 |
iface->iter_nth_child = dh_keyword_model_iter_nth_child;
|
|
Packit |
116408 |
iface->iter_parent = dh_keyword_model_iter_parent;
|
|
Packit |
116408 |
}
|
|
Packit |
116408 |
|
|
Packit |
116408 |
/**
|
|
Packit |
116408 |
* dh_keyword_model_new:
|
|
Packit |
116408 |
*
|
|
Packit |
116408 |
* Returns: a new #DhKeywordModel object.
|
|
Packit |
116408 |
*/
|
|
Packit |
116408 |
DhKeywordModel *
|
|
Packit |
116408 |
dh_keyword_model_new (void)
|
|
Packit |
116408 |
{
|
|
Packit |
116408 |
return g_object_new (DH_TYPE_KEYWORD_MODEL, NULL);
|
|
Packit |
116408 |
}
|
|
Packit |
116408 |
|
|
Packit |
116408 |
static GQueue *
|
|
Packit |
116408 |
search_single_book (DhBook *book,
|
|
Packit |
116408 |
SearchSettings *settings,
|
|
Packit |
116408 |
guint max_hits,
|
|
Packit |
116408 |
DhLink **exact_link)
|
|
Packit |
116408 |
{
|
|
Packit |
116408 |
GQueue *ret;
|
|
Packit |
116408 |
GList *l;
|
|
Packit |
116408 |
|
|
Packit |
116408 |
ret = g_queue_new ();
|
|
Packit |
116408 |
|
|
Packit |
116408 |
for (l = dh_book_get_links (book);
|
|
Packit |
116408 |
l != NULL && ret->length < max_hits;
|
|
Packit |
116408 |
l = l->next) {
|
|
Packit |
116408 |
DhLink *link = l->data;
|
|
Packit |
116408 |
|
|
Packit |
116408 |
if (!_dh_search_context_match_link (settings->search_context,
|
|
Packit |
116408 |
link,
|
|
Packit |
116408 |
settings->prefix)) {
|
|
Packit |
116408 |
continue;
|
|
Packit |
116408 |
}
|
|
Packit |
116408 |
|
|
Packit |
116408 |
g_queue_push_tail (ret, dh_link_ref (link));
|
|
Packit |
116408 |
|
|
Packit |
116408 |
if (exact_link == NULL || !settings->prefix)
|
|
Packit |
116408 |
continue;
|
|
Packit |
116408 |
|
|
Packit |
116408 |
/* Look for an exact link match. If the link is a PAGE, we can
|
|
Packit |
116408 |
* overwrite any previous exact link set. For example, when
|
|
Packit |
116408 |
* looking for GFile, we want the page, not the struct.
|
|
Packit |
116408 |
*/
|
|
Packit |
116408 |
if ((*exact_link == NULL || dh_link_get_link_type (link) == DH_LINK_TYPE_PAGE) &&
|
|
Packit |
116408 |
_dh_search_context_is_exact_link (settings->search_context, link)) {
|
|
Packit |
116408 |
*exact_link = link;
|
|
Packit |
116408 |
}
|
|
Packit |
116408 |
}
|
|
Packit |
116408 |
|
|
Packit |
116408 |
return ret;
|
|
Packit |
116408 |
}
|
|
Packit |
116408 |
|
|
Packit Service |
308146 |
static gint
|
|
Packit Service |
308146 |
compare_links (gconstpointer a,
|
|
Packit Service |
308146 |
gconstpointer b,
|
|
Packit Service |
308146 |
gpointer user_data)
|
|
Packit Service |
308146 |
{
|
|
Packit Service |
308146 |
return dh_link_compare (a, b);
|
|
Packit Service |
308146 |
}
|
|
Packit Service |
308146 |
|
|
Packit |
116408 |
static GQueue *
|
|
Packit |
116408 |
search_books (SearchSettings *settings,
|
|
Packit |
116408 |
guint max_hits,
|
|
Packit |
116408 |
DhLink **exact_link)
|
|
Packit |
116408 |
{
|
|
Packit |
116408 |
DhBookManager *book_manager;
|
|
Packit |
116408 |
GList *books;
|
|
Packit |
116408 |
GList *l;
|
|
Packit |
116408 |
GQueue *ret;
|
|
Packit |
116408 |
|
|
Packit |
116408 |
ret = g_queue_new ();
|
|
Packit |
116408 |
|
|
Packit |
116408 |
book_manager = dh_book_manager_get_singleton ();
|
|
Packit |
116408 |
books = dh_book_manager_get_books (book_manager);
|
|
Packit |
116408 |
|
|
Packit |
116408 |
for (l = books;
|
|
Packit |
116408 |
l != NULL && ret->length < max_hits;
|
|
Packit |
116408 |
l = l->next) {
|
|
Packit |
116408 |
DhBook *book = DH_BOOK (l->data);
|
|
Packit |
116408 |
GQueue *book_result;
|
|
Packit |
116408 |
|
|
Packit |
116408 |
if (!_dh_search_context_match_book (settings->search_context, book))
|
|
Packit |
116408 |
continue;
|
|
Packit |
116408 |
|
|
Packit |
116408 |
/* Filtering by book? */
|
|
Packit |
116408 |
if (settings->book_id != NULL &&
|
|
Packit |
116408 |
g_strcmp0 (settings->book_id, dh_book_get_id (book)) != 0) {
|
|
Packit |
116408 |
continue;
|
|
Packit |
116408 |
}
|
|
Packit |
116408 |
|
|
Packit |
116408 |
/* Skipping a given book? */
|
|
Packit |
116408 |
if (settings->skip_book_id != NULL &&
|
|
Packit |
116408 |
g_strcmp0 (settings->skip_book_id, dh_book_get_id (book)) == 0) {
|
|
Packit |
116408 |
continue;
|
|
Packit |
116408 |
}
|
|
Packit |
116408 |
|
|
Packit |
116408 |
book_result = search_single_book (book,
|
|
Packit |
116408 |
settings,
|
|
Packit |
116408 |
max_hits - ret->length,
|
|
Packit |
116408 |
exact_link);
|
|
Packit |
116408 |
|
|
Packit |
116408 |
dh_util_queue_concat (ret, book_result);
|
|
Packit |
116408 |
}
|
|
Packit |
116408 |
|
|
Packit Service |
308146 |
g_queue_sort (ret, (GCompareDataFunc) compare_links, NULL);
|
|
Packit |
116408 |
return ret;
|
|
Packit |
116408 |
}
|
|
Packit |
116408 |
|
|
Packit |
116408 |
static GQueue *
|
|
Packit |
116408 |
handle_book_id_only (DhSearchContext *search_context,
|
|
Packit |
116408 |
DhLink **exact_link)
|
|
Packit |
116408 |
{
|
|
Packit |
116408 |
DhBookManager *book_manager;
|
|
Packit |
116408 |
GList *books;
|
|
Packit |
116408 |
GList *l;
|
|
Packit |
116408 |
GQueue *ret;
|
|
Packit |
116408 |
|
|
Packit |
116408 |
if (_dh_search_context_get_book_id (search_context) == NULL ||
|
|
Packit |
116408 |
_dh_search_context_get_page_id (search_context) != NULL ||
|
|
Packit |
116408 |
_dh_search_context_get_keywords (search_context) != NULL) {
|
|
Packit |
116408 |
return NULL;
|
|
Packit |
116408 |
}
|
|
Packit |
116408 |
|
|
Packit |
116408 |
ret = g_queue_new ();
|
|
Packit |
116408 |
|
|
Packit |
116408 |
book_manager = dh_book_manager_get_singleton ();
|
|
Packit |
116408 |
books = dh_book_manager_get_books (book_manager);
|
|
Packit |
116408 |
|
|
Packit |
116408 |
for (l = books; l != NULL; l = l->next) {
|
|
Packit |
116408 |
DhBook *book = DH_BOOK (l->data);
|
|
Packit |
116408 |
GNode *node;
|
|
Packit |
116408 |
|
|
Packit |
116408 |
if (!_dh_search_context_match_book (search_context, book))
|
|
Packit |
116408 |
continue;
|
|
Packit |
116408 |
|
|
Packit |
116408 |
/* Return only the top-level book page. */
|
|
Packit |
116408 |
node = dh_book_get_tree (book);
|
|
Packit |
116408 |
if (node != NULL) {
|
|
Packit |
116408 |
DhLink *link;
|
|
Packit |
116408 |
|
|
Packit |
116408 |
link = node->data;
|
|
Packit |
116408 |
g_queue_push_tail (ret, dh_link_ref (link));
|
|
Packit |
116408 |
|
|
Packit |
116408 |
if (exact_link != NULL)
|
|
Packit |
116408 |
*exact_link = link;
|
|
Packit |
116408 |
}
|
|
Packit |
116408 |
|
|
Packit |
116408 |
break;
|
|
Packit |
116408 |
}
|
|
Packit |
116408 |
|
|
Packit |
116408 |
return ret;
|
|
Packit |
116408 |
}
|
|
Packit |
116408 |
|
|
Packit |
116408 |
/* The Search rationale is as follows:
|
|
Packit |
116408 |
*
|
|
Packit |
116408 |
* - If 'book_id' is given, but no 'page_id' or 'keywords', the main page of
|
|
Packit |
116408 |
* the book will only be shown, giving as exact match this book link.
|
|
Packit |
116408 |
* - If 'book_id' and 'page_id' are given, but no 'keywords', all the items
|
|
Packit |
116408 |
* in the given page of the given book will be shown.
|
|
Packit |
116408 |
* - If 'book_id' and 'keywords' are given, but no 'page_id', up to MAX_HITS
|
|
Packit |
116408 |
* items matching the keywords in the given book will be shown.
|
|
Packit |
116408 |
* - If 'book_id' and 'page_id' and 'keywords' are given, all the items
|
|
Packit |
116408 |
* matching the keywords in the given page of the given book will be shown.
|
|
Packit |
116408 |
*
|
|
Packit |
116408 |
* - If 'page_id' is given, but no 'book_id' or 'keywords', all the items
|
|
Packit |
116408 |
* in the given page will be shown, giving as exact match the page link.
|
|
Packit |
116408 |
* - If 'page_id' and 'keywords' are given but no 'book_id', all the items
|
|
Packit |
116408 |
* matching the keywords in the given page will be shown.
|
|
Packit |
116408 |
*
|
|
Packit |
116408 |
* - If 'keywords' only are given, up to max_hits items matching the keywords
|
|
Packit |
116408 |
* will be shown. If keyword matches both a page link and a non-page one,
|
|
Packit |
116408 |
* the page link is the one given as exact match.
|
|
Packit |
116408 |
*/
|
|
Packit |
116408 |
static GQueue *
|
|
Packit |
116408 |
keyword_model_search (DhKeywordModel *model,
|
|
Packit |
116408 |
DhSearchContext *search_context,
|
|
Packit |
116408 |
DhLink **exact_link)
|
|
Packit |
116408 |
{
|
|
Packit |
116408 |
DhKeywordModelPrivate *priv = dh_keyword_model_get_instance_private (model);
|
|
Packit |
116408 |
SearchSettings settings;
|
|
Packit |
116408 |
guint max_hits = MAX_HITS;
|
|
Packit |
116408 |
GQueue *in_book = NULL;
|
|
Packit |
116408 |
GQueue *other_books = NULL;
|
|
Packit |
116408 |
DhLink *in_book_exact_link = NULL;
|
|
Packit |
116408 |
DhLink *other_books_exact_link = NULL;
|
|
Packit |
116408 |
GQueue *out;
|
|
Packit |
116408 |
|
|
Packit |
116408 |
out = handle_book_id_only (search_context, exact_link);
|
|
Packit |
116408 |
if (out != NULL)
|
|
Packit |
116408 |
return out;
|
|
Packit |
116408 |
|
|
Packit |
116408 |
out = g_queue_new ();
|
|
Packit |
116408 |
|
|
Packit |
116408 |
settings.search_context = search_context;
|
|
Packit |
116408 |
settings.book_id = priv->current_book_id;
|
|
Packit |
116408 |
settings.skip_book_id = NULL;
|
|
Packit |
116408 |
settings.prefix = TRUE;
|
|
Packit |
116408 |
|
|
Packit |
116408 |
if (_dh_search_context_get_page_id (search_context) != NULL) {
|
|
Packit |
116408 |
/* If filtering per page, increase the maximum number of
|
|
Packit |
116408 |
* hits. This is due to the fact that a page may have
|
|
Packit |
116408 |
* more than MAX_HITS keywords, and the page link may be
|
|
Packit |
116408 |
* the last one in the list, but we always want to get it.
|
|
Packit |
116408 |
*/
|
|
Packit |
116408 |
max_hits = G_MAXUINT;
|
|
Packit |
116408 |
}
|
|
Packit |
116408 |
|
|
Packit |
116408 |
/* First look for prefixed items in the given book id. */
|
|
Packit |
116408 |
if (priv->current_book_id != NULL) {
|
|
Packit |
116408 |
in_book = search_books (&settings,
|
|
Packit |
116408 |
max_hits,
|
|
Packit |
116408 |
&in_book_exact_link);
|
|
Packit |
116408 |
}
|
|
Packit |
116408 |
|
|
Packit |
116408 |
/* Next, always check other books as well, as the exact match may be in
|
|
Packit |
116408 |
* there.
|
|
Packit |
116408 |
*/
|
|
Packit |
116408 |
settings.book_id = NULL;
|
|
Packit |
116408 |
settings.skip_book_id = priv->current_book_id;
|
|
Packit |
116408 |
other_books = search_books (&settings,
|
|
Packit |
116408 |
max_hits,
|
|
Packit |
116408 |
&other_books_exact_link);
|
|
Packit |
116408 |
|
|
Packit |
116408 |
/* Now that we got prefix searches in current and other books, decide
|
|
Packit |
116408 |
* which the preferred exact link is. If the exact match is in other
|
|
Packit |
116408 |
* books, prefer those to the current book.
|
|
Packit |
116408 |
*/
|
|
Packit |
116408 |
if (in_book_exact_link != NULL) {
|
|
Packit |
116408 |
*exact_link = in_book_exact_link;
|
|
Packit |
116408 |
dh_util_queue_concat (out, in_book);
|
|
Packit |
116408 |
dh_util_queue_concat (out, other_books);
|
|
Packit |
116408 |
} else if (other_books_exact_link != NULL) {
|
|
Packit |
116408 |
*exact_link = other_books_exact_link;
|
|
Packit |
116408 |
dh_util_queue_concat (out, other_books);
|
|
Packit |
116408 |
dh_util_queue_concat (out, in_book);
|
|
Packit |
116408 |
} else {
|
|
Packit |
116408 |
*exact_link = NULL;
|
|
Packit |
116408 |
dh_util_queue_concat (out, in_book);
|
|
Packit |
116408 |
dh_util_queue_concat (out, other_books);
|
|
Packit |
116408 |
}
|
|
Packit |
116408 |
|
|
Packit |
116408 |
if (out->length >= max_hits)
|
|
Packit |
116408 |
return out;
|
|
Packit |
116408 |
|
|
Packit |
116408 |
/* Look for non-prefixed matches in current book. */
|
|
Packit |
116408 |
settings.prefix = FALSE;
|
|
Packit |
116408 |
|
|
Packit |
116408 |
if (priv->current_book_id != NULL) {
|
|
Packit |
116408 |
settings.book_id = priv->current_book_id;
|
|
Packit |
116408 |
settings.skip_book_id = NULL;
|
|
Packit |
116408 |
|
|
Packit |
116408 |
in_book = search_books (&settings,
|
|
Packit |
116408 |
max_hits - out->length,
|
|
Packit |
116408 |
NULL);
|
|
Packit |
116408 |
|
|
Packit |
116408 |
dh_util_queue_concat (out, in_book);
|
|
Packit |
116408 |
if (out->length >= max_hits)
|
|
Packit |
116408 |
return out;
|
|
Packit |
116408 |
}
|
|
Packit |
116408 |
|
|
Packit |
116408 |
/* If still room for more items, look for non-prefixed items in other
|
|
Packit |
116408 |
* books.
|
|
Packit |
116408 |
*/
|
|
Packit |
116408 |
settings.book_id = NULL;
|
|
Packit |
116408 |
settings.skip_book_id = priv->current_book_id;
|
|
Packit |
116408 |
other_books = search_books (&settings,
|
|
Packit |
116408 |
max_hits - out->length,
|
|
Packit |
116408 |
NULL);
|
|
Packit |
116408 |
dh_util_queue_concat (out, other_books);
|
|
Packit |
116408 |
|
|
Packit |
116408 |
return out;
|
|
Packit |
116408 |
}
|
|
Packit |
116408 |
|
|
Packit |
116408 |
/**
|
|
Packit |
116408 |
* dh_keyword_model_filter:
|
|
Packit |
116408 |
* @model: a #DhKeywordModel.
|
|
Packit |
116408 |
* @search_string: a search query.
|
|
Packit |
116408 |
* @current_book_id: (nullable): the ID of the book currently shown, or %NULL.
|
|
Packit |
116408 |
* @language: (nullable): deprecated, must be %NULL.
|
|
Packit |
116408 |
*
|
|
Packit |
116408 |
* Searches in the #DhBookManager the list of #DhLink's that correspond to
|
|
Packit |
116408 |
* @search_string, and fills the @model with that list (erasing the previous
|
|
Packit |
116408 |
* content).
|
|
Packit |
116408 |
*
|
|
Packit |
116408 |
* Attention, when calling this function the @model needs to be disconnected
|
|
Packit |
116408 |
* from the #GtkTreeView, because the #GtkTreeModel signals are not emitted, to
|
|
Packit |
116408 |
* improve the performances (sending a lot of signals is slow) and have a
|
|
Packit |
116408 |
* simpler implementation. The previous row selection is anyway no longer
|
|
Packit |
116408 |
* relevant.
|
|
Packit |
116408 |
*
|
|
Packit |
116408 |
* Note that there is a maximum number of matches (configured internally). When
|
|
Packit |
116408 |
* the maximum is reached the search is stopped, to avoid blocking the GUI
|
|
Packit |
116408 |
* (since this function runs synchronously) if the @search_string contains for
|
|
Packit |
116408 |
* example only one character. (And it is anyway not very useful to show to the
|
|
Packit |
116408 |
* user tens of thousands search results).
|
|
Packit |
116408 |
*
|
|
Packit |
116408 |
* Returns: (nullable) (transfer none): the #DhLink that matches exactly
|
|
Packit |
116408 |
* @search_string, or %NULL if no such #DhLink was found within the maximum
|
|
Packit |
116408 |
* number of matches.
|
|
Packit |
116408 |
*/
|
|
Packit |
116408 |
DhLink *
|
|
Packit |
116408 |
dh_keyword_model_filter (DhKeywordModel *model,
|
|
Packit |
116408 |
const gchar *search_string,
|
|
Packit |
116408 |
const gchar *current_book_id,
|
|
Packit |
116408 |
const gchar *language)
|
|
Packit |
116408 |
{
|
|
Packit |
116408 |
DhKeywordModelPrivate *priv;
|
|
Packit |
116408 |
DhSearchContext *search_context;
|
|
Packit |
116408 |
GQueue *new_links = NULL;
|
|
Packit |
116408 |
DhLink *exact_link = NULL;
|
|
Packit |
116408 |
|
|
Packit |
116408 |
g_return_val_if_fail (DH_IS_KEYWORD_MODEL (model), NULL);
|
|
Packit |
116408 |
g_return_val_if_fail (search_string != NULL, NULL);
|
|
Packit |
116408 |
g_return_val_if_fail (language == NULL, NULL);
|
|
Packit |
116408 |
|
|
Packit |
116408 |
priv = dh_keyword_model_get_instance_private (model);
|
|
Packit |
116408 |
|
|
Packit |
116408 |
g_free (priv->current_book_id);
|
|
Packit |
116408 |
priv->current_book_id = NULL;
|
|
Packit |
116408 |
|
|
Packit |
116408 |
search_context = _dh_search_context_new (search_string);
|
|
Packit |
116408 |
|
|
Packit |
116408 |
if (search_context != NULL) {
|
|
Packit |
116408 |
const gchar *book_id_in_search_string;
|
|
Packit |
116408 |
|
|
Packit |
116408 |
book_id_in_search_string = _dh_search_context_get_book_id (search_context);
|
|
Packit |
116408 |
|
|
Packit |
116408 |
if (book_id_in_search_string != NULL)
|
|
Packit |
116408 |
priv->current_book_id = g_strdup (book_id_in_search_string);
|
|
Packit |
116408 |
else
|
|
Packit |
116408 |
priv->current_book_id = g_strdup (current_book_id);
|
|
Packit |
116408 |
|
|
Packit |
116408 |
new_links = keyword_model_search (model, search_context, &exact_link);
|
|
Packit |
116408 |
}
|
|
Packit |
116408 |
|
|
Packit |
116408 |
clear_links (model);
|
|
Packit |
116408 |
dh_util_queue_concat (&priv->links, new_links);
|
|
Packit |
116408 |
new_links = NULL;
|
|
Packit |
116408 |
|
|
Packit |
116408 |
/* The content has been modified, change the stamp so that older
|
|
Packit |
116408 |
* GtkTreeIter's become invalid.
|
|
Packit |
116408 |
*/
|
|
Packit |
116408 |
priv->stamp++;
|
|
Packit |
116408 |
|
|
Packit |
116408 |
_dh_search_context_free (search_context);
|
|
Packit |
116408 |
|
|
Packit |
116408 |
/* One hit */
|
|
Packit |
116408 |
if (priv->links.length == 1)
|
|
Packit |
116408 |
return g_queue_peek_head (&priv->links);
|
|
Packit |
116408 |
|
|
Packit |
116408 |
return exact_link;
|
|
Packit |
116408 |
}
|