diff --git a/src/dh-book-tree.c b/src/dh-book-tree.c index 7d8f239..e97fe28 100644 --- a/src/dh-book-tree.c +++ b/src/dh-book-tree.c @@ -177,7 +177,7 @@ book_tree_find_language_group (DhBookTree *tree, g_return_if_reached (); } - if (exact_iter != NULL && exact_found && + if (exact_iter != NULL && g_ascii_strcasecmp (title, language) == 0) { /* Exact match found! */ *exact_iter = loop_iter; @@ -187,7 +187,7 @@ book_tree_find_language_group (DhBookTree *tree, g_free (title); return; } - } else if (next_iter != NULL && next_found && + } else if (next_iter != NULL && g_ascii_strcasecmp (title, language) > 0) { *next_iter = loop_iter; *next_found = TRUE; @@ -252,7 +252,7 @@ book_tree_find_book (DhBookTree *tree, /* We can compare pointers directly as we're playing with references * of the same object */ - if (exact_iter != NULL && exact_found && + if (exact_iter != NULL && in_tree_book == book) { *exact_iter = loop_iter; *exact_found = TRUE; diff --git a/src/dh-book-tree.c.covscan b/src/dh-book-tree.c.covscan deleted file mode 100644 index e97fe28..0000000 --- a/src/dh-book-tree.c.covscan +++ /dev/null @@ -1,957 +0,0 @@ -/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- */ -/* - * Copyright (C) 2001-2003 Mikael Hallendal - * Copyright (C) 2003 CodeFactory AB - * Copyright (C) 2008 Imendio AB - * Copyright (C) 2010 Lanedo GmbH - * Copyright (C) 2015, 2017 Sébastien Wilmet - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation; either version 2 of the - * License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, see . - */ - -#include "config.h" -#include "dh-book-tree.h" -#include -#include "dh-book-manager.h" -#include "dh-book.h" - -/** - * SECTION:dh-book-tree - * @Title: DhBookTree - * @Short_description: A #GtkTreeView containing the tree structure of all - * enabled #DhBook's - * - * #DhBookTree is a #GtkTreeView (showing a tree, not a list) containing the - * general tree structure of all enabled #DhBook's. - * - * The dh_book_get_tree() function is called to get the tree structure of a - * #DhBook. As such the tree contains only #DhLink's of type %DH_LINK_TYPE_BOOK - * or %DH_LINK_TYPE_PAGE. - * - * When an element is selected, the #DhBookTree::link-selected signal is - * emitted. Only one element can be selected at a time. - */ - -typedef struct { - const gchar *uri; - GtkTreeIter iter; - GtkTreePath *path; - guint found : 1; -} FindURIData; - -typedef struct { - GtkTreeStore *store; - DhLink *selected_link; - GtkMenu *context_menu; -} DhBookTreePrivate; - -enum { - LINK_SELECTED, - N_SIGNALS -}; - -enum { - COL_TITLE, - COL_LINK, - COL_BOOK, - COL_WEIGHT, - COL_UNDERLINE, - N_COLUMNS -}; - -G_DEFINE_TYPE_WITH_PRIVATE (DhBookTree, dh_book_tree, GTK_TYPE_TREE_VIEW); - -static guint signals[N_SIGNALS] = { 0 }; - -static void -book_tree_selection_changed_cb (GtkTreeSelection *selection, - DhBookTree *tree) -{ - DhBookTreePrivate *priv = dh_book_tree_get_instance_private (tree); - GtkTreeIter iter; - - if (gtk_tree_selection_get_selected (selection, NULL, &iter)) { - DhLink *link; - - gtk_tree_model_get (GTK_TREE_MODEL (priv->store), - &iter, - COL_LINK, &link, - -1); - - if (link != NULL && - link != priv->selected_link) { - g_clear_pointer (&priv->selected_link, (GDestroyNotify)dh_link_unref); - priv->selected_link = dh_link_ref (link); - g_signal_emit (tree, signals[LINK_SELECTED], 0, link); - } - - if (link != NULL) - dh_link_unref (link); - } -} - -static void -book_tree_setup_selection (DhBookTree *tree) -{ - GtkTreeSelection *selection; - - selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (tree)); - - gtk_tree_selection_set_mode (selection, GTK_SELECTION_BROWSE); - - g_signal_connect_object (selection, - "changed", - G_CALLBACK (book_tree_selection_changed_cb), - tree, - 0); -} - -/* Tries to find: - * - An exact match of the language group - * - Or the language group which should be just after our given language group. - * - Or both. - * - * FIXME: not great code. Maybe add a new column in the GtkTreeModel storing a - * DhLanguage object. Instead of @language as a string, it would be a - * DhLanguage. - */ -static void -book_tree_find_language_group (DhBookTree *tree, - const gchar *language, - GtkTreeIter *exact_iter, - gboolean *exact_found, - GtkTreeIter *next_iter, - gboolean *next_found) -{ - DhBookTreePrivate *priv = dh_book_tree_get_instance_private (tree); - DhBookManager *book_manager; - GtkTreeIter loop_iter; - - g_assert ((exact_iter != NULL && exact_found != NULL) || - (next_iter != NULL && next_found != NULL)); - - /* Reset all flags to not found */ - if (exact_found != NULL) - *exact_found = FALSE; - if (next_found != NULL) - *next_found = FALSE; - - /* If we're not doing language grouping, return not found */ - book_manager = dh_book_manager_get_singleton (); - if (!dh_book_manager_get_group_by_language (book_manager)) - return; - - if (!gtk_tree_model_get_iter_first (GTK_TREE_MODEL (priv->store), - &loop_iter)) { - /* Store is empty, not found */ - return; - } - - do { - gchar *title = NULL; - DhLink *link; - - /* Look for language titles, which are those where there - * is no book object associated in the row */ - gtk_tree_model_get (GTK_TREE_MODEL (priv->store), - &loop_iter, - COL_TITLE, &title, - COL_LINK, &link, - -1); - - if (link != NULL) { - /* Not a language */ - g_free (title); - dh_link_unref (link); - g_return_if_reached (); - } - - if (exact_iter != NULL && - g_ascii_strcasecmp (title, language) == 0) { - /* Exact match found! */ - *exact_iter = loop_iter; - *exact_found = TRUE; - if (next_iter == NULL) { - /* If we were not requested to look for the next one, end here */ - g_free (title); - return; - } - } else if (next_iter != NULL && - g_ascii_strcasecmp (title, language) > 0) { - *next_iter = loop_iter; - *next_found = TRUE; - /* There's no way to have an exact match after the next, so end here */ - g_free (title); - return; - } - - g_free (title); - } while (gtk_tree_model_iter_next (GTK_TREE_MODEL (priv->store), - &loop_iter)); -} - -/* Tries to find, starting at 'first' (if given), and always in the same - * level of the tree: - * - An exact match of the book - * - Or the book which should be just after our given book - * - Or both. - */ -static void -book_tree_find_book (DhBookTree *tree, - DhBook *book, - const GtkTreeIter *first, - GtkTreeIter *exact_iter, - gboolean *exact_found, - GtkTreeIter *next_iter, - gboolean *next_found) -{ - DhBookTreePrivate *priv = dh_book_tree_get_instance_private (tree); - GtkTreeIter loop_iter; - - g_assert ((exact_iter != NULL && exact_found != NULL) || - (next_iter != NULL && next_found != NULL)); - - /* Reset all flags to not found */ - if (exact_found != NULL) - *exact_found = FALSE; - if (next_found != NULL) - *next_found = FALSE; - - /* Setup iteration start */ - if (first == NULL) { - /* If no first given, start iterating from the start of the model */ - if (!gtk_tree_model_get_iter_first (GTK_TREE_MODEL (priv->store), - &loop_iter)) { - /* Store is empty, not found */ - return; - } - } else { - loop_iter = *first; - } - - do { - DhBook *in_tree_book = NULL; - - gtk_tree_model_get (GTK_TREE_MODEL (priv->store), - &loop_iter, - COL_BOOK, &in_tree_book, - -1); - - g_return_if_fail (DH_IS_BOOK (in_tree_book)); - - /* We can compare pointers directly as we're playing with references - * of the same object */ - if (exact_iter != NULL && - in_tree_book == book) { - *exact_iter = loop_iter; - *exact_found = TRUE; - if (next_iter == NULL) { - /* If we were not requested to look for the next one, end here */ - g_object_unref (in_tree_book); - return; - } - } else if (next_iter != NULL && - dh_book_cmp_by_title (in_tree_book, book) > 0) { - *next_iter = loop_iter; - *next_found = TRUE; - g_object_unref (in_tree_book); - return; - } - - g_object_unref (in_tree_book); - } while (gtk_tree_model_iter_next (GTK_TREE_MODEL (priv->store), - &loop_iter)); -} - -static void -book_tree_insert_node (DhBookTree *tree, - GNode *node, - GtkTreeIter *current_iter, - DhBook *book) - -{ - DhBookTreePrivate *priv = dh_book_tree_get_instance_private (tree); - DhLink *link; - PangoWeight weight; - GNode *child; - - link = node->data; - g_assert (link != NULL); - - if (dh_link_get_link_type (link) == DH_LINK_TYPE_BOOK) { - weight = PANGO_WEIGHT_BOLD; - } else { - weight = PANGO_WEIGHT_NORMAL; - } - - gtk_tree_store_set (priv->store, - current_iter, - COL_TITLE, dh_link_get_name (link), - COL_LINK, link, - COL_BOOK, book, - COL_WEIGHT, weight, - COL_UNDERLINE, PANGO_UNDERLINE_NONE, - -1); - - for (child = g_node_first_child (node); - child != NULL; - child = g_node_next_sibling (child)) { - GtkTreeIter iter; - - /* Append new iter */ - gtk_tree_store_append (priv->store, &iter, current_iter); - book_tree_insert_node (tree, child, &iter, NULL); - } -} - -static void -book_tree_add_book_to_store (DhBookTree *tree, - DhBook *book) -{ - DhBookTreePrivate *priv = dh_book_tree_get_instance_private (tree); - DhBookManager *book_manager; - GtkTreeIter book_iter; - - /* If grouping by language we need to add the language categories */ - book_manager = dh_book_manager_get_singleton (); - if (dh_book_manager_get_group_by_language (book_manager)) { - GtkTreeIter language_iter; - gboolean language_iter_found; - GtkTreeIter next_language_iter; - gboolean next_language_iter_found; - const gchar *language_title; - gboolean new_language = FALSE; - - language_title = dh_book_get_language (book); - - /* Look for the proper language group */ - book_tree_find_language_group (tree, - language_title, - &language_iter, - &language_iter_found, - &next_language_iter, - &next_language_iter_found); - /* New language group needs to be created? */ - if (!language_iter_found) { - if (!next_language_iter_found) { - gtk_tree_store_append (priv->store, - &language_iter, - NULL); - } else { - gtk_tree_store_insert_before (priv->store, - &language_iter, - NULL, - &next_language_iter); - } - - gtk_tree_store_set (priv->store, - &language_iter, - COL_TITLE, language_title, - COL_LINK, NULL, - COL_BOOK, NULL, - COL_WEIGHT, PANGO_WEIGHT_BOLD, - COL_UNDERLINE, PANGO_UNDERLINE_SINGLE, - -1); - - new_language = TRUE; - } - - /* If we got to add first book in a given language group, just append it. */ - if (new_language) { - GtkTreePath *path; - - gtk_tree_store_append (priv->store, - &book_iter, - &language_iter); - - /* Make sure we start with the language row expanded */ - path = gtk_tree_model_get_path (GTK_TREE_MODEL (priv->store), - &language_iter); - gtk_tree_view_expand_row (GTK_TREE_VIEW (tree), - path, - FALSE); - gtk_tree_path_free (path); - } else { - GtkTreeIter first_book_iter; - GtkTreeIter next_book_iter; - gboolean next_book_iter_found; - - /* The language will have at least one book, so we move iter to it */ - gtk_tree_model_iter_children (GTK_TREE_MODEL (priv->store), - &first_book_iter, - &language_iter); - - /* Find next possible book in language group */ - book_tree_find_book (tree, - book, - &first_book_iter, - NULL, - NULL, - &next_book_iter, - &next_book_iter_found); - - if (!next_book_iter_found) { - gtk_tree_store_append (priv->store, - &book_iter, - &language_iter); - } else { - gtk_tree_store_insert_before (priv->store, - &book_iter, - &language_iter, - &next_book_iter); - } - } - } else { - /* No language grouping, just order by book title */ - GtkTreeIter next_book_iter; - gboolean next_book_iter_found; - - book_tree_find_book (tree, - book, - NULL, - NULL, - NULL, - &next_book_iter, - &next_book_iter_found); - - if (!next_book_iter_found) { - gtk_tree_store_append (priv->store, - &book_iter, - NULL); - } else { - gtk_tree_store_insert_before (priv->store, - &book_iter, - NULL, - &next_book_iter); - } - } - - /* Now book_iter contains the proper iterator where we'll add the whole - * book tree. */ - book_tree_insert_node (tree, - dh_book_get_tree (book), - &book_iter, - book); -} - -static void -book_tree_book_created_or_enabled_cb (DhBookManager *book_manager, - DhBook *book, - DhBookTree *tree) -{ - if (!dh_book_get_enabled (book)) - return; - - book_tree_add_book_to_store (tree, book); -} - -static void -book_tree_book_deleted_or_disabled_cb (DhBookManager *book_manager, - DhBook *book, - DhBookTree *tree) -{ - DhBookTreePrivate *priv = dh_book_tree_get_instance_private (tree); - GtkTreeIter exact_iter; - gboolean exact_iter_found = FALSE; - GtkTreeIter language_iter; - gboolean language_iter_found = FALSE; - - if (dh_book_manager_get_group_by_language (book_manager)) { - GtkTreeIter first_book_iter; - - book_tree_find_language_group (tree, - dh_book_get_language (book), - &language_iter, - &language_iter_found, - NULL, - NULL); - - if (language_iter_found && - gtk_tree_model_iter_children (GTK_TREE_MODEL (priv->store), - &first_book_iter, - &language_iter)) { - book_tree_find_book (tree, - book, - &first_book_iter, - &exact_iter, - &exact_iter_found, - NULL, - NULL); - } - } else { - book_tree_find_book (tree, - book, - NULL, - &exact_iter, - &exact_iter_found, - NULL, - NULL); - } - - if (exact_iter_found) { - /* Remove the book from the tree */ - gtk_tree_store_remove (priv->store, &exact_iter); - /* If this book was inside a language group, check if the group - * is now empty and so removable */ - if (language_iter_found) { - GtkTreeIter first_book_iter; - - if (!gtk_tree_model_iter_children (GTK_TREE_MODEL (priv->store), - &first_book_iter, - &language_iter)) { - /* Oh, well, no more books in this language... remove! */ - gtk_tree_store_remove (priv->store, &language_iter); - } - } - } -} - -static void -book_tree_init_selection (DhBookTree *tree) -{ - DhBookTreePrivate *priv; - DhBookManager *book_manager; - GtkTreeSelection *selection; - GtkTreeIter iter; - gboolean iter_found = FALSE; - - priv = dh_book_tree_get_instance_private (tree); - - /* Mark the first item as selected, or it would get automatically - * selected when the treeview will get focus; but that's not even - * enough as a selection changed would still be emitted when there - * is no change, hence the manual tracking of selection in - * selected_link. - * https://bugzilla.gnome.org/show_bug.cgi?id=492206 - */ - selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (tree)); - g_signal_handlers_block_by_func (selection, - book_tree_selection_changed_cb, - tree); - - /* If grouping by languages, get first book in the first language */ - book_manager = dh_book_manager_get_singleton (); - if (dh_book_manager_get_group_by_language (book_manager)) { - GtkTreeIter language_iter; - - if (gtk_tree_model_get_iter_first (GTK_TREE_MODEL (priv->store), - &language_iter)) { - iter_found = gtk_tree_model_iter_children (GTK_TREE_MODEL (priv->store), - &iter, - &language_iter); - } - } else { - iter_found = gtk_tree_model_get_iter_first (GTK_TREE_MODEL (priv->store), - &iter); - } - - if (iter_found) { - DhLink *link; - - gtk_tree_model_get (GTK_TREE_MODEL (priv->store), - &iter, - COL_LINK, &link, - -1); - - g_clear_pointer (&priv->selected_link, (GDestroyNotify)dh_link_unref); - priv->selected_link = link; - gtk_tree_selection_select_iter (selection, &iter); - - if (dh_link_get_link_type (link) != DH_LINK_TYPE_BOOK) - g_warn_if_reached (); - } - - g_signal_handlers_unblock_by_func (selection, - book_tree_selection_changed_cb, - tree); -} - -static void -book_tree_populate_tree (DhBookTree *tree) -{ - DhBookTreePrivate *priv = dh_book_tree_get_instance_private (tree); - DhBookManager *book_manager; - GList *l; - - gtk_tree_view_set_model (GTK_TREE_VIEW (tree), NULL); - gtk_tree_store_clear (priv->store); - gtk_tree_view_set_model (GTK_TREE_VIEW (tree), - GTK_TREE_MODEL (priv->store)); - - /* This list comes in order, but we don't really mind */ - book_manager = dh_book_manager_get_singleton (); - for (l = dh_book_manager_get_books (book_manager); - l != NULL; - l = l->next) { - DhBook *book = DH_BOOK (l->data); - - /* Only add enabled books to the tree */ - if (dh_book_get_enabled (book)) - book_tree_add_book_to_store (tree, book); - } - - book_tree_init_selection (tree); -} - -static void -book_tree_group_by_language_cb (GObject *object, - GParamSpec *pspec, - DhBookTree *tree) -{ - book_tree_populate_tree (tree); -} - -static void -dh_book_tree_dispose (GObject *object) -{ - DhBookTreePrivate *priv = dh_book_tree_get_instance_private (DH_BOOK_TREE (object)); - - g_clear_object (&priv->store); - g_clear_pointer (&priv->selected_link, (GDestroyNotify)dh_link_unref); - priv->context_menu = NULL; - - G_OBJECT_CLASS (dh_book_tree_parent_class)->dispose (object); -} - -static void -collapse_all_activate_cb (GtkMenuItem *menu_item, - DhBookTree *tree) -{ - gtk_tree_view_collapse_all (GTK_TREE_VIEW (tree)); -} - -static void -do_popup_menu (DhBookTree *tree, - GdkEventButton *event) -{ - DhBookTreePrivate *priv = dh_book_tree_get_instance_private (tree); - - if (priv->context_menu == NULL) { - GtkWidget *menu_item; - - /* Create the menu only once. At first I wanted to create a new - * menu each time this function is called, connect to the - * GtkMenuShell::deactivate signal to call gtk_widget_destroy(). - * But GtkMenuShell::deactivate is emitted before - * collapse_all_activate_cb(), so collapse_all_activate_cb() was - * never called... It's maybe a GTK+ bug. - */ - priv->context_menu = GTK_MENU (gtk_menu_new ()); - - /* When tree is destroyed, the context menu is destroyed too. */ - gtk_menu_attach_to_widget (priv->context_menu, GTK_WIDGET (tree), NULL); - - menu_item = gtk_menu_item_new_with_mnemonic (_("_Collapse All")); - gtk_menu_shell_append (GTK_MENU_SHELL (priv->context_menu), menu_item); - gtk_widget_show (menu_item); - - g_signal_connect_object (menu_item, - "activate", - G_CALLBACK (collapse_all_activate_cb), - tree, - 0); - } - - if (event != NULL) { - gtk_menu_popup_at_pointer (priv->context_menu, (GdkEvent *) event); - } else { - gtk_menu_popup_at_widget (priv->context_menu, - GTK_WIDGET (tree), - GDK_GRAVITY_NORTH_EAST, - GDK_GRAVITY_NORTH_WEST, - NULL); - } -} - -static gboolean -dh_book_tree_button_press_event (GtkWidget *widget, - GdkEventButton *event) -{ - DhBookTree *tree = DH_BOOK_TREE (widget); - - if (gdk_event_triggers_context_menu ((GdkEvent *) event) && - event->type == GDK_BUTTON_PRESS) { - do_popup_menu (tree, event); - return GDK_EVENT_STOP; - } - - if (GTK_WIDGET_CLASS (dh_book_tree_parent_class)->button_press_event != NULL) - return GTK_WIDGET_CLASS (dh_book_tree_parent_class)->button_press_event (widget, event); - - return GDK_EVENT_PROPAGATE; -} - -static gboolean -dh_book_tree_popup_menu (GtkWidget *widget) -{ - if (GTK_WIDGET_CLASS (dh_book_tree_parent_class)->popup_menu != NULL) - g_warning ("%s(): chain-up?", G_STRFUNC); - - do_popup_menu (DH_BOOK_TREE (widget), NULL); - return TRUE; -} - -static void -dh_book_tree_class_init (DhBookTreeClass *klass) -{ - GObjectClass *object_class = G_OBJECT_CLASS (klass); - GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); - - object_class->dispose = dh_book_tree_dispose; - - widget_class->button_press_event = dh_book_tree_button_press_event; - widget_class->popup_menu = dh_book_tree_popup_menu; - - /** - * DhBookTree::link-selected: - * @tree: the #DhBookTree. - * @link: the selected #DhLink. - */ - signals[LINK_SELECTED] = - g_signal_new ("link-selected", - G_TYPE_FROM_CLASS (klass), - G_SIGNAL_RUN_LAST, - 0, - NULL, NULL, NULL, - G_TYPE_NONE, - 1, DH_TYPE_LINK); -} - -static void -book_tree_add_columns (DhBookTree *tree) -{ - GtkCellRenderer *cell; - GtkTreeViewColumn *column; - - column = gtk_tree_view_column_new (); - - cell = gtk_cell_renderer_text_new (); - g_object_set (cell, - "ellipsize", PANGO_ELLIPSIZE_END, - NULL); - gtk_tree_view_column_pack_start (column, cell, TRUE); - gtk_tree_view_column_set_attributes (column, cell, - "text", COL_TITLE, - "weight", COL_WEIGHT, - "underline", COL_UNDERLINE, - NULL); - - gtk_tree_view_append_column (GTK_TREE_VIEW (tree), column); -} - -static void -dh_book_tree_init (DhBookTree *tree) -{ - DhBookTreePrivate *priv; - DhBookManager *book_manager; - - priv = dh_book_tree_get_instance_private (tree); - - gtk_tree_view_set_headers_visible (GTK_TREE_VIEW (tree), FALSE); - gtk_tree_view_set_enable_search (GTK_TREE_VIEW (tree), FALSE); - - priv->store = gtk_tree_store_new (N_COLUMNS, - G_TYPE_STRING, /* Title */ - DH_TYPE_LINK, - DH_TYPE_BOOK, - PANGO_TYPE_WEIGHT, - PANGO_TYPE_UNDERLINE); - priv->selected_link = NULL; - gtk_tree_view_set_model (GTK_TREE_VIEW (tree), - GTK_TREE_MODEL (priv->store)); - - book_tree_add_columns (tree); - book_tree_setup_selection (tree); - - book_manager = dh_book_manager_get_singleton (); - - g_signal_connect_object (book_manager, - "book-created", - G_CALLBACK (book_tree_book_created_or_enabled_cb), - tree, - 0); - - g_signal_connect_object (book_manager, - "book-enabled", - G_CALLBACK (book_tree_book_created_or_enabled_cb), - tree, - 0); - - g_signal_connect_object (book_manager, - "book-deleted", - G_CALLBACK (book_tree_book_deleted_or_disabled_cb), - tree, - 0); - - g_signal_connect_object (book_manager, - "book-disabled", - G_CALLBACK (book_tree_book_deleted_or_disabled_cb), - tree, - 0); - - g_signal_connect_object (book_manager, - "notify::group-by-language", - G_CALLBACK (book_tree_group_by_language_cb), - tree, - 0); - - book_tree_populate_tree (tree); -} - -/** - * dh_book_tree_new: - * - * Returns: (transfer floating): a new #DhBookTree widget. - */ -DhBookTree * -dh_book_tree_new (void) -{ - return g_object_new (DH_TYPE_BOOK_TREE, NULL); -} - -static gboolean -book_tree_find_uri_foreach (GtkTreeModel *model, - GtkTreePath *path, - GtkTreeIter *iter, - FindURIData *data) -{ - DhLink *link; - - gtk_tree_model_get (model, iter, - COL_LINK, &link, - -1); - - if (link != NULL) { - gchar *link_uri; - - link_uri = dh_link_get_uri (link); - - if (link_uri != NULL && - g_str_has_prefix (data->uri, link_uri)) { - data->found = TRUE; - data->iter = *iter; - data->path = gtk_tree_path_copy (path); - } - - g_free (link_uri); - dh_link_unref (link); - } - - return data->found; -} - -/** - * dh_book_tree_select_uri: - * @tree: a #DhBookTree. - * @uri: the URI to select. - * - * Selects the given @uri. - */ -void -dh_book_tree_select_uri (DhBookTree *tree, - const gchar *uri) -{ - DhBookTreePrivate *priv = dh_book_tree_get_instance_private (tree); - GtkTreeSelection *selection; - FindURIData data; - DhLink *link; - - data.found = FALSE; - data.uri = uri; - - gtk_tree_model_foreach (GTK_TREE_MODEL (priv->store), - (GtkTreeModelForeachFunc) book_tree_find_uri_foreach, - &data); - - if (!data.found) - return; - - selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (tree)); - - /* Do not re-select (which will expand current additionally) if already - * there. */ - if (gtk_tree_selection_iter_is_selected (selection, &data.iter)) - goto out; - - /* FIXME: it's strange to block the signal here. The signal handler - * should probably be blocked in DhWindow instead. - */ - g_signal_handlers_block_by_func (selection, - book_tree_selection_changed_cb, - tree); - - gtk_tree_view_expand_to_path (GTK_TREE_VIEW (tree), data.path); - - gtk_tree_model_get (GTK_TREE_MODEL (priv->store), - &data.iter, - COL_LINK, &link, - -1); - g_clear_pointer (&priv->selected_link, (GDestroyNotify)dh_link_unref); - priv->selected_link = link; - gtk_tree_selection_select_iter (selection, &data.iter); - - gtk_tree_view_set_cursor (GTK_TREE_VIEW (tree), data.path, NULL, 0); - - g_signal_handlers_unblock_by_func (selection, - book_tree_selection_changed_cb, - tree); - -out: - gtk_tree_path_free (data.path); -} - -/** - * dh_book_tree_get_selected_book: - * @tree: a #DhBookTree. - * - * Returns: (nullable) (transfer full): the #DhLink of the selected book, or - * %NULL if there is no selection. Unref with dh_link_unref() when no longer - * needed. - */ -DhLink * -dh_book_tree_get_selected_book (DhBookTree *tree) -{ - GtkTreeSelection *selection; - GtkTreeModel *model; - GtkTreeIter iter; - - selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (tree)); - if (!gtk_tree_selection_get_selected (selection, &model, &iter)) - return NULL; - - /* Depending on whether books are grouped by language, the book link can - * be at a different depth. And it's safer to check that the returned - * link has the good type. So walk up the tree to find the book. - */ - while (TRUE) { - DhLink *link; - GtkTreeIter parent; - - gtk_tree_model_get (model, &iter, - COL_LINK, &link, - -1); - - if (dh_link_get_link_type (link) == DH_LINK_TYPE_BOOK) - return link; - - dh_link_unref (link); - - if (!gtk_tree_model_iter_parent (model, &parent, &iter)) - break; - - iter = parent; - } - - g_return_val_if_reached (NULL); -} diff --git a/src/dh-keyword-model.c b/src/dh-keyword-model.c index 6a7fc50..a0b716f 100644 --- a/src/dh-keyword-model.c +++ b/src/dh-keyword-model.c @@ -442,14 +442,6 @@ search_single_book (DhBook *book, return ret; } -static gint -compare_links (gconstpointer a, - gconstpointer b, - gpointer user_data) -{ - return dh_link_compare (a, b); -} - static GQueue * search_books (SearchSettings *settings, guint max_hits, @@ -494,7 +486,7 @@ search_books (SearchSettings *settings, dh_util_queue_concat (ret, book_result); } - g_queue_sort (ret, (GCompareDataFunc) compare_links, NULL); + g_queue_sort (ret, (GCompareDataFunc) dh_link_compare, NULL); return ret; } diff --git a/src/dh-keyword-model.c.covscan b/src/dh-keyword-model.c.covscan deleted file mode 100644 index a0b716f..0000000 --- a/src/dh-keyword-model.c.covscan +++ /dev/null @@ -1,736 +0,0 @@ -/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- */ -/* - * Copyright (C) 2002 CodeFactory AB - * Copyright (C) 2002 Mikael Hallendal - * Copyright (C) 2008 Imendio AB - * Copyright (C) 2010 Lanedo GmbH - * Copyright (C) 2015-2018 Sébastien Wilmet - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation; either version 2 of the - * License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, see . - */ - -#include "dh-keyword-model.h" -#include -#include "dh-book.h" -#include "dh-book-manager.h" -#include "dh-search-context.h" -#include "dh-util.h" - -/** - * SECTION:dh-keyword-model - * @Title: DhKeywordModel - * @Short_description: A custom #GtkTreeModel implementation for searching - * #DhLink's - * - * #DhKeywordModel is a custom #GtkTreeModel implementation (as a list, not a - * tree) for searching #DhLink's. - * - * The dh_keyword_model_filter() function is used to set the search criteria. It - * fills the #GtkTreeModel with the list of #DhLink's that match the search - * criteria (up to a certain maximum number of matches). - * - * How the search works (for end users) is explained in the user documentation - * of the Devhelp application. - * - * # Filter by book and page - * - * As a kind of API for integrating Devhelp with other applications, the search - * string supports additional features. Those features are not intended to be - * used directly by end users when typing the search string in the GUI, because - * it's not really convenient. It is intended to be used with the - * `devhelp --search "search-string"` command line, so that another application - * can launch Devhelp and set a specific search string. - * - * It is possible to filter by book by prefixing the search string with - * “book:the-book-ID”. For example “book:gtk3”. If there are no other search - * terms, it shows the top-level page of that book. If there are other search - * terms, it limits the search to the specified book. See also the - * dh_book_get_id() function (in the `*.devhelp2` index file format it's called - * the book “name”, not ID, but ID is clearer). - * - * Similarly, it is possible to filter by page, by prefixing the search string - * with “page:the-page-ID”. For example “page:GtkWindow”. If there are no other - * search terms, the top of the page is shown and the search matches all the - * symbols part of that page. If there are other search terms, it limits the - * search to the specified page. To know what is the “page ID”, see the - * dh_link_belongs_to_page() function. - * - * “book:” and “page:” can be combined. Normal search terms must be - * after “book:” and “page:”. - * - * The book and page IDs – even if they contain an uppercase letter – don't - * affect the case sensitivity for the other search terms. - */ - -typedef struct { - gchar *current_book_id; - - /* List of owned DhLink*. - * - * Note: GQueue, not GQueue* so we are sure that it always exists, we - * don't need to check if priv->links == NULL. - */ - GQueue links; - - gint stamp; -} DhKeywordModelPrivate; - -typedef struct { - DhSearchContext *search_context; - const gchar *book_id; - const gchar *skip_book_id; - guint prefix : 1; -} SearchSettings; - -#define MAX_HITS 1000 - -static void dh_keyword_model_tree_model_init (GtkTreeModelIface *iface); - -G_DEFINE_TYPE_WITH_CODE (DhKeywordModel, dh_keyword_model, G_TYPE_OBJECT, - G_ADD_PRIVATE (DhKeywordModel) - G_IMPLEMENT_INTERFACE (GTK_TYPE_TREE_MODEL, - dh_keyword_model_tree_model_init)); - -static void -clear_links (DhKeywordModel *model) -{ - DhKeywordModelPrivate *priv = dh_keyword_model_get_instance_private (model); - GList *l; - - for (l = priv->links.head; l != NULL; l = l->next) { - DhLink *cur_link = l->data; - dh_link_unref (cur_link); - } - - g_queue_clear (&priv->links); -} - -static void -dh_keyword_model_finalize (GObject *object) -{ - DhKeywordModel *model = DH_KEYWORD_MODEL (object); - DhKeywordModelPrivate *priv = dh_keyword_model_get_instance_private (model); - - g_free (priv->current_book_id); - clear_links (model); - - G_OBJECT_CLASS (dh_keyword_model_parent_class)->finalize (object); -} - -static void -dh_keyword_model_class_init (DhKeywordModelClass *klass) -{ - GObjectClass *object_class = G_OBJECT_CLASS (klass); - - object_class->finalize = dh_keyword_model_finalize; -} - -static void -dh_keyword_model_init (DhKeywordModel *model) -{ - DhKeywordModelPrivate *priv = dh_keyword_model_get_instance_private (model); - - priv->stamp = g_random_int_range (1, G_MAXINT32); -} - -static GtkTreeModelFlags -dh_keyword_model_get_flags (GtkTreeModel *tree_model) -{ - return GTK_TREE_MODEL_LIST_ONLY; -} - -static gint -dh_keyword_model_get_n_columns (GtkTreeModel *tree_model) -{ - return DH_KEYWORD_MODEL_NUM_COLS; -} - -static GType -dh_keyword_model_get_column_type (GtkTreeModel *tree_model, - gint column) -{ - switch (column) { - case DH_KEYWORD_MODEL_COL_NAME: - return G_TYPE_STRING; - - case DH_KEYWORD_MODEL_COL_LINK: - return DH_TYPE_LINK; - - case DH_KEYWORD_MODEL_COL_CURRENT_BOOK_FLAG: - return G_TYPE_BOOLEAN; - - default: - return G_TYPE_INVALID; - } -} - -static gboolean -dh_keyword_model_get_iter (GtkTreeModel *tree_model, - GtkTreeIter *iter, - GtkTreePath *path) -{ - DhKeywordModelPrivate *priv; - const gint *indices; - GList *node; - - priv = dh_keyword_model_get_instance_private (DH_KEYWORD_MODEL (tree_model)); - - if (gtk_tree_path_get_depth (path) > 1) { - return FALSE; - } - - indices = gtk_tree_path_get_indices (path); - - if (indices == NULL) { - return FALSE; - } - - node = g_queue_peek_nth_link (&priv->links, indices[0]); - - if (node != NULL) { - iter->stamp = priv->stamp; - iter->user_data = node; - return TRUE; - } - - return FALSE; -} - -static GtkTreePath * -dh_keyword_model_get_path (GtkTreeModel *tree_model, - GtkTreeIter *iter) -{ - DhKeywordModelPrivate *priv; - GList *node; - GtkTreePath *path; - gint pos; - - priv = dh_keyword_model_get_instance_private (DH_KEYWORD_MODEL (tree_model)); - - g_return_val_if_fail (iter->stamp == priv->stamp, NULL); - - node = iter->user_data; - pos = g_queue_link_index (&priv->links, node); - - if (pos < 0) { - return NULL; - } - - path = gtk_tree_path_new (); - gtk_tree_path_append_index (path, pos); - - return path; -} - -static void -dh_keyword_model_get_value (GtkTreeModel *tree_model, - GtkTreeIter *iter, - gint column, - GValue *value) -{ - DhKeywordModelPrivate *priv; - GList *node; - DhLink *link; - gboolean in_current_book; - - priv = dh_keyword_model_get_instance_private (DH_KEYWORD_MODEL (tree_model)); - - g_return_if_fail (iter->stamp == priv->stamp); - - node = iter->user_data; - link = node->data; - - switch (column) { - case DH_KEYWORD_MODEL_COL_NAME: - g_value_init (value, G_TYPE_STRING); - g_value_set_string (value, dh_link_get_name (link)); - break; - - case DH_KEYWORD_MODEL_COL_LINK: - g_value_init (value, DH_TYPE_LINK); - g_value_set_boxed (value, link); - break; - - case DH_KEYWORD_MODEL_COL_CURRENT_BOOK_FLAG: - in_current_book = g_strcmp0 (dh_link_get_book_id (link), priv->current_book_id) == 0; - g_value_init (value, G_TYPE_BOOLEAN); - g_value_set_boolean (value, in_current_book); - break; - - default: - g_warning ("Bad column %d requested", column); - } -} - -static gboolean -dh_keyword_model_iter_next (GtkTreeModel *tree_model, - GtkTreeIter *iter) -{ - DhKeywordModelPrivate *priv; - GList *node; - - priv = dh_keyword_model_get_instance_private (DH_KEYWORD_MODEL (tree_model)); - - g_return_val_if_fail (priv->stamp == iter->stamp, FALSE); - - node = iter->user_data; - iter->user_data = node->next; - - return iter->user_data != NULL; -} - -static gboolean -dh_keyword_model_iter_children (GtkTreeModel *tree_model, - GtkTreeIter *iter, - GtkTreeIter *parent) -{ - DhKeywordModelPrivate *priv; - - priv = dh_keyword_model_get_instance_private (DH_KEYWORD_MODEL (tree_model)); - - /* This is a list, nodes have no children. */ - if (parent != NULL) { - return FALSE; - } - - /* But if parent == NULL we return the list itself as children of - * the "root". - */ - if (priv->links.head != NULL) { - iter->stamp = priv->stamp; - iter->user_data = priv->links.head; - return TRUE; - } - - return FALSE; -} - -static gboolean -dh_keyword_model_iter_has_child (GtkTreeModel *tree_model, - GtkTreeIter *iter) -{ - return FALSE; -} - -static gint -dh_keyword_model_iter_n_children (GtkTreeModel *tree_model, - GtkTreeIter *iter) -{ - DhKeywordModelPrivate *priv; - - priv = dh_keyword_model_get_instance_private (DH_KEYWORD_MODEL (tree_model)); - - if (iter == NULL) { - return priv->links.length; - } - - g_return_val_if_fail (priv->stamp == iter->stamp, -1); - - return 0; -} - -static gboolean -dh_keyword_model_iter_nth_child (GtkTreeModel *tree_model, - GtkTreeIter *iter, - GtkTreeIter *parent, - gint n) -{ - DhKeywordModelPrivate *priv; - GList *child; - - priv = dh_keyword_model_get_instance_private (DH_KEYWORD_MODEL (tree_model)); - - if (parent != NULL) { - return FALSE; - } - - child = g_queue_peek_nth_link (&priv->links, n); - - if (child != NULL) { - iter->stamp = priv->stamp; - iter->user_data = child; - return TRUE; - } - - return FALSE; -} - -static gboolean -dh_keyword_model_iter_parent (GtkTreeModel *tree_model, - GtkTreeIter *iter, - GtkTreeIter *child) -{ - return FALSE; -} - -static void -dh_keyword_model_tree_model_init (GtkTreeModelIface *iface) -{ - iface->get_flags = dh_keyword_model_get_flags; - iface->get_n_columns = dh_keyword_model_get_n_columns; - iface->get_column_type = dh_keyword_model_get_column_type; - iface->get_iter = dh_keyword_model_get_iter; - iface->get_path = dh_keyword_model_get_path; - iface->get_value = dh_keyword_model_get_value; - iface->iter_next = dh_keyword_model_iter_next; - iface->iter_children = dh_keyword_model_iter_children; - iface->iter_has_child = dh_keyword_model_iter_has_child; - iface->iter_n_children = dh_keyword_model_iter_n_children; - iface->iter_nth_child = dh_keyword_model_iter_nth_child; - iface->iter_parent = dh_keyword_model_iter_parent; -} - -/** - * dh_keyword_model_new: - * - * Returns: a new #DhKeywordModel object. - */ -DhKeywordModel * -dh_keyword_model_new (void) -{ - return g_object_new (DH_TYPE_KEYWORD_MODEL, NULL); -} - -static GQueue * -search_single_book (DhBook *book, - SearchSettings *settings, - guint max_hits, - DhLink **exact_link) -{ - GQueue *ret; - GList *l; - - ret = g_queue_new (); - - for (l = dh_book_get_links (book); - l != NULL && ret->length < max_hits; - l = l->next) { - DhLink *link = l->data; - - if (!_dh_search_context_match_link (settings->search_context, - link, - settings->prefix)) { - continue; - } - - g_queue_push_tail (ret, dh_link_ref (link)); - - if (exact_link == NULL || !settings->prefix) - continue; - - /* Look for an exact link match. If the link is a PAGE, we can - * overwrite any previous exact link set. For example, when - * looking for GFile, we want the page, not the struct. - */ - if ((*exact_link == NULL || dh_link_get_link_type (link) == DH_LINK_TYPE_PAGE) && - _dh_search_context_is_exact_link (settings->search_context, link)) { - *exact_link = link; - } - } - - return ret; -} - -static GQueue * -search_books (SearchSettings *settings, - guint max_hits, - DhLink **exact_link) -{ - DhBookManager *book_manager; - GList *books; - GList *l; - GQueue *ret; - - ret = g_queue_new (); - - book_manager = dh_book_manager_get_singleton (); - books = dh_book_manager_get_books (book_manager); - - for (l = books; - l != NULL && ret->length < max_hits; - l = l->next) { - DhBook *book = DH_BOOK (l->data); - GQueue *book_result; - - if (!_dh_search_context_match_book (settings->search_context, book)) - continue; - - /* Filtering by book? */ - if (settings->book_id != NULL && - g_strcmp0 (settings->book_id, dh_book_get_id (book)) != 0) { - continue; - } - - /* Skipping a given book? */ - if (settings->skip_book_id != NULL && - g_strcmp0 (settings->skip_book_id, dh_book_get_id (book)) == 0) { - continue; - } - - book_result = search_single_book (book, - settings, - max_hits - ret->length, - exact_link); - - dh_util_queue_concat (ret, book_result); - } - - g_queue_sort (ret, (GCompareDataFunc) dh_link_compare, NULL); - return ret; -} - -static GQueue * -handle_book_id_only (DhSearchContext *search_context, - DhLink **exact_link) -{ - DhBookManager *book_manager; - GList *books; - GList *l; - GQueue *ret; - - if (_dh_search_context_get_book_id (search_context) == NULL || - _dh_search_context_get_page_id (search_context) != NULL || - _dh_search_context_get_keywords (search_context) != NULL) { - return NULL; - } - - ret = g_queue_new (); - - book_manager = dh_book_manager_get_singleton (); - books = dh_book_manager_get_books (book_manager); - - for (l = books; l != NULL; l = l->next) { - DhBook *book = DH_BOOK (l->data); - GNode *node; - - if (!_dh_search_context_match_book (search_context, book)) - continue; - - /* Return only the top-level book page. */ - node = dh_book_get_tree (book); - if (node != NULL) { - DhLink *link; - - link = node->data; - g_queue_push_tail (ret, dh_link_ref (link)); - - if (exact_link != NULL) - *exact_link = link; - } - - break; - } - - return ret; -} - -/* The Search rationale is as follows: - * - * - If 'book_id' is given, but no 'page_id' or 'keywords', the main page of - * the book will only be shown, giving as exact match this book link. - * - If 'book_id' and 'page_id' are given, but no 'keywords', all the items - * in the given page of the given book will be shown. - * - If 'book_id' and 'keywords' are given, but no 'page_id', up to MAX_HITS - * items matching the keywords in the given book will be shown. - * - If 'book_id' and 'page_id' and 'keywords' are given, all the items - * matching the keywords in the given page of the given book will be shown. - * - * - If 'page_id' is given, but no 'book_id' or 'keywords', all the items - * in the given page will be shown, giving as exact match the page link. - * - If 'page_id' and 'keywords' are given but no 'book_id', all the items - * matching the keywords in the given page will be shown. - * - * - If 'keywords' only are given, up to max_hits items matching the keywords - * will be shown. If keyword matches both a page link and a non-page one, - * the page link is the one given as exact match. - */ -static GQueue * -keyword_model_search (DhKeywordModel *model, - DhSearchContext *search_context, - DhLink **exact_link) -{ - DhKeywordModelPrivate *priv = dh_keyword_model_get_instance_private (model); - SearchSettings settings; - guint max_hits = MAX_HITS; - GQueue *in_book = NULL; - GQueue *other_books = NULL; - DhLink *in_book_exact_link = NULL; - DhLink *other_books_exact_link = NULL; - GQueue *out; - - out = handle_book_id_only (search_context, exact_link); - if (out != NULL) - return out; - - out = g_queue_new (); - - settings.search_context = search_context; - settings.book_id = priv->current_book_id; - settings.skip_book_id = NULL; - settings.prefix = TRUE; - - if (_dh_search_context_get_page_id (search_context) != NULL) { - /* If filtering per page, increase the maximum number of - * hits. This is due to the fact that a page may have - * more than MAX_HITS keywords, and the page link may be - * the last one in the list, but we always want to get it. - */ - max_hits = G_MAXUINT; - } - - /* First look for prefixed items in the given book id. */ - if (priv->current_book_id != NULL) { - in_book = search_books (&settings, - max_hits, - &in_book_exact_link); - } - - /* Next, always check other books as well, as the exact match may be in - * there. - */ - settings.book_id = NULL; - settings.skip_book_id = priv->current_book_id; - other_books = search_books (&settings, - max_hits, - &other_books_exact_link); - - /* Now that we got prefix searches in current and other books, decide - * which the preferred exact link is. If the exact match is in other - * books, prefer those to the current book. - */ - if (in_book_exact_link != NULL) { - *exact_link = in_book_exact_link; - dh_util_queue_concat (out, in_book); - dh_util_queue_concat (out, other_books); - } else if (other_books_exact_link != NULL) { - *exact_link = other_books_exact_link; - dh_util_queue_concat (out, other_books); - dh_util_queue_concat (out, in_book); - } else { - *exact_link = NULL; - dh_util_queue_concat (out, in_book); - dh_util_queue_concat (out, other_books); - } - - if (out->length >= max_hits) - return out; - - /* Look for non-prefixed matches in current book. */ - settings.prefix = FALSE; - - if (priv->current_book_id != NULL) { - settings.book_id = priv->current_book_id; - settings.skip_book_id = NULL; - - in_book = search_books (&settings, - max_hits - out->length, - NULL); - - dh_util_queue_concat (out, in_book); - if (out->length >= max_hits) - return out; - } - - /* If still room for more items, look for non-prefixed items in other - * books. - */ - settings.book_id = NULL; - settings.skip_book_id = priv->current_book_id; - other_books = search_books (&settings, - max_hits - out->length, - NULL); - dh_util_queue_concat (out, other_books); - - return out; -} - -/** - * dh_keyword_model_filter: - * @model: a #DhKeywordModel. - * @search_string: a search query. - * @current_book_id: (nullable): the ID of the book currently shown, or %NULL. - * @language: (nullable): deprecated, must be %NULL. - * - * Searches in the #DhBookManager the list of #DhLink's that correspond to - * @search_string, and fills the @model with that list (erasing the previous - * content). - * - * Attention, when calling this function the @model needs to be disconnected - * from the #GtkTreeView, because the #GtkTreeModel signals are not emitted, to - * improve the performances (sending a lot of signals is slow) and have a - * simpler implementation. The previous row selection is anyway no longer - * relevant. - * - * Note that there is a maximum number of matches (configured internally). When - * the maximum is reached the search is stopped, to avoid blocking the GUI - * (since this function runs synchronously) if the @search_string contains for - * example only one character. (And it is anyway not very useful to show to the - * user tens of thousands search results). - * - * Returns: (nullable) (transfer none): the #DhLink that matches exactly - * @search_string, or %NULL if no such #DhLink was found within the maximum - * number of matches. - */ -DhLink * -dh_keyword_model_filter (DhKeywordModel *model, - const gchar *search_string, - const gchar *current_book_id, - const gchar *language) -{ - DhKeywordModelPrivate *priv; - DhSearchContext *search_context; - GQueue *new_links = NULL; - DhLink *exact_link = NULL; - - g_return_val_if_fail (DH_IS_KEYWORD_MODEL (model), NULL); - g_return_val_if_fail (search_string != NULL, NULL); - g_return_val_if_fail (language == NULL, NULL); - - priv = dh_keyword_model_get_instance_private (model); - - g_free (priv->current_book_id); - priv->current_book_id = NULL; - - search_context = _dh_search_context_new (search_string); - - if (search_context != NULL) { - const gchar *book_id_in_search_string; - - book_id_in_search_string = _dh_search_context_get_book_id (search_context); - - if (book_id_in_search_string != NULL) - priv->current_book_id = g_strdup (book_id_in_search_string); - else - priv->current_book_id = g_strdup (current_book_id); - - new_links = keyword_model_search (model, search_context, &exact_link); - } - - clear_links (model); - dh_util_queue_concat (&priv->links, new_links); - new_links = NULL; - - /* The content has been modified, change the stamp so that older - * GtkTreeIter's become invalid. - */ - priv->stamp++; - - _dh_search_context_free (search_context); - - /* One hit */ - if (priv->links.length == 1) - return g_queue_peek_head (&priv->links); - - return exact_link; -} diff --git a/src/dh-preferences.c b/src/dh-preferences.c index ffae33b..0568c50 100644 --- a/src/dh-preferences.c +++ b/src/dh-preferences.c @@ -143,7 +143,7 @@ preferences_bookshelf_find_book (DhPreferences *prefs, -1); /* We may have reached the start of the next language group here */ - if (exact_found && first && !in_list_book) { + if (first && !in_list_book) { *next_iter = loop_iter; *next_found = TRUE; return; @@ -151,7 +151,7 @@ preferences_bookshelf_find_book (DhPreferences *prefs, /* We can compare pointers directly as we're playing with references * of the same object */ - if (exact_iter && exact_found && + if (exact_iter && in_list_book == book) { *exact_iter = loop_iter; *exact_found = TRUE; @@ -160,7 +160,7 @@ preferences_bookshelf_find_book (DhPreferences *prefs, g_object_unref (in_list_book); return; } - } else if (next_iter && next_found && + } else if (next_iter && dh_book_cmp_by_title (in_list_book, book) > 0) { *next_iter = loop_iter; *next_found = TRUE; @@ -223,7 +223,7 @@ preferences_bookshelf_find_language_group (DhPreferences *prefs, continue; } - if (exact_iter && exact_found && + if (exact_iter && g_ascii_strcasecmp (title, language) == 0) { /* Exact match found! */ *exact_iter = loop_iter; @@ -233,7 +233,7 @@ preferences_bookshelf_find_language_group (DhPreferences *prefs, g_free (title); return; } - } else if (next_iter && next_found && + } else if (next_iter && g_ascii_strcasecmp (title, language) > 0) { *next_iter = loop_iter; *next_found = TRUE; diff --git a/src/dh-preferences.c.covscan b/src/dh-preferences.c.covscan deleted file mode 100644 index 0568c50..0000000 --- a/src/dh-preferences.c.covscan +++ /dev/null @@ -1,673 +0,0 @@ -/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- */ -/* - * Copyright (C) 2004-2008 Imendio AB - * Copyright (C) 2010 Lanedo GmbH - * Copyright (C) 2012 Thomas Bechtold - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation; either version 2 of the - * License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, see . - */ - -#include "config.h" -#include "dh-preferences.h" - -#include - -#include "dh-book.h" -#include "dh-book-manager.h" -#include "dh-util.h" -#include "dh-settings.h" - -static GtkWidget *prefs_dialog = NULL; - -enum { - COLUMN_ENABLED = 0, - COLUMN_TITLE, - COLUMN_BOOK, - COLUMN_WEIGHT, - COLUMN_INCONSISTENT, - N_COLUMNS -}; - -typedef struct { - /* Fonts tab */ - GtkCheckButton *system_fonts_button; - GtkGrid *fonts_grid; - GtkFontButton *variable_font_button; - GtkFontButton *fixed_font_button; - guint use_system_fonts_id; - guint system_var_id; - guint system_fixed_id; - guint var_id; - guint fixed_id; - - /* Book Shelf tab */ - GtkCellRendererToggle *bookshelf_enabled_toggle; - GtkListStore *bookshelf_store; - GtkCheckButton *bookshelf_group_by_language_button; -} DhPreferencesPrivate; - -G_DEFINE_TYPE_WITH_PRIVATE (DhPreferences, dh_preferences, GTK_TYPE_DIALOG) - -static void -dh_preferences_response (GtkDialog *dlg, - gint response_id) -{ - gtk_widget_destroy (GTK_WIDGET (dlg)); -} - -static void -dh_preferences_class_init (DhPreferencesClass *klass) -{ - GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); - GtkDialogClass *dialog_class = GTK_DIALOG_CLASS (klass); - - dialog_class->response = dh_preferences_response; - - /* Bind class to template */ - gtk_widget_class_set_template_from_resource (widget_class, - "/org/gnome/devhelp/dh-preferences.ui"); - gtk_widget_class_bind_template_child_private (widget_class, DhPreferences, system_fonts_button); - gtk_widget_class_bind_template_child_private (widget_class, DhPreferences, fonts_grid); - gtk_widget_class_bind_template_child_private (widget_class, DhPreferences, variable_font_button); - gtk_widget_class_bind_template_child_private (widget_class, DhPreferences, fixed_font_button); - gtk_widget_class_bind_template_child_private (widget_class, DhPreferences, bookshelf_store); - gtk_widget_class_bind_template_child_private (widget_class, DhPreferences, bookshelf_group_by_language_button); - gtk_widget_class_bind_template_child_private (widget_class, DhPreferences, bookshelf_enabled_toggle); -} - -static void -preferences_bookshelf_clean_store (DhPreferences *prefs) -{ - DhPreferencesPrivate *priv = dh_preferences_get_instance_private (prefs); - - gtk_list_store_clear (priv->bookshelf_store); -} - -/* Tries to find, starting at 'first' (if given): - * - An exact match of the book - * - The book which should be just after our given book: - * - If first is set, the next book must be in the same language group - * as the given book. - * - If first is NOT set, we don't care about language groups as we're - * iterating from the beginning of the list. - * - Both. - */ -static void -preferences_bookshelf_find_book (DhPreferences *prefs, - DhBook *book, - const GtkTreeIter *first, - GtkTreeIter *exact_iter, - gboolean *exact_found, - GtkTreeIter *next_iter, - gboolean *next_found) -{ - DhPreferencesPrivate *priv = dh_preferences_get_instance_private (prefs); - GtkTreeIter loop_iter; - - g_assert ((exact_iter && exact_found) || (next_iter && next_found)); - - /* Reset all flags to not found */ - if (exact_found) - *exact_found = FALSE; - if (next_found) - *next_found = FALSE; - - /* Setup iteration start */ - if (!first) { - /* If no first given, start iterating from the start of the model */ - if (!gtk_tree_model_get_iter_first (GTK_TREE_MODEL (priv->bookshelf_store), &loop_iter)) { - /* Store is empty, not found */ - return; - } - } else { - loop_iter = *first; - } - - do { - DhBook *in_list_book = NULL; - - gtk_tree_model_get (GTK_TREE_MODEL (priv->bookshelf_store), - &loop_iter, - COLUMN_BOOK, &in_list_book, - -1); - - /* We may have reached the start of the next language group here */ - if (first && !in_list_book) { - *next_iter = loop_iter; - *next_found = TRUE; - return; - } - - /* We can compare pointers directly as we're playing with references - * of the same object */ - if (exact_iter && - in_list_book == book) { - *exact_iter = loop_iter; - *exact_found = TRUE; - if (!next_iter) { - /* If we were not requested to look for the next one, end here */ - g_object_unref (in_list_book); - return; - } - } else if (next_iter && - dh_book_cmp_by_title (in_list_book, book) > 0) { - *next_iter = loop_iter; - *next_found = TRUE; - g_object_unref (in_list_book); - return; - } - - if (in_list_book) - g_object_unref (in_list_book); - } while (gtk_tree_model_iter_next (GTK_TREE_MODEL (priv->bookshelf_store), - &loop_iter)); -} - -/* Tries to find: - * - An exact match of the language group - * - The language group which should be just after our given language group. - * - Both. - */ -static void -preferences_bookshelf_find_language_group (DhPreferences *prefs, - const gchar *language, - GtkTreeIter *exact_iter, - gboolean *exact_found, - GtkTreeIter *next_iter, - gboolean *next_found) -{ - DhPreferencesPrivate *priv = dh_preferences_get_instance_private (prefs); - GtkTreeIter loop_iter; - - g_assert ((exact_iter && exact_found) || (next_iter && next_found)); - - /* Reset all flags to not found */ - if (exact_found) - *exact_found = FALSE; - if (next_found) - *next_found = FALSE; - - if (!gtk_tree_model_get_iter_first (GTK_TREE_MODEL (priv->bookshelf_store), - &loop_iter)) { - /* Store is empty, not found */ - return; - } - - do { - DhBook *book = NULL; - gchar *title = NULL; - - /* Look for language titles, which are those where there - * is no book object associated in the row */ - gtk_tree_model_get (GTK_TREE_MODEL (priv->bookshelf_store), - &loop_iter, - COLUMN_TITLE, &title, - COLUMN_BOOK, &book, - -1); - - /* If we got a book, it's not a language row */ - if (book) { - g_free (title); - g_object_unref (book); - continue; - } - - if (exact_iter && - g_ascii_strcasecmp (title, language) == 0) { - /* Exact match found! */ - *exact_iter = loop_iter; - *exact_found = TRUE; - if (!next_iter) { - /* If we were not requested to look for the next one, end here */ - g_free (title); - return; - } - } else if (next_iter && - g_ascii_strcasecmp (title, language) > 0) { - *next_iter = loop_iter; - *next_found = TRUE; - /* There's no way to have an exact match after the next, so end here */ - g_free (title); - return; - } - - g_free (title); - } while (gtk_tree_model_iter_next (GTK_TREE_MODEL (priv->bookshelf_store), - &loop_iter)); -} - -static void -preferences_bookshelf_add_book_to_store (DhPreferences *prefs, - DhBook *book, - gboolean group_by_language) -{ - DhPreferencesPrivate *priv = dh_preferences_get_instance_private (prefs); - GtkTreeIter book_iter; - - /* If grouping by language we need to add the language categories */ - if (group_by_language) { - gchar *indented_title; - GtkTreeIter language_iter; - gboolean language_iter_found; - GtkTreeIter next_language_iter; - gboolean next_language_iter_found; - const gchar *language_title; - gboolean first_in_language = FALSE; - - language_title = dh_book_get_language (book); - - /* Look for the proper language group */ - preferences_bookshelf_find_language_group (prefs, - language_title, - &language_iter, - &language_iter_found, - &next_language_iter, - &next_language_iter_found); - /* New language group needs to be created? */ - if (!language_iter_found) { - if (!next_language_iter_found) { - gtk_list_store_append (priv->bookshelf_store, - &language_iter); - } else { - gtk_list_store_insert_before (priv->bookshelf_store, - &language_iter, - &next_language_iter); - } - - gtk_list_store_set (priv->bookshelf_store, - &language_iter, - COLUMN_ENABLED, dh_book_get_enabled (book), - COLUMN_TITLE, language_title, - COLUMN_BOOK, NULL, - COLUMN_WEIGHT, PANGO_WEIGHT_BOLD, - COLUMN_INCONSISTENT, FALSE, - -1); - - first_in_language = TRUE; - } - - /* If we got to add first book in a given language group, just append it. */ - if (first_in_language) { - gtk_list_store_insert_after (priv->bookshelf_store, - &book_iter, - &language_iter); - } else { - GtkTreeIter first_book_iter; - GtkTreeIter next_book_iter; - gboolean next_book_iter_found; - gboolean language_inconsistent = FALSE; - gboolean language_enabled = FALSE; - - /* We may need to reset the inconsistent status of the language item */ - gtk_tree_model_get (GTK_TREE_MODEL (priv->bookshelf_store), - &language_iter, - COLUMN_ENABLED, &language_enabled, - COLUMN_INCONSISTENT, &language_inconsistent, - -1); - /* If inconsistent already, do nothing */ - if (!language_inconsistent) { - if (language_enabled != dh_book_get_enabled (book)) { - gtk_list_store_set (priv->bookshelf_store, - &language_iter, - COLUMN_INCONSISTENT, TRUE, - -1); - } - } - - /* The language will have at least one book, so we move iter to it */ - first_book_iter = language_iter; - gtk_tree_model_iter_next (GTK_TREE_MODEL (priv->bookshelf_store), &first_book_iter); - - /* Find next possible book in language group */ - preferences_bookshelf_find_book (prefs, - book, - &first_book_iter, - NULL, - NULL, - &next_book_iter, - &next_book_iter_found); - if (!next_book_iter_found) { - gtk_list_store_append (priv->bookshelf_store, - &book_iter); - } else { - gtk_list_store_insert_before (priv->bookshelf_store, - &book_iter, - &next_book_iter); - } - } - - /* Add new item with indented title */ - indented_title = g_strdup_printf (" %s", dh_book_get_title (book)); - gtk_list_store_set (priv->bookshelf_store, - &book_iter, - COLUMN_ENABLED, dh_book_get_enabled (book), - COLUMN_TITLE, indented_title, - COLUMN_BOOK, book, - COLUMN_WEIGHT, PANGO_WEIGHT_NORMAL, - COLUMN_INCONSISTENT, FALSE, - -1); - g_free (indented_title); - } else { - /* No language grouping, just order by book title */ - GtkTreeIter next_book_iter; - gboolean next_book_iter_found; - - preferences_bookshelf_find_book (prefs, - book, - NULL, - NULL, - NULL, - &next_book_iter, - &next_book_iter_found); - if (!next_book_iter_found) { - gtk_list_store_append (priv->bookshelf_store, - &book_iter); - } else { - gtk_list_store_insert_before (priv->bookshelf_store, - &book_iter, - &next_book_iter); - } - - gtk_list_store_set (priv->bookshelf_store, - &book_iter, - COLUMN_ENABLED, dh_book_get_enabled (book), - COLUMN_TITLE, dh_book_get_title (book), - COLUMN_BOOK, book, - COLUMN_WEIGHT, PANGO_WEIGHT_NORMAL, - -1); - } -} - -static void -preferences_bookshelf_populate_store (DhPreferences *prefs) -{ - DhBookManager *book_manager; - GList *l; - gboolean group_by_language; - - book_manager = dh_book_manager_get_singleton (); - group_by_language = dh_book_manager_get_group_by_language (book_manager); - - /* This list already comes ordered, but we don't care */ - for (l = dh_book_manager_get_books (book_manager); - l; - l = g_list_next (l)) { - preferences_bookshelf_add_book_to_store (prefs, - DH_BOOK (l->data), - group_by_language); - } -} - -static void -preferences_bookshelf_group_by_language_cb (GObject *object, - GParamSpec *pspec, - DhPreferences *prefs) -{ - preferences_bookshelf_clean_store (prefs); - preferences_bookshelf_populate_store (prefs); -} - -static void -preferences_bookshelf_set_language_inconsistent (DhPreferences *prefs, - const gchar *language) -{ - DhPreferencesPrivate *priv = dh_preferences_get_instance_private (prefs); - GtkTreeIter loop_iter; - GtkTreeIter language_iter; - gboolean language_iter_found; - gboolean one_book_enabled = FALSE; - gboolean one_book_disabled = FALSE; - - preferences_bookshelf_find_language_group (prefs, - language, - &language_iter, - &language_iter_found, - NULL, - NULL); - if (!language_iter_found) { - return; - } - - loop_iter = language_iter; - while (gtk_tree_model_iter_next (GTK_TREE_MODEL (priv->bookshelf_store), - &loop_iter)) { - DhBook *book; - gboolean enabled; - - gtk_tree_model_get (GTK_TREE_MODEL (priv->bookshelf_store), - &loop_iter, - COLUMN_BOOK, &book, - COLUMN_ENABLED, &enabled, - -1); - if (!book) { - /* Reached next language group */ - break; - } - g_object_unref (book); - - if (enabled) - one_book_enabled = TRUE; - else - one_book_disabled = TRUE; - - if (one_book_enabled == one_book_disabled) - break; - } - - /* If at least one book is enabled AND another book is disabled, - * we need to set inconsistent state */ - if (one_book_enabled == one_book_disabled) { - gtk_list_store_set (priv->bookshelf_store, &language_iter, - COLUMN_INCONSISTENT, TRUE, - -1); - return; - } - - gtk_list_store_set (priv->bookshelf_store, &language_iter, - COLUMN_ENABLED, one_book_enabled, - COLUMN_INCONSISTENT, FALSE, - -1); -} - -static void -preferences_bookshelf_book_deleted_cb (DhBookManager *book_manager, - DhBook *book, - DhPreferences *prefs) -{ - DhPreferencesPrivate *priv = dh_preferences_get_instance_private (prefs); - GtkTreeIter exact_iter; - gboolean exact_iter_found; - - preferences_bookshelf_find_book (prefs, - book, - NULL, - &exact_iter, - &exact_iter_found, - NULL, - NULL); - if (exact_iter_found) { - gtk_list_store_remove (priv->bookshelf_store, &exact_iter); - preferences_bookshelf_set_language_inconsistent (prefs, dh_book_get_language (book)); - } -} - -static void -preferences_bookshelf_book_created_cb (DhBookManager *book_manager, - DhBook *book, - DhPreferences *prefs) -{ - gboolean group_by_language; - - group_by_language = dh_book_manager_get_group_by_language (book_manager); - preferences_bookshelf_add_book_to_store (prefs, book, group_by_language); -} - -static void -preferences_bookshelf_tree_selection_toggled_cb (GtkCellRendererToggle *cell_renderer, - gchar *path, - DhPreferences *prefs) -{ - DhPreferencesPrivate *priv = dh_preferences_get_instance_private (prefs); - DhBookManager *book_manager; - GtkTreeIter iter; - - book_manager = dh_book_manager_get_singleton (); - - if (gtk_tree_model_get_iter_from_string (GTK_TREE_MODEL (priv->bookshelf_store), - &iter, - path)) { - gpointer book = NULL; - gboolean enabled; - - gtk_tree_model_get (GTK_TREE_MODEL (priv->bookshelf_store), - &iter, - COLUMN_BOOK, &book, - COLUMN_ENABLED, &enabled, - -1); - - if (book) { - /* Update book conf */ - dh_book_set_enabled (book, !enabled); - - gtk_list_store_set (priv->bookshelf_store, &iter, - COLUMN_ENABLED, !enabled, - -1); - /* Now we need to look for the language group of this item, - * in order to set the inconsistent state if applies */ - if (dh_book_manager_get_group_by_language (book_manager)) { - preferences_bookshelf_set_language_inconsistent (prefs, dh_book_get_language (book)); - } - - } else { - GtkTreeIter loop_iter; - - /* We should only reach this if we are grouping by language */ - g_assert (dh_book_manager_get_group_by_language (book_manager) == TRUE); - - /* Set new status in the language group item */ - gtk_list_store_set (priv->bookshelf_store, &iter, - COLUMN_ENABLED, !enabled, - COLUMN_INCONSISTENT, FALSE, - -1); - - /* And set new status in all books of the same language */ - loop_iter = iter; - while (gtk_tree_model_iter_next (GTK_TREE_MODEL (priv->bookshelf_store), - &loop_iter)) { - gtk_tree_model_get (GTK_TREE_MODEL (priv->bookshelf_store), - &loop_iter, - COLUMN_BOOK, &book, - -1); - if (!book) { - /* Found next language group, finish */ - return; - } - - /* Update book conf */ - dh_book_set_enabled (book, !enabled); - - gtk_list_store_set (priv->bookshelf_store, - &loop_iter, - COLUMN_ENABLED, !enabled, - -1); - } - } - } -} - -static void -dh_preferences_init (DhPreferences *prefs) -{ - DhPreferencesPrivate *priv; - DhBookManager *book_manager; - DhSettings *settings; - GSettings *settings_fonts; - GSettings *settings_contents; - - priv = dh_preferences_get_instance_private (prefs); - - gtk_widget_init_template (GTK_WIDGET (prefs)); - - book_manager = dh_book_manager_get_singleton (); - - g_signal_connect_object (book_manager, - "book-created", - G_CALLBACK (preferences_bookshelf_book_created_cb), - prefs, - 0); - - g_signal_connect_object (book_manager, - "book-deleted", - G_CALLBACK (preferences_bookshelf_book_deleted_cb), - prefs, - 0); - - g_signal_connect_object (book_manager, - "notify::group-by-language", - G_CALLBACK (preferences_bookshelf_group_by_language_cb), - prefs, - 0); - - /* setup GSettings bindings */ - settings = dh_settings_get_singleton (); - settings_fonts = dh_settings_peek_fonts_settings (settings); - settings_contents = dh_settings_peek_contents_settings (settings); - g_settings_bind (settings_fonts, "use-system-fonts", - priv->system_fonts_button, "active", - G_SETTINGS_BIND_DEFAULT); - g_settings_bind (settings_fonts, "use-system-fonts", - priv->fonts_grid, "sensitive", - G_SETTINGS_BIND_DEFAULT | G_SETTINGS_BIND_INVERT_BOOLEAN); - g_settings_bind (settings_fonts, "fixed-font", - priv->fixed_font_button, "font-name", - G_SETTINGS_BIND_DEFAULT); - g_settings_bind (settings_fonts, "variable-font", - priv->variable_font_button, "font-name", - G_SETTINGS_BIND_DEFAULT); - - g_settings_bind (settings_contents, "group-books-by-language", - priv->bookshelf_group_by_language_button, "active", - G_SETTINGS_BIND_DEFAULT); - - g_signal_connect (priv->bookshelf_enabled_toggle, - "toggled", - G_CALLBACK (preferences_bookshelf_tree_selection_toggled_cb), - prefs); - - preferences_bookshelf_populate_store (prefs); -} - -void -dh_preferences_show_dialog (GtkWindow *parent) -{ - g_return_if_fail (GTK_IS_WINDOW (parent)); - - if (prefs_dialog == NULL) { - prefs_dialog = GTK_WIDGET (g_object_new (DH_TYPE_PREFERENCES, - "use-header-bar", 1, - NULL)); - g_signal_connect (prefs_dialog, - "destroy", - G_CALLBACK (gtk_widget_destroyed), - &prefs_dialog); - } - - if (parent != gtk_window_get_transient_for (GTK_WINDOW (prefs_dialog))) { - gtk_window_set_transient_for (GTK_WINDOW (prefs_dialog), - parent); - gtk_window_set_destroy_with_parent (GTK_WINDOW (prefs_dialog), TRUE); - } - - gtk_window_present (GTK_WINDOW (prefs_dialog)); -}