From 238e7a2bb961eb448dee1542e03cbdb84dea027d Mon Sep 17 00:00:00 2001 From: Přemysl Eric Janouch Date: Sun, 11 Jun 2023 16:18:21 +0200 Subject: Merge TUI and GUI binaries, using a new name The appropriate interface will be chosen automatically. --- src/sdgui.c | 604 ------------------------------------------------------------ 1 file changed, 604 deletions(-) delete mode 100644 src/sdgui.c (limited to 'src/sdgui.c') diff --git a/src/sdgui.c b/src/sdgui.c deleted file mode 100644 index 993d1f5..0000000 --- a/src/sdgui.c +++ /dev/null @@ -1,604 +0,0 @@ -/* - * StarDict GTK+ UI - * - * Copyright (c) 2020 - 2022, Přemysl Eric Janouch - * - * 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 -#include - -#include -#include - -#include "config.h" -#include "stardict.h" -#include "utils.h" -#include "stardict-view.h" - -#undef PROJECT_NAME -#define PROJECT_NAME "sdgui" - -static struct -{ - GtkWidget *window; ///< Top-level window - GtkWidget *notebook; ///< Notebook with tabs - GtkWidget *hamburger; ///< Hamburger menu - GtkWidget *entry; ///< Search entry widget - GtkWidget *view; ///< Entries view - - gint dictionary; ///< Index of the current dictionary - gint last; ///< The last dictionary index - GPtrArray *dictionaries; ///< All open dictionaries - - gboolean loading; ///< Dictionaries are being loaded - - gboolean watch_selection; ///< Following X11 PRIMARY? -} -g; - -// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -static void -load_from_filenames (GPtrArray *out, gchar **filenames) -{ - for (gsize i = 0; filenames[i]; i++) - { - Dictionary *dict = g_malloc0 (sizeof *dict); - dict->filename = g_strdup (filenames[i]); - g_ptr_array_add (out, dict); - } -} - -// TODO: try to deduplicate, similar to app_load_config_values() -static gboolean -load_from_key_file (GPtrArray *out, GKeyFile *kf, GError **error) -{ - const gchar *dictionaries = "Dictionaries"; - gchar **names = g_key_file_get_keys (kf, dictionaries, NULL, NULL); - if (!names) - return TRUE; - - for (gsize i = 0; names[i]; i++) - { - Dictionary *dict = g_malloc0 (sizeof *dict); - dict->name = names[i]; - g_ptr_array_add (out, dict); - } - g_free (names); - - for (gsize i = 0; i < out->len; i++) - { - Dictionary *dict = g_ptr_array_index (out, i); - gchar *path = - g_key_file_get_string (kf, dictionaries, dict->name, error); - if (!path) - return FALSE; - - // Try to resolve relative paths and expand tildes - if (!(dict->filename = - resolve_filename (path, resolve_relative_config_filename))) - dict->filename = path; - else - g_free (path); - } - return TRUE; -} - -static gboolean -load_from_config (GPtrArray *out, GError **error) -{ - GKeyFile *key_file = load_project_config_file (error); - if (!key_file) - return FALSE; - - gboolean result = load_from_key_file (out, key_file, error); - g_key_file_free (key_file); - return result; -} - -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); - if (!dict->dict) - return; - - StardictIterator *iterator = - stardict_dict_search (dict->dict, input_utf8, NULL); - stardict_view_set_position (STARDICT_VIEW (g.view), - dict->dict, stardict_iterator_get_offset (iterator)); - g_object_unref (iterator); - - 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_ptr_array_index (g.dictionaries, g.dictionary)); -} - -static void -on_send (G_GNUC_UNUSED StardictView *view, - const char *word, G_GNUC_UNUSED gpointer data) -{ - GtkEntryBuffer *buf = gtk_entry_get_buffer (GTK_ENTRY (g.entry)); - gtk_entry_buffer_set_text (buf, word, -1); - gtk_editable_select_region (GTK_EDITABLE (g.entry), 0, -1); -} - -static void -on_selection_received (G_GNUC_UNUSED GtkClipboard *clipboard, const gchar *text, - G_GNUC_UNUSED gpointer data) -{ - if (!text) - return; - - gchar *trimmed = g_strstrip (g_strdup (text)); - gtk_entry_set_text (GTK_ENTRY (g.entry), trimmed); - g_free (trimmed); - 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.last = g.dictionary; - g.dictionary = page_num; - search (g_ptr_array_index (g.dictionaries, g.dictionary)); - - // Hack: Make right-clicking notebook arrows also re-focus the entry. - GdkEvent *event = gtk_get_current_event (); - if (event && event->type == GDK_BUTTON_PRESS) - gtk_widget_grab_focus (g.entry); -} - -static gboolean -accelerate_hamburger (GdkEvent *event) -{ - gchar *accelerator = NULL; - g_object_get (gtk_widget_get_settings (g.window), "gtk-menu-bar-accel", - &accelerator, NULL); - if (!accelerator) - return FALSE; - - guint key = 0; - GdkModifierType mods = 0; - gtk_accelerator_parse (accelerator, &key, &mods); - g_free (accelerator); - - guint mask = gtk_accelerator_get_default_mod_mask (); - if (!key || event->key.keyval != key || (event->key.state & mask) != mods) - return FALSE; - - gtk_button_clicked (GTK_BUTTON (g.hamburger)); - return TRUE; -} - -static gboolean -on_key_press (G_GNUC_UNUSED GtkWidget *widget, GdkEvent *event, - G_GNUC_UNUSED gpointer data) -{ - // The "activate" signal of the GtkMenuButton cannot be used - // from a real accelerator, due to "no trigger event for menu popup". - if (accelerate_hamburger (event)) - return TRUE; - - GtkNotebook *notebook = GTK_NOTEBOOK (g.notebook); - guint mods = event->key.state & gtk_accelerator_get_default_mod_mask (); - if (mods == GDK_CONTROL_MASK) - { - // Can't use gtk_widget_add_accelerator() to change-current-page(-1/+1) - // because that signal has arguments, which cannot be passed. - gint current = gtk_notebook_get_current_page (notebook); - if (event->key.keyval == GDK_KEY_Page_Up) - return gtk_notebook_set_current_page (notebook, --current), TRUE; - if (event->key.keyval == GDK_KEY_Page_Down) - return gtk_notebook_set_current_page (notebook, - ++current % gtk_notebook_get_n_pages (notebook)), TRUE; - } - if (mods == 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 (notebook, (n ? n : 10) - 1); - return TRUE; - } - if (event->key.keyval == GDK_KEY_Tab) - { - gtk_notebook_set_current_page (notebook, g.last); - return TRUE; - } - } - if (mods == 0) - { - StardictView *view = STARDICT_VIEW (g.view); - if (event->key.keyval == GDK_KEY_Page_Up) - return stardict_view_scroll (view, GTK_SCROLL_PAGES, -0.5), TRUE; - if (event->key.keyval == GDK_KEY_Page_Down) - return stardict_view_scroll (view, GTK_SCROLL_PAGES, +0.5), TRUE; - if (event->key.keyval == GDK_KEY_Up) - return stardict_view_scroll (view, GTK_SCROLL_STEPS, -1), TRUE; - if (event->key.keyval == GDK_KEY_Down) - return stardict_view_scroll (view, GTK_SCROLL_STEPS, +1), TRUE; - } - return FALSE; -} - -static gboolean -on_tab_focus (G_GNUC_UNUSED GtkWidget *widget, - G_GNUC_UNUSED GtkDirectionType direction, G_GNUC_UNUSED gpointer user_data) -{ - // Hack: Make it so that tab headers don't retain newly gained focus - // when clicked, re-focus the entry instead. - GdkEvent *event = gtk_get_current_event (); - if (!event || event->type != GDK_BUTTON_PRESS - || event->button.button != GDK_BUTTON_PRIMARY) - return FALSE; - - gtk_widget_grab_focus (g.entry); - return TRUE; -} - -static void -init_tabs (void) -{ - for (gsize i = g.dictionaries->len; i--; ) - { - Dictionary *dict = g_ptr_array_index (g.dictionaries, i); - GtkWidget *dummy = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0); - g_signal_connect (dummy, "focus", G_CALLBACK (on_tab_focus), NULL); - GtkWidget *label = gtk_label_new (dict->name); - gtk_notebook_insert_page (GTK_NOTEBOOK (g.notebook), dummy, label, 0); - } - - gtk_widget_show_all (g.notebook); - gtk_widget_grab_focus (g.entry); -} - -static void -show_error_dialog (GError *error) -{ - GtkWidget *dialog = gtk_message_dialog_new (GTK_WINDOW (g.window), 0, - GTK_MESSAGE_ERROR, GTK_BUTTONS_CLOSE, "%s", error->message); - gtk_dialog_run (GTK_DIALOG (dialog)); - gtk_widget_destroy (dialog); - g_error_free (error); -} - -static void -on_new_dictionaries_loaded (G_GNUC_UNUSED GObject* source_object, - GAsyncResult* res, G_GNUC_UNUSED gpointer user_data) -{ - g.loading = FALSE; - - GError *error = NULL; - GPtrArray *new_dictionaries = - g_task_propagate_pointer (G_TASK (res), &error); - if (!new_dictionaries) - { - show_error_dialog (error); - return; - } - - while (gtk_notebook_get_n_pages (GTK_NOTEBOOK (g.notebook))) - gtk_notebook_remove_page (GTK_NOTEBOOK (g.notebook), -1); - - g.dictionary = -1; - if (g.dictionaries) - g_ptr_array_free (g.dictionaries, TRUE); - - stardict_view_set_position (STARDICT_VIEW (g.view), NULL, 0); - g.dictionaries = new_dictionaries; - init_tabs (); -} - -static void -on_reload_dictionaries_task (GTask *task, G_GNUC_UNUSED gpointer source_object, - gpointer task_data, G_GNUC_UNUSED GCancellable *cancellable) -{ - GError *error = NULL; - if (load_dictionaries (task_data, &error)) - { - g_task_return_pointer (task, - g_ptr_array_ref (task_data), (GDestroyNotify) g_ptr_array_unref); - } - else - g_task_return_error (task, error); -} - -static gboolean -reload_dictionaries (GPtrArray *new_dictionaries, GError **error) -{ - // TODO: We could cancel that task. - if (g.loading) - { - g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, - "already loading dictionaries"); - return FALSE; - } - - // TODO: Some other kind of indication. - // Note that "action widgets" aren't visible without GtkNotebook tabs. - g.loading = TRUE; - - GTask *task = g_task_new (NULL, NULL, on_new_dictionaries_loaded, NULL); - g_task_set_name (task, __func__); - g_task_set_task_data (task, - new_dictionaries, (GDestroyNotify) g_ptr_array_unref); - g_task_run_in_thread (task, on_reload_dictionaries_task); - g_object_unref (task); - return TRUE; -} - -static void -on_open (G_GNUC_UNUSED GtkMenuItem *item, G_GNUC_UNUSED gpointer data) -{ - // The default is local-only. Paths are returned absolute. - GtkWidget *dialog = gtk_file_chooser_dialog_new (_("Open dictionary"), - GTK_WINDOW (g.window), GTK_FILE_CHOOSER_ACTION_OPEN, - _("_Cancel"), GTK_RESPONSE_CANCEL, - _("_Open"), GTK_RESPONSE_ACCEPT, NULL); - - GtkFileFilter *filter = gtk_file_filter_new (); - gtk_file_filter_add_pattern (filter, "*.ifo"); - gtk_file_filter_set_name (filter, "*.ifo"); - GtkFileChooser *chooser = GTK_FILE_CHOOSER (dialog); - gtk_file_chooser_add_filter (chooser, filter); - gtk_file_chooser_set_select_multiple (chooser, TRUE); - - GPtrArray *new_dictionaries = - g_ptr_array_new_with_free_func ((GDestroyNotify) dictionary_destroy); - if (gtk_dialog_run (GTK_DIALOG (dialog)) == GTK_RESPONSE_ACCEPT) - { - GSList *paths = gtk_file_chooser_get_filenames (chooser); - for (GSList *iter = paths; iter; iter = iter->next) - { - Dictionary *dict = g_malloc0 (sizeof *dict); - dict->filename = iter->data; - g_ptr_array_add (new_dictionaries, dict); - } - g_slist_free (paths); - } - - gtk_widget_destroy (dialog); - - GError *error = NULL; - if (!new_dictionaries->len - || !reload_dictionaries (new_dictionaries, &error)) - g_ptr_array_free (new_dictionaries, TRUE); - - if (error) - show_error_dialog (error); -} - -static void -on_drag_data_received (G_GNUC_UNUSED GtkWidget *widget, GdkDragContext *context, - G_GNUC_UNUSED gint x, G_GNUC_UNUSED gint y, GtkSelectionData *data, - G_GNUC_UNUSED guint info, guint time, G_GNUC_UNUSED gpointer user_data) -{ - GError *error = NULL; - gchar **dropped_uris = gtk_selection_data_get_uris (data); - if (!dropped_uris) - return; - - GPtrArray *new_dictionaries = - g_ptr_array_new_with_free_func ((GDestroyNotify) dictionary_destroy); - for (gsize i = 0; !error && dropped_uris[i]; i++) - { - Dictionary *dict = g_malloc0 (sizeof *dict); - dict->filename = g_filename_from_uri (dropped_uris[i], NULL, &error); - g_ptr_array_add (new_dictionaries, dict); - } - - g_strfreev (dropped_uris); - if (!new_dictionaries->len - || !reload_dictionaries (new_dictionaries, &error)) - g_ptr_array_free (new_dictionaries, TRUE); - - gtk_drag_finish (context, error == NULL, FALSE, time); - - if (error) - show_error_dialog (error); -} - -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; - } - - gtk_window_set_default_icon_name (PROJECT_NAME); - - GPtrArray *new_dictionaries = - g_ptr_array_new_with_free_func ((GDestroyNotify) dictionary_destroy); - if (filenames) - { - load_from_filenames (new_dictionaries, filenames); - g_strfreev (filenames); - } - else if (!load_from_config (new_dictionaries, &error) && error) - die_with_dialog (error->message); - - if (!new_dictionaries->len) - die_with_dialog (_("No dictionaries found either in " - "the configuration or on the command line")); - - // Some Adwaita stupidity, plus defaults for our own widget. - // All the named colours have been there since GNOME 3.4 - // (see gnome-extra-themes git history, Adwaita used to live there). - const char *style = "notebook header tab { padding: 2px 8px; margin: 0; }" - // `gsettings set org.gnome.desktop.interface gtk-key-theme "Emacs"` - // isn't quite what I want, and note that ^U works by default - "@binding-set Readline {" - "bind 'H' { 'delete-from-cursor' (chars, -1) };" - "bind 'W' { 'delete-from-cursor' (word-ends, -1) }; }" - "entry { -gtk-key-bindings: Readline; border-radius: 0; }" - "stardict-view { padding: 0 .25em; }" - "stardict-view.odd {" - "background: @theme_base_color; " - "color: @theme_text_color; }" - "stardict-view.odd:backdrop {" - "background: @theme_unfocused_base_color; " - "color: @theme_fg_color; /* should be more faded than 'text' */ }" - "stardict-view.even {" - "background: mix(@theme_base_color, @theme_text_color, 0.03); " - "color: @theme_text_color; }" - "stardict-view.even:backdrop {" - "background: mix(@theme_unfocused_base_color, " - "@theme_fg_color, 0.03); " - "color: @theme_fg_color; /* should be more faded than 'text' */ }" - "stardict-view:selected {" - "background-color: @theme_selected_bg_color; " - "color: @theme_selected_fg_color; }"; - - 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); - - GtkWidget *item_open = gtk_menu_item_new_with_mnemonic (_("_Open...")); - g_signal_connect (item_open, "activate", G_CALLBACK (on_open), NULL); - - g.watch_selection = TRUE; - GtkWidget *item_selection = - gtk_check_menu_item_new_with_mnemonic (_("_Follow selection")); - gtk_check_menu_item_set_active - (GTK_CHECK_MENU_ITEM (item_selection), g.watch_selection); - g_signal_connect (item_selection, "toggled", - G_CALLBACK (on_selection_watch_toggle), NULL); - - GtkWidget *menu = gtk_menu_new (); - gtk_widget_set_halign (menu, GTK_ALIGN_END); - gtk_menu_shell_append (GTK_MENU_SHELL (menu), item_open); -#ifndef G_OS_WIN32 - gtk_menu_shell_append (GTK_MENU_SHELL (menu), item_selection); -#endif // ! G_OS_WIN32 - gtk_widget_show_all (menu); - - g.hamburger = gtk_menu_button_new (); - gtk_button_set_relief (GTK_BUTTON (g.hamburger), GTK_RELIEF_NONE); - gtk_button_set_image (GTK_BUTTON (g.hamburger), gtk_image_new_from_icon_name - ("open-menu-symbolic", GTK_ICON_SIZE_BUTTON)); - gtk_menu_button_set_popup (GTK_MENU_BUTTON (g.hamburger), menu); - gtk_widget_show (g.hamburger); - - 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); - gtk_notebook_set_action_widget - (GTK_NOTEBOOK (g.notebook), g.hamburger, GTK_PACK_END); - - g.entry = gtk_search_entry_new (); - g_signal_connect (g.entry, "changed", G_CALLBACK (on_changed), g.view); - - g.window = gtk_window_new (GTK_WINDOW_TOPLEVEL); - gtk_window_set_title (GTK_WINDOW (g.window), PROJECT_NAME); - 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); - - GtkClipboard *clipboard = gtk_clipboard_get (GDK_SELECTION_PRIMARY); - g_signal_connect (clipboard, "owner-change", - G_CALLBACK (on_selection), NULL); - - gtk_drag_dest_set (g.view, - GTK_DEST_DEFAULT_ALL, NULL, 0, GDK_ACTION_COPY); - gtk_drag_dest_add_uri_targets (g.view); - g_signal_connect (g.view, "drag-data-received", - G_CALLBACK (on_drag_data_received), NULL); - g_signal_connect (g.view, "send", - G_CALLBACK (on_send), NULL); - - if (!reload_dictionaries (new_dictionaries, &error)) - die_with_dialog (error->message); - - gtk_widget_show_all (g.window); - gtk_main (); - return 0; -} -- cgit v1.2.3-70-g09d2