sdgui: make Ctrl+click put hovered word in search

This commit is contained in:
Přemysl Eric Janouch 2022-09-03 21:10:39 +02:00
parent 832842bf81
commit ded899933c
Signed by: p
GPG Key ID: A0420B94F92B9493
3 changed files with 384 additions and 180 deletions

View File

@ -96,7 +96,7 @@ should be addressed before including the software in regular Linux and/or
BSD distributions: BSD distributions:
- The tab bar doesn't handle overflows well in the TUI. - The tab bar doesn't handle overflows well in the TUI.
- The GUI has a few usability issues, and is awkward to configure. - The GUI is awkward to configure.
- Lacking configuration, standard StarDict locations should be scanned. - Lacking configuration, standard StarDict locations should be scanned.
Given all issues with the file format, it might be better to start anew. Given all issues with the file format, it might be better to start anew.

View File

@ -127,6 +127,15 @@ on_changed (G_GNUC_UNUSED GtkWidget *widget, G_GNUC_UNUSED gpointer data)
search (g_ptr_array_index (g.dictionaries, g.dictionary)); 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 static void
on_selection_received (G_GNUC_UNUSED GtkClipboard *clipboard, const gchar *text, on_selection_received (G_GNUC_UNUSED GtkClipboard *clipboard, const gchar *text,
G_GNUC_UNUSED gpointer data) G_GNUC_UNUSED gpointer data)
@ -529,6 +538,8 @@ main (int argc, char *argv[])
gtk_drag_dest_add_uri_targets (g.view); gtk_drag_dest_add_uri_targets (g.view);
g_signal_connect (g.view, "drag-data-received", g_signal_connect (g.view, "drag-data-received",
G_CALLBACK (on_drag_data_received), NULL); G_CALLBACK (on_drag_data_received), NULL);
g_signal_connect (g.view, "send",
G_CALLBACK (on_send), NULL);
gtk_widget_show_all (g.window); gtk_widget_show_all (g.window);
gtk_main (); gtk_main ();

View File

