diff options
Diffstat (limited to 'src/sdgui.c')
-rw-r--r-- | src/sdgui.c | 311 |
1 files changed, 311 insertions, 0 deletions
diff --git a/src/sdgui.c b/src/sdgui.c new file mode 100644 index 0000000..3565bc6 --- /dev/null +++ b/src/sdgui.c @@ -0,0 +1,311 @@ +/* + * StarDict GTK+ UI + * + * Copyright (c) 2020 - 2021, 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. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION + * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + */ + +#include <gtk/gtk.h> +#include <glib/gi18n.h> + +#include <locale.h> + +#include "config.h" +#include "stardict.h" +#include "utils.h" +#include "stardict-view.h" + +typedef struct dictionary Dictionary; + +struct dictionary +{ + const gchar *filename; ///< Filename + StardictDict *dict; ///< Stardict dictionary data + gchar *name; ///< Name to show + guint position; ///< Current position +}; + +static struct +{ + GtkWidget *window; ///< Top-level window + GtkWidget *notebook; ///< Notebook with tabs + GtkWidget *entry; ///< Search entry widget + GtkWidget *view; ///< Entries view + + gint dictionary; ///< Index of the current dictionary + Dictionary *dictionaries; ///< All open dictionaries + gsize dictionaries_len; ///< Total number of dictionaries + + gboolean watch_selection; ///< Following X11 PRIMARY? +} +g; + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + +static gboolean +dictionary_load (Dictionary *self, gchar *filename, GError **e) +{ + self->filename = filename; + 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))); + } + return TRUE; +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + +static gboolean +init (gchar **filenames, GError **e) +{ + while (filenames[g.dictionaries_len]) + g.dictionaries_len++; + + g.dictionaries = g_malloc0_n (sizeof *g.dictionaries, g.dictionaries_len); + for (gsize i = 0; i < g.dictionaries_len; i++) + { + Dictionary *dict = &g.dictionaries[i]; + if (!dictionary_load (dict, filenames[i], e)) + return FALSE; + } + return TRUE; +} + +static void +search (Dictionary *dict) +{ + GtkEntryBuffer *buf = gtk_entry_get_buffer (GTK_ENTRY (g.entry)); + const gchar *input_utf8 = gtk_entry_buffer_get_text (buf); + + StardictIterator *iterator = + stardict_dict_search (dict->dict, input_utf8, NULL); + dict->position = stardict_iterator_get_offset (iterator); + g_object_unref (iterator); + + stardict_view_set_position (STARDICT_VIEW (g.view), + dict->dict, dict->position); + stardict_view_set_matched (STARDICT_VIEW (g.view), input_utf8); +} + +static void +on_changed (G_GNUC_UNUSED GtkWidget *widget, G_GNUC_UNUSED gpointer data) +{ + search (&g.dictionaries[g.dictionary]); +} + +static void +on_selection_received (G_GNUC_UNUSED GtkClipboard *clipboard, const gchar *text, + G_GNUC_UNUSED gpointer data) +{ + if (!text) + return; + + gtk_entry_set_text (GTK_ENTRY (g.entry), text); + g_signal_emit_by_name (g.entry, + "move-cursor", GTK_MOVEMENT_BUFFER_ENDS, 1, FALSE); +} + +static void +on_selection (GtkClipboard *clipboard, GdkEvent *event, + G_GNUC_UNUSED gpointer data) +{ + if (g.watch_selection + && !gtk_window_has_toplevel_focus (GTK_WINDOW (g.window)) + && event->owner_change.owner != NULL) + gtk_clipboard_request_text (clipboard, on_selection_received, NULL); +} + +static void +on_selection_watch_toggle (GtkCheckMenuItem *item, G_GNUC_UNUSED gpointer data) +{ + g.watch_selection = gtk_check_menu_item_get_active (item); +} + +static void +on_switch_page (G_GNUC_UNUSED GtkWidget *widget, G_GNUC_UNUSED GtkWidget *page, + guint page_num, G_GNUC_UNUSED gpointer data) +{ + g.dictionary = page_num; + search (&g.dictionaries[g.dictionary]); +} + +static gboolean +on_key_press (G_GNUC_UNUSED GtkWidget *widget, GdkEvent *event, + G_GNUC_UNUSED gpointer data) +{ + if (event->key.state == GDK_CONTROL_MASK) + { + if (event->key.keyval == GDK_KEY_Page_Up) + { + gtk_notebook_prev_page (GTK_NOTEBOOK (g.notebook)); + return TRUE; + } + if (event->key.keyval == GDK_KEY_Page_Down) + { + gtk_notebook_next_page (GTK_NOTEBOOK (g.notebook)); + return TRUE; + } + } + if (event->key.state == GDK_MOD1_MASK) + { + if (event->key.keyval >= GDK_KEY_0 + && event->key.keyval <= GDK_KEY_9) + { + gint n = event->key.keyval - GDK_KEY_0; + gtk_notebook_set_current_page + (GTK_NOTEBOOK (g.notebook), n ? (n - 1) : 10); + return TRUE; + } + } + return FALSE; +} + +static void +on_destroy (G_GNUC_UNUSED GtkWidget *widget, G_GNUC_UNUSED gpointer data) +{ + gtk_main_quit (); +} + +static void +die_with_dialog (const gchar *message) +{ + GtkWidget *dialog = gtk_message_dialog_new (NULL, 0, + GTK_MESSAGE_ERROR, GTK_BUTTONS_CLOSE, "%s", message); + gtk_dialog_run (GTK_DIALOG (dialog)); + gtk_widget_destroy (dialog); + exit (EXIT_FAILURE); +} + +int +main (int argc, char *argv[]) +{ + if (!setlocale (LC_ALL, "")) + g_printerr ("%s: %s\n", _("Warning"), _("failed to set the locale")); + + bindtextdomain (GETTEXT_PACKAGE, GETTEXT_DIRNAME); + bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8"); + textdomain (GETTEXT_PACKAGE); + + gchar **filenames = NULL; + GOptionEntry option_entries[] = + { + {G_OPTION_REMAINING, 0, 0, G_OPTION_ARG_FILENAME_ARRAY, &filenames, + NULL, N_("FILE...")}, + {}, + }; + + GError *error = NULL; + gtk_init_with_args (&argc, &argv, N_("- StarDict GTK+ UI"), + option_entries, GETTEXT_PACKAGE, &error); + if (error) + { + g_warning ("%s", error->message); + g_error_free (error); + return 1; + } + + if (!filenames) + { + // TODO: eventually just load all dictionaries from configuration + die_with_dialog ("No arguments have been passed."); + } + if (!init (filenames, &error)) + die_with_dialog (error->message); + + // Some Adwaita stupidity + const char *style = "notebook header tab { padding: 2px 8px; margin: 0; }"; + + GdkScreen *screen = gdk_screen_get_default (); + GtkCssProvider *provider = gtk_css_provider_new (); + gtk_css_provider_load_from_data (provider, style, strlen (style), NULL); + gtk_style_context_add_provider_for_screen (screen, + GTK_STYLE_PROVIDER (provider), GTK_STYLE_PROVIDER_PRIORITY_APPLICATION); + + g.notebook = gtk_notebook_new (); + g_signal_connect (g.notebook, "switch-page", + G_CALLBACK (on_switch_page), NULL); + gtk_notebook_set_scrollable (GTK_NOTEBOOK (g.notebook), TRUE); + + g.watch_selection = TRUE; + GtkWidget *item = + gtk_check_menu_item_new_with_label (_("Follow selection")); + gtk_check_menu_item_set_active + (GTK_CHECK_MENU_ITEM (item), g.watch_selection); + g_signal_connect (item, "toggled", + G_CALLBACK (on_selection_watch_toggle), NULL); + + GtkWidget *menu = gtk_menu_new (); + gtk_menu_shell_append (GTK_MENU_SHELL (menu), item); + gtk_widget_show_all (menu); + + GtkWidget *hamburger = gtk_menu_button_new (); + gtk_menu_button_set_direction (GTK_MENU_BUTTON (hamburger), GTK_ARROW_NONE); + gtk_menu_button_set_popup (GTK_MENU_BUTTON (hamburger), menu); + gtk_button_set_relief (GTK_BUTTON (hamburger), GTK_RELIEF_NONE); + gtk_widget_show (hamburger); + + gtk_notebook_set_action_widget + (GTK_NOTEBOOK (g.notebook), hamburger, GTK_PACK_END); + + // FIXME: when the clear icon shows, the widget changes in height + g.entry = gtk_search_entry_new (); + // TODO: attach to the "key-press-event" signal and implement ^W at least, + // though ^U is working already! Note that bindings can be done in CSS + // as well, if we have any extra specially for the editor + g_signal_connect (g.entry, "changed", G_CALLBACK (on_changed), g.view); + // TODO: make the entry have a background colour, rather than transparency + gtk_entry_set_has_frame (GTK_ENTRY (g.entry), FALSE); + + // TODO: supposedly attach to "key-press-event" here and react to + // PageUp/PageDown and up/down arrow keys... either here or in the Entry + g.window = gtk_window_new (GTK_WINDOW_TOPLEVEL); + gtk_window_set_default_size (GTK_WINDOW (g.window), 300, 600); + g_signal_connect (g.window, "destroy", + G_CALLBACK (on_destroy), NULL); + g_signal_connect (g.window, "key-press-event", + G_CALLBACK (on_key_press), NULL); + + GtkWidget *superbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 1); + gtk_container_add (GTK_CONTAINER (g.window), superbox); + gtk_container_add (GTK_CONTAINER (superbox), g.notebook); + gtk_container_add (GTK_CONTAINER (superbox), g.entry); + gtk_container_add (GTK_CONTAINER (superbox), + gtk_separator_new (GTK_ORIENTATION_HORIZONTAL)); + + g.view = stardict_view_new (); + gtk_box_pack_end (GTK_BOX (superbox), g.view, TRUE, TRUE, 0); + + for (gsize i = 0; i < g.dictionaries_len; i++) + { + Dictionary *dict = &g.dictionaries[i]; + GtkWidget *dummy = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0); + GtkWidget *label = gtk_label_new (dict->name); + gtk_notebook_append_page (GTK_NOTEBOOK (g.notebook), dummy, label); + } + + GtkClipboard *clipboard = gtk_clipboard_get (GDK_SELECTION_PRIMARY); + g_signal_connect (clipboard, "owner-change", + G_CALLBACK (on_selection), NULL); + + gtk_widget_grab_focus (g.entry); + gtk_widget_show_all (g.window); + gtk_main (); + + g_strfreev (filenames); + return 0; +} |