Allow loading multiple dictionaries
They can also be specified in the configuration. They can be switched between using Alt+<number>.
This commit is contained in:
		
							parent
							
								
									a063328ac8
								
							
						
					
					
						commit
						4f0a47d5f7
					
				
							
								
								
									
										230
									
								
								src/sdtui.c
									
									
									
									
									
								
							
							
						
						
									
										230
									
								
								src/sdtui.c
									
									
									
									
									
								
							| @ -30,11 +30,13 @@ | |||||||
| #include <gio/gio.h> | #include <gio/gio.h> | ||||||
| #include <pango/pango.h> | #include <pango/pango.h> | ||||||
| #include <glib/gi18n.h> | #include <glib/gi18n.h> | ||||||
|  | #include <glib/gstdio.h> | ||||||
| 
 | 
 | ||||||
| #include <unistd.h> | #include <unistd.h> | ||||||
| #include <poll.h> | #include <poll.h> | ||||||
| #include <errno.h> | #include <errno.h> | ||||||
| #include <signal.h> | #include <signal.h> | ||||||
|  | #include <pwd.h> | ||||||
| 
 | 
 | ||||||
| #include <termo.h> // input | #include <termo.h> // input | ||||||
| #include <ncurses.h> // output | #include <ncurses.h> // output | ||||||
| @ -76,6 +78,79 @@ get_xdg_config_dirs (void) | |||||||
| 	return (gchar **) g_ptr_array_free (paths, FALSE); | 	return (gchar **) g_ptr_array_free (paths, FALSE); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | static gchar * | ||||||
|  | resolve_relative_filename_generic | ||||||
|  | 	(gchar **paths, const gchar *tail, const gchar *filename) | ||||||
|  | { | ||||||
|  | 	for (; *paths; paths++) | ||||||
|  | 	{ | ||||||
|  | 		// As per XDG spec, relative paths are ignored
 | ||||||
|  | 		if (**paths != '/') | ||||||
|  | 			continue; | ||||||
|  | 
 | ||||||
|  | 		gchar *file = g_build_filename (*paths, tail, filename, NULL); | ||||||
|  | 		GStatBuf st; | ||||||
|  | 		if (!g_stat (file, &st)) | ||||||
|  | 			return file; | ||||||
|  | 		g_free (file); | ||||||
|  | 	} | ||||||
|  | 	return NULL; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static gchar * | ||||||
|  | resolve_relative_config_filename (const gchar *filename) | ||||||
|  | { | ||||||
|  | 	gchar **paths = get_xdg_config_dirs (); | ||||||
|  | 	gchar *result = resolve_relative_filename_generic | ||||||
|  | 		(paths, PROJECT_NAME, filename); | ||||||
|  | 	g_strfreev (paths); | ||||||
|  | 	return result; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static gchar * | ||||||
|  | try_expand_tilde (const gchar *filename) | ||||||
|  | { | ||||||
|  | 	size_t until_slash = strcspn (filename, "/"); | ||||||
|  | 	if (!until_slash) | ||||||
|  | 		return g_build_filename (g_get_home_dir () ?: "", filename, NULL); | ||||||
|  | 
 | ||||||
|  | 	long buf_len = sysconf (_SC_GETPW_R_SIZE_MAX); | ||||||
|  | 	if (buf_len < 0) | ||||||
|  | 		buf_len = 1024; | ||||||
|  | 	struct passwd pwd, *success = NULL; | ||||||
|  | 
 | ||||||
|  | 	gchar *user = g_strndup (filename, until_slash); | ||||||
|  | 	gchar *buf = g_malloc (buf_len); | ||||||
|  | 	while (getpwnam_r (user, &pwd, buf, buf_len, &success) == ERANGE) | ||||||
|  | 		buf = g_realloc (buf, buf_len <<= 1); | ||||||
|  | 	g_free (user); | ||||||
|  | 
 | ||||||
|  | 	gchar *result = NULL; | ||||||
|  | 	if (success) | ||||||
|  | 		result = g_strdup_printf ("%s%s", pwd.pw_dir, filename + until_slash); | ||||||
|  | 	g_free (buf); | ||||||
|  | 	return result; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static gchar * | ||||||
|  | resolve_filename (const gchar *filename, gchar *(*relative_cb) (const char *)) | ||||||
|  | { | ||||||
|  | 	// Absolute path is absolute
 | ||||||
|  | 	if (*filename == '/') | ||||||
|  | 		return g_strdup (filename); | ||||||
|  | 
 | ||||||
|  | 	// We don't want to use wordexp() for this as it may execute /bin/sh
 | ||||||
|  | 	if (*filename == '~') | ||||||
|  | 	{ | ||||||
|  | 		// Paths to home directories ought to be absolute
 | ||||||
|  | 		char *expanded = try_expand_tilde (filename + 1); | ||||||
|  | 		if (expanded) | ||||||
|  | 			return expanded; | ||||||
|  | 		g_debug ("failed to expand the home directory in `%s'", filename); | ||||||
|  | 	} | ||||||
|  | 	return relative_cb (filename); | ||||||
|  | } | ||||||
|  | 
 | ||||||
| // --- Application -------------------------------------------------------------
 | // --- Application -------------------------------------------------------------
 | ||||||
| 
 | 
 | ||||||
| #define ATTRIBUTE_TABLE(XX)                     \ | #define ATTRIBUTE_TABLE(XX)                     \ | ||||||
| @ -103,6 +178,8 @@ struct attrs | |||||||
| 
 | 
 | ||||||
| /// Data relating to one entry within the dictionary.
 | /// Data relating to one entry within the dictionary.
 | ||||||
| typedef struct view_entry               ViewEntry; | typedef struct view_entry               ViewEntry; | ||||||
|  | /// Data relating to a dictionary file.
 | ||||||
|  | typedef struct dictionary               Dictionary; | ||||||
| /// Encloses application data.
 | /// Encloses application data.
 | ||||||
| typedef struct application              Application; | typedef struct application              Application; | ||||||
| /// Application options.
 | /// Application options.
 | ||||||
| @ -115,6 +192,14 @@ struct view_entry | |||||||
| 	gsize     definitions_length;       ///< Length of the @a definitions array
 | 	gsize     definitions_length;       ///< Length of the @a definitions array
 | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
|  | struct dictionary | ||||||
|  | { | ||||||
|  | 	gchar         * name;               ///< Visible identifier
 | ||||||
|  | 	gsize           name_width;         ///< Visible width of the name
 | ||||||
|  | 	gchar         * filename;           ///< Path to the dictionary
 | ||||||
|  | 	StardictDict  * dict;               ///< Dictionary
 | ||||||
|  | }; | ||||||
|  | 
 | ||||||
| struct application | struct application | ||||||
| { | { | ||||||
| 	GMainLoop     * loop;               ///< Main loop
 | 	GMainLoop     * loop;               ///< Main loop
 | ||||||
| @ -123,6 +208,8 @@ struct application | |||||||
| 	GIConv          ucs4_to_locale;     ///< UTF-32 -> locale conversion
 | 	GIConv          ucs4_to_locale;     ///< UTF-32 -> locale conversion
 | ||||||
| 	gboolean        locale_is_utf8;     ///< The locale is Unicode
 | 	gboolean        locale_is_utf8;     ///< The locale is Unicode
 | ||||||
| 
 | 
 | ||||||
|  | 	GArray        * dictionaries;       ///< All loaded dictionaries
 | ||||||
|  | 
 | ||||||
| 	StardictDict  * dict;               ///< The current dictionary
 | 	StardictDict  * dict;               ///< The current dictionary
 | ||||||
| 	guint           show_help : 1;      ///< Whether help can be shown
 | 	guint           show_help : 1;      ///< Whether help can be shown
 | ||||||
| 	guint           center_search : 1;  ///< Whether to center the search
 | 	guint           center_search : 1;  ///< Whether to center the search
 | ||||||
| @ -157,6 +244,8 @@ struct app_options | |||||||
| 	gint     selection_watcher;         ///< Interval in milliseconds, or -1
 | 	gint     selection_watcher;         ///< Interval in milliseconds, or -1
 | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
|  | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 | ||||||
|  | 
 | ||||||
| /// Splits the entry and adds it to a pointer array.
 | /// Splits the entry and adds it to a pointer array.
 | ||||||
| static void | static void | ||||||
| view_entry_split_add (GPtrArray *out, const gchar *text) | view_entry_split_add (GPtrArray *out, const gchar *text) | ||||||
| @ -234,6 +323,39 @@ view_entry_free (ViewEntry *ve) | |||||||
| 	g_slice_free1 (sizeof *ve, ve); | 	g_slice_free1 (sizeof *ve, ve); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 | ||||||
|  | 
 | ||||||
|  | static gboolean | ||||||
|  | dictionary_load (Dictionary *self, GError **e) | ||||||
|  | { | ||||||
|  | 	if (!(self->dict = stardict_dict_new (self->filename, e))) | ||||||
|  | 		return FALSE; | ||||||
|  | 
 | ||||||
|  | 	if (!self->name) | ||||||
|  | 	{ | ||||||
|  | 		self->name = g_strdup (stardict_info_get_book_name | ||||||
|  | 			(stardict_dict_get_info (self->dict))); | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	gunichar *ucs4 = g_utf8_to_ucs4_fast (self->name, -1, NULL); | ||||||
|  | 	for (gunichar *it = ucs4; *it; it++) | ||||||
|  | 		self->name_width += unichar_width (*it); | ||||||
|  | 	g_free (ucs4); | ||||||
|  | 	return TRUE; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static void | ||||||
|  | dictionary_free (Dictionary *self) | ||||||
|  | { | ||||||
|  | 	g_free (self->name); | ||||||
|  | 	g_free (self->filename); | ||||||
|  | 
 | ||||||
|  | 	if (self->dict) | ||||||
|  | 		g_object_unref (self->dict); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 | ||||||
|  | 
 | ||||||
| /// Reload view items.
 | /// Reload view items.
 | ||||||
| static void | static void | ||||||
| app_reload_view (Application *self) | app_reload_view (Application *self) | ||||||
| @ -329,6 +451,30 @@ app_load_config_values (Application *self, GKeyFile *kf) | |||||||
| 	app_load_color (self, kf, config, ATTRIBUTE_ ## name); | 	app_load_color (self, kf, config, ATTRIBUTE_ ## name); | ||||||
| 	ATTRIBUTE_TABLE (XX) | 	ATTRIBUTE_TABLE (XX) | ||||||
| #undef XX | #undef XX | ||||||
|  | 
 | ||||||
|  | 	const gchar *dictionaries = "Dictionaries"; | ||||||
|  | 	gchar **names = g_key_file_get_keys (kf, dictionaries, NULL, NULL); | ||||||
|  | 	if (!names) | ||||||
|  | 		return; | ||||||
|  | 
 | ||||||
|  | 	for (gchar **it = names; *it; it++) | ||||||
|  | 	{ | ||||||
|  | 		gchar *path = g_key_file_get_string (kf, dictionaries, *it, NULL); | ||||||
|  | 		if (!path) | ||||||
|  | 			continue; | ||||||
|  | 
 | ||||||
|  | 		// Try to resolve relative paths and expand tildes
 | ||||||
|  | 		gchar *resolved = resolve_filename | ||||||
|  | 			(path, resolve_relative_config_filename); | ||||||
|  | 		if (resolved) | ||||||
|  | 			g_free (path); | ||||||
|  | 		else | ||||||
|  | 			resolved = path; | ||||||
|  | 
 | ||||||
|  | 		Dictionary dict = { .name = g_strdup (*it), .filename = resolved }; | ||||||
|  | 		g_array_append_val (self->dictionaries, dict); | ||||||
|  | 	} | ||||||
|  | 	g_strfreev (names); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| static void | static void | ||||||
| @ -368,9 +514,10 @@ app_init_attrs (Application *self) | |||||||
| 	ATTRIBUTE_TABLE (XX) | 	ATTRIBUTE_TABLE (XX) | ||||||
| #undef XX | #undef XX | ||||||
| } | } | ||||||
|  | 
 | ||||||
| /// Initialize the application core.
 | /// Initialize the application core.
 | ||||||
| static void | static void | ||||||
| app_init (Application *self, AppOptions *options, const gchar *filename) | app_init (Application *self, AppOptions *options, char **filenames) | ||||||
| { | { | ||||||
| 	self->loop = NULL; | 	self->loop = NULL; | ||||||
| 	self->selection_interval = options->selection_watcher; | 	self->selection_interval = options->selection_watcher; | ||||||
| @ -392,14 +539,6 @@ app_init (Application *self, AppOptions *options, const gchar *filename) | |||||||
| 	self->tk = NULL; | 	self->tk = NULL; | ||||||
| 	self->tk_timer = 0; | 	self->tk_timer = 0; | ||||||
| 
 | 
 | ||||||
| 	GError *error = NULL; |  | ||||||
| 	self->dict = stardict_dict_new (filename, &error); |  | ||||||
| 	if (!self->dict) |  | ||||||
| 	{ |  | ||||||
| 		g_printerr ("%s: %s\n", _("Error loading dictionary"), error->message); |  | ||||||
| 		exit (EXIT_FAILURE); |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	self->show_help = TRUE; | 	self->show_help = TRUE; | ||||||
| 	self->center_search = TRUE; | 	self->center_search = TRUE; | ||||||
| 	self->underline_last = TRUE; | 	self->underline_last = TRUE; | ||||||
| @ -427,15 +566,47 @@ app_init (Application *self, AppOptions *options, const gchar *filename) | |||||||
| 	self->ucs4_to_locale = g_iconv_open (charset, "UTF-32BE"); | 	self->ucs4_to_locale = g_iconv_open (charset, "UTF-32BE"); | ||||||
| #endif // G_BYTE_ORDER != G_LITTLE_ENDIAN
 | #endif // G_BYTE_ORDER != G_LITTLE_ENDIAN
 | ||||||
| 
 | 
 | ||||||
| 	app_reload_view (self); |  | ||||||
| 	app_init_attrs (self); | 	app_init_attrs (self); | ||||||
|  | 	self->dictionaries = g_array_new (FALSE, TRUE, sizeof (Dictionary)); | ||||||
|  | 	g_array_set_clear_func | ||||||
|  | 		(self->dictionaries, (GDestroyNotify) dictionary_free); | ||||||
| 
 | 
 | ||||||
|  | 	GError *error = NULL; | ||||||
| 	app_load_config (self, &error); | 	app_load_config (self, &error); | ||||||
| 	if (error) | 	if (error) | ||||||
| 	{ | 	{ | ||||||
| 		g_printerr ("%s: %s\n", _("Cannot load configuration"), error->message); | 		g_printerr ("%s: %s\n", _("Cannot load configuration"), error->message); | ||||||
| 		exit (EXIT_FAILURE); | 		exit (EXIT_FAILURE); | ||||||
| 	} | 	} | ||||||
|  | 
 | ||||||
|  | 	// Dictionaries given on the command line override the configuration
 | ||||||
|  | 	if (*filenames) | ||||||
|  | 	{ | ||||||
|  | 		g_array_set_size (self->dictionaries, 0); | ||||||
|  | 		while (*filenames) | ||||||
|  | 		{ | ||||||
|  | 			Dictionary dict = { .filename = g_strdup (*filenames++) }; | ||||||
|  | 			g_array_append_val (self->dictionaries, dict); | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	for (guint i = 0; i < self->dictionaries->len && dictionary_load | ||||||
|  | 		(&g_array_index (self->dictionaries, Dictionary, i), &error); i++) | ||||||
|  | 		; | ||||||
|  | 	if (error) | ||||||
|  | 	{ | ||||||
|  | 		g_printerr ("%s: %s\n", _("Error loading dictionary"), error->message); | ||||||
|  | 		exit (EXIT_FAILURE); | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	if (!self->dictionaries->len) | ||||||
|  | 	{ | ||||||
|  | 		g_printerr ("%s\n", _("No dictionaries found either in " | ||||||
|  | 			"the configuration or on the command line")); | ||||||
|  | 		exit (EXIT_FAILURE); | ||||||
|  | 	} | ||||||
|  | 	self->dict = g_array_index (self->dictionaries, Dictionary, 0).dict; | ||||||
|  | 	app_reload_view (self); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| static void | static void | ||||||
| @ -487,10 +658,10 @@ app_destroy (Application *self) | |||||||
| 		g_source_remove (self->selection_timer); | 		g_source_remove (self->selection_timer); | ||||||
| 	g_free (self->selection_contents); | 	g_free (self->selection_contents); | ||||||
| 
 | 
 | ||||||
| 	g_object_unref (self->dict); |  | ||||||
| 	g_ptr_array_free (self->entries, TRUE); | 	g_ptr_array_free (self->entries, TRUE); | ||||||
| 	g_free (self->search_label); | 	g_free (self->search_label); | ||||||
| 	g_array_free (self->input, TRUE); | 	g_array_free (self->input, TRUE); | ||||||
|  | 	g_array_free (self->dictionaries, TRUE); | ||||||
| 
 | 
 | ||||||
| 	g_iconv_close (self->ucs4_to_locale); | 	g_iconv_close (self->ucs4_to_locale); | ||||||
| } | } | ||||||
| @ -1081,6 +1252,20 @@ app_search_for_entry (Application *self) | |||||||
| 	app_redraw_view (self); | 	app_redraw_view (self); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | /// Switch to a different dictionary by number.
 | ||||||
|  | static gboolean | ||||||
|  | app_goto_dictionary (Application *self, guint n) | ||||||
|  | { | ||||||
|  | 	if (n >= self->dictionaries->len) | ||||||
|  | 		return FALSE; | ||||||
|  | 
 | ||||||
|  | 	Dictionary *dict = &g_array_index (self->dictionaries, Dictionary, n); | ||||||
|  | 	self->dict = dict->dict; | ||||||
|  | 	app_search_for_entry (self); | ||||||
|  | 	app_redraw_top (self); | ||||||
|  | 	return TRUE; | ||||||
|  | } | ||||||
|  | 
 | ||||||
| #define SAVE_CURSOR                 \ | #define SAVE_CURSOR                 \ | ||||||
| 	int last_x, last_y;             \ | 	int last_x, last_y;             \ | ||||||
| 	getyx (stdscr, last_y, last_x); | 	getyx (stdscr, last_y, last_x); | ||||||
| @ -1422,6 +1607,14 @@ app_process_alt_key (Application *self, termo_key_t *event) | |||||||
| { | { | ||||||
| 	if (event->code.codepoint == 'c') | 	if (event->code.codepoint == 'c') | ||||||
| 		self->center_search = !self->center_search; | 		self->center_search = !self->center_search; | ||||||
|  | 
 | ||||||
|  | 	if (event->code.codepoint >= '0' | ||||||
|  | 	 && event->code.codepoint <= '9') | ||||||
|  | 	{ | ||||||
|  | 		gint n = event->code.codepoint - '0'; | ||||||
|  | 		if (!app_goto_dictionary (self, (n == 0 ? 10 : n) - 1)) | ||||||
|  | 			beep (); | ||||||
|  | 	} | ||||||
| 	return TRUE; | 	return TRUE; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| @ -1797,7 +1990,7 @@ G_GNUC_END_IGNORE_DEPRECATIONS | |||||||
| 
 | 
 | ||||||
| 	GError *error = NULL; | 	GError *error = NULL; | ||||||
| 	GOptionContext *ctx = g_option_context_new | 	GOptionContext *ctx = g_option_context_new | ||||||
| 		(N_("dictionary.ifo - StarDict terminal UI")); | 		(N_("[dictionary.ifo]... - StarDict terminal UI")); | ||||||
| 	GOptionGroup *group = g_option_group_new ("", "", "", &options, NULL); | 	GOptionGroup *group = g_option_group_new ("", "", "", &options, NULL); | ||||||
| 	g_option_group_add_entries (group, entries); | 	g_option_group_add_entries (group, entries); | ||||||
| 	g_option_group_set_translation_domain (group, GETTEXT_PACKAGE); | 	g_option_group_set_translation_domain (group, GETTEXT_PACKAGE); | ||||||
| @ -1809,6 +2002,7 @@ G_GNUC_END_IGNORE_DEPRECATIONS | |||||||
| 			error->message); | 			error->message); | ||||||
| 		exit (EXIT_FAILURE); | 		exit (EXIT_FAILURE); | ||||||
| 	} | 	} | ||||||
|  | 	g_option_context_free (ctx); | ||||||
| 
 | 
 | ||||||
| 	if (options.show_version) | 	if (options.show_version) | ||||||
| 	{ | 	{ | ||||||
| @ -1816,18 +2010,8 @@ G_GNUC_END_IGNORE_DEPRECATIONS | |||||||
| 		exit (EXIT_SUCCESS); | 		exit (EXIT_SUCCESS); | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	if (argc != 2) |  | ||||||
| 	{ |  | ||||||
| 		gchar *help = g_option_context_get_help (ctx, TRUE, FALSE); |  | ||||||
| 		g_printerr ("%s", help); |  | ||||||
| 		g_free (help); |  | ||||||
| 		exit (EXIT_FAILURE); |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	g_option_context_free (ctx); |  | ||||||
| 
 |  | ||||||
| 	Application app; | 	Application app; | ||||||
| 	app_init (&app, &options, argv[1]); | 	app_init (&app, &options, argv + 1); | ||||||
| 	app_init_terminal (&app); | 	app_init_terminal (&app); | ||||||
| 	app_redraw (&app); | 	app_redraw (&app); | ||||||
| 
 | 
 | ||||||
|  | |||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user