1 /*
2  *  tooltip.c
3  *
4  *  Copyright 2012 Dimitar Toshkov Zhekov <dimitar.zhekov@gmail.com>
5  *
6  *  This program is free software; you can redistribute it and/or modify
7  *  it under the terms of the GNU General Public License as published by
8  *  the Free Software Foundation; either version 2 of the License, or
9  *  (at your option) any later version.
10  *
11  *  This program is distributed in the hope that it will be useful,
12  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
13  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14  *  GNU General Public License for more details.
15  *
16  *  You should have received a copy of the GNU General Public License
17  *  along with this program. If not, see <http://www.gnu.org/licenses/>.
18  */
19 
20 #include <stdlib.h>
21 #include <string.h>
22 
23 #include "common.h"
24 
25 /* Wordchars defining possible signs in an expression. */
26 #define SCOPE_EXPR_WORDCHARS	"_abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789.->[]"
27 
tooltip_trigger(void)28 static void tooltip_trigger(void)
29 {
30 	GdkDisplay *display = gdk_display_get_default();
31 #if GTK_CHECK_VERSION(3, 0, 0)
32 	GdkDeviceManager *manager = gdk_display_get_device_manager(display);
33 	GdkDevice *device = gdk_device_manager_get_client_pointer(manager);
34 	GdkWindow *window = gdk_device_get_window_at_position(device, NULL, NULL);
35 #else
36 	GdkWindow *window = gdk_display_get_window_at_pointer(display, NULL, NULL);
37 #endif
38 	GeanyDocument *doc = document_get_current();
39 
40 	if (doc && window)
41 	{
42 		GtkWidget *event_widget;
43 
44 		gdk_window_get_user_data(window, (void **) &event_widget);
45 		/* if you know a better working way, do not hesistate to tell me */
46 		if (event_widget &&
47 			gtk_widget_is_ancestor(event_widget, GTK_WIDGET(doc->editor->sci)))
48 		{
49 			gtk_tooltip_trigger_tooltip_query(display);
50 		}
51 	}
52 }
53 
54 static gchar *last_expr = NULL;
55 static gchar *output = NULL;
56 static gint last_pos = -1;
57 static gint peek_pos = -1;
58 static gboolean show;
59 
tooltip_set(gchar * text)60 static void tooltip_set(gchar *text)
61 {
62 	show = text != NULL;
63 	g_free(output);
64 	output = text;
65 	last_pos = peek_pos;
66 
67 	if (show)
68 	{
69 		if (pref_tooltips_length && strlen(output) > (size_t) pref_tooltips_length + 3)
70 			strcpy(output + pref_tooltips_length, "...");
71 
72 		tooltip_trigger();
73 	}
74 }
75 
tooltip_set_expr(gchar * expr,gchar * text)76 static void tooltip_set_expr(gchar *expr, gchar *text)
77 {
78 	show = text != NULL;
79 	g_free(output);
80 	output = g_strdup_printf ("%s =\n %s", expr, text);
81 	g_free(text);
82 	g_free(expr);
83 	last_pos = peek_pos;
84 
85 	if (show)
86 	{
87 		if (pref_tooltips_length && strlen(output) > (size_t) pref_tooltips_length + 3)
88 			strcpy(output + pref_tooltips_length, "...");
89 
90 		tooltip_trigger();
91 	}
92 }
93 
94 static gint scid_gen = 0;
95 
on_tooltip_error(GArray * nodes)96 void on_tooltip_error(GArray *nodes)
97 {
98 	if (atoi(parse_grab_token(nodes)) == scid_gen)
99 	{
100 		if (pref_tooltips_fail_action == 1)
101 			tooltip_set(parse_get_error(nodes));
102 		else
103 		{
104 			tooltip_set(NULL);
105 			if (pref_tooltips_fail_action)
106 				plugin_blink();
107 		}
108 	}
109 }
110 
111 static char *input = NULL;
112 
on_tooltip_value(GArray * nodes)113 void on_tooltip_value(GArray *nodes)
114 {
115 	if (atoi(parse_grab_token(nodes)) == scid_gen)
116 	{
117 		tooltip_set_expr(last_expr, parse_get_display_from_7bit(parse_lead_value(nodes),
118 			parse_mode_get(input, MODE_HBIT), parse_mode_get(input, MODE_MEMBER)));
119 	}
120 }
121 
122 static guint query_id = 0;
123 
tooltip_launch(gpointer gdata)124 static gboolean tooltip_launch(gpointer gdata)
125 {
126 	GeanyDocument *doc = document_get_current();
127 
128 	if (doc && utils_source_document(doc) && doc->editor == gdata &&
129 		(debug_state() & DS_SENDABLE))
130 	{
131 		ScintillaObject *sci = doc->editor->sci;
132 		gchar *expr;
133 		if (sci_get_selection_mode(sci) == SC_SEL_STREAM &&
134 			peek_pos >= sci_get_selection_start(sci) &&
135 			peek_pos < sci_get_selection_end(sci))
136 		{
137 			expr = editor_get_default_selection(doc->editor, FALSE, NULL);
138 		}
139 		else
140 		{
141 			expr = utils_read_evaluate_expr(doc->editor, peek_pos);
142 		}
143 
144 		if ((expr = utils_verify_selection(expr)) != NULL)
145 		{
146 			g_free(input);
147 			input = debug_send_evaluate('3', scid_gen, expr);
148 			last_expr = expr;
149 		}
150 		else
151 			tooltip_set(NULL);
152 	}
153 	else
154 		tooltip_set(NULL);
155 
156 	query_id = 0;
157 	return FALSE;
158 }
159 
on_query_tooltip(G_GNUC_UNUSED GtkWidget * widget,gint x,gint y,gboolean keyboard_mode,GtkTooltip * tooltip,GeanyEditor * editor)160 static gboolean on_query_tooltip(G_GNUC_UNUSED GtkWidget *widget, gint x, gint y,
161 	gboolean keyboard_mode, GtkTooltip *tooltip, GeanyEditor *editor)
162 {
163 	gint pos = keyboard_mode ? sci_get_current_position(editor->sci) :
164 		scintilla_send_message(editor->sci, SCI_POSITIONFROMPOINT, x, y);
165 
166 	if (pos >= 0)
167 	{
168 		if (pos == last_pos)
169 		{
170 			gtk_tooltip_set_text(tooltip, output);
171 			return show;
172 		}
173 		else if (pos != peek_pos)
174 		{
175 			if (query_id)
176 				g_source_remove(query_id);
177 			else
178 				scid_gen++;
179 
180 			peek_pos = pos;
181 			query_id = plugin_timeout_add(geany_plugin, pref_tooltips_send_delay * 10,
182 				tooltip_launch, editor);
183 		}
184 	}
185 
186 	return FALSE;
187 }
188 
tooltip_attach(GeanyEditor * editor)189 void tooltip_attach(GeanyEditor *editor)
190 {
191 	if (option_editor_tooltips)
192 	{
193 		gtk_widget_set_has_tooltip(GTK_WIDGET(editor->sci), TRUE);
194 		g_signal_connect(editor->sci, "query-tooltip", G_CALLBACK(on_query_tooltip), editor);
195 	}
196 }
197 
tooltip_remove(GeanyEditor * editor)198 void tooltip_remove(GeanyEditor *editor)
199 {
200 	GtkWidget *widget = GTK_WIDGET(editor->sci);
201 
202 	if (gtk_widget_get_has_tooltip(widget))
203 	{
204 		gulong query_tooltip_id = g_signal_handler_find(widget, G_SIGNAL_MATCH_ID,
205 			g_signal_lookup("query-tooltip", GTK_TYPE_WIDGET), 0, NULL, NULL, NULL);
206 
207 		if (query_tooltip_id)
208 			g_signal_handler_disconnect(widget, query_tooltip_id);
209 		gtk_widget_set_has_tooltip(widget, FALSE);
210 	}
211 }
212 
tooltip_clear(void)213 void tooltip_clear(void)
214 {
215 	scid_gen = 0;
216 	last_pos = -1;
217 	peek_pos = -1;
218 }
219 
tooltip_update(void)220 gboolean tooltip_update(void)
221 {
222 	if (option_editor_tooltips)
223 	{
224 		last_pos = -1;
225 		peek_pos = -1;
226 		tooltip_trigger();
227 	}
228 	return TRUE;
229 }
230 
tooltip_finalize(void)231 void tooltip_finalize(void)
232 {
233 	g_free(output);
234 	g_free(input);
235 }
236