aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--CMakeLists.txt59
-rw-r--r--LICENSE2
-rw-r--r--README.adoc19
m---------liberty0
-rw-r--r--src/stardict.c22
-rw-r--r--src/stardict.h1
-rw-r--r--src/tdv-gui.c307
-rw-r--r--src/utils.c27
-rw-r--r--src/utils.h1
-rw-r--r--tdv.pngbin40457 -> 36704 bytes
m---------termo0
11 files changed, 385 insertions, 53 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 36638ea..4d8cb00 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -78,7 +78,7 @@ if (USE_SYSTEM_TERMO)
if (NOT Termo_FOUND)
message (FATAL_ERROR "System termo library not found")
endif ()
-else ()
+elseif (NOT WIN32)
# We don't want the library to install, but EXCLUDE_FROM_ALL ignores tests
add_subdirectory (termo EXCLUDE_FROM_ALL)
file (WRITE ${PROJECT_BINARY_DIR}/CTestCustom.cmake
@@ -127,9 +127,9 @@ CHECK_FUNCTION_EXISTS ("resizeterm" HAVE_RESIZETERM)
# Localization
find_package (Gettext REQUIRED)
-file (GLOB project_PO_FILES ${PROJECT_SOURCE_DIR}/po/*.po)
+file (GLOB project_PO_FILES "${PROJECT_SOURCE_DIR}/po/*.po")
GETTEXT_CREATE_TRANSLATIONS (
- ${PROJECT_SOURCE_DIR}/po/${PROJECT_NAME}.pot
+ "${PROJECT_SOURCE_DIR}/po/${PROJECT_NAME}.pot"
ALL ${project_PO_FILES})
# Documentation
@@ -144,7 +144,7 @@ foreach (page "${PROJECT_NAME}.1")
set (page_output "${PROJECT_BINARY_DIR}/${page}")
list (APPEND project_MAN_PAGES "${page_output}")
if (ASCIIDOCTOR_EXECUTABLE)
- add_custom_command (OUTPUT ${page_output}
+ add_custom_command (OUTPUT "${page_output}"
COMMAND ${ASCIIDOCTOR_EXECUTABLE} -b manpage
-a release-version=${PROJECT_VERSION}
-o "${page_output}"
@@ -152,7 +152,7 @@ foreach (page "${PROJECT_NAME}.1")
DEPENDS "docs/${page}.adoc"
COMMENT "Generating man page for ${page}" VERBATIM)
elseif (A2X_EXECUTABLE)
- add_custom_command (OUTPUT ${page_output}
+ add_custom_command (OUTPUT "${page_output}"
COMMAND ${A2X_EXECUTABLE} --doctype manpage --format manpage
-a release-version=${PROJECT_VERSION}
-D "${PROJECT_BINARY_DIR}"
@@ -161,10 +161,10 @@ foreach (page "${PROJECT_NAME}.1")
COMMENT "Generating man page for ${page}" VERBATIM)
else ()
set (ASCIIMAN ${PROJECT_SOURCE_DIR}/liberty/tools/asciiman.awk)
- add_custom_command (OUTPUT ${page_output}
+ add_custom_command (OUTPUT "${page_output}"
COMMAND env LC_ALL=C asciidoc-release-version=${PROJECT_VERSION}
awk -f ${ASCIIMAN} "${PROJECT_SOURCE_DIR}/docs/${page}.adoc"
- > ${page_output}
+ > "${page_output}"
DEPENDS "docs/${page}.adoc" ${ASCIIMAN}
COMMENT "Generating man page for ${page}" VERBATIM)
endif ()
@@ -181,7 +181,7 @@ if (WIN32)
endif (WIN32)
set (project_common_headers
- ${PROJECT_BINARY_DIR}/config.h
+ "${PROJECT_BINARY_DIR}/config.h"
src/dictzip-input-stream.h
src/stardict.h
src/stardict-private.h
@@ -198,9 +198,9 @@ add_library (stardict OBJECT
set (project_common_sources $<TARGET_OBJECTS:stardict>)
# Generate a configuration file
-configure_file (${PROJECT_SOURCE_DIR}/config.h.in
- ${PROJECT_BINARY_DIR}/config.h)
-include_directories (${PROJECT_SOURCE_DIR} ${PROJECT_BINARY_DIR})
+configure_file ("${PROJECT_SOURCE_DIR}/config.h.in"
+ "${PROJECT_BINARY_DIR}/config.h")
+include_directories ("${PROJECT_SOURCE_DIR}" "${PROJECT_BINARY_DIR}")
# Build the main executable and link it
set (project_libraries
@@ -215,30 +215,31 @@ if (WITH_GUI)
include (IconUtils)
# The largest size is mainly for an appropriately sized Windows icon
- set (icon_base ${PROJECT_BINARY_DIR}/icons)
+ set (icon_base "${PROJECT_BINARY_DIR}/icons")
set (icon_png_list)
foreach (icon_size 16 32 48 256)
- icon_to_png (${PROJECT_NAME} ${PROJECT_SOURCE_DIR}/${PROJECT_NAME}.svg
- ${icon_size} ${icon_base} icon_png)
- list (APPEND icon_png_list ${icon_png})
+ icon_to_png (${PROJECT_NAME} "${PROJECT_SOURCE_DIR}/${PROJECT_NAME}.svg"
+ ${icon_size} "${icon_base}" icon_png)
+ list (APPEND icon_png_list "${icon_png}")
endforeach ()
add_custom_target (icons ALL DEPENDS ${icon_png_list})
endif ()
if (WIN32)
- set (icon_ico ${PROJECT_BINARY_DIR}/${PROJECT_NAME}.ico)
- icon_for_win32 (${icon_ico} "${icon_png_list}" "")
+ list (REMOVE_ITEM icon_png_list "${icon_png}")
+ set (icon_ico "${PROJECT_BINARY_DIR}/${PROJECT_NAME}.ico")
+ icon_for_win32 ("${icon_ico}" "${icon_png_list}" "${icon_png}")
- set (resource_file ${PROJECT_BINARY_DIR}/${PROJECT_NAME}.rc)
- list (APPEND project_sources ${resource_file})
- add_custom_command (OUTPUT ${resource_file}
+ set (resource_file "${PROJECT_BINARY_DIR}/${PROJECT_NAME}.rc")
+ list (APPEND project_sources "${resource_file}")
+ add_custom_command (OUTPUT "${resource_file}"
COMMAND ${CMAKE_COMMAND} -E echo "1 ICON \"${PROJECT_NAME}.ico\""
- > ${resource_file} VERBATIM)
- set_property (SOURCE ${resource_file}
- APPEND PROPERTY OBJECT_DEPENDS ${icon_ico})
+ > "${resource_file}" VERBATIM)
+ set_property (SOURCE "${resource_file}"
+ APPEND PROPERTY OBJECT_DEPENDS "${icon_ico}")
else ()
- list (APPEND project_libraries ${Ncursesw_LIBRARIES} termo-static)
+ list (APPEND project_libraries ${Ncursesw_LIBRARIES} ${Termo_LIBRARIES})
list (APPEND project_sources
src/${PROJECT_NAME}-tui.c)
endif ()
@@ -336,7 +337,7 @@ elseif (WITH_GUI)
install (FILES
${win32_deps_prefix}/share/icons/hicolor/index.theme
DESTINATION share/icons/hicolor)
- install (DIRECTORY ${icon_base} DESTINATION share)
+ install (DIRECTORY "${icon_base}" DESTINATION share)
install (SCRIPT cmake/Win32Cleanup.cmake)
@@ -363,11 +364,11 @@ if (BUILD_TESTING)
foreach (xml ${PROJECT_NAME}.xml ${PROJECT_NAME}.svg)
if (xmlwf_EXECUTABLE)
add_test (test-xmlwf-${xml} ${xmlwf_EXECUTABLE}
- ${PROJECT_SOURCE_DIR}/${xml})
+ "${PROJECT_SOURCE_DIR}/${xml}")
endif ()
if (xmllint_EXECUTABLE)
add_test (test-xmllint-${xml} ${xmllint_EXECUTABLE} --noout
- ${PROJECT_SOURCE_DIR}/${xml})
+ "${PROJECT_SOURCE_DIR}/${xml}")
endif ()
endforeach ()
@@ -375,7 +376,7 @@ if (BUILD_TESTING)
if (dfv_EXECUTABLE)
foreach (df ${PROJECT_NAME}.desktop)
add_test (test-dfv-${df} ${dfv_EXECUTABLE}
- ${PROJECT_SOURCE_DIR}/${df})
+ "${PROJECT_SOURCE_DIR}/${df}")
endforeach ()
endif ()
@@ -383,7 +384,7 @@ if (BUILD_TESTING)
add_executable (test-${name}
src/test-${name}.c ${project_common_sources})
target_link_libraries (test-${name} ${project_common_libraries})
- add_test (test-${name} test-${name})
+ add_test (NAME test-${name} COMMAND test-${name})
endforeach ()
endif ()
diff --git a/LICENSE b/LICENSE
index 7f1d888..04bc6cb 100644
--- a/LICENSE
+++ b/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
purpose with or without fee is hereby granted.
diff --git a/README.adoc b/README.adoc
index 461236e..b4da7ec 100644
--- a/README.adoc
+++ b/README.adoc
@@ -6,15 +6,18 @@ of StarDict dictionaries, and is inspired by the dictionary component
of PC Translator. I was unsuccessful in finding any free software of this kind,
and thus decided to write my own.
+The program offers both a terminal user interface, and a GTK+ 3 based UI.
+The styling of the latter will follow your theme, and may be customized
+from 'gtk.css'.
+
The project is covered by a permissive license, unlike vast majority of other
similar projects, and can serve as a base for implementing other dictionary
software.
+Screenshot
+----------
image::tdv.png[align="center"]
-As a recent addition, the program also offers a GTK+ 3 based user interface,
-whose styling will follow your theme, and may be customized from 'gtk.css'.
-
Packages
--------
Regular releases are sporadic. git master should be stable enough.
@@ -92,14 +95,8 @@ https://mega.co.nz/#!axtD0QRK!sbtBgizksyfkPqKvKEgr8GQ11rsWhtqyRgUUV0B7pwg[CZ <--
Further Development
-------------------
-While I've been successfully using 'tdv' for many years now, some issues
-should be addressed before including the software in regular Linux and/or
-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.
+Lacking configuration, standard StarDict locations should be scanned.
+We should try harder to display arbitrary dictionaries sensibly.
Contributing and Support
------------------------
diff --git a/liberty b/liberty
-Subproject f04cc2c61e1a00db4d1af1bb55ca7e20b9c3db2
+Subproject 0f20cce9c8cbda57b95f789e325686ee9c1c53f
diff --git a/src/stardict.c b/src/stardict.c
index 6775553..8fb2f68 100644
--- a/src/stardict.c
+++ b/src/stardict.c
@@ -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);
diff --git a/src/stardict.h b/src/stardict.h
index 85fd396..79dde47 100644
--- a/src/stardict.h
+++ b/src/stardict.h
@@ -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;
diff --git a/src/tdv-gui.c b/src/tdv-gui.c
index fcb254f..6c1cf59 100644
--- a/src/tdv-gui.c
+++ b/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
diff --git a/src/utils.c b/src/utils.c
index 6a6c5d5..ab5f2e7 100644
--- a/src/utils.c
+++ b/src/utils.c
@@ -16,8 +16,10 @@
*
*/
-// getpwnam_r
-#define _POSIX_C_SOURCE 199506L
+// getpwnam_r, _SC_GETPW_R_SIZE_MAX
+#ifndef _POSIX_C_SOURCE
+#define _POSIX_C_SOURCE 200112L
+#endif
#include <glib.h>
#include <glib/gprintf.h>
@@ -220,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;
@@ -234,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
diff --git a/src/utils.h b/src/utils.h
index bc5775b..ccd660e 100644
--- a/src/utils.h
+++ b/src/utils.h
@@ -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 -----------------------------------------------------------------
diff --git a/tdv.png b/tdv.png
index e0603a0..eaa3cc1 100644
--- a/tdv.png
+++ b/tdv.png
Binary files differ
diff --git a/termo b/termo
-Subproject 2518b53e5ae4579bf84ed58fa7a62806f64e861
+Subproject f9a102456fa6a0b43a916ceaf031f21ea5665e6