Blob Blame History Raw
/*
 * This file is part of gspell, a spell-checking library.
 *
 * Copyright 2002 - Paolo Maggi
 * Copyright 2015, 2016 - Sébastien Wilmet
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this library; if not, see <http://www.gnu.org/licenses/>.
 */

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

#include "gspell-language-chooser-dialog.h"
#include "gspell-language-chooser.h"

/**
 * SECTION:language-chooser-dialog
 * @Short_description: Dialog to choose a GspellLanguage
 * @Title: GspellLanguageChooserDialog
 * @See_also: #GspellLanguage, #GspellLanguageChooser
 *
 * #GspellLanguageChooserDialog is a #GtkDialog to choose an available
 * #GspellLanguage. #GspellLanguageChooserDialog implements the
 * #GspellLanguageChooser interface.
 *
 * The #GspellLanguageChooser:language and #GspellLanguageChooser:language-code
 * properties are updated only when the Select button is pressed or when a row
 * is activated (e.g. with a double-click).
 *
 * The application is responsible to destroy the dialog, typically when the
 * #GtkDialog::response signal has been received or gtk_dialog_run() has
 * returned.
 */

typedef struct _GspellLanguageChooserDialogPrivate GspellLanguageChooserDialogPrivate;

struct _GspellLanguageChooserDialogPrivate
{
	GtkTreeView *treeview;
	const GspellLanguage *language;
	guint default_language : 1;
};

enum
{
	PROP_0,
	PROP_LANGUAGE,
	PROP_LANGUAGE_CODE,
};

enum
{
	COLUMN_LANGUAGE_NAME,
	COLUMN_LANGUAGE_POINTER,
	N_COLUMNS
};

static void gspell_language_chooser_dialog_iface_init (GspellLanguageChooserInterface *iface);

G_DEFINE_TYPE_WITH_CODE (GspellLanguageChooserDialog,
			 gspell_language_chooser_dialog,
			 GTK_TYPE_DIALOG,
			 G_ADD_PRIVATE (GspellLanguageChooserDialog)
			 G_IMPLEMENT_INTERFACE (GSPELL_TYPE_LANGUAGE_CHOOSER,
						gspell_language_chooser_dialog_iface_init))

static void
scroll_to_selected (GtkTreeView *tree_view)
{
	GtkTreeModel *model;
	GtkTreeSelection *selection;
	GtkTreeIter iter;

	model = gtk_tree_view_get_model (tree_view);
	g_return_if_fail (model != NULL);

	selection = gtk_tree_view_get_selection (tree_view);

	if (gtk_tree_selection_get_selected (selection, NULL, &iter))
	{
		GtkTreePath *path;

		path = gtk_tree_model_get_path (model, &iter);
		g_return_if_fail (path != NULL);

		gtk_tree_view_scroll_to_cell (tree_view, path, NULL, TRUE, 1.0, 0.0);
		gtk_tree_path_free (path);
	}
}

static const GspellLanguage *
gspell_language_chooser_dialog_get_language_full (GspellLanguageChooser *chooser,
						  gboolean              *default_language)
{
	GspellLanguageChooserDialog *dialog;
	GspellLanguageChooserDialogPrivate *priv;

	dialog = GSPELL_LANGUAGE_CHOOSER_DIALOG (chooser);
	priv = gspell_language_chooser_dialog_get_instance_private (dialog);

	if (default_language != NULL)
	{
		*default_language = priv->default_language;
	}

	return priv->language;
}

static void
gspell_language_chooser_dialog_set_language (GspellLanguageChooser *chooser,
					     const GspellLanguage  *language_param)
{
	GspellLanguageChooserDialog *dialog;
	GspellLanguageChooserDialogPrivate *priv;
	const GspellLanguage *language;
	GtkTreeSelection *selection;
	GtkTreeModel *model;
	GtkTreeIter iter;

	dialog = GSPELL_LANGUAGE_CHOOSER_DIALOG (chooser);
	priv = gspell_language_chooser_dialog_get_instance_private (dialog);

	language = language_param;

	if (language == NULL)
	{
		language = gspell_language_get_default ();
	}

	selection = gtk_tree_view_get_selection (priv->treeview);

	if (language == NULL)
	{
		gboolean notify_language_code = FALSE;

		gtk_tree_selection_unselect_all (selection);

		/* Update first the full state before notifying the properties. */
		if (!priv->default_language)
		{
			priv->default_language = TRUE;
			notify_language_code = TRUE;
		}

		if (priv->language != NULL)
		{
			priv->language = NULL;
			g_object_notify (G_OBJECT (dialog), "language");
		}

		if (notify_language_code)
		{
			g_object_notify (G_OBJECT (dialog), "language-code");
		}

		return;
	}

	model = gtk_tree_view_get_model (priv->treeview);

	if (!gtk_tree_model_get_iter_first (model, &iter))
	{
		goto warning;
	}

	do
	{
		const GspellLanguage *cur_lang;

		gtk_tree_model_get (model, &iter,
				    COLUMN_LANGUAGE_POINTER, &cur_lang,
				    -1);

		if (language == cur_lang)
		{
			gboolean default_language;
			gboolean notify_language_code = FALSE;

			gtk_tree_selection_select_iter (selection, &iter);
			scroll_to_selected (priv->treeview);

			/* Update first the full state before notifying the properties. */

			default_language = language_param == NULL;

			if (priv->default_language != default_language)
			{
				priv->default_language = default_language;
				notify_language_code = TRUE;
			}

			if (priv->language != language)
			{
				priv->language = language;
				g_object_notify (G_OBJECT (dialog), "language");
				notify_language_code = TRUE;
			}

			if (notify_language_code)
			{
				g_object_notify (G_OBJECT (dialog), "language-code");
			}

			return;
		}
	}
	while (gtk_tree_model_iter_next (model, &iter));

warning:
	g_warning ("GspellLanguageChooserDialog: setting language failed, language not found.");
}

