Compare commits
4 Commits
695f71d946
...
aa19848499
Author | SHA1 | Date | |
---|---|---|---|
aa19848499 | |||
588b6ef8bb | |||
fda956093c | |||
74f2bcae34 |
@ -89,12 +89,12 @@ endif (WITH_X11)
|
|||||||
|
|
||||||
link_directories (${dependencies_LIBRARY_DIRS})
|
link_directories (${dependencies_LIBRARY_DIRS})
|
||||||
include_directories (${ZLIB_INCLUDE_DIRS} ${icu_INCLUDE_DIRS}
|
include_directories (${ZLIB_INCLUDE_DIRS} ${icu_INCLUDE_DIRS}
|
||||||
${dependencies_INCLUDE_DIRS} ${NCURSESW_INCLUDE_DIRS}
|
${dependencies_INCLUDE_DIRS} ${Ncursesw_INCLUDE_DIRS}
|
||||||
${Termo_INCLUDE_DIRS})
|
${Termo_INCLUDE_DIRS})
|
||||||
|
|
||||||
# Configuration
|
# Configuration
|
||||||
include (CheckFunctionExists)
|
include (CheckFunctionExists)
|
||||||
set (CMAKE_REQUIRED_LIBRARIES ${NCURSESW_LIBRARIES})
|
set (CMAKE_REQUIRED_LIBRARIES ${Ncursesw_LIBRARIES})
|
||||||
CHECK_FUNCTION_EXISTS ("resizeterm" HAVE_RESIZETERM)
|
CHECK_FUNCTION_EXISTS ("resizeterm" HAVE_RESIZETERM)
|
||||||
|
|
||||||
# Localization
|
# Localization
|
||||||
@ -145,7 +145,7 @@ set (project_common_headers
|
|||||||
|
|
||||||
# Project libraries
|
# Project libraries
|
||||||
set (project_common_libraries ${ZLIB_LIBRARIES} ${icu_LIBRARIES}
|
set (project_common_libraries ${ZLIB_LIBRARIES} ${icu_LIBRARIES}
|
||||||
${dependencies_LIBRARIES} ${NCURSESW_LIBRARIES} termo-static)
|
${dependencies_LIBRARIES} ${Ncursesw_LIBRARIES} termo-static)
|
||||||
|
|
||||||
# Create a common project library so that source files are only compiled once
|
# Create a common project library so that source files are only compiled once
|
||||||
if (${CMAKE_VERSION} VERSION_GREATER "2.8.7")
|
if (${CMAKE_VERSION} VERSION_GREATER "2.8.7")
|
||||||
@ -179,6 +179,15 @@ add_executable (${PROJECT_NAME}
|
|||||||
${project_sources} ${project_headers} ${project_common_sources})
|
${project_sources} ${project_headers} ${project_common_sources})
|
||||||
target_link_libraries (${PROJECT_NAME} ${project_common_libraries})
|
target_link_libraries (${PROJECT_NAME} ${project_common_libraries})
|
||||||
|
|
||||||
|
# Experimental GTK+ frontend, we link it with ncurses but we don't care
|
||||||
|
pkg_check_modules (gtk gtk+-3.0)
|
||||||
|
if (gtk_FOUND)
|
||||||
|
add_executable (sdgtk EXCLUDE_FROM_ALL
|
||||||
|
src/sdgtk.c ${project_common_sources})
|
||||||
|
target_include_directories (sdgtk PUBLIC ${gtk_INCLUDE_DIRS})
|
||||||
|
target_link_libraries (sdgtk ${gtk_LIBRARIES} ${project_common_libraries})
|
||||||
|
endif (gtk_FOUND)
|
||||||
|
|
||||||
# Tools
|
# Tools
|
||||||
set (tools add-pronunciation query-tool transform)
|
set (tools add-pronunciation query-tool transform)
|
||||||
foreach (tool ${tools})
|
foreach (tool ${tools})
|
||||||
|
@ -1,17 +1,17 @@
|
|||||||
# Public Domain
|
# Public Domain
|
||||||
|
|
||||||
find_package (PkgConfig REQUIRED)
|
find_package (PkgConfig REQUIRED)
|
||||||
pkg_check_modules (NCURSESW QUIET ncursesw)
|
pkg_check_modules (Ncursesw QUIET ncursesw)
|
||||||
|
|
||||||
# OpenBSD doesn't provide a pkg-config file
|
# OpenBSD doesn't provide a pkg-config file
|
||||||
set (required_vars NCURSESW_LIBRARIES)
|
set (required_vars Ncursesw_LIBRARIES)
|
||||||
if (NOT NCURSESW_FOUND)
|
if (NOT Ncursesw_FOUND)
|
||||||
find_library (NCURSESW_LIBRARIES NAMES ncursesw)
|
find_library (Ncursesw_LIBRARIES NAMES ncursesw)
|
||||||
find_path (NCURSESW_INCLUDE_DIRS ncurses.h)
|
find_path (Ncursesw_INCLUDE_DIRS ncurses.h)
|
||||||
list (APPEND required_vars NCURSESW_INCLUDE_DIRS)
|
list (APPEND required_vars Ncursesw_INCLUDE_DIRS)
|
||||||
endif (NOT NCURSESW_FOUND)
|
endif (NOT Ncursesw_FOUND)
|
||||||
|
|
||||||
include (FindPackageHandleStandardArgs)
|
include (FindPackageHandleStandardArgs)
|
||||||
FIND_PACKAGE_HANDLE_STANDARD_ARGS (NCURSESW DEFAULT_MSG ${required_vars})
|
FIND_PACKAGE_HANDLE_STANDARD_ARGS (Ncursesw DEFAULT_MSG ${required_vars})
|
||||||
|
|
||||||
mark_as_advanced (NCURSESW_LIBRARIES NCURSESW_INCLUDE_DIRS)
|
mark_as_advanced (Ncursesw_LIBRARIES Ncursesw_INCLUDE_DIRS)
|
||||||
|
418
src/sdgtk.c
Normal file
418
src/sdgtk.c
Normal file
@ -0,0 +1,418 @@
|
|||||||
|
/*
|
||||||
|
* StarDict GTK+ UI
|
||||||
|
*
|
||||||
|
* Copyright (c) 2020, 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"
|
||||||
|
|
||||||
|
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 *grid; ///< Entries container
|
||||||
|
|
||||||
|
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
|
||||||
|
add_row (StardictIterator *iterator, gint row, gint *height_acc)
|
||||||
|
{
|
||||||
|
Dictionary *dict = &g.dictionaries[g.dictionary];
|
||||||
|
|
||||||
|
StardictEntry *entry = stardict_iterator_get_entry (iterator);
|
||||||
|
g_return_if_fail (entry != NULL);
|
||||||
|
StardictEntryField *field = entry->fields->data;
|
||||||
|
g_return_if_fail (g_ascii_islower (field->type));
|
||||||
|
|
||||||
|
GtkEntryBuffer *buf = gtk_entry_get_buffer (GTK_ENTRY (g.entry));
|
||||||
|
const gchar *input_utf8 = gtk_entry_buffer_get_text (buf);
|
||||||
|
g_return_if_fail (input_utf8 != NULL);
|
||||||
|
|
||||||
|
const gchar *word_str = stardict_iterator_get_word (iterator);
|
||||||
|
gsize common_prefix = stardict_longest_common_collation_prefix
|
||||||
|
(dict->dict, word_str, input_utf8);
|
||||||
|
gchar *pre = g_markup_escape_text (word_str, common_prefix),
|
||||||
|
*post = g_markup_escape_text (word_str + common_prefix, -1),
|
||||||
|
*marked_up = g_strdup_printf ("<u>%s</u>%s", pre, post);
|
||||||
|
|
||||||
|
GtkWidget *word = gtk_label_new (marked_up);
|
||||||
|
gtk_label_set_use_markup (GTK_LABEL (word), TRUE);
|
||||||
|
gtk_label_set_ellipsize (GTK_LABEL (word), PANGO_ELLIPSIZE_END);
|
||||||
|
gtk_label_set_selectable (GTK_LABEL (word), TRUE);
|
||||||
|
gtk_label_set_xalign (GTK_LABEL (word), 0);
|
||||||
|
gtk_label_set_yalign (GTK_LABEL (word), 0);
|
||||||
|
// FIXME: they can't be deselected by just clicking outside of them
|
||||||
|
gtk_widget_set_can_focus (word, FALSE);
|
||||||
|
|
||||||
|
g_free (pre);
|
||||||
|
g_free (post);
|
||||||
|
g_free (marked_up);
|
||||||
|
|
||||||
|
GtkWidget *desc = gtk_label_new (field->data);
|
||||||
|
gtk_label_set_ellipsize (GTK_LABEL (desc), PANGO_ELLIPSIZE_END);
|
||||||
|
gtk_label_set_selectable (GTK_LABEL (desc), TRUE);
|
||||||
|
gtk_label_set_xalign (GTK_LABEL (desc), 0);
|
||||||
|
gtk_widget_set_can_focus (desc, FALSE);
|
||||||
|
|
||||||
|
g_object_unref (entry);
|
||||||
|
|
||||||
|
if (iterator->offset % 2 == 0)
|
||||||
|
{
|
||||||
|
GtkStyleContext *ctx;
|
||||||
|
ctx = gtk_widget_get_style_context (word);
|
||||||
|
gtk_style_context_add_class (ctx, "odd");
|
||||||
|
ctx = gtk_widget_get_style_context (desc);
|
||||||
|
gtk_style_context_add_class (ctx, "odd");
|
||||||
|
}
|
||||||
|
|
||||||
|
gtk_grid_attach (GTK_GRID (g.grid), word, 0, row, 1, 1);
|
||||||
|
gtk_grid_attach (GTK_GRID (g.grid), desc, 1, row, 1, 1);
|
||||||
|
|
||||||
|
gtk_widget_show (word);
|
||||||
|
gtk_widget_show (desc);
|
||||||
|
|
||||||
|
gint minimum_word = 0, minimum_desc = 0;
|
||||||
|
gtk_widget_get_preferred_height (word, &minimum_word, NULL);
|
||||||
|
gtk_widget_get_preferred_height (desc, &minimum_desc, NULL);
|
||||||
|
*height_acc += MAX (minimum_word, minimum_desc);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
reload (GtkWidget *grid)
|
||||||
|
{
|
||||||
|
Dictionary *dict = &g.dictionaries[g.dictionary];
|
||||||
|
|
||||||
|
GList *children = gtk_container_get_children (GTK_CONTAINER (grid));
|
||||||
|
for (GList *iter = children; iter != NULL; iter = g_list_next (iter))
|
||||||
|
gtk_widget_destroy (GTK_WIDGET (iter->data));
|
||||||
|
g_list_free (children);
|
||||||
|
|
||||||
|
gint window_height = 0;
|
||||||
|
gtk_window_get_size (GTK_WINDOW (g.window), NULL, &window_height);
|
||||||
|
if (window_height <= 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
StardictIterator *iterator =
|
||||||
|
stardict_iterator_new (dict->dict, dict->position);
|
||||||
|
gint row = 0, height_acc = 0;
|
||||||
|
while (stardict_iterator_is_valid (iterator))
|
||||||
|
{
|
||||||
|
add_row (iterator, row++, &height_acc);
|
||||||
|
if (height_acc >= window_height)
|
||||||
|
break;
|
||||||
|
|
||||||
|
stardict_iterator_next (iterator);
|
||||||
|
}
|
||||||
|
gtk_widget_show_all (grid);
|
||||||
|
g_object_unref (iterator);
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
on_changed (G_GNUC_UNUSED GtkWidget *widget, G_GNUC_UNUSED gpointer data)
|
||||||
|
{
|
||||||
|
search (&g.dictionaries[g.dictionary]);
|
||||||
|
reload (g.grid);
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
&& 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]);
|
||||||
|
reload (g.grid);
|
||||||
|
}
|
||||||
|
|
||||||
|
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 and our own additions
|
||||||
|
const char *style = "notebook header tab { padding: 2px 8px; margin: 0; }"
|
||||||
|
"grid { border-top: 1px solid rgba(0, 0, 0, 0.2); background: white; }"
|
||||||
|
"grid label { padding: 0 5px; "
|
||||||
|
"/*border-bottom: 1px solid rgba(0, 0, 0, 0.2);*/ }"
|
||||||
|
"grid label.odd { background: rgba(0, 0, 0, 0.05); }";
|
||||||
|
|
||||||
|
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.grid = gtk_grid_new ();
|
||||||
|
gtk_grid_set_column_homogeneous (GTK_GRID (g.grid), TRUE);
|
||||||
|
|
||||||
|
// FIXME: we'd rather like to trim the contents, not make it scrollable.
|
||||||
|
// This just limits the allocation.
|
||||||
|
// TODO: probably create a whole new custom widget, everything is text
|
||||||
|
// anyway and mostly handled by Pango, including pango_layout_xy_to_index()
|
||||||
|
// - I don't know where to get selection colour but inversion works, too
|
||||||
|
GtkWidget *scrolled_window = gtk_scrolled_window_new (NULL, NULL);
|
||||||
|
gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scrolled_window),
|
||||||
|
GTK_POLICY_NEVER, GTK_POLICY_EXTERNAL);
|
||||||
|
gtk_widget_set_can_focus (scrolled_window, FALSE);
|
||||||
|
gtk_container_add (GTK_CONTAINER (scrolled_window), g.grid);
|
||||||
|
|
||||||
|
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.grid);
|
||||||
|
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);
|
||||||
|
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_box_pack_end (GTK_BOX (superbox), scrolled_window, 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);
|
||||||
|
|
||||||
|
// Make sure to fill up the window with entries once we're resized
|
||||||
|
// XXX: this is rather inefficient as we rebuild everything each time
|
||||||
|
g_signal_connect (g.window, "configure-event",
|
||||||
|
G_CALLBACK (on_changed), NULL);
|
||||||
|
g_signal_connect (g.window, "map-event",
|
||||||
|
G_CALLBACK (on_changed), NULL);
|
||||||
|
|
||||||
|
gtk_widget_grab_focus (g.entry);
|
||||||
|
gtk_widget_show_all (g.window);
|
||||||
|
gtk_main ();
|
||||||
|
|
||||||
|
g_strfreev (filenames);
|
||||||
|
return 0;
|
||||||
|
}
|
@ -980,7 +980,7 @@ app_show_help (Application *self)
|
|||||||
{
|
{
|
||||||
PROJECT_NAME " " PROJECT_VERSION,
|
PROJECT_NAME " " PROJECT_VERSION,
|
||||||
_("Terminal UI for StarDict dictionaries"),
|
_("Terminal UI for StarDict dictionaries"),
|
||||||
"Copyright (c) 2013 - 2018, Přemysl Eric Janouch",
|
"Copyright (c) 2013 - 2020, Přemysl Eric Janouch",
|
||||||
"",
|
"",
|
||||||
_("Type to search")
|
_("Type to search")
|
||||||
};
|
};
|
||||||
|
@ -915,6 +915,15 @@ stardict_dict_cmp_index (StardictDict *sd, const gchar *word, gint i)
|
|||||||
return g_ascii_strcasecmp (word, target);
|
return g_ascii_strcasecmp (word, target);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static size_t
|
||||||
|
prefix (StardictDict *sd, const gchar *word, gint i)
|
||||||
|
{
|
||||||
|
GArray *index = sd->priv->index;
|
||||||
|
return (guint) i >= index->len ? 0 :
|
||||||
|
stardict_longest_common_collation_prefix
|
||||||
|
(sd, word, g_array_index (index, StardictIndexEntry, i).name);
|
||||||
|
}
|
||||||
|
|
||||||
/// Search for a word. The search is ASCII-case-insensitive.
|
/// Search for a word. The search is ASCII-case-insensitive.
|
||||||
/// @param[in] word The word in utf-8 encoding
|
/// @param[in] word The word in utf-8 encoding
|
||||||
/// @param[out] success TRUE if found
|
/// @param[out] success TRUE if found
|
||||||
@ -936,14 +945,11 @@ stardict_dict_search (StardictDict *sd, const gchar *word, gboolean *success)
|
|||||||
|
|
||||||
BINARY_SEARCH_END
|
BINARY_SEARCH_END
|
||||||
|
|
||||||
// Try to find a longer common prefix with a preceding entry
|
// Try to find a longer common prefix with a preceding entry.
|
||||||
#define PREFIX(i) stardict_longest_common_collation_prefix \
|
|
||||||
(sd, word, g_array_index (index, StardictIndexEntry, i).name)
|
|
||||||
|
|
||||||
// We need to take care not to step through the entire dictionary
|
// We need to take care not to step through the entire dictionary
|
||||||
// if not a single character matches, because it can be quite costly
|
// if not a single character matches, because it can be quite costly.
|
||||||
size_t probe, best = PREFIX (imin);
|
size_t probe, best = prefix (sd, word, imin);
|
||||||
while (best && imin > 0 && (probe = PREFIX (imin - 1)) >= best)
|
while (best && imin > 0 && (probe = prefix (sd, word, imin - 1)) >= best)
|
||||||
{
|
{
|
||||||
// TODO: take more care to not screw up exact matches,
|
// TODO: take more care to not screw up exact matches,
|
||||||
// use several "best"s according to quality
|
// use several "best"s according to quality
|
||||||
@ -956,8 +962,6 @@ stardict_dict_search (StardictDict *sd, const gchar *word, gboolean *success)
|
|||||||
imin--;
|
imin--;
|
||||||
}
|
}
|
||||||
|
|
||||||
#undef PREFIX
|
|
||||||
|
|
||||||
if (success) *success = FALSE;
|
if (success) *success = FALSE;
|
||||||
return stardict_iterator_new (sd, imin);
|
return stardict_iterator_new (sd, imin);
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user