@ -149,15 +149,51 @@ struct view_entry_render_ctx
PangoLayout *selection_layout; PangoLayout *selection_layout;
int selection_begin; int selection_begin;
int selection_end; int selection_end;
PangoLayout *hover_layout;
int hover_begin;
int hover_end;
}; };
static PangoLayout *
view_entry_adjust_layout (ViewEntryRenderCtx *ctx, PangoLayout *layout)
{
if (layout != ctx->hover_layout)
return g_object_ref (layout);
layout = pango_layout_copy (layout);
PangoAttrList *attrs = pango_layout_get_attributes (layout);
attrs = attrs
? pango_attr_list_copy (attrs)
: pango_attr_list_new ();
PangoAttribute *u = pango_attr_underline_new (PANGO_UNDERLINE_SINGLE);
u->start_index = ctx->hover_begin;
u->end_index = ctx->hover_end;
pango_attr_list_change (attrs, u);
PangoAttribute *uc = pango_attr_underline_color_new (0, 0, 0xffff);
uc->start_index = ctx->hover_begin;
uc->end_index = ctx->hover_end;
pango_attr_list_change (attrs, uc);
PangoAttribute *c = pango_attr_foreground_new (0, 0, 0xffff);
c->start_index = ctx->hover_begin;
c->end_index = ctx->hover_end;
pango_attr_list_change (attrs, c);
pango_layout_set_attributes (layout, attrs);
pango_attr_list_unref (attrs);
return layout;
}
static void static void
view_entry_render (ViewEntryRenderCtx *ctx, gdouble x, gdouble y, view_entry_render (ViewEntryRenderCtx *ctx, gdouble x, gdouble y,
PangoLayout *layout) PangoLayout *layout)
{ {
gtk_render_layout (ctx->style, ctx->cr, x, y, layout); PangoLayout *adjusted = view_entry_adjust_layout (ctx, layout);
gtk_render_layout (ctx->style, ctx->cr, x, y, adjusted);
if (layout != ctx->selection_layout) if (layout != ctx->selection_layout)
return; goto out;
gtk_style_context_save (ctx->style); gtk_style_context_save (ctx->style);
gtk_style_context_set_state (ctx->style, GTK_STATE_FLAG_SELECTED); gtk_style_context_set_state (ctx->style, GTK_STATE_FLAG_SELECTED);
@ -166,16 +202,18 @@ view_entry_render (ViewEntryRenderCtx *ctx, gdouble x, gdouble y,
int ranges[2] = { MIN (ctx->selection_begin, ctx->selection_end), int ranges[2] = { MIN (ctx->selection_begin, ctx->selection_end),
MAX (ctx->selection_begin, ctx->selection_end) }; MAX (ctx->selection_begin, ctx->selection_end) };
cairo_region_t *region cairo_region_t *region
= gdk_pango_layout_get_clip_region (layout, x, y, ranges, 1); = gdk_pango_layout_get_clip_region (adjusted, x, y, ranges, 1);
gdk_cairo_region (ctx->cr, region); gdk_cairo_region (ctx->cr, region);
cairo_clip (ctx->cr); cairo_clip (ctx->cr);
cairo_region_destroy (region); cairo_region_destroy (region);
gtk_render_background (ctx->style, ctx->cr, 0, 0, ctx->width, ctx->height); gtk_render_background (ctx->style, ctx->cr, 0, 0, ctx->width, ctx->height);
gtk_render_layout (ctx->style, ctx->cr, x, y, layout); gtk_render_layout (ctx->style, ctx->cr, x, y, adjusted);
cairo_restore (ctx->cr); cairo_restore (ctx->cr);
gtk_style_context_restore (ctx->style); gtk_style_context_restore (ctx->style);
out:
g_object_unref (adjusted);
} }
static gint static gint
@ -272,6 +310,10 @@ struct _StardictView
GWeakRef selection; ///< Selected PangoLayout, if any GWeakRef selection; ///< Selected PangoLayout, if any
int selection_begin; ///< Start index within `selection` int selection_begin; ///< Start index within `selection`
int selection_end; ///< End index within `selection` int selection_end; ///< End index within `selection`
GWeakRef hover; ///< Hovered PangoLayout, if any
int hover_begin; ///< Word start index within `hover`
int hover_end; ///< Word end index within `hover`
}; };
static ViewEntry * static ViewEntry *
@ -283,6 +325,23 @@ make_entry (StardictView *self, StardictIterator *iterator)
return ve; return ve;
} }
static void
reset_hover (StardictView *self)
{
GtkWidget *widget = GTK_WIDGET (self);
PangoLayout *hover = g_weak_ref_get (&self->hover);
if (hover)
{
g_object_unref (hover);
g_weak_ref_set (&self->hover, NULL);
self->hover_begin = self->hover_end = -1;
gtk_widget_queue_draw (widget);
}
if (gtk_widget_get_realized (widget))
gdk_window_set_cursor (gtk_widget_get_window (widget), NULL);
}
static void static void
adjust_for_height (StardictView *self) adjust_for_height (StardictView *self)
{ {
@ -314,15 +373,16 @@ adjust_for_height (StardictView *self)
} }
g_object_unref (iterator); g_object_unref (iterator);
self->entries = g_list_concat (self->entries, g_list_reverse (append));
gtk_widget_queue_draw (widget);
// Also handling this for adjust_for_offset(), which calls this. // Also handling this for adjust_for_offset(), which calls this.
PangoLayout *selection = g_weak_ref_get (&self->selection); PangoLayout *selection = g_weak_ref_get (&self->selection);
if (selection) if (selection)
g_object_unref (selection); g_object_unref (selection);
else else
self->selection_begin = self->selection_end = -1; self->selection_begin = self->selection_end = -1;
reset_hover (self);
self->entries = g_list_concat (self->entries, g_list_reverse (append));
gtk_widget_queue_draw (widget);
} }
static void static void
@ -378,6 +438,7 @@ reload (StardictView *self)
// For consistency, and the check in make_context_menu() // For consistency, and the check in make_context_menu()
self->selection_begin = self->selection_end = -1; self->selection_begin = self->selection_end = -1;
reset_hover (self);
if (gtk_widget_get_realized (widget) && self->dict) if (gtk_widget_get_realized (widget) && self->dict)
adjust_for_height (self); adjust_for_height (self);
@ -393,131 +454,7 @@ natural_row_size (GtkWidget *widget)
return height; return height;
} }
// --- Boilerplate ------------------------------------------------------------- // --- Figuring out where stuff is----------------------------------------------
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_object_unref (self->selection_gesture);
g_weak_ref_clear (&self->selection);
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)
{
// There isn't any value that would make any real sense
if (!STARDICT_VIEW (widget)->dict)
*natural = *minimum = 0;
else
*natural = *minimum = natural_row_size (widget);
}
static void
stardict_view_get_preferred_width (GtkWidget *widget,
gint *minimum, gint *natural)
{
GtkStyleContext *style = gtk_widget_get_style_context (widget);
GtkBorder padding = view_entry_get_padding (style);
*natural = *minimum = 2 * (padding.left + 1 * padding.right);
}
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
| GDK_SMOOTH_SCROLL_MASK | GDK_BUTTON_PRESS_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
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);
GtkStyleContext *style = gtk_widget_get_style_context (widget);
gtk_render_background (style, cr,
0, 0, allocation.width, allocation.height);
gtk_render_frame (style, cr,
0, 0, allocation.width, allocation.height);
ViewEntryRenderCtx ctx =
{
.style = style,
.cr = cr,
.width = allocation.width,
.height = 0,
.selection_layout = g_weak_ref_get (&self->selection),
.selection_begin = self->selection_begin,
.selection_end = self->selection_end,
};
gint offset = -self->top_offset;
gint i = self->top_position;
for (GList *iter = self->entries; iter; iter = iter->next)
{
// Style regions would be appropriate, if they weren't deprecated.
// GTK+ CSS gadgets/nodes are an internal API. We don't want to turn
// this widget into a container, to avoid needless complexity.
//
// gtk_style_context_{get,set}_path() may be misused by adding the same
// GType with gtk_widget_path_append_type() and changing its name
// using gtk_widget_path_iter_set_name()... but that is ugly.
gtk_style_context_save (style);
gtk_style_context_add_class (style, (i++ & 1) ? "even" : "odd");
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, &ctx);
cairo_restore (cr);
gtk_style_context_restore (style);
}
g_clear_object (&ctx.selection_layout);
return TRUE;
}
/// Figure out which layout is at given widget coordinates, and translate them. /// Figure out which layout is at given widget coordinates, and translate them.
static PangoLayout * static PangoLayout *
@ -590,6 +527,242 @@ layout_coords (StardictView *self, PangoLayout *layout, int *x, int *y)
return FALSE; return FALSE;
} }
static int
layout_index_at (PangoLayout *layout, int x, int y)
{
int index = 0, trailing = 0;
(void) pango_layout_xy_to_index (layout,
x * PANGO_SCALE,
y * PANGO_SCALE,
&index,
&trailing);
const char *text = pango_layout_get_text (layout) + index;
while (trailing--)
{
int len = g_utf8_next_char (text) - text;
text += len;
index += len;
}
return index;
}
static PangoLayout *
locate_word_at (StardictView *self, int x, int y, int *beginpos, int *endpos)
{
*beginpos = -1;
*endpos = -1;
PangoLayout *layout = layout_at (self, &x, &y);
if (!layout)
return NULL;
const char *text = pango_layout_get_text (layout), *p = NULL;
const char *begin = text + layout_index_at (layout, x, y), *end = begin;
while ((p = g_utf8_find_prev_char (text, begin))
&& !g_unichar_isspace (g_utf8_get_char (p)))
begin = p;
gunichar c;
while ((c = g_utf8_get_char (end)) && !g_unichar_isspace (c))
end = g_utf8_next_char (end);
*beginpos = begin - text;
*endpos = end - text;
return layout;
}
// --- Boilerplate -------------------------------------------------------------
G_DEFINE_TYPE (StardictView, stardict_view, GTK_TYPE_WIDGET)
enum {
SEND,
LAST_SIGNAL,
};
static guint view_signals[LAST_SIGNAL];
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_object_unref (self->selection_gesture);
g_weak_ref_clear (&self->selection);
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)
{
// There isn't any value that would make any real sense
if (!STARDICT_VIEW (widget)->dict)
*natural = *minimum = 0;
else
*natural = *minimum = natural_row_size (widget);
}
static void
stardict_view_get_preferred_width (GtkWidget *widget,
gint *minimum, gint *natural)
{
GtkStyleContext *style = gtk_widget_get_style_context (widget);
GtkBorder padding = view_entry_get_padding (style);
*natural = *minimum = 2 * (padding.left + 1 * padding.right);
}
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
| GDK_SMOOTH_SCROLL_MASK | GDK_BUTTON_PRESS_MASK
| GDK_POINTER_MOTION_MASK | GDK_LEAVE_NOTIFY_MASK,
};
// We need this window to receive input events at all.
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
gtk_widget_register_window (widget, window);
gtk_widget_set_window (widget, window);
gtk_widget_set_realized (widget, TRUE);
}
static void
reset_hover_for_event (StardictView *self, guint state, int x, int y)
{
reset_hover (self);
if ((state &= gtk_accelerator_get_default_mod_mask ()) != GDK_CONTROL_MASK)
return;
g_weak_ref_set (&self->hover,
locate_word_at (self, x, y, &self->hover_begin, &self->hover_end));
gtk_widget_queue_draw (GTK_WIDGET (self));
GdkWindow *window = gtk_widget_get_window (GTK_WIDGET (self));
GdkCursor *cursor = gdk_cursor_new_from_name
(gdk_window_get_display (window), "pointer");
gdk_window_set_cursor (window, cursor);
g_object_unref (cursor);
}
static void
on_keymap_state_changed (G_GNUC_UNUSED GdkKeymap *keymap, StardictView *self)
{
GdkDisplay *display = gtk_widget_get_display (GTK_WIDGET (self));
GdkSeat *seat = gdk_display_get_default_seat (display);
GdkDevice *pointer = gdk_seat_get_pointer (seat);
int x = -1, y = -1;
GdkModifierType state = 0;
GdkWindow *window = gtk_widget_get_window (GTK_WIDGET (self));
gdk_window_get_device_position (window, pointer, &x, &y, &state);
reset_hover_for_event (self, state, x, y);
}
static void
stardict_view_map (GtkWidget *widget)
{
GTK_WIDGET_CLASS (stardict_view_parent_class)->map (widget);
GdkWindow *window = gtk_widget_get_window (widget);
GdkDisplay *display = gdk_window_get_display (window);
GdkKeymap *keymap = gdk_keymap_get_for_display (display);
g_signal_connect (keymap, "state-changed",
G_CALLBACK (on_keymap_state_changed), widget);
}
static void
stardict_view_unmap (GtkWidget *widget)
{
GdkWindow *window = gtk_widget_get_window (widget);
GdkDisplay *display = gdk_window_get_display (window);
GdkKeymap *keymap = gdk_keymap_get_for_display (display);
g_signal_handlers_disconnect_by_data (keymap, widget);
GTK_WIDGET_CLASS (stardict_view_parent_class)->unmap (widget);
}
static gboolean
stardict_view_draw (GtkWidget *widget, cairo_t *cr)
{
StardictView *self = STARDICT_VIEW (widget);
GtkAllocation allocation;
gtk_widget_get_allocation (widget, &allocation);
GtkStyleContext *style = gtk_widget_get_style_context (widget);
gtk_render_background (style, cr,
0, 0, allocation.width, allocation.height);
gtk_render_frame (style, cr,
0, 0, allocation.width, allocation.height);
ViewEntryRenderCtx ctx =
{
.style = style,
.cr = cr,
.width = allocation.width,
.height = 0,
.selection_layout = g_weak_ref_get (&self->selection),
.selection_begin = self->selection_begin,
.selection_end = self->selection_end,
.hover_layout = g_weak_ref_get (&self->hover),
.hover_begin = self->hover_begin,
.hover_end = self->hover_end,
};
gint offset = -self->top_offset;
gint i = self->top_position;
for (GList *iter = self->entries; iter; iter = iter->next)
{
// Style regions would be appropriate, if they weren't deprecated.
// GTK+ CSS gadgets/nodes are an internal API. We don't want to turn
// this widget into a container, to avoid needless complexity.
//
// gtk_style_context_{get,set}_path() may be misused by adding the same
// GType with gtk_widget_path_append_type() and changing its name
// using gtk_widget_path_iter_set_name()... but that is ugly.
gtk_style_context_save (style);
gtk_style_context_add_class (style, (i++ & 1) ? "even" : "odd");
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, &ctx);
cairo_restore (cr);
gtk_style_context_restore (style);
}
g_clear_object (&ctx.selection_layout);
g_clear_object (&ctx.hover_layout);
return TRUE;
}
static void static void
stardict_view_size_allocate (GtkWidget *widget, GtkAllocation *allocation) stardict_view_size_allocate (GtkWidget *widget, GtkAllocation *allocation)
{ {
@ -662,31 +835,11 @@ stardict_view_scroll_event (GtkWidget *widget, GdkEventScroll *event)
} }
} }
static int
layout_index_at (PangoLayout *layout, int x, int y)
{
int index = 0, trailing = 0;
(void) pango_layout_xy_to_index (layout,
x * PANGO_SCALE,
y * PANGO_SCALE,
&index,
&trailing);
const char *text = pango_layout_get_text (layout) + index;
while (trailing--)
{
int len = g_utf8_next_char(text) - text;
text += len;
index += len;
}
return index;
}
static void static void
publish_selection (StardictView *self, GdkAtom target) publish_selection (StardictView *self, GdkAtom target)
{ {
PangoLayout *layout = g_weak_ref_get (&self->selection); PangoLayout *layout = g_weak_ref_get (&self->selection);
if (!layout || self->selection_begin == self->selection_end) if (!layout)
return; return;
// Unlike GtkLabel, we don't place the selection in PRIMARY immediately. // Unlike GtkLabel, we don't place the selection in PRIMARY immediately.
@ -694,7 +847,7 @@ publish_selection (StardictView *self, GdkAtom target)
int len = strlen (text), int len = strlen (text),
s1 = MIN (self->selection_begin, self->selection_end), s1 = MIN (self->selection_begin, self->selection_end),
s2 = MAX (self->selection_begin, self->selection_end); s2 = MAX (self->selection_begin, self->selection_end);
if (s1 >= 0 && s1 <= len && s2 >= 0 && s2 <= len) if (s1 != s2 && s1 >= 0 && s1 <= len && s2 >= 0 && s2 <= len)
gtk_clipboard_set_text (gtk_clipboard_get (target), text + s1, s2 - s1); gtk_clipboard_set_text (gtk_clipboard_get (target), text + s1, s2 - s1);
g_object_unref (layout); g_object_unref (layout);
} }
@ -702,22 +855,9 @@ publish_selection (StardictView *self, GdkAtom target)
static void static void
select_word_at (StardictView *self, int x, int y) select_word_at (StardictView *self, int x, int y)
{ {
PangoLayout *layout = layout_at (self, &x, &y); g_weak_ref_set (&self->selection, locate_word_at (self,
if (!layout) x, y, &self->selection_begin, &self->selection_end));
return;
const char *text = pango_layout_get_text (layout), *p = NULL;
const char *begin = text + layout_index_at (layout, x, y), *end = begin;
while ((p = g_utf8_find_prev_char (text, begin))
&& !g_unichar_isspace (g_utf8_get_char (p)))
begin = p;
gunichar c;
while ((c = g_utf8_get_char (end)) && !g_unichar_isspace (c))
end = g_utf8_next_char (end);
g_weak_ref_set (&self->selection, layout);
self->selection_begin = begin - text;
self->selection_end = end - text;
gtk_widget_queue_draw (GTK_WIDGET (self)); gtk_widget_queue_draw (GTK_WIDGET (self));
publish_selection (self, GDK_SELECTION_PRIMARY); publish_selection (self, GDK_SELECTION_PRIMARY);
} }
@ -779,7 +919,7 @@ static gboolean
stardict_view_button_press_event (GtkWidget *widget, GdkEventButton *event) stardict_view_button_press_event (GtkWidget *widget, GdkEventButton *event)
{ {
StardictView *self = STARDICT_VIEW (widget); StardictView *self = STARDICT_VIEW (widget);
if (gdk_event_triggers_context_menu((const GdkEvent *) event)) if (gdk_event_triggers_context_menu ((const GdkEvent *) event))
{ {
gtk_menu_popup_at_pointer (make_context_menu (self), gtk_menu_popup_at_pointer (make_context_menu (self),
(const GdkEvent *) event); (const GdkEvent *) event);
@ -806,6 +946,23 @@ stardict_view_button_press_event (GtkWidget *widget, GdkEventButton *event)
->button_press_event (widget, event); ->button_press_event (widget, event);
} }
static gboolean
stardict_view_motion_notify_event (GtkWidget *widget, GdkEventMotion *event)
{
StardictView *self = STARDICT_VIEW (widget);
reset_hover_for_event (self, event->state, event->x, event->y);
return GTK_WIDGET_CLASS (stardict_view_parent_class)
->motion_notify_event (widget, event);
}
static gboolean
stardict_view_leave_notify_event
(GtkWidget *widget, G_GNUC_UNUSED GdkEventCrossing *event)
{
reset_hover (STARDICT_VIEW (widget));
return GDK_EVENT_PROPAGATE;
}
static void static void
on_drag_begin (GtkGestureDrag *drag, G_GNUC_UNUSED gdouble start_x, on_drag_begin (GtkGestureDrag *drag, G_GNUC_UNUSED gdouble start_x,
G_GNUC_UNUSED gdouble start_y, gpointer user_data) G_GNUC_UNUSED gdouble start_y, gpointer user_data)
@ -838,21 +995,49 @@ on_drag_update (G_GNUC_UNUSED GtkGestureDrag *drag,
self->drag_last_offset = offset_y; self->drag_last_offset = offset_y;
} }
static gboolean
send_hover (StardictView *self)
{
PangoLayout *layout = g_weak_ref_get (&self->hover);
if (!layout)
return FALSE;
const char *text = pango_layout_get_text (layout);
int len = strlen (text),
s1 = MIN (self->hover_begin, self->hover_end),
s2 = MAX (self->hover_begin, self->hover_end);
if (s1 != s2 && s1 >= 0 && s1 <= len && s2 >= 0 && s2 <= len)
{
gchar *word = g_strndup (text + s1, s2 - s1);
g_signal_emit (self, view_signals[SEND], 0, word);
g_free (word);
}
g_object_unref (layout);
return TRUE;
}
static void static void
on_select_begin (GtkGestureDrag *drag, gdouble start_x, gdouble start_y, on_select_begin (GtkGestureDrag *drag, gdouble start_x, gdouble start_y,
gpointer user_data) gpointer user_data)
{ {
// Despite our two gestures not being grouped up, claiming one doesn't // We probably don't need to check modifiers and mouse position again.
// deny the other, and :exclusive isn't the opposite of :touch-only. StardictView *self = STARDICT_VIEW (user_data);
// A non-NULL sequence indicates a touch event.
GtkGesture *gesture = GTK_GESTURE (drag); GtkGesture *gesture = GTK_GESTURE (drag);
if (gtk_gesture_get_last_updated_sequence (gesture)) if (send_hover (self))
{ {
gtk_gesture_set_state(gesture, GTK_EVENT_SEQUENCE_DENIED); gtk_gesture_set_state (gesture, GTK_EVENT_SEQUENCE_DENIED);
return;
}
// Despite our two gestures not being grouped up, claiming one doesn't
// deny the other, and :exclusive isn't the opposite of :touch-only.
// A non-NULL sequence indicates a touch event.
if (gtk_gesture_get_last_updated_sequence (gesture))
{
gtk_gesture_set_state (gesture, GTK_EVENT_SEQUENCE_DENIED);
return; return;
} }
StardictView *self = STARDICT_VIEW (user_data);
g_weak_ref_set (&self->selection, NULL); g_weak_ref_set (&self->selection, NULL);
self->selection_begin = -1; self->selection_begin = -1;
self->selection_end = -1; self->selection_end = -1;
@ -862,7 +1047,7 @@ on_select_begin (GtkGestureDrag *drag, gdouble start_x, gdouble start_y,
PangoLayout *layout = layout_at (self, &layout_x, &layout_y); PangoLayout *layout = layout_at (self, &layout_x, &layout_y);
if (!layout) if (!layout)
{ {
gtk_gesture_set_state(gesture, GTK_EVENT_SEQUENCE_DENIED); gtk_gesture_set_state (gesture, GTK_EVENT_SEQUENCE_DENIED);
return; return;
} }
@ -881,7 +1066,7 @@ on_select_update (GtkGestureDrag *drag, gdouble offset_x, gdouble offset_y,
PangoLayout *layout = g_weak_ref_get (&self->selection); PangoLayout *layout = g_weak_ref_get (&self->selection);
if (!layout) if (!layout)
{ {
gtk_gesture_set_state(gesture, GTK_EVENT_SEQUENCE_DENIED); gtk_gesture_set_state (gesture, GTK_EVENT_SEQUENCE_DENIED);
return; return;
} }
@ -892,7 +1077,7 @@ on_select_update (GtkGestureDrag *drag, gdouble offset_x, gdouble offset_y,
if (!layout_coords (self, layout, &x, &y)) if (!layout_coords (self, layout, &x, &y))
{ {
g_warning ("internal error: weakly referenced layout not found"); g_warning ("internal error: weakly referenced layout not found");
gtk_gesture_set_state(gesture, GTK_EVENT_SEQUENCE_DENIED); gtk_gesture_set_state (gesture, GTK_EVENT_SEQUENCE_DENIED);
goto out; goto out;
} }
@ -916,6 +1101,10 @@ on_select_end (G_GNUC_UNUSED GtkGestureDrag *drag,
static void static void
stardict_view_class_init (StardictViewClass *klass) stardict_view_class_init (StardictViewClass *klass)
{ {
view_signals[SEND] = g_signal_new ("send",
G_TYPE_FROM_CLASS (klass), 0, 0, NULL, NULL, NULL,
G_TYPE_NONE, 1, G_TYPE_STRING);
GObjectClass *object_class = G_OBJECT_CLASS (klass); GObjectClass *object_class = G_OBJECT_CLASS (klass);
object_class->finalize = stardict_view_finalize; object_class->finalize = stardict_view_finalize;
@ -923,11 +1112,15 @@ stardict_view_class_init (StardictViewClass *klass)
widget_class->get_preferred_height = stardict_view_get_preferred_height; widget_class->get_preferred_height = stardict_view_get_preferred_height;
widget_class->get_preferred_width = stardict_view_get_preferred_width; widget_class->get_preferred_width = stardict_view_get_preferred_width;
widget_class->realize = stardict_view_realize; widget_class->realize = stardict_view_realize;
widget_class->map = stardict_view_map;
widget_class->unmap = stardict_view_unmap;
widget_class->draw = stardict_view_draw; widget_class->draw = stardict_view_draw;
widget_class->size_allocate = stardict_view_size_allocate; widget_class->size_allocate = stardict_view_size_allocate;
widget_class->screen_changed = stardict_view_screen_changed; widget_class->screen_changed = stardict_view_screen_changed;
widget_class->scroll_event = stardict_view_scroll_event; widget_class->scroll_event = stardict_view_scroll_event;
widget_class->button_press_event = stardict_view_button_press_event; widget_class->button_press_event = stardict_view_button_press_event;
widget_class->motion_notify_event = stardict_view_motion_notify_event;
widget_class->leave_notify_event = stardict_view_leave_notify_event;
gtk_widget_class_set_css_name (widget_class, "stardict-view"); gtk_widget_class_set_css_name (widget_class, "stardict-view");
} }