Blame src/dh-keyword-model.c

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
}