1117
|
1 /* gtkspell - a spell-checking addon for GtkText
|
|
2 * Copyright (c) 2000 Evan Martin.
|
|
3 * vim: ts=4 sw=4
|
|
4 * This library is free software; you can redistribute it and/or
|
|
5 * modify it under the terms of the GNU Lesser General Public
|
|
6 * License as published by the Free Software Foundation; either
|
|
7 * version 2 of the License, or (at your option) any later version.
|
|
8 *
|
|
9 * This library is distributed in the hope that it will be useful,
|
|
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
12 * Lesser General Public License for more details.
|
|
13 *
|
|
14 * You should have received a copy of the GNU Lesser General Public
|
|
15 * License along with this library; if not, write to the Free Software
|
|
16 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
|
17 */
|
|
18
|
|
19 #include <gtk/gtk.h>
|
|
20
|
|
21 #include <sys/types.h>
|
|
22 #include <sys/wait.h>
|
|
23 #include <sys/time.h>
|
|
24 #include <unistd.h>
|
|
25 #include <stdio.h>
|
|
26 #include <signal.h>
|
|
27 #include <ctype.h>
|
|
28 #include <string.h>
|
|
29 #include <stdlib.h>
|
|
30 #include <errno.h>
|
|
31
|
|
32 /* TODO:
|
|
33 * handle dictionary changes
|
|
34 * asynchronous lookups
|
|
35 */
|
|
36
|
|
37 /* size of the text buffer used in various word-processing routines. */
|
|
38 #define BUFSIZE 1024
|
|
39 /* number of suggestions to display on each menu. */
|
|
40 #define MENUCOUNT 10
|
|
41 #define BUGEMAIL "gtkspell-devel@lists.sourceforge.net"
|
|
42
|
|
43 /* because we keep only one copy of the spell program running,
|
|
44 * all ispell-related variables can be static.
|
|
45 */
|
|
46 static pid_t spell_pid = -1;
|
|
47 static int fd_write[2], fd_read[2];
|
|
48 static int signal_set_up = 0;
|
|
49
|
|
50 /* FIXME? */
|
|
51 static GdkColor highlight = { 0, 255*256, 0, 0 };
|
|
52
|
|
53 static void entry_insert_cb(GtkText *gtktext,
|
|
54 gchar *newtext, guint len, guint *ppos, gpointer d);
|
|
55 static void set_up_signal();
|
|
56
|
|
57 int gtkspell_running() {
|
|
58 return (spell_pid > 0);
|
|
59 }
|
|
60
|
|
61 static void error_print(const char *fmt, ...) {
|
|
62 va_list ap;
|
|
63 va_start(ap, fmt);
|
|
64 fprintf(stderr, "gtkspell: ");
|
|
65 vfprintf(stderr, fmt, ap);
|
|
66 va_end(ap);
|
|
67 }
|
|
68
|
|
69 /* functions to interface with pipe */
|
|
70 static void writetext(char *text) {
|
|
71 write(fd_write[1], text, strlen(text));
|
|
72 }
|
|
73 static int readpipe(char *buf, int bufsize) {
|
|
74 int len;
|
|
75 len = read(fd_read[0], buf, bufsize-1);
|
|
76 if (len < 0) {
|
|
77 error_print("read: %s\n", strerror(errno));
|
|
78 return -1;
|
|
79 } else if (len == 0) {
|
|
80 error_print("pipe closed.\n");
|
|
81 return -1;
|
|
82 } else if (len == bufsize-1) {
|
|
83 error_print("buffer overflowed?\n");
|
|
84 }
|
|
85
|
|
86 buf[len] = 0;
|
|
87 return len;
|
|
88 }
|
|
89 static int readline(char *buf) {
|
|
90 return readpipe(buf, BUFSIZE);
|
|
91 }
|
|
92
|
|
93 static int readresponse(char *buf) {
|
|
94 int len;
|
|
95 len = readpipe(buf, BUFSIZE);
|
|
96
|
|
97 /* all ispell responses of any reasonable length should end in \n\n.
|
|
98 * depending on the speed of the spell checker, this may require more
|
|
99 * reading. */
|
|
100 if (len >= 2 && (buf[len-1] != '\n' || buf[len-2] != '\n')) {
|
|
101 len += readpipe(buf+len, BUFSIZE-len);
|
|
102 }
|
|
103
|
|
104 /* now we can remove all of the the trailing newlines. */
|
|
105 while (len > 0 && buf[len-1] == '\n')
|
|
106 buf[--len] = 0;
|
|
107
|
|
108 return len;
|
|
109 }
|
|
110
|
|
111
|
|
112 void gtkspell_stop() {
|
|
113 if (gtkspell_running()) {
|
|
114 kill(spell_pid, SIGQUIT); /* FIXME: is this the correct signal? */
|
|
115 }
|
|
116 }
|
|
117
|
|
118 int gtkspell_start(char *path, char * args[]) {
|
|
119 int fd_error[2];
|
|
120 char buf[BUFSIZE];
|
|
121
|
|
122 if (gtkspell_running()) {
|
|
123 error_print("gtkspell_start called while already running.\n");
|
|
124 gtkspell_stop();
|
|
125 }
|
|
126
|
|
127 if (!signal_set_up) {
|
|
128 set_up_signal();
|
|
129 signal_set_up = 1;
|
|
130 }
|
|
131
|
|
132 pipe(fd_write);
|
|
133 pipe(fd_read);
|
|
134 pipe(fd_error);
|
|
135
|
|
136 spell_pid = fork();
|
|
137 if (spell_pid < 0) {
|
|
138 error_print("fork: %s\n", strerror(errno));
|
|
139 return -1;
|
|
140 } else if (spell_pid == 0) {
|
|
141 dup2(fd_write[0], 0);
|
|
142 dup2(fd_read[1], 1);
|
|
143 dup2(fd_error[1], 2);
|
|
144 close(fd_read[0]);
|
|
145 close(fd_error[0]);
|
|
146 close(fd_write[1]);
|
|
147
|
|
148 if (path == NULL) {
|
|
149 if (execvp(args[0], args) < 0)
|
|
150 error_print("execvp('%s'): %s\n", args[0], strerror(errno));
|
|
151 } else {
|
|
152 if (execv(path, args) < 0)
|
|
153 error_print("execv('%s'): %s\n", path, strerror(errno));
|
|
154 }
|
|
155 /* if we get here, we failed.
|
|
156 * send some text on the pipe to indicate status.
|
|
157 */
|
|
158 write(fd_read[1], "!", 1);
|
|
159
|
|
160 _exit(0);
|
|
161 } else {
|
|
162 /* there are at least two ways to fail:
|
|
163 * - the exec() can fail
|
|
164 * - the exec() can succeed, but the program can dump the help screen
|
|
165 * we must check for both.
|
|
166 */
|
|
167 fd_set rfds;
|
|
168 struct timeval tv;
|
|
169
|
|
170 FD_ZERO(&rfds);
|
|
171 FD_SET(fd_error[0], &rfds);
|
|
172 FD_SET(fd_read[0], &rfds);
|
|
173 tv.tv_sec = 2;
|
|
174 tv.tv_usec = 0;
|
|
175 if (select(MAX(fd_error[0], fd_read[0])+1,
|
|
176 &rfds, NULL, NULL, &tv) < 0) {
|
|
177 /* FIXME: is this needed? */
|
|
178 error_print("Timed out waiting for spell command.\n");
|
|
179 gtkspell_stop();
|
|
180 return -1;
|
|
181 }
|
|
182
|
|
183 if (FD_ISSET(fd_error[0], &rfds)) { /* stderr readable? */
|
|
184 error_print("Spell command printed on stderr -- probably failed.\n");
|
|
185 gtkspell_stop();
|
|
186 return -1;
|
|
187 }
|
|
188
|
|
189 /* otherwise, fd_read[0] is set. */
|
|
190 readline(buf);
|
|
191
|
|
192 /* ispell should print something like this:
|
|
193 * @(#) International Ispell Version 3.1.20 10/10/95
|
|
194 * if it doesn't, it's an error. */
|
|
195 if (buf[0] != '@') {
|
|
196 gtkspell_stop();
|
|
197 return -1;
|
|
198 }
|
|
199 }
|
|
200
|
|
201 /* put ispell into terse mode.
|
|
202 * this makes it not respond on correctly spelled words. */
|
|
203 sprintf(buf, "!\n");
|
|
204 writetext(buf);
|
|
205 return 0;
|
|
206 }
|
|
207
|
|
208 static GList* misspelled_suggest(char *word) {
|
|
209 char buf[BUFSIZE];
|
|
210 char *newword;
|
|
211 GList *l = NULL;
|
|
212 int count;
|
|
213
|
|
214 sprintf(buf, "^%s\n", word); /* guard against ispell control chars */
|
|
215 writetext(buf);
|
|
216 readresponse(buf);
|
|
217
|
|
218 switch (buf[0]) { /* first char is ispell command. */
|
|
219 case 0: /* no response: word is ok. */
|
|
220 return NULL;
|
|
221 case '&': /* misspelled, with suggestions */
|
|
222 /* & <orig> <count> <ofs>: <miss>, <miss>, <guess>, ... */
|
|
223 strtok(buf, " "); /* & */
|
|
224 newword = strtok(NULL, " "); /* orig */
|
|
225 l = g_list_append(l, g_strdup(newword));
|
|
226 newword = strtok(NULL, " "); /* count */
|
|
227 count = atoi(newword);
|
|
228 strtok(NULL, " "); /* ofs: */
|
|
229
|
|
230 while ((newword = strtok(NULL, ",")) != NULL) {
|
|
231 int len = strlen(newword);
|
|
232 if (newword[len-1] == ' ' || newword[len-1] == '\n')
|
|
233 newword[len-1] = 0;
|
|
234 if (count == 0) {
|
|
235 g_list_append(l, NULL); /* signal the "suggestions" */
|
|
236 }
|
|
237 /* add it to the list, skipping the initial space. */
|
|
238 l = g_list_append(l,
|
|
239 g_strdup(newword[0] == ' ' ? newword+1 : newword));
|
|
240
|
|
241 count--;
|
|
242 }
|
|
243 return l;
|
|
244
|
|
245 case '#': /* misspelled, no suggestions */
|
|
246 /* # <orig> <ofs> */
|
|
247 strtok(buf, " "); /* & */
|
|
248 newword = strtok(NULL, " "); /* orig */
|
|
249 l = g_list_append(l, g_strdup(newword));
|
|
250 return l;
|
|
251 default:
|
|
252 error_print("Unsupported spell command '%c'.\n"
|
|
253 "This is a bug; mail " BUGEMAIL " about it.\n", buf[0]);
|
|
254 }
|
|
255 return NULL;
|
|
256 }
|
|
257
|
|
258 static int misspelled_test(char *word) {
|
|
259 char buf[BUFSIZE];
|
|
260 sprintf(buf, "^%s\n", word); /* guard against ispell control chars */
|
|
261 writetext(buf);
|
|
262 readresponse(buf);
|
|
263
|
|
264 if (buf[0] == 0) {
|
|
265 return 0;
|
|
266 } else if (buf[0] == '&' || buf[0] == '#') {
|
|
267 return 1;
|
|
268 }
|
|
269
|
|
270 error_print("Unsupported spell command '%c'.\n"
|
|
271 "This is a bug; mail " BUGEMAIL " about it.\n", buf[0]);
|
|
272 return -1;
|
|
273 }
|
|
274
|
|
275 static gboolean iswordsep(char c) {
|
|
276 return !isalpha(c) && c != '\'';
|
|
277 }
|
|
278
|
|
279 static gboolean get_word_from_pos(GtkText* gtktext, int pos, char* buf,
|
|
280 int *pstart, int *pend) {
|
|
281 gint start, end;
|
|
282
|
|
283 if (iswordsep(GTK_TEXT_INDEX(gtktext, pos))) return FALSE;
|
|
284
|
|
285 for (start = pos; start >= 0; --start) {
|
|
286 if (iswordsep(GTK_TEXT_INDEX(gtktext, start))) break;
|
|
287 }
|
|
288 start++;
|
|
289
|
|
290 for (end = pos; end <= gtk_text_get_length(gtktext); end++) {
|
|
291 if (iswordsep(GTK_TEXT_INDEX(gtktext, end))) break;
|
|
292 }
|
|
293
|
|
294 if (buf) {
|
|
295 for (pos = start; pos < end; pos++)
|
|
296 buf[pos-start] = GTK_TEXT_INDEX(gtktext, pos);
|
|
297 buf[pos-start] = 0;
|
|
298 }
|
|
299
|
|
300 if (pstart) *pstart = start;
|
|
301 if (pend) *pend = end;
|
|
302
|
|
303 return TRUE;
|
|
304 }
|
|
305
|
|
306 static gboolean get_curword(GtkText* gtktext, char* buf,
|
|
307 int *pstart, int *pend) {
|
|
308 int pos = gtk_editable_get_position(GTK_EDITABLE(gtktext));
|
|
309 return get_word_from_pos(gtktext, pos, buf, pstart, pend);
|
|
310 }
|
|
311
|
|
312 static void change_color(GtkText *gtktext,
|
|
313 int start, int end, GdkColor *color) {
|
|
314 char *newtext = gtk_editable_get_chars(GTK_EDITABLE(gtktext), start, end);
|
|
315 gtk_text_freeze(gtktext);
|
|
316 gtk_signal_handler_block_by_func(GTK_OBJECT(gtktext),
|
|
317 GTK_SIGNAL_FUNC(entry_insert_cb), NULL);
|
|
318
|
|
319 gtk_text_set_point(gtktext, start);
|
|
320 gtk_text_forward_delete(gtktext, end-start);
|
|
321
|
|
322 if (newtext && end-start > 0)
|
|
323 gtk_text_insert(gtktext, NULL, color, NULL, newtext, end-start);
|
|
324
|
|
325 gtk_signal_handler_unblock_by_func(GTK_OBJECT(gtktext),
|
|
326 GTK_SIGNAL_FUNC(entry_insert_cb), NULL);
|
|
327 gtk_text_thaw(gtktext);
|
|
328 }
|
|
329
|
|
330 static gboolean check_at(GtkText *gtktext, int from_pos) {
|
|
331 int start, end;
|
|
332 char buf[BUFSIZE];
|
|
333
|
|
334 if (!get_word_from_pos(gtktext, from_pos, buf, &start, &end)) {
|
|
335 return FALSE;
|
|
336 }
|
|
337
|
|
338 if (misspelled_test(buf)) {
|
|
339 if (highlight.pixel == 0) {
|
|
340 /* add an entry for the highlight in the color map. */
|
|
341 GdkColormap *gc = gtk_widget_get_colormap(GTK_WIDGET(gtktext));
|
|
342 gdk_colormap_alloc_color(gc, &highlight, FALSE, TRUE);;
|
|
343 }
|
|
344 change_color(gtktext, start, end, &highlight);
|
|
345 return TRUE;
|
|
346 } else {
|
|
347 change_color(gtktext, start, end,
|
|
348 &(GTK_WIDGET(gtktext)->style->fg[0]));
|
|
349 return FALSE;
|
|
350 }
|
|
351 }
|
|
352
|
|
353 void gtkspell_check_all(GtkText *gtktext) {
|
|
354 guint origpos;
|
|
355 guint pos = 0;
|
|
356 guint len;
|
|
357 float adj_value;
|
|
358
|
|
359 if (!gtkspell_running()) return;
|
|
360
|
|
361 len = gtk_text_get_length(gtktext);
|
|
362
|
|
363 adj_value = gtktext->vadj->value;
|
|
364 gtk_text_freeze(gtktext);
|
|
365 origpos = gtk_editable_get_position(GTK_EDITABLE(gtktext));
|
|
366 while (pos < len) {
|
|
367 while (pos < len && iswordsep(GTK_TEXT_INDEX(gtktext, pos)))
|
|
368 pos++;
|
|
369 while (pos < len && !iswordsep(GTK_TEXT_INDEX(gtktext, pos)))
|
|
370 pos++;
|
|
371 if (pos > 0)
|
|
372 check_at(gtktext, pos-1);
|
|
373 }
|
|
374 gtk_text_thaw(gtktext);
|
|
375 gtk_editable_set_position(GTK_EDITABLE(gtktext), origpos);
|
|
376 }
|
|
377
|
|
378 static void entry_insert_cb(GtkText *gtktext,
|
|
379 gchar *newtext, guint len, guint *ppos, gpointer d) {
|
|
380 int origpos;
|
|
381
|
|
382 if (!gtkspell_running()) return;
|
|
383
|
|
384 gtk_signal_handler_block_by_func(GTK_OBJECT(gtktext),
|
|
385 GTK_SIGNAL_FUNC(entry_insert_cb),
|
|
386 NULL);
|
|
387 gtk_text_insert(GTK_TEXT(gtktext), NULL,
|
|
388 &(GTK_WIDGET(gtktext)->style->fg[0]), NULL, newtext, len);
|
|
389 gtk_signal_handler_unblock_by_func(GTK_OBJECT(gtktext),
|
|
390 GTK_SIGNAL_FUNC(entry_insert_cb),
|
|
391 NULL);
|
|
392 gtk_signal_emit_stop_by_name(GTK_OBJECT(gtktext), "insert-text");
|
|
393 *ppos += len;
|
|
394
|
|
395 origpos = gtk_editable_get_position(GTK_EDITABLE(gtktext));
|
|
396
|
|
397 if (iswordsep(newtext[0])) {
|
|
398 /* did we just end a word? */
|
|
399 if (*ppos >= 2) check_at(gtktext, *ppos-2);
|
|
400
|
|
401 /* did we just split a word? */
|
|
402 if (*ppos < gtk_text_get_length(gtktext))
|
|
403 check_at(gtktext, *ppos+1);
|
|
404 } else {
|
|
405 /* check as they type, *except* if they're typing at the end (the most
|
|
406 * common case.
|
|
407 */
|
|
408 if (*ppos < gtk_text_get_length(gtktext) &&
|
|
409 !iswordsep(GTK_TEXT_INDEX(gtktext, *ppos)))
|
|
410 check_at(gtktext, *ppos-1);
|
|
411 }
|
|
412
|
|
413 gtk_editable_set_position(GTK_EDITABLE(gtktext), origpos);
|
|
414 }
|
|
415
|
|
416 static void entry_delete_cb(GtkText *gtktext,
|
|
417 gint start, gint end, gpointer d) {
|
|
418 int origpos;
|
|
419
|
|
420 if (!gtkspell_running()) return;
|
|
421
|
|
422 origpos = gtk_editable_get_position(GTK_EDITABLE(gtktext));
|
|
423 check_at(gtktext, start-1);
|
|
424 gtk_editable_set_position(GTK_EDITABLE(gtktext), origpos);
|
|
425 gtk_editable_select_region(GTK_EDITABLE(gtktext), origpos, origpos);
|
|
426 /* this is to *UNDO* the selection, in case they were holding shift
|
|
427 * while hitting backspace. */
|
|
428 }
|
|
429
|
|
430 static void replace_word(GtkWidget *w, gpointer d) {
|
|
431 int start, end;
|
|
432 char *newword;
|
|
433 char buf[BUFSIZE];
|
|
434
|
|
435 /* we don't save their position,
|
|
436 * because the cursor is moved by the click. */
|
|
437
|
|
438 gtk_text_freeze(GTK_TEXT(d));
|
|
439
|
|
440 gtk_label_get(GTK_LABEL(GTK_BIN(w)->child), &newword);
|
|
441 get_curword(GTK_TEXT(d), buf, &start, &end);
|
|
442
|
|
443 gtk_text_set_point(GTK_TEXT(d), end);
|
|
444 gtk_text_backward_delete(GTK_TEXT(d), end-start);
|
|
445 gtk_text_insert(GTK_TEXT(d), NULL, NULL, NULL, newword, strlen(newword));
|
|
446
|
|
447 gtk_text_thaw(GTK_TEXT(d));
|
|
448 }
|
|
449
|
|
450 static GtkMenu *make_menu(GList *l, GtkText *gtktext) {
|
|
451 GtkWidget *menu, *item;
|
|
452 char *caption;
|
|
453 menu = gtk_menu_new(); {
|
|
454 caption = g_strdup_printf("Not in dictionary: %s", (char*)l->data);
|
|
455 item = gtk_menu_item_new_with_label(caption);
|
|
456 /* I'd like to make it so this item is never selectable, like
|
|
457 * the menu titles in the GNOME panel... unfortunately, the GNOME
|
|
458 * panel creates their own custom widget to do this! */
|
|
459 gtk_widget_show(item);
|
|
460 gtk_menu_append(GTK_MENU(menu), item);
|
|
461
|
|
462 item = gtk_menu_item_new();
|
|
463 gtk_widget_show(item);
|
|
464 gtk_menu_append(GTK_MENU(menu), item);
|
|
465
|
|
466 l = l->next;
|
|
467 if (l == NULL) {
|
|
468 item = gtk_menu_item_new_with_label("(no suggestions)");
|
|
469 gtk_widget_show(item);
|
|
470 gtk_menu_append(GTK_MENU(menu), item);
|
|
471 } else {
|
|
472 GtkWidget *curmenu = menu;
|
|
473 int count = 0;
|
|
474 do {
|
|
475 if (l->data == NULL && l->next != NULL) {
|
|
476 count = 0;
|
|
477 curmenu = gtk_menu_new();
|
|
478 item = gtk_menu_item_new_with_label("Other Possibilities...");
|
|
479 gtk_widget_show(item);
|
|
480 gtk_menu_item_set_submenu(GTK_MENU_ITEM(item), curmenu);
|
|
481 gtk_menu_append(GTK_MENU(curmenu), item);
|
|
482 l = l->next;
|
|
483 } else if (count > MENUCOUNT) {
|
|
484 count -= MENUCOUNT;
|
|
485 item = gtk_menu_item_new_with_label("More...");
|
|
486 gtk_widget_show(item);
|
|
487 gtk_menu_append(GTK_MENU(curmenu), item);
|
|
488 curmenu = gtk_menu_new();
|
|
489 gtk_menu_item_set_submenu(GTK_MENU_ITEM(item), curmenu);
|
|
490 }
|
|
491 item = gtk_menu_item_new_with_label((char*)l->data);
|
|
492 gtk_signal_connect(GTK_OBJECT(item), "activate",
|
|
493 GTK_SIGNAL_FUNC(replace_word), gtktext);
|
|
494 gtk_widget_show(item);
|
|
495 gtk_menu_append(GTK_MENU(curmenu), item);
|
|
496 count++;
|
|
497 } while ((l = l->next) != NULL);
|
|
498 }
|
|
499 }
|
|
500 return GTK_MENU(menu);
|
|
501 }
|
|
502
|
|
503 static void popup_menu(GtkText *gtktext, GdkEventButton *eb) {
|
|
504 char buf[BUFSIZE];
|
|
505 GList *list, *l;
|
|
506
|
|
507 get_curword(gtktext, buf, NULL, NULL);
|
|
508
|
|
509 list = misspelled_suggest(buf);
|
|
510 if (list != NULL) {
|
|
511 gtk_menu_popup(make_menu(list, gtktext), NULL, NULL, NULL, NULL,
|
|
512 eb->button, eb->time);
|
|
513 for (l = list; l != NULL; l = l->next)
|
|
514 g_free(l->data);
|
|
515 g_list_free(list);
|
|
516 }
|
|
517 }
|
|
518
|
|
519 /* ok, this is pretty wacky:
|
|
520 * we need to let the right-mouse-click go through, so it moves the cursor,
|
|
521 * but we *can't* let it go through, because GtkText interprets rightclicks as
|
|
522 * weird selection modifiers.
|
|
523 *
|
|
524 * so what do we do? forge rightclicks as leftclicks, then popup the menu.
|
|
525 * HACK HACK HACK.
|
|
526 */
|
|
527 static gint button_press_intercept_cb(GtkText *gtktext, GdkEvent *e, gpointer d) {
|
|
528 GdkEventButton *eb;
|
|
529 gboolean retval;
|
|
530
|
|
531 if (!gtkspell_running()) return FALSE;
|
|
532
|
|
533 if (e->type != GDK_BUTTON_PRESS) return FALSE;
|
|
534 eb = (GdkEventButton*) e;
|
|
535
|
|
536 if (eb->button != 3) return FALSE;
|
|
537
|
|
538 /* forge the leftclick */
|
|
539 eb->button = 1;
|
|
540
|
|
541 gtk_signal_handler_block_by_func(GTK_OBJECT(gtktext),
|
|
542 GTK_SIGNAL_FUNC(button_press_intercept_cb), d);
|
|
543 gtk_signal_emit_by_name(GTK_OBJECT(gtktext), "button-press-event",
|
|
544 e, &retval);
|
|
545 gtk_signal_handler_unblock_by_func(GTK_OBJECT(gtktext),
|
|
546 GTK_SIGNAL_FUNC(button_press_intercept_cb), d);
|
|
547 gtk_signal_emit_stop_by_name(GTK_OBJECT(gtktext), "button-press-event");
|
|
548
|
|
549 /* now do the menu wackiness */
|
|
550 popup_menu(gtktext, eb);
|
|
551 return TRUE;
|
|
552 }
|
|
553
|
|
554 void gtkspell_uncheck_all(GtkText *gtktext) {
|
|
555 int origpos;
|
|
556 char *text;
|
|
557 float adj_value;
|
|
558
|
|
559 adj_value = gtktext->vadj->value;
|
|
560 gtk_text_freeze(gtktext);
|
|
561 origpos = gtk_editable_get_position(GTK_EDITABLE(gtktext));
|
|
562 text = gtk_editable_get_chars(GTK_EDITABLE(gtktext), 0, -1);
|
|
563 gtk_text_set_point(gtktext, 0);
|
|
564 gtk_text_forward_delete(gtktext, gtk_text_get_length(gtktext));
|
|
565 gtk_text_insert(gtktext, NULL, NULL, NULL, text, strlen(text));
|
|
566 gtk_text_thaw(gtktext);
|
|
567
|
|
568 gtk_editable_set_position(GTK_EDITABLE(gtktext), origpos);
|
|
569 gtk_adjustment_set_value(gtktext->vadj, adj_value);
|
|
570 }
|
|
571
|
|
572 void gtkspell_attach(GtkText *gtktext) {
|
|
573 gtk_signal_connect(GTK_OBJECT(gtktext), "insert-text",
|
|
574 GTK_SIGNAL_FUNC(entry_insert_cb), NULL);
|
|
575 gtk_signal_connect_after(GTK_OBJECT(gtktext), "delete-text",
|
|
576 GTK_SIGNAL_FUNC(entry_delete_cb), NULL);
|
|
577 gtk_signal_connect(GTK_OBJECT(gtktext), "button-press-event",
|
|
578 GTK_SIGNAL_FUNC(button_press_intercept_cb), NULL);
|
|
579 }
|
|
580
|
|
581 void gtkspell_detach(GtkText *gtktext) {
|
|
582 gtk_signal_disconnect_by_func(GTK_OBJECT(gtktext),
|
|
583 GTK_SIGNAL_FUNC(entry_insert_cb), NULL);
|
|
584 gtk_signal_disconnect_by_func(GTK_OBJECT(gtktext),
|
|
585 GTK_SIGNAL_FUNC(entry_delete_cb), NULL);
|
|
586 gtk_signal_disconnect_by_func(GTK_OBJECT(gtktext),
|
|
587 GTK_SIGNAL_FUNC(button_press_intercept_cb), NULL);
|
|
588
|
|
589 gtkspell_uncheck_all(gtktext);
|
|
590 }
|
|
591
|
|
592 static void sigchld(int param) {
|
|
593 if (gtkspell_running() &&
|
|
594 (waitpid(spell_pid, NULL, WNOHANG) == spell_pid)) {
|
|
595 spell_pid = 0;
|
|
596 } else {
|
|
597 /* a default SIGCHLD handler.
|
|
598 * what else to do here? */
|
|
599 waitpid(-1, NULL, WNOHANG);
|
|
600 }
|
|
601 }
|
|
602
|
|
603 static void set_up_signal() {
|
|
604 struct sigaction sigact;
|
|
605 memset(&sigact, 0, sizeof(struct sigaction));
|
|
606
|
|
607 sigact.sa_handler = sigchld;
|
|
608 sigaction(SIGCHLD, &sigact, NULL);
|
|
609 }
|