/* -*- 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 <toabctl@gnome.org>
*
* 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 <http://www.gnu.org/licenses/>.
*/
#include "config.h"
#include "dh-preferences.h"
#include <string.h>
#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 (exact_found && 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 && exact_found &&
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 && next_found &&
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 && exact_found &&
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 && next_found &&
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));
}