Compare commits
No commits in common. "573554b9decf74f3b57ab6d26252700132255525" and "f812fae922eec06235c9e566b78c7f0fb46a709b" have entirely different histories.
573554b9de
...
f812fae922
@ -78,9 +78,6 @@ if (WITH_X11)
|
|||||||
list (APPEND dependencies_LIBRARIES ${xcb_LIBRARIES})
|
list (APPEND dependencies_LIBRARIES ${xcb_LIBRARIES})
|
||||||
endif ()
|
endif ()
|
||||||
|
|
||||||
pkg_check_modules (gtk gtk+-3.0)
|
|
||||||
option (WITH_GUI "Build a work-in-progress GTK+ UI" ${gtk_FOUND})
|
|
||||||
|
|
||||||
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}
|
||||||
@ -120,7 +117,7 @@ add_custom_target (docs ALL DEPENDS ${project_MAN_PAGES})
|
|||||||
|
|
||||||
# Project libraries
|
# Project libraries
|
||||||
set (project_common_libraries ${ZLIB_LIBRARIES} ${icu_LIBRARIES}
|
set (project_common_libraries ${ZLIB_LIBRARIES} ${icu_LIBRARIES}
|
||||||
${dependencies_LIBRARIES})
|
${dependencies_LIBRARIES} ${Ncursesw_LIBRARIES} termo-static)
|
||||||
|
|
||||||
set (project_common_headers
|
set (project_common_headers
|
||||||
${PROJECT_BINARY_DIR}/config.h
|
${PROJECT_BINARY_DIR}/config.h
|
||||||
@ -154,17 +151,15 @@ set (project_headers
|
|||||||
add_definitions (-DGLIB_DISABLE_DEPRECATION_WARNINGS)
|
add_definitions (-DGLIB_DISABLE_DEPRECATION_WARNINGS)
|
||||||
add_executable (${PROJECT_NAME}
|
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})
|
||||||
${Ncursesw_LIBRARIES} termo-static)
|
|
||||||
|
|
||||||
# The same for the alternative GTK+ UI
|
# Experimental GTK+ frontend, we link it with ncurses but we don't care
|
||||||
if (WITH_GUI)
|
pkg_check_modules (gtk gtk+-3.0)
|
||||||
add_executable (sdgui
|
if (gtk_FOUND)
|
||||||
src/sdgui.c
|
add_executable (sdgtk EXCLUDE_FROM_ALL
|
||||||
src/stardict-view.c
|
src/sdgtk.c ${project_common_sources})
|
||||||
${project_common_sources})
|
target_include_directories (sdgtk PUBLIC ${gtk_INCLUDE_DIRS})
|
||||||
target_include_directories (sdgui PUBLIC ${gtk_INCLUDE_DIRS})
|
target_link_libraries (sdgtk ${gtk_LIBRARIES} ${project_common_libraries})
|
||||||
target_link_libraries (sdgui ${gtk_LIBRARIES} ${project_common_libraries})
|
|
||||||
endif ()
|
endif ()
|
||||||
|
|
||||||
# Tools
|
# Tools
|
||||||
@ -196,9 +191,6 @@ add_custom_target (dicts DEPENDS ${dicts_targets})
|
|||||||
include (GNUInstallDirs)
|
include (GNUInstallDirs)
|
||||||
install (TARGETS ${PROJECT_NAME} DESTINATION ${CMAKE_INSTALL_BINDIR})
|
install (TARGETS ${PROJECT_NAME} DESTINATION ${CMAKE_INSTALL_BINDIR})
|
||||||
install (FILES LICENSE DESTINATION ${CMAKE_INSTALL_DOCDIR})
|
install (FILES LICENSE DESTINATION ${CMAKE_INSTALL_DOCDIR})
|
||||||
if (WITH_GUI)
|
|
||||||
install (TARGETS sdgui DESTINATION ${CMAKE_INSTALL_BINDIR})
|
|
||||||
endif ()
|
|
||||||
|
|
||||||
foreach (page ${project_MAN_PAGES})
|
foreach (page ${project_MAN_PAGES})
|
||||||
string (REGEX MATCH "\\.([0-9])$" manpage_suffix "${page}")
|
string (REGEX MATCH "\\.([0-9])$" manpage_suffix "${page}")
|
||||||
|
@ -79,11 +79,6 @@ Linux and/or BSD distributions:
|
|||||||
Given the entangledness of this codebase, issues with the file format,
|
Given the entangledness of this codebase, issues with the file format,
|
||||||
and general undesirability of terminal UIs, it might be better to start anew.
|
and general undesirability of terminal UIs, it might be better to start anew.
|
||||||
|
|
||||||
Graphical UI
|
|
||||||
------------
|
|
||||||
With GTK+ 3 development packages installed, an alternative, work-in-progress
|
|
||||||
frontend will be built and installed.
|
|
||||||
|
|
||||||
Contributing and Support
|
Contributing and Support
|
||||||
------------------------
|
------------------------
|
||||||
Use https://git.janouch.name/p/sdtui to report any bugs, request features,
|
Use https://git.janouch.name/p/sdtui to report any bugs, request features,
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
/*
|
/*
|
||||||
* StarDict GTK+ UI
|
* StarDict GTK+ UI
|
||||||
*
|
*
|
||||||
* Copyright (c) 2020 - 2021, Přemysl Eric Janouch <p@janouch.name>
|
* Copyright (c) 2020, Přemysl Eric Janouch <p@janouch.name>
|
||||||
*
|
*
|
||||||
* Permission to use, copy, modify, and/or distribute this software for any
|
* Permission to use, copy, modify, and/or distribute this software for any
|
||||||
* purpose with or without fee is hereby granted.
|
* purpose with or without fee is hereby granted.
|
||||||
@ -24,7 +24,6 @@
|
|||||||
#include "config.h"
|
#include "config.h"
|
||||||
#include "stardict.h"
|
#include "stardict.h"
|
||||||
#include "utils.h"
|
#include "utils.h"
|
||||||
#include "stardict-view.h"
|
|
||||||
|
|
||||||
typedef struct dictionary Dictionary;
|
typedef struct dictionary Dictionary;
|
||||||
|
|
||||||
@ -41,7 +40,7 @@ static struct
|
|||||||
GtkWidget *window; ///< Top-level window
|
GtkWidget *window; ///< Top-level window
|
||||||
GtkWidget *notebook; ///< Notebook with tabs
|
GtkWidget *notebook; ///< Notebook with tabs
|
||||||
GtkWidget *entry; ///< Search entry widget
|
GtkWidget *entry; ///< Search entry widget
|
||||||
GtkWidget *view; ///< Entries view
|
GtkWidget *grid; ///< Entries container
|
||||||
|
|
||||||
gint dictionary; ///< Index of the current dictionary
|
gint dictionary; ///< Index of the current dictionary
|
||||||
Dictionary *dictionaries; ///< All open dictionaries
|
Dictionary *dictionaries; ///< All open dictionaries
|
||||||
@ -86,6 +85,99 @@ init (gchar **filenames, GError **e)
|
|||||||
return TRUE;
|
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
|
static void
|
||||||
search (Dictionary *dict)
|
search (Dictionary *dict)
|
||||||
{
|
{
|
||||||
@ -96,16 +188,13 @@ search (Dictionary *dict)
|
|||||||
stardict_dict_search (dict->dict, input_utf8, NULL);
|
stardict_dict_search (dict->dict, input_utf8, NULL);
|
||||||
dict->position = stardict_iterator_get_offset (iterator);
|
dict->position = stardict_iterator_get_offset (iterator);
|
||||||
g_object_unref (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
|
static void
|
||||||
on_changed (G_GNUC_UNUSED GtkWidget *widget, G_GNUC_UNUSED gpointer data)
|
on_changed (G_GNUC_UNUSED GtkWidget *widget, G_GNUC_UNUSED gpointer data)
|
||||||
{
|
{
|
||||||
search (&g.dictionaries[g.dictionary]);
|
search (&g.dictionaries[g.dictionary]);
|
||||||
|
reload (g.grid);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
@ -142,6 +231,7 @@ on_switch_page (G_GNUC_UNUSED GtkWidget *widget, G_GNUC_UNUSED GtkWidget *page,
|
|||||||
{
|
{
|
||||||
g.dictionary = page_num;
|
g.dictionary = page_num;
|
||||||
search (&g.dictionaries[g.dictionary]);
|
search (&g.dictionaries[g.dictionary]);
|
||||||
|
reload (g.grid);
|
||||||
}
|
}
|
||||||
|
|
||||||
static gboolean
|
static gboolean
|
||||||
@ -227,8 +317,12 @@ main (int argc, char *argv[])
|
|||||||
if (!init (filenames, &error))
|
if (!init (filenames, &error))
|
||||||
die_with_dialog (error->message);
|
die_with_dialog (error->message);
|
||||||
|
|
||||||
// Some Adwaita stupidity
|
// Some Adwaita stupidity and our own additions
|
||||||
const char *style = "notebook header tab { padding: 2px 8px; margin: 0; }";
|
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 ();
|
GdkScreen *screen = gdk_screen_get_default ();
|
||||||
GtkCssProvider *provider = gtk_css_provider_new ();
|
GtkCssProvider *provider = gtk_css_provider_new ();
|
||||||
@ -236,6 +330,20 @@ main (int argc, char *argv[])
|
|||||||
gtk_style_context_add_provider_for_screen (screen,
|
gtk_style_context_add_provider_for_screen (screen,
|
||||||
GTK_STYLE_PROVIDER (provider), GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
|
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.notebook = gtk_notebook_new ();
|
||||||
g_signal_connect (g.notebook, "switch-page",
|
g_signal_connect (g.notebook, "switch-page",
|
||||||
G_CALLBACK (on_switch_page), NULL);
|
G_CALLBACK (on_switch_page), NULL);
|
||||||
@ -267,28 +375,21 @@ main (int argc, char *argv[])
|
|||||||
// TODO: attach to the "key-press-event" signal and implement ^W at least,
|
// 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
|
// though ^U is working already! Note that bindings can be done in CSS
|
||||||
// as well, if we have any extra specially for the editor
|
// as well, if we have any extra specially for the editor
|
||||||
g_signal_connect (g.entry, "changed", G_CALLBACK (on_changed), g.view);
|
g_signal_connect (g.entry, "changed", G_CALLBACK (on_changed), g.grid);
|
||||||
// TODO: make the entry have a background colour, rather than transparency
|
|
||||||
gtk_entry_set_has_frame (GTK_ENTRY (g.entry), FALSE);
|
gtk_entry_set_has_frame (GTK_ENTRY (g.entry), FALSE);
|
||||||
|
|
||||||
// TODO: supposedly attach to "key-press-event" here and react to
|
// TODO: supposedly attach to "key-press-event" here and react to
|
||||||
// PageUp/PageDown and up/down arrow keys... either here or in the Entry
|
// PageUp/PageDown and up/down arrow keys... either here or in the Entry
|
||||||
g.window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
|
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_signal_connect (g.window, "destroy",
|
||||||
G_CALLBACK (on_destroy), NULL);
|
G_CALLBACK (on_destroy), NULL);
|
||||||
g_signal_connect (g.window, "key-press-event",
|
g_signal_connect (g.window, "key-press-event",
|
||||||
G_CALLBACK (on_key_press), NULL);
|
G_CALLBACK (on_key_press), NULL);
|
||||||
|
|
||||||
GtkWidget *superbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 1);
|
GtkWidget *superbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 1);
|
||||||
gtk_container_add (GTK_CONTAINER (g.window), superbox);
|
gtk_container_add (GTK_CONTAINER (g.window), superbox);
|
||||||
gtk_container_add (GTK_CONTAINER (superbox), g.notebook);
|
gtk_container_add (GTK_CONTAINER (superbox), g.notebook);
|
||||||
gtk_container_add (GTK_CONTAINER (superbox), g.entry);
|
gtk_container_add (GTK_CONTAINER (superbox), g.entry);
|
||||||
gtk_container_add (GTK_CONTAINER (superbox),
|
gtk_box_pack_end (GTK_BOX (superbox), scrolled_window, TRUE, TRUE, 0);
|
||||||
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++)
|
for (gsize i = 0; i < g.dictionaries_len; i++)
|
||||||
{
|
{
|
||||||
@ -302,6 +403,13 @@ main (int argc, char *argv[])
|
|||||||
g_signal_connect (clipboard, "owner-change",
|
g_signal_connect (clipboard, "owner-change",
|
||||||
G_CALLBACK (on_selection), NULL);
|
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_grab_focus (g.entry);
|
||||||
gtk_widget_show_all (g.window);
|
gtk_widget_show_all (g.window);
|
||||||
gtk_main ();
|
gtk_main ();
|
28
src/sdtui.c
28
src/sdtui.c
@ -38,10 +38,6 @@
|
|||||||
|
|
||||||
#include <termo.h> // input
|
#include <termo.h> // input
|
||||||
#include <ncurses.h> // output
|
#include <ncurses.h> // output
|
||||||
#include <termios.h>
|
|
||||||
#ifndef TIOCGWINSZ
|
|
||||||
#include <sys/ioctl.h>
|
|
||||||
#endif // ! TIOCGWINSZ
|
|
||||||
|
|
||||||
#include "config.h"
|
#include "config.h"
|
||||||
#include "stardict.h"
|
#include "stardict.h"
|
||||||
@ -66,27 +62,6 @@ unichar_width (gunichar ch)
|
|||||||
return 1 + g_unichar_iswide (ch);
|
return 1 + g_unichar_iswide (ch);
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
|
||||||
update_curses_terminal_size (void)
|
|
||||||
{
|
|
||||||
#if defined (HAVE_RESIZETERM) && defined (TIOCGWINSZ)
|
|
||||||
struct winsize size;
|
|
||||||
if (!ioctl (STDOUT_FILENO, TIOCGWINSZ, (char *) &size))
|
|
||||||
{
|
|
||||||
char *row = getenv ("LINES");
|
|
||||||
char *col = getenv ("COLUMNS");
|
|
||||||
unsigned long tmp;
|
|
||||||
resizeterm (
|
|
||||||
(row && xstrtoul (&tmp, row, 10)) ? tmp : size.ws_row,
|
|
||||||
(col && xstrtoul (&tmp, col, 10)) ? tmp : size.ws_col);
|
|
||||||
}
|
|
||||||
#else // HAVE_RESIZETERM && TIOCGWINSZ
|
|
||||||
// The standard endwin/refresh sequence makes the terminal flicker.
|
|
||||||
endwin ();
|
|
||||||
refresh ();
|
|
||||||
#endif // HAVE_RESIZETERM && TIOCGWINSZ
|
|
||||||
}
|
|
||||||
|
|
||||||
static guint
|
static guint
|
||||||
add_read_watch (int fd, GIOFunc func, gpointer user_data)
|
add_read_watch (int fd, GIOFunc func, gpointer user_data)
|
||||||
{
|
{
|
||||||
@ -255,7 +230,7 @@ struct application
|
|||||||
guint32 top_position; ///< Index of the topmost dict. entry
|
guint32 top_position; ///< Index of the topmost dict. entry
|
||||||
guint top_offset; ///< Offset into the top entry
|
guint top_offset; ///< Offset into the top entry
|
||||||
guint selected; ///< Offset to the selected definition
|
guint selected; ///< Offset to the selected definition
|
||||||
GPtrArray * entries; ///< ViewEntry-s within the view
|
GPtrArray * entries; ///< ViewEntry's within the view
|
||||||
|
|
||||||
gchar * search_label; ///< Text of the "Search" label
|
gchar * search_label; ///< Text of the "Search" label
|
||||||
GArray * input; ///< The current search input
|
GArray * input; ///< The current search input
|
||||||
@ -411,7 +386,6 @@ view_entry_new (StardictIterator *iterator)
|
|||||||
found_anything_displayable = TRUE;
|
found_anything_displayable = TRUE;
|
||||||
break;
|
break;
|
||||||
case STARDICT_FIELD_PHONETIC:
|
case STARDICT_FIELD_PHONETIC:
|
||||||
// FIXME this makes it highlightable
|
|
||||||
g_string_append_printf (word, " /%s/", (const gchar *) field->data);
|
g_string_append_printf (word, " /%s/", (const gchar *) field->data);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
|
@ -1,526 +0,0 @@
|
|||||||
/*
|
|
||||||
* StarDict GTK+ UI - dictionary view component
|
|
||||||
*
|
|
||||||
* Copyright (c) 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 "stardict-view.h"
|
|
||||||
#include "utils.h"
|
|
||||||
|
|
||||||
|
|
||||||
typedef struct view_entry ViewEntry;
|
|
||||||
|
|
||||||
struct view_entry
|
|
||||||
{
|
|
||||||
gchar *word; ///< The word, in Pango markup
|
|
||||||
gchar *definition; ///< Definition lines, in Pango markup
|
|
||||||
|
|
||||||
PangoLayout *word_layout; ///< Ellipsized one-line layout or NULL
|
|
||||||
PangoLayout *definition_layout; ///< Multiline layout or NULL
|
|
||||||
};
|
|
||||||
|
|
||||||
static void
|
|
||||||
view_entry_destroy (ViewEntry *self)
|
|
||||||
{
|
|
||||||
g_free (self->word);
|
|
||||||
g_free (self->definition);
|
|
||||||
g_clear_object (&self->word_layout);
|
|
||||||
g_clear_object (&self->definition_layout);
|
|
||||||
g_slice_free1 (sizeof *self, self);
|
|
||||||
}
|
|
||||||
|
|
||||||
static ViewEntry *
|
|
||||||
view_entry_new (StardictIterator *iterator, const gchar *matched)
|
|
||||||
{
|
|
||||||
g_return_val_if_fail (stardict_iterator_is_valid (iterator), NULL);
|
|
||||||
|
|
||||||
StardictEntry *entry = stardict_iterator_get_entry (iterator);
|
|
||||||
g_return_val_if_fail (entry != NULL, NULL);
|
|
||||||
|
|
||||||
// Highlighting may change the rendition, so far it's easiest to recompute
|
|
||||||
// it on each search field change by rebuilding the list of view entries.
|
|
||||||
// The phonetics suffix would need to be stored separately.
|
|
||||||
const gchar *word = stardict_iterator_get_word (iterator);
|
|
||||||
gsize common_prefix = stardict_longest_common_collation_prefix
|
|
||||||
(iterator->owner, word, matched);
|
|
||||||
|
|
||||||
ViewEntry *ve = g_slice_alloc0 (sizeof *ve);
|
|
||||||
|
|
||||||
GString *adjusted_word = g_string_new ("");
|
|
||||||
gchar *pre = g_markup_escape_text (word, common_prefix);
|
|
||||||
gchar *post = g_markup_escape_text (word + common_prefix, -1);
|
|
||||||
g_string_printf (adjusted_word, "<u>%s</u>%s", pre, post);
|
|
||||||
g_free (pre);
|
|
||||||
g_free (post);
|
|
||||||
|
|
||||||
GPtrArray *definitions = g_ptr_array_new_full (2, g_free);
|
|
||||||
for (const GList *fields = stardict_entry_get_fields (entry); fields; )
|
|
||||||
{
|
|
||||||
const StardictEntryField *field = fields->data;
|
|
||||||
switch (field->type)
|
|
||||||
{
|
|
||||||
case STARDICT_FIELD_MEANING:
|
|
||||||
g_ptr_array_add (definitions,
|
|
||||||
g_markup_escape_text (field->data, -1));
|
|
||||||
break;
|
|
||||||
case STARDICT_FIELD_PANGO:
|
|
||||||
g_ptr_array_add (definitions, g_strdup (field->data));
|
|
||||||
break;
|
|
||||||
case STARDICT_FIELD_XDXF:
|
|
||||||
g_ptr_array_add (definitions,
|
|
||||||
xdxf_to_pango_markup_with_reduced_effort (field->data));
|
|
||||||
break;
|
|
||||||
case STARDICT_FIELD_PHONETIC:
|
|
||||||
{
|
|
||||||
gchar *escaped = g_markup_escape_text (field->data, -1);
|
|
||||||
g_string_append_printf (adjusted_word, " /%s/", escaped);
|
|
||||||
g_free (escaped);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
// TODO: support more of them
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
fields = fields->next;
|
|
||||||
}
|
|
||||||
g_object_unref (entry);
|
|
||||||
|
|
||||||
ve->word = g_string_free (adjusted_word, FALSE);
|
|
||||||
if (!definitions->len)
|
|
||||||
{
|
|
||||||
gchar *message = g_markup_escape_text (_("no usable field found"), -1);
|
|
||||||
g_ptr_array_add (definitions, g_strdup_printf ("<%s>", message));
|
|
||||||
g_free (message);
|
|
||||||
}
|
|
||||||
|
|
||||||
g_ptr_array_add (definitions, NULL);
|
|
||||||
ve->definition = g_strjoinv ("\n", (gchar **) definitions->pdata);
|
|
||||||
g_ptr_array_free (definitions, TRUE);
|
|
||||||
return ve;
|
|
||||||
}
|
|
||||||
|
|
||||||
static gint
|
|
||||||
view_entry_height (ViewEntry *ve, gint *word_offset, gint *defn_offset)
|
|
||||||
{
|
|
||||||
gint word_w = 0, word_h = 0;
|
|
||||||
gint defn_w = 0, defn_h = 0;
|
|
||||||
pango_layout_get_pixel_size (ve->word_layout, &word_w, &word_h);
|
|
||||||
pango_layout_get_pixel_size (ve->definition_layout, &defn_w, &defn_h);
|
|
||||||
|
|
||||||
// Align baselines, without further considerations
|
|
||||||
gint wb = pango_layout_get_baseline (ve->word_layout) / PANGO_SCALE;
|
|
||||||
gint db = pango_layout_get_baseline (ve->definition_layout) / PANGO_SCALE;
|
|
||||||
gint word_y = MAX (0, db - wb);
|
|
||||||
gint defn_y = MAX (0, wb - db);
|
|
||||||
|
|
||||||
if (word_offset)
|
|
||||||
*word_offset = word_y;
|
|
||||||
if (defn_offset)
|
|
||||||
*defn_offset = defn_y;
|
|
||||||
|
|
||||||
return MAX (word_y + word_h, defn_y + defn_h);
|
|
||||||
}
|
|
||||||
|
|
||||||
#define PADDING 5
|
|
||||||
|
|
||||||
static gint
|
|
||||||
view_entry_draw (ViewEntry *ve, cairo_t *cr, gint full_width, gboolean even)
|
|
||||||
{
|
|
||||||
// TODO: this shouldn't be hardcoded, read it out from somewhere
|
|
||||||
gdouble g = even ? 1. : .95;
|
|
||||||
|
|
||||||
gint word_y = 0, defn_y = 0,
|
|
||||||
height = view_entry_height (ve, &word_y, &defn_y);
|
|
||||||
cairo_rectangle (cr, 0, 0, full_width, height);
|
|
||||||
cairo_set_source_rgb (cr, g, g, g);
|
|
||||||
cairo_fill (cr);
|
|
||||||
|
|
||||||
cairo_set_source_rgb (cr, 0, 0, 0);
|
|
||||||
cairo_move_to (cr, full_width / 2 + PADDING, defn_y);
|
|
||||||
pango_cairo_show_layout (cr, ve->definition_layout);
|
|
||||||
|
|
||||||
PangoLayoutIter *iter = pango_layout_get_iter (ve->definition_layout);
|
|
||||||
do
|
|
||||||
{
|
|
||||||
if (!pango_layout_iter_get_line_readonly (iter)->is_paragraph_start)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
PangoRectangle logical = {};
|
|
||||||
pango_layout_iter_get_line_extents (iter, NULL, &logical);
|
|
||||||
cairo_move_to (cr, PADDING, word_y + logical.y / PANGO_SCALE);
|
|
||||||
pango_cairo_show_layout (cr, ve->word_layout);
|
|
||||||
}
|
|
||||||
while (pango_layout_iter_next_line (iter));
|
|
||||||
pango_layout_iter_free (iter);
|
|
||||||
return height;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void
|
|
||||||
view_entry_rebuild_layout (ViewEntry *ve, PangoContext *pc, gint width)
|
|
||||||
{
|
|
||||||
g_clear_object (&ve->word_layout);
|
|
||||||
g_clear_object (&ve->definition_layout);
|
|
||||||
|
|
||||||
int left_width = width / 2 - 2 * PADDING;
|
|
||||||
int right_width = width - left_width - 2 * PADDING;
|
|
||||||
if (left_width < 1 || right_width < 1)
|
|
||||||
return;
|
|
||||||
|
|
||||||
// TODO: preferably pre-validate the layouts with pango_parse_markup(),
|
|
||||||
// so that it doesn't warn without indication on the frontend
|
|
||||||
ve->word_layout = pango_layout_new (pc);
|
|
||||||
pango_layout_set_markup (ve->word_layout, ve->word, -1);
|
|
||||||
pango_layout_set_ellipsize (ve->word_layout, PANGO_ELLIPSIZE_END);
|
|
||||||
pango_layout_set_single_paragraph_mode (ve->word_layout, TRUE);
|
|
||||||
pango_layout_set_width (ve->word_layout, PANGO_SCALE * left_width);
|
|
||||||
|
|
||||||
ve->definition_layout = pango_layout_new (pc);
|
|
||||||
pango_layout_set_markup (ve->definition_layout, ve->definition, -1);
|
|
||||||
pango_layout_set_width (ve->definition_layout, PANGO_SCALE * right_width);
|
|
||||||
}
|
|
||||||
|
|
||||||
// --- Widget ------------------------------------------------------------------
|
|
||||||
|
|
||||||
struct _StardictView
|
|
||||||
{
|
|
||||||
GtkWidget parent_instance;
|
|
||||||
|
|
||||||
StardictDict *dict; ///< The displayed dictionary
|
|
||||||
guint top_position; ///< Index of the topmost dict. entry
|
|
||||||
gchar *matched; ///< Highlight common word part of this
|
|
||||||
|
|
||||||
gint top_offset; ///< Pixel offset into the entry
|
|
||||||
// TODO: think about making it, e.g., a pair of (ViewEntry *, guint)
|
|
||||||
// NOTE: this is the index of a Pango paragraph (a virtual entity)
|
|
||||||
guint selected; ///< Offset to the selected definition
|
|
||||||
GList *entries; ///< ViewEntry-s within the view
|
|
||||||
};
|
|
||||||
|
|
||||||
static void
|
|
||||||
adjust_for_offset (StardictView *self)
|
|
||||||
{
|
|
||||||
// FIXME: lots of code duplication with reload(), could be refactored
|
|
||||||
GtkWidget *widget = GTK_WIDGET (self);
|
|
||||||
PangoContext *pc = gtk_widget_get_pango_context (widget);
|
|
||||||
const gchar *matched = self->matched ? self->matched : "";
|
|
||||||
|
|
||||||
GtkAllocation allocation = {};
|
|
||||||
gtk_widget_get_allocation (widget, &allocation);
|
|
||||||
|
|
||||||
// If scrolled way up, prepend entries so long as it's possible
|
|
||||||
StardictIterator *iterator =
|
|
||||||
stardict_iterator_new (self->dict, self->top_position);
|
|
||||||
while (self->top_offset < 0)
|
|
||||||
{
|
|
||||||
stardict_iterator_prev (iterator);
|
|
||||||
if (!stardict_iterator_is_valid (iterator))
|
|
||||||
{
|
|
||||||
self->top_offset = 0;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
self->top_position = stardict_iterator_get_offset (iterator);
|
|
||||||
ViewEntry *ve = view_entry_new (iterator, matched);
|
|
||||||
view_entry_rebuild_layout (ve, pc, allocation.width);
|
|
||||||
self->top_offset += view_entry_height (ve, NULL, NULL);
|
|
||||||
self->entries = g_list_prepend (self->entries, ve);
|
|
||||||
}
|
|
||||||
g_object_unref (iterator);
|
|
||||||
|
|
||||||
// If scrolled way down, drop leading entries so long as it's possible
|
|
||||||
while (self->entries)
|
|
||||||
{
|
|
||||||
gint height = view_entry_height (self->entries->data, NULL, NULL);
|
|
||||||
if (self->top_offset < height)
|
|
||||||
break;
|
|
||||||
|
|
||||||
self->top_offset -= height;
|
|
||||||
view_entry_destroy (self->entries->data);
|
|
||||||
self->entries = g_list_delete_link (self->entries, self->entries);
|
|
||||||
self->top_position++;
|
|
||||||
|
|
||||||
}
|
|
||||||
if (self->top_offset && !self->entries)
|
|
||||||
self->top_offset = 0;
|
|
||||||
|
|
||||||
// Load replacement trailing entries, or drop those no longer visible
|
|
||||||
iterator = stardict_iterator_new (self->dict, self->top_position);
|
|
||||||
gint used = -self->top_offset;
|
|
||||||
for (GList *iter = self->entries, *next;
|
|
||||||
next = g_list_next (iter), iter; iter = next)
|
|
||||||
{
|
|
||||||
if (used < allocation.height)
|
|
||||||
used += view_entry_height (iter->data, NULL, NULL);
|
|
||||||
else
|
|
||||||
{
|
|
||||||
view_entry_destroy (iter->data);
|
|
||||||
self->entries = g_list_delete_link (self->entries, iter);
|
|
||||||
}
|
|
||||||
stardict_iterator_next (iterator);
|
|
||||||
}
|
|
||||||
while (used < allocation.height && stardict_iterator_is_valid (iterator))
|
|
||||||
{
|
|
||||||
ViewEntry *ve = view_entry_new (iterator, matched);
|
|
||||||
view_entry_rebuild_layout (ve, pc, allocation.width);
|
|
||||||
used += view_entry_height (ve, NULL, NULL);
|
|
||||||
self->entries = g_list_append (self->entries, ve);
|
|
||||||
stardict_iterator_next (iterator);
|
|
||||||
}
|
|
||||||
g_object_unref (iterator);
|
|
||||||
|
|
||||||
gtk_widget_queue_draw (widget);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void
|
|
||||||
reload (StardictView *self)
|
|
||||||
{
|
|
||||||
g_list_free_full (self->entries, (GDestroyNotify) view_entry_destroy);
|
|
||||||
self->entries = NULL;
|
|
||||||
|
|
||||||
GtkWidget *widget = GTK_WIDGET (self);
|
|
||||||
if (!gtk_widget_get_realized (widget) || !self->dict)
|
|
||||||
return;
|
|
||||||
|
|
||||||
GtkAllocation allocation = {};
|
|
||||||
gtk_widget_get_allocation (widget, &allocation);
|
|
||||||
|
|
||||||
PangoContext *pc = gtk_widget_get_pango_context (widget);
|
|
||||||
StardictIterator *iterator =
|
|
||||||
stardict_iterator_new (self->dict, self->top_position);
|
|
||||||
|
|
||||||
gint used = 0;
|
|
||||||
const gchar *matched = self->matched ? self->matched : "";
|
|
||||||
while (used < allocation.height && stardict_iterator_is_valid (iterator))
|
|
||||||
{
|
|
||||||
ViewEntry *ve = view_entry_new (iterator, matched);
|
|
||||||
view_entry_rebuild_layout (ve, pc, allocation.width);
|
|
||||||
used += view_entry_height (ve, NULL, NULL);
|
|
||||||
self->entries = g_list_prepend (self->entries, ve);
|
|
||||||
stardict_iterator_next (iterator);
|
|
||||||
}
|
|
||||||
g_object_unref (iterator);
|
|
||||||
self->entries = g_list_reverse (self->entries);
|
|
||||||
|
|
||||||
// Right now, we're being lazy--this could be integrated here
|
|
||||||
adjust_for_offset (self);
|
|
||||||
|
|
||||||
gtk_widget_queue_draw (widget);
|
|
||||||
}
|
|
||||||
|
|
||||||
// --- Boilerplate -------------------------------------------------------------
|
|
||||||
|
|
||||||
G_DEFINE_TYPE (StardictView, stardict_view, GTK_TYPE_WIDGET)
|
|
||||||
|
|
||||||
static void
|
|
||||||
stardict_view_finalize (GObject *gobject)
|
|
||||||
{
|
|
||||||
StardictView *self = STARDICT_VIEW (gobject);
|
|
||||||
g_clear_object (&self->dict);
|
|
||||||
|
|
||||||
g_list_free_full (self->entries, (GDestroyNotify) view_entry_destroy);
|
|
||||||
self->entries = NULL;
|
|
||||||
|
|
||||||
g_free (self->matched);
|
|
||||||
self->matched = NULL;
|
|
||||||
|
|
||||||
G_OBJECT_CLASS (stardict_view_parent_class)->finalize (gobject);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void
|
|
||||||
stardict_view_get_preferred_height (GtkWidget *widget,
|
|
||||||
gint *minimum, gint *natural)
|
|
||||||
{
|
|
||||||
PangoLayout *layout = gtk_widget_create_pango_layout (widget, NULL);
|
|
||||||
|
|
||||||
gint width = 0, height = 0;
|
|
||||||
pango_layout_get_pixel_size (layout, &width, &height);
|
|
||||||
g_object_unref (layout);
|
|
||||||
|
|
||||||
// There isn't any value that would make any real sense
|
|
||||||
if (!STARDICT_VIEW (widget)->dict)
|
|
||||||
*natural = *minimum = 0;
|
|
||||||
else
|
|
||||||
*natural = *minimum = height;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void
|
|
||||||
stardict_view_get_preferred_width (GtkWidget *widget G_GNUC_UNUSED,
|
|
||||||
gint *minimum, gint *natural)
|
|
||||||
{
|
|
||||||
*natural = *minimum = 4 * PADDING;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void
|
|
||||||
stardict_view_realize (GtkWidget *widget)
|
|
||||||
{
|
|
||||||
GtkAllocation allocation;
|
|
||||||
gtk_widget_get_allocation (widget, &allocation);
|
|
||||||
|
|
||||||
GdkWindowAttr attributes =
|
|
||||||
{
|
|
||||||
.window_type = GDK_WINDOW_CHILD,
|
|
||||||
.x = allocation.x,
|
|
||||||
.y = allocation.y,
|
|
||||||
.width = allocation.width,
|
|
||||||
.height = allocation.height,
|
|
||||||
|
|
||||||
// Input-only would presumably also work (as in GtkPathBar, e.g.),
|
|
||||||
// but it merely seems to involve more work.
|
|
||||||
.wclass = GDK_INPUT_OUTPUT,
|
|
||||||
|
|
||||||
.visual = gtk_widget_get_visual (widget),
|
|
||||||
.event_mask = gtk_widget_get_events (widget) | GDK_SCROLL_MASK,
|
|
||||||
};
|
|
||||||
|
|
||||||
// We need this window to receive input events at all.
|
|
||||||
// TODO: see if we don't want GDK_WA_CURSOR for setting a text cursor
|
|
||||||
GdkWindow *window = gdk_window_new (gtk_widget_get_parent_window (widget),
|
|
||||||
&attributes, GDK_WA_X | GDK_WA_Y | GDK_WA_VISUAL);
|
|
||||||
|
|
||||||
// The default background colour of the GDK window is transparent,
|
|
||||||
// we'll keep it that way, rather than apply the style context.
|
|
||||||
|
|
||||||
gtk_widget_register_window (widget, window);
|
|
||||||
gtk_widget_set_window (widget, window);
|
|
||||||
gtk_widget_set_realized (widget, TRUE);
|
|
||||||
}
|
|
||||||
|
|
||||||
static gboolean
|
|
||||||
stardict_view_draw (GtkWidget *widget, cairo_t *cr)
|
|
||||||
{
|
|
||||||
StardictView *self = STARDICT_VIEW (widget);
|
|
||||||
|
|
||||||
GtkAllocation allocation;
|
|
||||||
gtk_widget_get_allocation (widget, &allocation);
|
|
||||||
|
|
||||||
gint offset = -self->top_offset;
|
|
||||||
gint i = self->top_position;
|
|
||||||
for (GList *iter = self->entries; iter; iter = iter->next)
|
|
||||||
{
|
|
||||||
cairo_save (cr);
|
|
||||||
cairo_translate (cr, 0, offset);
|
|
||||||
// TODO: later exclude clipped entries, but it's not that important
|
|
||||||
offset += view_entry_draw (iter->data, cr, allocation.width, i++ & 1);
|
|
||||||
cairo_restore (cr);
|
|
||||||
}
|
|
||||||
return TRUE;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void
|
|
||||||
stardict_view_size_allocate (GtkWidget *widget, GtkAllocation *allocation)
|
|
||||||
{
|
|
||||||
GTK_WIDGET_CLASS (stardict_view_parent_class)
|
|
||||||
->size_allocate (widget, allocation);
|
|
||||||
|
|
||||||
reload (STARDICT_VIEW (widget));
|
|
||||||
}
|
|
||||||
|
|
||||||
static void
|
|
||||||
stardict_view_screen_changed (GtkWidget *widget, G_GNUC_UNUSED GdkScreen *prev)
|
|
||||||
{
|
|
||||||
// Update the minimum size
|
|
||||||
gtk_widget_queue_resize (widget);
|
|
||||||
|
|
||||||
// Recreate Pango layouts
|
|
||||||
reload (STARDICT_VIEW (widget));
|
|
||||||
}
|
|
||||||
|
|
||||||
static gboolean
|
|
||||||
stardict_view_scroll_event (GtkWidget *widget, GdkEventScroll *event)
|
|
||||||
{
|
|
||||||
// TODO: rethink the notes here to rather iterate over /definition lines/
|
|
||||||
// - iterate over selected lines, maybe one, maybe three
|
|
||||||
StardictView *self = STARDICT_VIEW (widget);
|
|
||||||
if (!self->dict)
|
|
||||||
return FALSE;
|
|
||||||
|
|
||||||
switch (event->direction)
|
|
||||||
{
|
|
||||||
case GDK_SCROLL_UP:
|
|
||||||
self->top_offset -= 50;
|
|
||||||
adjust_for_offset (self);
|
|
||||||
return TRUE;
|
|
||||||
case GDK_SCROLL_DOWN:
|
|
||||||
self->top_offset += 50;
|
|
||||||
adjust_for_offset (self);
|
|
||||||
return TRUE;
|
|
||||||
default:
|
|
||||||
// GDK_SCROLL_SMOOTH doesn't fit the intended way of usage
|
|
||||||
return FALSE;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static void
|
|
||||||
stardict_view_class_init (StardictViewClass *klass)
|
|
||||||
{
|
|
||||||
GObjectClass *object_class = G_OBJECT_CLASS (klass);
|
|
||||||
object_class->finalize = stardict_view_finalize;
|
|
||||||
|
|
||||||
// TODO: handle mouse events for text selection
|
|
||||||
// See https://wiki.gnome.org/HowDoI/CustomWidgets for some guidelines.
|
|
||||||
GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
|
|
||||||
widget_class->get_preferred_height = stardict_view_get_preferred_height;
|
|
||||||
widget_class->get_preferred_width = stardict_view_get_preferred_width;
|
|
||||||
widget_class->realize = stardict_view_realize;
|
|
||||||
widget_class->draw = stardict_view_draw;
|
|
||||||
widget_class->size_allocate = stardict_view_size_allocate;
|
|
||||||
widget_class->screen_changed = stardict_view_screen_changed;
|
|
||||||
widget_class->scroll_event = stardict_view_scroll_event;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void
|
|
||||||
stardict_view_init (G_GNUC_UNUSED StardictView *self)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
// --- Public ------------------------------------------------------------------
|
|
||||||
|
|
||||||
GtkWidget *
|
|
||||||
stardict_view_new (void)
|
|
||||||
{
|
|
||||||
return GTK_WIDGET (g_object_new (STARDICT_TYPE_VIEW, NULL));
|
|
||||||
}
|
|
||||||
|
|
||||||
void
|
|
||||||
stardict_view_set_position (StardictView *self,
|
|
||||||
StardictDict *dict, guint position)
|
|
||||||
{
|
|
||||||
g_return_if_fail (STARDICT_IS_VIEW (self));
|
|
||||||
g_return_if_fail (STARDICT_IS_DICT (dict));
|
|
||||||
|
|
||||||
// Update the minimum size, if appropriate (almost never)
|
|
||||||
if (!self->dict != !dict)
|
|
||||||
gtk_widget_queue_resize (GTK_WIDGET (self));
|
|
||||||
|
|
||||||
g_clear_object (&self->dict);
|
|
||||||
self->dict = g_object_ref (dict);
|
|
||||||
self->top_position = position;
|
|
||||||
self->top_offset = 0;
|
|
||||||
|
|
||||||
reload (self);
|
|
||||||
}
|
|
||||||
|
|
||||||
void
|
|
||||||
stardict_view_set_matched (StardictView *self, const gchar *matched)
|
|
||||||
{
|
|
||||||
g_return_if_fail (STARDICT_IS_VIEW (self));
|
|
||||||
|
|
||||||
g_free (self->matched);
|
|
||||||
self->matched = g_strdup (matched);
|
|
||||||
reload (self);
|
|
||||||
}
|
|
@ -1,34 +0,0 @@
|
|||||||
/*
|
|
||||||
* StarDict GTK+ UI - dictionary view component
|
|
||||||
*
|
|
||||||
* Copyright (c) 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.
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
|
|
||||||
#ifndef STARDICT_VIEW_H
|
|
||||||
#define STARDICT_VIEW_H
|
|
||||||
|
|
||||||
#include <gtk/gtk.h>
|
|
||||||
|
|
||||||
#include "stardict.h"
|
|
||||||
|
|
||||||
#define STARDICT_TYPE_VIEW (stardict_view_get_type ())
|
|
||||||
G_DECLARE_FINAL_TYPE (StardictView, stardict_view, STARDICT, VIEW, GtkWidget)
|
|
||||||
|
|
||||||
GtkWidget *stardict_view_new (void);
|
|
||||||
void stardict_view_set_position (StardictView *view,
|
|
||||||
StardictDict *dict, guint position);
|
|
||||||
void stardict_view_set_matched (StardictView *view, const gchar *matched);
|
|
||||||
|
|
||||||
#endif // ! STARDICT_VIEW_H
|
|
@ -198,7 +198,7 @@ struct stardict_entry_field
|
|||||||
struct stardict_entry
|
struct stardict_entry
|
||||||
{
|
{
|
||||||
GObject parent_instance;
|
GObject parent_instance;
|
||||||
GList * fields; ///< List of StardictEntryField-s
|
GList * fields; ///< List of StardictEntryField's
|
||||||
};
|
};
|
||||||
|
|
||||||
struct stardict_entry_class
|
struct stardict_entry_class
|
||||||
|
28
src/utils.c
28
src/utils.c
@ -23,6 +23,12 @@
|
|||||||
#include <errno.h>
|
#include <errno.h>
|
||||||
#include <stdarg.h>
|
#include <stdarg.h>
|
||||||
|
|
||||||
|
#include <curses.h>
|
||||||
|
#include <termios.h>
|
||||||
|
#ifndef TIOCGWINSZ
|
||||||
|
#include <sys/ioctl.h>
|
||||||
|
#endif // ! TIOCGWINSZ
|
||||||
|
|
||||||
#include "config.h"
|
#include "config.h"
|
||||||
#include "utils.h"
|
#include "utils.h"
|
||||||
|
|
||||||
@ -95,6 +101,28 @@ xstrtoul (unsigned long *out, const char *s, int base)
|
|||||||
return errno == 0 && !*end && end != s;
|
return errno == 0 && !*end && end != s;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Didn't want to have this ugly piece of code in the main source file;
|
||||||
|
// the standard endwin/refresh sequence makes the terminal flicker.
|
||||||
|
void
|
||||||
|
update_curses_terminal_size (void)
|
||||||
|
{
|
||||||
|
#if defined (HAVE_RESIZETERM) && defined (TIOCGWINSZ)
|
||||||
|
struct winsize size;
|
||||||
|
if (!ioctl (STDOUT_FILENO, TIOCGWINSZ, (char *) &size))
|
||||||
|
{
|
||||||
|
char *row = getenv ("LINES");
|
||||||
|
char *col = getenv ("COLUMNS");
|
||||||
|
unsigned long tmp;
|
||||||
|
resizeterm (
|
||||||
|
(row && xstrtoul (&tmp, row, 10)) ? tmp : size.ws_row,
|
||||||
|
(col && xstrtoul (&tmp, col, 10)) ? tmp : size.ws_col);
|
||||||
|
}
|
||||||
|
#else // HAVE_RESIZETERM && TIOCGWINSZ
|
||||||
|
endwin ();
|
||||||
|
refresh ();
|
||||||
|
#endif // HAVE_RESIZETERM && TIOCGWINSZ
|
||||||
|
}
|
||||||
|
|
||||||
/// Print a fatal error message and terminate the process immediately.
|
/// Print a fatal error message and terminate the process immediately.
|
||||||
void
|
void
|
||||||
fatal (const gchar *format, ...)
|
fatal (const gchar *format, ...)
|
||||||
|
@ -41,6 +41,7 @@ gchar *xdxf_to_pango_markup_with_reduced_effort (const gchar *xml);
|
|||||||
gboolean stream_read_all (GByteArray *ba, GInputStream *is, GError **error);
|
gboolean stream_read_all (GByteArray *ba, GInputStream *is, GError **error);
|
||||||
gchar *stream_read_string (GDataInputStream *dis, GError **error);
|
gchar *stream_read_string (GDataInputStream *dis, GError **error);
|
||||||
gboolean xstrtoul (unsigned long *out, const char *s, int base);
|
gboolean xstrtoul (unsigned long *out, const char *s, int base);
|
||||||
|
void update_curses_terminal_size (void);
|
||||||
void fatal (const gchar *format, ...) G_GNUC_PRINTF (1, 2) G_GNUC_NORETURN;
|
void fatal (const gchar *format, ...) G_GNUC_PRINTF (1, 2) G_GNUC_NORETURN;
|
||||||
|
|
||||||
#endif // ! UTILS_H
|
#endif // ! UTILS_H
|
||||||
|
Loading…
x
Reference in New Issue
Block a user