GUI: add basic configuration
It is simply not feasible to write the text file by hand on Windows.
This commit is contained in:
		@@ -357,6 +357,20 @@ error:
 | 
			
		||||
	return ret_val;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Read an .ifo file.
 | 
			
		||||
/// @return StardictInfo *. Deallocate with stardict_info_free();
 | 
			
		||||
StardictInfo *
 | 
			
		||||
stardict_info_new (const gchar *filename, GError **error)
 | 
			
		||||
{
 | 
			
		||||
	StardictInfo *ifo = g_new (StardictInfo, 1);
 | 
			
		||||
	if (!load_ifo (ifo, filename, error))
 | 
			
		||||
	{
 | 
			
		||||
		g_free (ifo);
 | 
			
		||||
		return NULL;
 | 
			
		||||
	}
 | 
			
		||||
	return ifo;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// List all dictionary files located in a path.
 | 
			
		||||
/// @return GList<StardictInfo *>. Deallocate the list with:
 | 
			
		||||
/// @code
 | 
			
		||||
@@ -377,12 +391,10 @@ stardict_list_dictionaries (const gchar *path)
 | 
			
		||||
			continue;
 | 
			
		||||
 | 
			
		||||
		gchar *filename = g_build_filename (path, name, NULL);
 | 
			
		||||
		StardictInfo *ifo = g_new (StardictInfo, 1);
 | 
			
		||||
		if (load_ifo (ifo, filename, NULL))
 | 
			
		||||
			dicts = g_list_append (dicts, ifo);
 | 
			
		||||
		else
 | 
			
		||||
			g_free (ifo);
 | 
			
		||||
		StardictInfo *ifo = stardict_info_new (filename, NULL);
 | 
			
		||||
		g_free (filename);
 | 
			
		||||
		if (ifo)
 | 
			
		||||
			dicts = g_list_append (dicts, ifo);
 | 
			
		||||
	}
 | 
			
		||||
	g_dir_close (dir);
 | 
			
		||||
	g_pattern_spec_free (ps);
 | 
			
		||||
 
 | 
			
		||||
@@ -108,6 +108,7 @@ GQuark stardict_error_quark (void);
 | 
			
		||||
 | 
			
		||||
// --- Dictionary information --------------------------------------------------
 | 
			
		||||
 | 
			
		||||
StardictInfo *stardict_info_new (const gchar *filename, GError **error);
 | 
			
		||||
const gchar *stardict_info_get_path (StardictInfo *sdi) G_GNUC_PURE;
 | 
			
		||||
const gchar *stardict_info_get_book_name (StardictInfo *sdi) G_GNUC_PURE;
 | 
			
		||||
gsize stardict_info_get_word_count (StardictInfo *sd) G_GNUC_PURE;
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										307
									
								
								src/tdv-gui.c
									
									
									
									
									
								
							
							
						
						
									
										307
									
								
								src/tdv-gui.c
									
									
									
									
									
								
							@@ -1,7 +1,7 @@
 | 
			
		||||
