Compare commits
	
		
			No commits in common. "ded899933c50a976d10144f526ffd6a5e58e6ced" and "b0f1d3d6ea1b380e628958bd1b17ccb2c789ec2d" have entirely different histories.
		
	
	
		
			ded899933c
			...
			b0f1d3d6ea
		
	
		
| @ -96,7 +96,7 @@ should be addressed before including the software in regular Linux and/or | ||||
| BSD distributions: | ||||
| 
 | ||||
|  - The tab bar doesn't handle overflows well in the TUI. | ||||
|  - The GUI is awkward to configure. | ||||
|  - The GUI has a few usability issues, and is awkward to configure. | ||||
|  - Lacking configuration, standard StarDict locations should be scanned. | ||||
| 
 | ||||
| Given all issues with the file format, it might be better to start anew. | ||||
|  | ||||
							
								
								
									
										11
									
								
								src/sdgui.c
									
									
									
									
									
								
							
							
						
						
									
										11
									
								
								src/sdgui.c
									
									
									
									
									
								
							| @ -127,15 +127,6 @@ on_changed (G_GNUC_UNUSED GtkWidget *widget, G_GNUC_UNUSED gpointer data) | ||||
| 	search (g_ptr_array_index (g.dictionaries, g.dictionary)); | ||||
| } | ||||
| 
 | ||||
| static void | ||||
| on_send (G_GNUC_UNUSED StardictView *view, | ||||
| 	const char *word, G_GNUC_UNUSED gpointer data) | ||||
| { | ||||
| 	GtkEntryBuffer *buf = gtk_entry_get_buffer (GTK_ENTRY (g.entry)); | ||||
| 	gtk_entry_buffer_set_text (buf, word, -1); | ||||
| 	gtk_editable_select_region (GTK_EDITABLE (g.entry), 0, -1); | ||||
| } | ||||
| 
 | ||||
| static void | ||||
| on_selection_received (G_GNUC_UNUSED GtkClipboard *clipboard, const gchar *text, | ||||
| 	G_GNUC_UNUSED gpointer data) | ||||
| @ -538,8 +529,6 @@ main (int argc, char *argv[]) | ||||
| 	gtk_drag_dest_add_uri_targets (g.view); | ||||
| 	g_signal_connect (g.view, "drag-data-received", | ||||
| 		G_CALLBACK (on_drag_data_received), NULL); | ||||
| 	g_signal_connect (g.view, "send", | ||||
| 		G_CALLBACK (on_send), NULL); | ||||
| 
 | ||||