static void
gspell_language_chooser_dialog_iface_init (GspellLanguageChooserInterface *iface)
{
	iface->get_language_full = gspell_language_chooser_dialog_get_language_full;
	iface->set_language = gspell_language_chooser_dialog_set_language;
}

static void
gspell_language_chooser_dialog_get_property (GObject    *object,
					     guint       prop_id,
					     GValue     *value,
					     GParamSpec *pspec)
{
	GspellLanguageChooser *chooser = GSPELL_LANGUAGE_CHOOSER (object);

	switch (prop_id)
	{
		case PROP_LANGUAGE:
			g_value_set_boxed (value, gspell_language_chooser_get_language (chooser));
			break;

		case PROP_LANGUAGE_CODE:
			g_value_set_string (value, gspell_language_chooser_get_language_code (chooser));
			break;

		default:
			G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
			break;
	}
}

static void
gspell_language_chooser_dialog_set_property (GObject      *object,
					     guint         prop_id,
					     const GValue *value,
					     GParamSpec   *pspec)
{
	GspellLanguageChooser *chooser = GSPELL_LANGUAGE_CHOOSER (object);

	switch (prop_id)
	{
		case PROP_LANGUAGE:
			gspell_language_chooser_set_language (chooser, g_value_get_boxed (value));
			break;

		case PROP_LANGUAGE_CODE:
			gspell_language_chooser_set_language_code (chooser, g_value_get_string (value));
			break;

		default:
			G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
			break;
	}
}

static void
gspell_language_chooser_dialog_constructed (GObject *object)
{
	gint use_header_bar;

	if (G_OBJECT_CLASS (gspell_language_chooser_dialog_parent_class)->constructed != NULL)
	{
		G_OBJECT_CLASS (gspell_language_chooser_dialog_parent_class)->constructed (object);
	}

	g_object_get (object,
		      "use-header-bar", &use_header_bar,
		      NULL);

	if (use_header_bar)
	{
		/* Avoid the title being ellipsized, if possible (for translations too). */
		gtk_widget_set_size_request (GTK_WIDGET (object), 450, -1);
	}
}

static void
dialog_response_cb (GtkDialog *gtk_dialog,
		    gint       response)
{
	GspellLanguageChooserDialog *dialog;
	GspellLanguageChooserDialogPrivate *priv;
	GtkTreeSelection *selection;
	GtkTreeModel *model;
	GtkTreeIter iter;
	const GspellLanguage *lang;
	gboolean notify_language_code = FALSE;

	if (response != GTK_RESPONSE_OK)
	{
		return;
	}

	dialog = GSPELL_LANGUAGE_CHOOSER_DIALOG (gtk_dialog);
	priv = gspell_language_chooser_dialog_get_instance_private (dialog);

	selection = gtk_tree_view_get_selection (priv->treeview);

	if (!gtk_tree_selection_get_selected (selection, &model, &iter))
	{
		return;
	}

	gtk_tree_model_get (model, &iter,
			    COLUMN_LANGUAGE_POINTER, &lang,
			    -1);

	/* Update first the full state before notifying the properties. */

	if (priv->default_language)
	{
		priv->default_language = FALSE;
		notify_language_code = TRUE;
	}

	if (priv->language != lang)
	{
		priv->language = lang;
		g_object_notify (G_OBJECT (dialog), "language");
		notify_language_code = TRUE;
	}

	if (notify_language_code)
	{
		g_object_notify (G_OBJECT (dialog), "language-code");
	}
}

static void
gspell_language_chooser_dialog_class_init (GspellLanguageChooserDialogClass *klass)
{
	GObjectClass *object_class = G_OBJECT_CLASS (klass);
	GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);

	object_class->get_property = gspell_language_chooser_dialog_get_property;
	object_class->set_property = gspell_language_chooser_dialog_set_property;
	object_class->constructed = gspell_language_chooser_dialog_constructed;

	g_object_class_override_property (object_class, PROP_LANGUAGE, "language");
	g_object_class_override_property (object_class, PROP_LANGUAGE_CODE, "language-code");

	/* Bind class to template */
	gtk_widget_class_set_template_from_resource (widget_class, "/org/gnome/gspell/language-dialog.ui");
	gtk_widget_class_bind_template_child_private (widget_class, GspellLanguageChooserDialog, treeview);
}