/*
 | 
			
		||||
 * StarDict GTK+ UI
 | 
			
		||||
 *
 | 
			
		||||
 * Copyright (c) 2020 - 2022, Přemysl Eric Janouch <p@janouch.name>
 | 
			
		||||
 * Copyright (c) 2020 - 2024, Přemysl Eric Janouch <p@janouch.name>
 | 
			
		||||
 *
 | 
			
		||||
 * Permission to use, copy, modify, and/or distribute this software for any
 | 
			
		||||
 * purpose with or without fee is hereby granted.
 | 
			
		||||
@@ -295,6 +295,8 @@ show_error_dialog (GError *error)
 | 
			
		||||
	g_error_free (error);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// --- Loading -----------------------------------------------------------------
 | 
			
		||||
 | 
			
		||||
static void
 | 
			
		||||
on_new_dictionaries_loaded (G_GNUC_UNUSED GObject* source_object,
 | 
			
		||||
	GAsyncResult* res, G_GNUC_UNUSED gpointer user_data)
 | 
			
		||||
@@ -360,8 +362,8 @@ reload_dictionaries (GPtrArray *new_dictionaries, GError **error)
 | 
			
		||||
	return TRUE;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static void
 | 
			
		||||
on_open (G_GNUC_UNUSED GtkMenuItem *item, G_GNUC_UNUSED gpointer data)
 | 
			
		||||
static GtkWidget *
 | 
			
		||||
new_open_dialog (void)
 | 
			
		||||
{
 | 
			
		||||
	// The default is local-only.  Paths are returned absolute.
 | 
			
		||||
	GtkWidget *dialog = gtk_file_chooser_dialog_new (_("Open dictionary"),
 | 
			
		||||
@@ -375,7 +377,14 @@ on_open (G_GNUC_UNUSED GtkMenuItem *item, G_GNUC_UNUSED gpointer data)
 | 
			
		||||
	GtkFileChooser *chooser = GTK_FILE_CHOOSER (dialog);
 | 
			
		||||
	gtk_file_chooser_add_filter (chooser, filter);
 | 
			
		||||
	gtk_file_chooser_set_select_multiple (chooser, TRUE);
 | 
			
		||||
	return dialog;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static void
 | 
			
		||||
on_open (G_GNUC_UNUSED GtkMenuItem *item, G_GNUC_UNUSED gpointer data)
 | 
			
		||||
{
 | 
			
		||||
	GtkWidget *dialog = new_open_dialog ();
 | 
			
		||||
	GtkFileChooser *chooser = GTK_FILE_CHOOSER (dialog);
 | 
			
		||||
	GPtrArray *new_dictionaries =
 | 
			
		||||
		g_ptr_array_new_with_free_func ((GDestroyNotify) dictionary_destroy);
 | 
			
		||||
	if (gtk_dialog_run (GTK_DIALOG (dialog)) == GTK_RESPONSE_ACCEPT)
 | 
			
		||||
@@ -431,6 +440,280 @@ on_drag_data_received (G_GNUC_UNUSED GtkWidget *widget, GdkDragContext *context,
 | 
			
		||||
		show_error_dialog (error);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// --- Settings ----------------------------------------------------------------
 | 
			
		||||
 | 
			
		||||
typedef struct settings_data            SettingsData;
 | 
			
		||||
 | 
			
		||||
enum
 | 
			
		||||
{
 | 
			
		||||
	SETTINGS_COLUMN_NAME,
 | 
			
		||||
	SETTINGS_COLUMN_PATH,
 | 
			
		||||
	SETTINGS_COLUMN_COUNT
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
struct settings_data
 | 
			
		||||
{
 | 
			
		||||
	GKeyFile *key_file;                 ///< Configuration file
 | 
			
		||||
	GtkTreeModel *model;                ///< GtkListStore
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
static void
 | 
			
		||||
settings_load (SettingsData *data)
 | 
			
		||||
{
 | 
			
		||||
	// We want to keep original comments, as well as any other data.
 | 
			
		||||
	GError *error = NULL;
 | 
			
		||||
	data->key_file = load_project_config_file (&error);
 | 
			
		||||
	if (!data->key_file)
 | 
			
		||||
	{
 | 
			
		||||
		if (error)
 | 
			
		||||
			show_error_dialog (error);
 | 
			
		||||
		data->key_file = g_key_file_new ();
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	GtkListStore *list_store = gtk_list_store_new (SETTINGS_COLUMN_COUNT,
 | 
			
		||||
		G_TYPE_STRING, G_TYPE_STRING);
 | 
			
		||||
	data->model = GTK_TREE_MODEL (list_store);
 | 
			
		||||
 | 
			
		||||
	const gchar *dictionaries = "Dictionaries";
 | 
			
		||||
	gchar **names =
 | 
			
		||||
		g_key_file_get_keys (data->key_file, dictionaries, NULL, NULL);
 | 
			
		||||
	if (!names)
 | 
			
		||||
		return;
 | 
			
		||||
 | 
			
		||||
	for (gsize i = 0; names[i]; i++)
 | 
			
		||||
	{
 | 
			
		||||
		gchar *path = g_key_file_get_string (data->key_file,
 | 
			
		||||
			dictionaries, names[i], NULL);
 | 
			
		||||
		if (!path)
 | 
			
		||||
			continue;
 | 
			
		||||
 | 
			
		||||
		GtkTreeIter iter = { 0 };
 | 
			
		||||
		gtk_list_store_append (list_store, &iter);
 | 
			
		||||
		gtk_list_store_set (list_store, &iter,
 | 
			
		||||
			SETTINGS_COLUMN_NAME, names[i], SETTINGS_COLUMN_PATH, path, -1);
 | 
			
		||||
		g_free (path);
 | 
			
		||||
	}
 | 
			
		||||
	g_strfreev (names);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static void
 | 
			
		||||
settings_save (SettingsData *data)
 | 
			
		||||
{
 | 
			
		||||
	const gchar *dictionaries = "Dictionaries";
 | 
			
		||||
	g_key_file_remove_group (data->key_file, dictionaries, NULL);
 | 
			
		||||
 | 
			
		||||
	GtkTreeIter iter = { 0 };
 | 
			
		||||
	gboolean valid = gtk_tree_model_get_iter_first (data->model, &iter);
 | 
			
		||||
	while (valid)
 | 
			
		||||
	{
 | 
			
		||||
		gchar *name = NULL, *path = NULL;
 | 
			
		||||
		gtk_tree_model_get (data->model, &iter,
 | 
			
		||||
			SETTINGS_COLUMN_NAME, &name, SETTINGS_COLUMN_PATH, &path, -1);
 | 
			
		||||
		if (name && path)
 | 
			
		||||
			g_key_file_set_string (data->key_file, dictionaries, name, path);
 | 
			
		||||
		g_free (name);
 | 
			
		||||
		g_free (path);
 | 
			
		||||
 | 
			
		||||
		valid = gtk_tree_model_iter_next (data->model, &iter);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	GError *e = NULL;
 | 
			
		||||
	if (!save_project_config_file (data->key_file, &e))
 | 
			
		||||
		show_error_dialog (e);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static void
 | 
			
		||||
on_settings_name_edited (G_GNUC_UNUSED GtkCellRendererText *cell,
 | 
			
		||||
	const gchar *path_string, const gchar *new_text, gpointer data)
 | 
			
		||||
{
 | 
			
		||||
	GtkTreeModel *model = GTK_TREE_MODEL (data);
 | 
			
		||||
	GtkTreePath *path = gtk_tree_path_new_from_string (path_string);
 | 
			
		||||
	GtkTreeIter iter = { 0 };
 | 
			
		||||
	gtk_tree_model_get_iter (model, &iter, path);
 | 
			
		||||
	gtk_list_store_set (GTK_LIST_STORE (model), &iter,
 | 
			
		||||
		SETTINGS_COLUMN_NAME, new_text, -1);
 | 
			
		||||
	gtk_tree_path_free (path);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static void
 | 
			
		||||
on_settings_path_edited (G_GNUC_UNUSED GtkCellRendererText *cell,
 | 
			
		||||
	const gchar *path_string, const gchar *new_text, gpointer data)
 | 
			
		||||
{
 | 
			
		||||
	GtkTreeModel *model = GTK_TREE_MODEL (data);
 | 
			
		||||
	GtkTreePath *path = gtk_tree_path_new_from_string (path_string);
 | 
			
		||||
	GtkTreeIter iter = { 0 };
 | 
			
		||||
	gtk_tree_model_get_iter (model, &iter, path);
 | 
			
		||||
	gtk_list_store_set (GTK_LIST_STORE (model), &iter,
 | 
			
		||||
		SETTINGS_COLUMN_PATH, new_text, -1);
 | 
			
		||||
	gtk_tree_path_free (path);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static void
 | 
			
		||||
on_settings_add (G_GNUC_UNUSED GtkButton *button, gpointer user_data)
 | 
			
		||||
{
 | 
			
		||||
	GtkWidget *dialog = new_open_dialog ();
 | 
			
		||||
	GtkFileChooser *chooser = GTK_FILE_CHOOSER (dialog);
 | 
			
		||||
 | 
			
		||||
	GSList *paths = NULL;
 | 
			
		||||
	if (gtk_dialog_run (GTK_DIALOG (dialog)) == GTK_RESPONSE_ACCEPT)
 | 
			
		||||
		paths = gtk_file_chooser_get_filenames (chooser);
 | 
			
		||||
	gtk_widget_destroy (dialog);
 | 
			
		||||
	// When the dialog is aborted, we simply add an empty list.
 | 
			
		||||
 | 
			
		||||
	GtkTreeView *tree_view = GTK_TREE_VIEW (user_data);
 | 
			
		||||
	gtk_tree_selection_unselect_all (gtk_tree_view_get_selection (tree_view));
 | 
			
		||||
	GtkTreeModel *model = gtk_tree_view_get_model (tree_view);
 | 
			
		||||
	GtkListStore *list_store = GTK_LIST_STORE (model);
 | 
			
		||||
 | 
			
		||||
	const gchar *home = g_get_home_dir ();
 | 
			
		||||
	for (GSList *iter = paths; iter; iter = iter->next)
 | 
			
		||||
	{
 | 
			
		||||
		GError *error = NULL;
 | 
			
		||||
		StardictInfo *ifo = stardict_info_new (iter->data, &error);
 | 
			
		||||
		g_free (iter->data);
 | 
			
		||||
		if (!ifo)
 | 
			
		||||
		{
 | 
			
		||||
			show_error_dialog (error);
 | 
			
		||||
			continue;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		// We also expand tildes, even on Windows, so no problem there.
 | 
			
		||||
		const gchar *path = stardict_info_get_path (ifo);
 | 
			
		||||
		gchar *tildified = g_str_has_prefix (stardict_info_get_path (ifo), home)
 | 
			
		||||
			? g_strdup_printf ("~%s", path + strlen (home))
 | 
			
		||||
			: g_strdup (path);
 | 
			
		||||
 | 
			
		||||
		GtkTreeIter iter = { 0 };
 | 
			
		||||
		gtk_list_store_append (list_store, &iter);
 | 
			
		||||
		gtk_list_store_set (list_store, &iter,
 | 
			
		||||
			SETTINGS_COLUMN_NAME, stardict_info_get_book_name (ifo),
 | 
			
		||||
			SETTINGS_COLUMN_PATH, tildified, -1);
 | 
			
		||||
		g_free (tildified);
 | 
			
		||||
		stardict_info_free (ifo);
 | 
			
		||||
	}
 | 
			
		||||
	g_slist_free (paths);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static void
 | 
			
		||||
on_settings_remove (G_GNUC_UNUSED GtkButton *button, gpointer user_data)
 | 
			
		||||
{
 | 
			
		||||
	GtkTreeView *tree_view = GTK_TREE_VIEW (user_data);
 | 
			
		||||
	GtkTreeSelection *selection = gtk_tree_view_get_selection (tree_view);
 | 
			
		||||
	GtkTreeModel *model = gtk_tree_view_get_model (tree_view);
 | 
			
		||||
	GtkListStore *list_store = GTK_LIST_STORE (model);
 | 
			
		||||
 | 
			
		||||
	GList *selected = gtk_tree_selection_get_selected_rows (selection, &model);
 | 
			
		||||
	for (GList *iter = selected; iter; iter = iter->next)
 | 
			
		||||
	{
 | 
			
		||||
		GtkTreePath *path = iter->data;
 | 
			
		||||
		iter->data = gtk_tree_row_reference_new (model, path);
 | 
			
		||||
		gtk_tree_path_free (path);
 | 
			
		||||
	}
 | 
			
		||||
	for (GList *iter = selected; iter; iter = iter->next)
 | 
			
		||||
	{
 | 
			
		||||
		GtkTreePath *path = gtk_tree_row_reference_get_path (iter->data);
 | 
			
		||||
		if (path)
 | 
			
		||||
		{
 | 
			
		||||
			GtkTreeIter tree_iter = { 0 };
 | 
			
		||||
			if (gtk_tree_model_get_iter (model, &tree_iter, path))
 | 
			
		||||
				gtk_list_store_remove (list_store, &tree_iter);
 | 
			
		||||
			gtk_tree_path_free (path);
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	g_list_free_full (selected, (GDestroyNotify) gtk_tree_row_reference_free);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static void
 | 
			
		||||
on_settings_selection_changed
 | 
			
		||||
	(GtkTreeSelection* selection, gpointer user_data)
 | 
			
		||||
{
 | 
			
		||||
	GtkWidget *remove = GTK_WIDGET (user_data);
 | 
			
		||||
	gtk_widget_set_sensitive (remove,
 | 
			
		||||
		gtk_tree_selection_count_selected_rows (selection) > 0);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static void
 | 
			
		||||
on_settings (G_GNUC_UNUSED GtkMenuItem *item, G_GNUC_UNUSED gpointer data)
 | 
			
		||||
{
 | 
			
		||||
	SettingsData sd = {};
 | 
			
		||||
	settings_load (&sd);
 | 
			
		||||
 | 
			
		||||
	GtkWidget *treeview = gtk_tree_view_new_with_model (sd.model);
 | 
			
		||||
	gtk_tree_view_set_reorderable (GTK_TREE_VIEW (treeview), TRUE);
 | 
			
		||||
	g_object_unref (sd.model);
 | 
			
		||||
 | 
			
		||||
	GtkCellRenderer *renderer = gtk_cell_renderer_text_new ();
 | 
			
		||||
	g_object_set (renderer, "editable", TRUE, NULL);
 | 
			
		||||
	g_signal_connect (renderer, "edited",
 | 
			
		||||
		G_CALLBACK (on_settings_name_edited), sd.model);
 | 
			
		||||
	GtkTreeViewColumn *column = gtk_tree_view_column_new_with_attributes
 | 
			
		||||
		(_("Name"), renderer, "text", SETTINGS_COLUMN_NAME, NULL);
 | 
			
		||||
	gtk_tree_view_append_column (GTK_TREE_VIEW (treeview), column);
 | 
			
		||||
 | 
			
		||||
	renderer = gtk_cell_renderer_text_new ();
 | 
			
		||||
	g_object_set (renderer, "editable", TRUE, NULL);
 | 
			
		||||
	g_signal_connect (renderer, "edited",
 | 
			
		||||
		G_CALLBACK (on_settings_path_edited), sd.model);
 | 
			
		||||
	column = gtk_tree_view_column_new_with_attributes
 | 
			
		||||
		(_("Path"), renderer, "text", SETTINGS_COLUMN_PATH, NULL);
 | 
			
		||||
	gtk_tree_view_append_column (GTK_TREE_VIEW (treeview), column);
 | 
			
		||||
 | 
			
		||||
	GtkWidget *scrolled = gtk_scrolled_window_new (NULL, NULL);
 | 
			
		||||
	gtk_scrolled_window_set_shadow_type
 | 
			
		||||
		(GTK_SCROLLED_WINDOW (scrolled), GTK_SHADOW_ETCHED_IN);
 | 
			
		||||
	gtk_container_add (GTK_CONTAINER (scrolled), treeview);
 | 
			
		||||
	GtkWidget *dialog = gtk_dialog_new_with_buttons (_("Settings"),
 | 
			
		||||
		GTK_WINDOW (g.window),
 | 
			
		||||
		GTK_DIALOG_MODAL,
 | 
			
		||||
		_("_Cancel"), GTK_RESPONSE_CANCEL,
 | 
			
		||||
		_("_Save"), GTK_RESPONSE_ACCEPT,
 | 
			
		||||
		NULL);
 | 
			
		||||
	gtk_dialog_set_default_response (GTK_DIALOG (dialog), GTK_RESPONSE_ACCEPT);
 | 
			
		||||
	gtk_window_set_default_size (GTK_WINDOW (dialog), 600, 400);
 | 
			
		||||
 | 
			
		||||
	GtkWidget *remove = gtk_button_new_with_mnemonic (_("_Remove"));
 | 
			
		||||
	gtk_widget_set_sensitive (remove, FALSE);
 | 
			
		||||
	g_signal_connect (remove, "clicked",
 | 
			
		||||
		G_CALLBACK (on_settings_remove), treeview);
 | 
			
		||||
 | 
			
		||||
	GtkWidget *add = gtk_button_new_with_mnemonic (_("_Add..."));
 | 
			
		||||
	g_signal_connect (add, "clicked",
 | 
			
		||||
		G_CALLBACK (on_settings_add), treeview);
 | 
			
		||||
 | 
			
		||||
	GtkTreeSelection *selection =
 | 
			
		||||
		gtk_tree_view_get_selection (GTK_TREE_VIEW (treeview));
 | 
			
		||||
	gtk_tree_selection_set_mode (selection, GTK_SELECTION_MULTIPLE);
 | 
			
		||||
	g_signal_connect (selection, "changed",
 | 
			
		||||
		G_CALLBACK (on_settings_selection_changed), remove);
 | 
			
		||||
 | 
			
		||||
	GtkWidget *box = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 6);
 | 
			
		||||
	gtk_box_pack_start (GTK_BOX (box),
 | 
			
		||||
		gtk_label_new (_("Here you can configure the default dictionaries.")),
 | 
			
		||||
		FALSE, FALSE, 0);
 | 
			
		||||
	gtk_box_pack_end (GTK_BOX (box), remove, FALSE, FALSE, 0);
 | 
			
		||||
	gtk_box_pack_end (GTK_BOX (box), add, FALSE, FALSE, 0);
 | 
			
		||||
 | 
			
		||||
	GtkWidget *content_area = gtk_dialog_get_content_area (GTK_DIALOG (dialog));
 | 
			
		||||
	g_object_set (content_area, "margin", 12, NULL);
 | 
			
		||||
	gtk_box_pack_start (GTK_BOX (content_area), box, FALSE, FALSE, 0);
 | 
			
		||||
	gtk_box_pack_start (GTK_BOX (content_area), scrolled, TRUE, TRUE, 12);
 | 
			
		||||
 | 
			
		||||
	gtk_widget_show_all (dialog);
 | 
			
		||||
	switch (gtk_dialog_run (GTK_DIALOG (dialog)))
 | 
			
		||||
	{
 | 
			
		||||
	case GTK_RESPONSE_NONE:
 | 
			
		||||
		break;
 | 
			
		||||
	case GTK_RESPONSE_ACCEPT:
 | 
			
		||||
		settings_save (&sd);
 | 
			
		||||
		// Fall through
 | 
			
		||||
	default:
 | 
			
		||||
		gtk_widget_destroy (dialog);
 | 
			
		||||
	}
 | 
			
		||||
	g_key_file_free (sd.key_file);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// --- Main --------------------------------------------------------------------
 | 
			
		||||
 | 
			
		||||
static void
 | 
			
		||||
on_destroy (G_GNUC_UNUSED GtkWidget *widget, G_GNUC_UNUSED gpointer data)
 | 
			
		||||
{
 | 
			
		||||
@@ -464,8 +747,19 @@ gui_main (char *argv[])
 | 
			
		||||
		die_with_dialog (error->message);
 | 
			
		||||
 | 
			
		||||
	if (!new_dictionaries->len)
 | 
			
		||||
		die_with_dialog (_("No dictionaries found either in "
 | 
			
		||||
	{
 | 
			
		||||
		GtkWidget *dialog = gtk_message_dialog_new (NULL, 0,
 | 
			
		||||
			GTK_MESSAGE_ERROR, GTK_BUTTONS_CLOSE, "%s",
 | 
			
		||||
			_("No dictionaries found either in "
 | 
			
		||||
			"the configuration or on the command line"));
 | 
			
		||||
		gtk_dialog_run (GTK_DIALOG (dialog));
 | 
			
		||||
		gtk_widget_destroy (dialog);
 | 
			
		||||
 | 
			
		||||
		// This is better than nothing.
 | 
			
		||||
		// Our GtkNotebook action widget would be invisible without any tabs.
 | 
			
		||||
		on_settings (NULL, NULL);
 | 
			
		||||
		exit (EXIT_SUCCESS);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Some Adwaita stupidity, plus defaults for our own widget.
 | 
			
		||||
	// All the named colours have been there since GNOME 3.4
 | 
			
		||||
@@ -504,6 +798,10 @@ gui_main (char *argv[])
 | 
			
		||||
	GtkWidget *item_open = gtk_menu_item_new_with_mnemonic (_("_Open..."));
 | 
			
		||||
	g_signal_connect (item_open, "activate", G_CALLBACK (on_open), NULL);
 | 
			
		||||
 | 
			
		||||
	GtkWidget *item_settings = gtk_menu_item_new_with_mnemonic (_("_Settings"));
 | 
			
		||||
	g_signal_connect (item_settings, "activate",
 | 
			
		||||
		G_CALLBACK (on_settings), NULL);
 | 
			
		||||
 | 
			
		||||
	g.watch_selection = TRUE;
 | 
			
		||||
	GtkWidget *item_selection =
 | 
			
		||||
		gtk_check_menu_item_new_with_mnemonic (_("_Follow selection"));
 | 
			
		||||
@@ -515,6 +813,7 @@ gui_main (char *argv[])
 | 
			
		||||
	GtkWidget *menu = gtk_menu_new ();
 | 
			
		||||
	gtk_widget_set_halign (menu, GTK_ALIGN_END);
 | 
			
		||||
	gtk_menu_shell_append (GTK_MENU_SHELL (menu), item_open);
 | 
			
		||||
	gtk_menu_shell_append (GTK_MENU_SHELL (menu), item_settings);
 | 
			
		||||
#ifndef G_OS_WIN32
 | 
			
		||||
	gtk_menu_shell_append (GTK_MENU_SHELL (menu), item_selection);
 | 
			
		||||
#endif  // ! G_OS_WIN32
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										21
									
								
								src/utils.c
									
									
									
									
									
								
							
							
						
						
									
										21
									
								
								src/utils.c
									
									
									
									
									
								
							@@ -222,7 +222,7 @@ load_project_config_file (GError **error)
 | 
			
		||||
	//   which is completely undocumented
 | 
			
		||||
	g_key_file_load_from_dirs (key_file,
 | 
			
		||||
		PROJECT_NAME G_DIR_SEPARATOR_S PROJECT_NAME ".conf",
 | 
			
		||||
		paths, NULL, 0, &e);
 | 
			
		||||
		paths, NULL, G_KEY_FILE_KEEP_COMMENTS, &e);
 | 
			
		||||
	g_free (paths);
 | 
			
		||||
	if (!e)
 | 
			
		||||
		return key_file;
 | 
			
		||||
@@ -236,6 +236,25 @@ load_project_config_file (GError **error)
 | 
			
		||||
	return NULL;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
gboolean
 | 
			
		||||
save_project_config_file (GKeyFile *key_file, GError **error)
 | 
			
		||||
{
 | 
			
		||||
	gchar *dirname =
 | 
			
		||||
		g_build_filename (g_get_user_config_dir (), PROJECT_NAME, NULL);
 | 
			
		||||
	(void) g_mkdir_with_parents (dirname, 0755);
 | 
			
		||||
	gchar *path = g_build_filename (dirname, PROJECT_NAME ".conf", NULL);
 | 
			
		||||
	g_free (dirname);
 | 
			
		||||
 | 
			
		||||
	gsize length = 0;
 | 
			
		||||
	gchar *data = g_key_file_to_data (key_file, &length, error);
 | 
			
		||||
	if (!data)
 | 
			
		||||
		return FALSE;
 | 
			
		||||
 | 
			
		||||
	gboolean result = g_file_set_contents (path, data, length, error);
 | 
			
		||||
	g_free (data);
 | 
			
		||||
	return result;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// --- Loading -----------------------------------------------------------------
 | 
			
		||||
 | 
			
		||||
void
 | 
			
		||||
 
 | 
			
		||||
@@ -54,6 +54,7 @@ gchar *resolve_relative_config_filename (const gchar *filename);
 | 
			
		||||
gchar *resolve_filename
 | 
			
		||||
	(const gchar *filename, gchar *(*relative_cb) (const char *));
 | 
			
		||||
GKeyFile *load_project_config_file (GError **error);
 | 
			
		||||
gboolean save_project_config_file (GKeyFile *key_file, GError **error);
 | 
			
		||||
 | 
			
		||||
// --- Loading -----------------------------------------------------------------
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user