| 	gtk_widget_show_all (g.window); | ||||
| 	gtk_main (); | ||||
|  | ||||
| @ -149,51 +149,15 @@ struct view_entry_render_ctx | ||||
| 	PangoLayout *selection_layout; | ||||
| 	int selection_begin; | ||||
| 	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 | ||||
| view_entry_render (ViewEntryRenderCtx *ctx, gdouble x, gdouble y, | ||||
| 	PangoLayout *layout) | ||||
| { | ||||
| 	PangoLayout *adjusted = view_entry_adjust_layout (ctx, layout); | ||||
| 	gtk_render_layout (ctx->style, ctx->cr, x, y, adjusted); | ||||
| 	gtk_render_layout (ctx->style, ctx->cr, x, y, layout); | ||||
| 	if (layout != ctx->selection_layout) | ||||
| 		goto out; | ||||
| 		return; | ||||
| 
 | ||||
| 	gtk_style_context_save (ctx->style); | ||||
| 	gtk_style_context_set_state (ctx->style, GTK_STATE_FLAG_SELECTED); | ||||
| @ -202,18 +166,16 @@ view_entry_render (ViewEntryRenderCtx *ctx, gdouble x, gdouble y, | ||||
| 	int ranges[2] = { MIN (ctx->selection_begin, ctx->selection_end), | ||||
| 		MAX (ctx->selection_begin, ctx->selection_end) }; | ||||
| 	cairo_region_t *region | ||||
| 		= gdk_pango_layout_get_clip_region (adjusted, x, y, ranges, 1); | ||||
| 		= gdk_pango_layout_get_clip_region (layout, x, y, ranges, 1); | ||||
| 	gdk_cairo_region (ctx->cr, region); | ||||
| 	cairo_clip (ctx->cr); | ||||
| 	cairo_region_destroy (region); | ||||
| 
 | ||||
| 	gtk_render_background (ctx->style, ctx->cr, 0, 0, ctx->width, ctx->height); | ||||
| 	gtk_render_layout (ctx->style, ctx->cr, x, y, adjusted); | ||||
| 	gtk_render_layout (ctx->style, ctx->cr, x, y, layout); | ||||
| 
 | ||||
| 	cairo_restore (ctx->cr); | ||||
| 	gtk_style_context_restore (ctx->style); | ||||
| out: | ||||
| 	g_object_unref (adjusted); | ||||
| } | ||||
| 
 | ||||
| static gint | ||||
| @ -310,10 +272,6 @@ struct _StardictView | ||||
| 	GWeakRef selection;                 ///< Selected PangoLayout, if any
 | ||||
| 	int selection_begin;                ///< Start 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 * | ||||
| @ -325,23 +283,6 @@ make_entry (StardictView *self, StardictIterator *iterator) | ||||
| 	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 | ||||
| adjust_for_height (StardictView *self) | ||||
| { | ||||
| @ -373,14 +314,6 @@ adjust_for_height (StardictView *self) | ||||
| 	} | ||||
| 	g_object_unref (iterator); | ||||
| 
 | ||||
| 	// Also handling this for adjust_for_offset(), which calls this.
 | ||||
| 	PangoLayout *selection = g_weak_ref_get (&self->selection); | ||||
| 	if (selection) | ||||
| 		g_object_unref (selection); | ||||
| 	else | ||||
| 		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); | ||||
| } | ||||
| @ -432,13 +365,13 @@ reload (StardictView *self) | ||||
| { | ||||
| 	GtkWidget *widget = GTK_WIDGET (self); | ||||
| 
 | ||||
| 	// FIXME: this invalidates the selection, we'd need better identification
 | ||||
| 	g_list_free_full (self->entries, (GDestroyNotify) view_entry_destroy); | ||||
| 	self->entries = NULL; | ||||
| 	gtk_widget_queue_draw (widget); | ||||
| 
 | ||||
| 	// For consistency, and the check in make_context_menu()
 | ||||
| 	self->selection_begin = self->selection_end = -1; | ||||
| 	reset_hover (self); | ||||
| 
 | ||||
| 	if (gtk_widget_get_realized (widget) && self->dict) | ||||
| 		adjust_for_height (self); | ||||
| @ -454,7 +387,131 @@ natural_row_size (GtkWidget *widget) | ||||
| 	return height; | ||||
| } | ||||
| 
 | ||||
| // --- Figuring out where stuff is----------------------------------------------
 | ||||
| // --- 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_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.
 | ||||
| static PangoLayout * | ||||
| @ -527,270 +584,13 @@ layout_coords (StardictView *self, PangoLayout *layout, int *x, int *y) | ||||
| 	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 | ||||
| stardict_view_size_allocate (GtkWidget *widget, GtkAllocation *allocation) | ||||
| { | ||||
| 	GTK_WIDGET_CLASS (stardict_view_parent_class) | ||||
| 		->size_allocate (widget, allocation); | ||||
| 
 | ||||
| 	StardictView *self = STARDICT_VIEW (widget); | ||||
| 	if (!gtk_widget_get_realized (widget) || !self->dict) | ||||
| 		return; | ||||
| 
 | ||||
| 	PangoLayout *selection = g_weak_ref_get (&self->selection), **origin = NULL; | ||||
| 	for (GList *iter = self->entries; iter; iter = iter->next) | ||||
| 	{ | ||||
| 		ViewEntry *ve = iter->data; | ||||
| 		if (selection && selection == ve->word_layout) | ||||
| 			origin = &ve->word_layout; | ||||
| 		if (selection && selection == ve->definition_layout) | ||||
| 			origin = &ve->definition_layout; | ||||
| 	} | ||||
| 	if (selection) | ||||
| 		g_object_unref (selection); | ||||
| 
 | ||||
| 	for (GList *iter = self->entries; iter; iter = iter->next) | ||||
| 		view_entry_rebuild_layouts (iter->data, widget); | ||||
| 	if (origin) | ||||
| 		g_weak_ref_set (&self->selection, *origin); | ||||
| 
 | ||||
| 	adjust_for_offset (self); | ||||
| 	reload (STARDICT_VIEW (widget)); | ||||
| } | ||||
| 
 | ||||
