GUI: add basic configuration
It is simply not feasible to write the text file by hand on Windows.
This commit is contained in:
parent
862cde36ae
commit
531f18d827
2
LICENSE
2
LICENSE
@ -1,4 +1,4 @@
|
|||||||
Copyright (c) 2013 - 2023, Přemysl Eric Janouch <p@janouch.name>
|
Copyright (c) 2013 - 2024, Přemysl Eric Janouch <p@janouch.name>
|
||||||
|
|
||||||
Permission to use, copy, modify, and/or distribute this software for any
|
Permission to use, copy, modify, and/or distribute this software for any
|
||||||
purpose with or without fee is hereby granted.
|
purpose with or without fee is hereby granted.
|
||||||
|
21
README.adoc
21
README.adoc
@ -6,14 +6,15 @@ of StarDict dictionaries, and is inspired by the dictionary component
|
|||||||
of PC Translator. I was unsuccessful in finding any free software of this kind,
|
of PC Translator. I was unsuccessful in finding any free software of this kind,
|
||||||
and thus decided to write my own.
|
and thus decided to write my own.
|
||||||
|
|
||||||
The project is covered by a permissive license, unlike vast majority of other
|
The program offers both a terminal user interface, and a GTK+ 3 based UI.
|
||||||
similar projects, and can serve as a base for implementing other dictionary
|
The styling of the latter will follow your theme, and may be customized
|
||||||
software.
|
from 'gtk.css'.
|
||||||
|
|
||||||
image::tdv.png[align="center"]
|
image::tdv.png[align="center"]
|
||||||
|
|
||||||
As a recent addition, the program also offers a GTK+ 3 based user interface,
|
The project is covered by a permissive license, unlike vast majority of other
|
||||||
whose styling will follow your theme, and may be customized from 'gtk.css'.
|
similar projects, and can serve as a base for implementing other dictionary
|
||||||
|
software.
|
||||||
|
|
||||||
Packages
|
Packages
|
||||||
--------
|
--------
|
||||||
@ -92,14 +93,8 @@ https://mega.co.nz/#!axtD0QRK!sbtBgizksyfkPqKvKEgr8GQ11rsWhtqyRgUUV0B7pwg[CZ <--
|
|||||||
|
|
||||||
Further Development
|
Further Development
|
||||||
-------------------
|
-------------------
|
||||||
While I've been successfully using 'tdv' for many years now, some issues
|
Lacking configuration, standard StarDict locations should be scanned.
|
||||||
should be addressed before including the software in regular Linux and/or
|
We should try harder to display arbitrary dictionaries sensibly.
|
||||||
BSD distributions:
|
|
||||||
|
|
||||||
- The GUI is awkward to configure.
|
|
||||||
- Lacking configuration, standard StarDict locations should be scanned.
|
|
||||||
|
|
||||||
Given all issues with the file format, it might be better to start anew.
|
|
||||||
|
|
||||||
Contributing and Support
|
Contributing and Support
|
||||||
------------------------
|
------------------------
|
||||||
|
@ -357,6 +357,20 @@ error:
|
|||||||
return ret_val;
|
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.
|
/// List all dictionary files located in a path.
|
||||||
/// @return GList<StardictInfo *>. Deallocate the list with:
|
/// @return GList<StardictInfo *>. Deallocate the list with:
|
||||||
/// @code
|
/// @code
|
||||||
@ -377,12 +391,10 @@ stardict_list_dictionaries (const gchar *path)
|
|||||||
continue;
|
continue;
|
||||||
|
|
||||||
gchar *filename = g_build_filename (path, name, NULL);
|
gchar *filename = g_build_filename (path, name, NULL);
|
||||||
StardictInfo *ifo = g_new (StardictInfo, 1);
|
StardictInfo *ifo = stardict_info_new (filename, NULL);
|
||||||
if (load_ifo (ifo, filename, NULL))
|
|
||||||
dicts = g_list_append (dicts, ifo);
|
|
||||||
else
|
|
||||||
g_free (ifo);
|
|
||||||
g_free (filename);
|
g_free (filename);
|
||||||
|
if (ifo)
|
||||||
|
dicts = g_list_append (dicts, ifo);
|
||||||
}
|
}
|
||||||
g_dir_close (dir);
|
g_dir_close (dir);
|
||||||
g_pattern_spec_free (ps);
|
g_pattern_spec_free (ps);
|
||||||
|
@ -108,6 +108,7 @@ GQuark stardict_error_quark (void);
|
|||||||
|
|
||||||
// --- Dictionary information --------------------------------------------------
|
// --- 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_path (StardictInfo *sdi) G_GNUC_PURE;
|
||||||
const gchar *stardict_info_get_book_name (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;
|
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
|
* 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
|
* Permission to use, copy, modify, and/or distribute this software for any
|
||||||
* purpose with or without fee is hereby granted.
|
* purpose with or without fee is hereby granted.
|
||||||
@ -295,6 +295,8 @@ show_error_dialog (GError *error)
|
|||||||
g_error_free (error);
|
g_error_free (error);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// --- Loading -----------------------------------------------------------------
|
||||||
|
|
||||||
static void
|
static void
|
||||||
on_new_dictionaries_loaded (G_GNUC_UNUSED GObject* source_object,
|
on_new_dictionaries_loaded (G_GNUC_UNUSED GObject* source_object,
|
||||||
GAsyncResult* res, G_GNUC_UNUSED gpointer user_data)
|
GAsyncResult* res, G_GNUC_UNUSED gpointer user_data)
|
||||||
@ -360,8 +362,8 @@ reload_dictionaries (GPtrArray *new_dictionaries, GError **error)
|
|||||||
return TRUE;
|
return TRUE;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static GtkWidget *
|
||||||
on_open (G_GNUC_UNUSED GtkMenuItem *item, G_GNUC_UNUSED gpointer data)
|
new_open_dialog (void)
|
||||||
{
|
{
|
||||||
// The default is local-only. Paths are returned absolute.
|
// The default is local-only. Paths are returned absolute.
|
||||||
GtkWidget *dialog = gtk_file_chooser_dialog_new (_("Open dictionary"),
|
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);
|
GtkFileChooser *chooser = GTK_FILE_CHOOSER (dialog);
|
||||||
gtk_file_chooser_add_filter (chooser, filter);
|
gtk_file_chooser_add_filter (chooser, filter);
|
||||||
gtk_file_chooser_set_select_multiple (chooser, TRUE);
|
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 =
|
GPtrArray *new_dictionaries =
|
||||||
g_ptr_array_new_with_free_func ((GDestroyNotify) dictionary_destroy);
|
g_ptr_array_new_with_free_func ((GDestroyNotify) dictionary_destroy);
|
||||||
if (gtk_dialog_run (GTK_DIALOG (dialog)) == GTK_RESPONSE_ACCEPT)
|
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);
|
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
|
static void
|
||||||
on_destroy (G_GNUC_UNUSED GtkWidget *widget, G_GNUC_UNUSED gpointer data)
|
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);
|
die_with_dialog (error->message);
|
||||||
|
|
||||||
if (!new_dictionaries->len)
|
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"));
|
"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.
|
// Some Adwaita stupidity, plus defaults for our own widget.
|
||||||
// All the named colours have been there since GNOME 3.4
|
// 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..."));
|
GtkWidget *item_open = gtk_menu_item_new_with_mnemonic (_("_Open..."));
|
||||||
g_signal_connect (item_open, "activate", G_CALLBACK (on_open), NULL);
|
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;
|
g.watch_selection = TRUE;
|
||||||
GtkWidget *item_selection =
|
GtkWidget *item_selection =
|
||||||
gtk_check_menu_item_new_with_mnemonic (_("_Follow selection"));
|
gtk_check_menu_item_new_with_mnemonic (_("_Follow selection"));
|
||||||
@ -515,6 +813,7 @@ gui_main (char *argv[])
|
|||||||
GtkWidget *menu = gtk_menu_new ();
|
GtkWidget *menu = gtk_menu_new ();
|
||||||
gtk_widget_set_halign (menu, GTK_ALIGN_END);
|
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_open);
|
||||||
|
gtk_menu_shell_append (GTK_MENU_SHELL (menu), item_settings);
|
||||||
#ifndef G_OS_WIN32
|
#ifndef G_OS_WIN32
|
||||||
gtk_menu_shell_append (GTK_MENU_SHELL (menu), item_selection);
|
gtk_menu_shell_append (GTK_MENU_SHELL (menu), item_selection);
|
||||||
#endif // ! G_OS_WIN32
|
#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
|
// which is completely undocumented
|
||||||
g_key_file_load_from_dirs (key_file,
|
g_key_file_load_from_dirs (key_file,
|
||||||
PROJECT_NAME G_DIR_SEPARATOR_S PROJECT_NAME ".conf",
|
PROJECT_NAME G_DIR_SEPARATOR_S PROJECT_NAME ".conf",
|
||||||
paths, NULL, 0, &e);
|
paths, NULL, G_KEY_FILE_KEEP_COMMENTS, &e);
|
||||||
g_free (paths);
|
g_free (paths);
|
||||||
if (!e)
|
if (!e)
|
||||||
return key_file;
|
return key_file;
|
||||||
@ -236,6 +236,25 @@ load_project_config_file (GError **error)
|
|||||||
return NULL;
|
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 -----------------------------------------------------------------
|
// --- Loading -----------------------------------------------------------------
|
||||||
|
|
||||||
void
|
void
|
||||||
|
@ -54,6 +54,7 @@ gchar *resolve_relative_config_filename (const gchar *filename);
|
|||||||
gchar *resolve_filename
|
gchar *resolve_filename
|
||||||
(const gchar *filename, gchar *(*relative_cb) (const char *));
|
(const gchar *filename, gchar *(*relative_cb) (const char *));
|
||||||
GKeyFile *load_project_config_file (GError **error);
|
GKeyFile *load_project_config_file (GError **error);
|
||||||
|
gboolean save_project_config_file (GKeyFile *key_file, GError **error);
|
||||||
|
|
||||||
// --- Loading -----------------------------------------------------------------
|
// --- Loading -----------------------------------------------------------------
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user