static void
row_activated_cb (GtkTreeView                 *tree_view,
		  GtkTreePath                 *path,
		  GtkTreeViewColumn           *column,
		  GspellLanguageChooserDialog *dialog)
{
	gtk_dialog_response (GTK_DIALOG (dialog), GTK_RESPONSE_OK);
}

static void
populate_language_list (GspellLanguageChooserDialog *dialog)
{
	GspellLanguageChooserDialogPrivate *priv;
	GtkListStore *store;
	const GList *available_langs;
	const GList *l;

	priv = gspell_language_chooser_dialog_get_instance_private (dialog);

	store = GTK_LIST_STORE (gtk_tree_view_get_model (priv->treeview));

	available_langs = gspell_language_get_available ();

	for (l = available_langs; l != NULL; l = l->next)
	{
		const GspellLanguage *lang = l->data;
		const gchar *name;
		GtkTreeIter iter;

		name = gspell_language_get_name (lang);

		gtk_list_store_append (store, &iter);
		gtk_list_store_set (store, &iter,
				    COLUMN_LANGUAGE_NAME, name,
				    COLUMN_LANGUAGE_POINTER, lang,
				    -1);
	}
}

static void
gspell_language_chooser_dialog_init (GspellLanguageChooserDialog *dialog)
{
	GspellLanguageChooserDialogPrivate *priv;
	GtkListStore *store;
	GtkTreeSelection *selection;
	GtkTreeViewColumn *column;
	GtkCellRenderer *renderer;

	priv = gspell_language_chooser_dialog_get_instance_private (dialog);

	priv->default_language = TRUE;

	gtk_widget_init_template (GTK_WIDGET (dialog));

	store = gtk_list_store_new (N_COLUMNS, G_TYPE_STRING, GSPELL_TYPE_LANGUAGE);
	gtk_tree_view_set_model (priv->treeview, GTK_TREE_MODEL (store));
	g_object_unref (store);

	selection = gtk_tree_view_get_selection (priv->treeview);
	gtk_tree_selection_set_mode (selection, GTK_SELECTION_BROWSE);

	/* Add the language column */
	column = gtk_tree_view_column_new ();
	renderer = gtk_cell_renderer_text_new ();
	gtk_tree_view_column_pack_start (column, renderer, TRUE);
	gtk_tree_view_column_add_attribute (column, renderer,
					    "text", COLUMN_LANGUAGE_NAME);

	gtk_tree_view_append_column (priv->treeview, column);

	gtk_tree_view_set_search_column (priv->treeview, COLUMN_LANGUAGE_NAME);

	gtk_widget_grab_focus (GTK_WIDGET (priv->treeview));

	populate_language_list (dialog);

	g_signal_connect (priv->treeview,
			  "realize",
			  G_CALLBACK (scroll_to_selected),
			  dialog);

	g_signal_connect (priv->treeview,
			  "row-activated",
			  G_CALLBACK (row_activated_cb),
			  dialog);

	/* Be the first to receive the signal, to notify the property before the
	 * dialog gets destroyed by the app.
	 * It means that the behavior of the dialog is not fully overridable by
	 * an app, but I don't think it's really important and worst case a new
	 * GspellLanguageChooser implementation can be written. If our
	 * ::response handler was done in the object method handler, apps would
	 * need to connect to the ::response signal with the after flag to
	 * destroy the dialog, which is less convenient and needs more
	 * documentation.
	 */
	g_signal_connect (dialog,
			  "response",
			  G_CALLBACK (dialog_response_cb),
			  NULL);
}

/**
 * gspell_language_chooser_dialog_new:
 * @parent: transient parent of the dialog.
 * @current_language: (nullable): the #GspellLanguage to select initially, or
 *   %NULL to pick the default language.
 * @flags: #GtkDialogFlags
 *
 * Returns: a new #GspellLanguageChooserDialog widget.
 */
GtkWidget *
gspell_language_chooser_dialog_new (GtkWindow            *parent,
				    const GspellLanguage *current_language,
				    GtkDialogFlags        flags)
{
	g_return_val_if_fail (GTK_IS_WINDOW (parent), NULL);

	return g_object_new (GSPELL_TYPE_LANGUAGE_CHOOSER_DIALOG,
			     "transient-for", parent,
			     "language", current_language,
			     "modal", (flags & GTK_DIALOG_MODAL) != 0,
			     "destroy-with-parent", (flags & GTK_DIALOG_DESTROY_WITH_PARENT) != 0,
			     "use-header-bar", (flags & GTK_DIALOG_USE_HEADER_BAR) != 0,
			     NULL);
}

/* ex:set ts=8 noet: */