| static void | ||||
| @ -835,11 +635,31 @@ 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 | ||||
| publish_selection (StardictView *self, GdkAtom target) | ||||
| { | ||||
| 	PangoLayout *layout = g_weak_ref_get (&self->selection); | ||||
| 	if (!layout) | ||||
| 	if (!layout || self->selection_begin == self->selection_end) | ||||
| 		return; | ||||
| 
 | ||||
| 	// Unlike GtkLabel, we don't place the selection in PRIMARY immediately.
 | ||||
| @ -847,7 +667,7 @@ publish_selection (StardictView *self, GdkAtom target) | ||||
| 	int len = strlen (text), | ||||
| 		s1 = MIN (self->selection_begin, self->selection_end), | ||||
| 		s2 = MAX (self->selection_begin, self->selection_end); | ||||
| 	if (s1 != s2 && s1 >= 0 && s1 <= len && s2 >= 0 && s2 <= len) | ||||
| 	if (s1 >= 0 && s1 <= len && s2 >= 0 && s2 <= len) | ||||
| 		gtk_clipboard_set_text (gtk_clipboard_get (target), text + s1, s2 - s1); | ||||
| 	g_object_unref (layout); | ||||
| } | ||||
| @ -855,9 +675,22 @@ publish_selection (StardictView *self, GdkAtom target) | ||||
| static void | ||||
| select_word_at (StardictView *self, int x, int y) | ||||
| { | ||||
| 	g_weak_ref_set (&self->selection, locate_word_at (self, | ||||
| 		x, y, &self->selection_begin, &self->selection_end)); | ||||
| 	PangoLayout *layout = layout_at (self, &x, &y); | ||||
| 	if (!layout) | ||||
| 		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)); | ||||
| 	publish_selection (self, GDK_SELECTION_PRIMARY); | ||||
| } | ||||
| @ -896,7 +729,7 @@ make_context_menu (StardictView *self) | ||||
| { | ||||
| 	GtkWidget *copy = gtk_menu_item_new_with_mnemonic ("_Copy"); | ||||
| 	gtk_widget_set_sensitive (copy, | ||||
| 		self->selection_begin != self->selection_end); | ||||
| 		self->selection_begin < self->selection_end); | ||||
| 	g_signal_connect_data (copy, "activate", | ||||
| 		G_CALLBACK (on_copy_activate), g_object_ref (self), | ||||
| 		(GClosureNotify) g_object_unref, 0); | ||||
| @ -946,23 +779,6 @@ stardict_view_button_press_event (GtkWidget *widget, GdkEventButton *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 | ||||
| on_drag_begin (GtkGestureDrag *drag, G_GNUC_UNUSED gdouble start_x, | ||||
| 	G_GNUC_UNUSED gdouble start_y, gpointer user_data) | ||||
| @ -995,49 +811,21 @@ on_drag_update (G_GNUC_UNUSED GtkGestureDrag *drag, | ||||
| 	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 | ||||
| on_select_begin (GtkGestureDrag *drag, gdouble start_x, gdouble start_y, | ||||
| 	gpointer user_data) | ||||
| { | ||||
| 	// We probably don't need to check modifiers and mouse position again.
 | ||||
| 	StardictView *self = STARDICT_VIEW (user_data); | ||||
| 	GtkGesture *gesture = GTK_GESTURE (drag); | ||||
| 	if (send_hover (self)) | ||||
| 	{ | ||||
| 		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.
 | ||||
| 	GtkGesture *gesture = GTK_GESTURE (drag); | ||||
| 	if (gtk_gesture_get_last_updated_sequence (gesture)) | ||||
| 	{ | ||||
| 		gtk_gesture_set_state(gesture, GTK_EVENT_SEQUENCE_DENIED); | ||||
| 		return; | ||||
| 	} | ||||
| 
 | ||||
| 	StardictView *self = STARDICT_VIEW (user_data); | ||||
| 	g_weak_ref_set (&self->selection, NULL); | ||||
| 	self->selection_begin = -1; | ||||
| 	self->selection_end = -1; | ||||
| @ -1101,10 +889,6 @@ on_select_end (G_GNUC_UNUSED GtkGestureDrag *drag, | ||||
| static void | ||||
| 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); | ||||
| 	object_class->finalize = stardict_view_finalize; | ||||
| 
 | ||||
| @ -1112,15 +896,11 @@ stardict_view_class_init (StardictViewClass *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->map = stardict_view_map; | ||||
| 	widget_class->unmap = stardict_view_unmap; | ||||
| 	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; | ||||
| 	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"); | ||||
| } | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user