1 /* GtkUrl - A addon for GtkText that enables colored and clickable URLs
2 * Copyright (C) 2001 Benedikt Roth <Benedikt.Roth@bratislav.de>
4 * gtkspell - a spell-checking addon for GtkText
5 * Copyright (c) 2000 Evan Martin.
7 * This library is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU Lesser General Public
9 * License as published by the Free Software Foundation; either
10 * version 2 of the License, or (at your option) any later version.
12 * This library is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 * Lesser General Public License for more details.
17 * You should have received a copy of the GNU Lesser General Public
18 * License along with this library; if not, write to the Free Software
19 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
22 #define GTKURL_USE_GNOME
25 #ifdef GTKURL_USE_GNOME
27 #endif /* GTKURL_USE_GNOME */
36 static GdkColor highlight = { 0, 0, 0, 255*256 };
45 static void entry_insert_cb(GtkText *gtktext, gchar *newtext, guint len, guint *ppos, gpointer d);
46 static void entry_delete_cb(GtkText *gtktext, gint start, gint end, gpointer d);
47 static gint button_press_intercept_cb(GtkText *gtktext, GdkEvent *e, gpointer d);
49 static void popup_menu(GtkText *gtktext, GdkEventButton *eb);
50 static GtkMenu *make_menu(gchar *url);
52 static gboolean visit_url_gnome_cb( GtkWidget *widget, gpointer *data);
53 static int my_poptParseArgvString(const char * s, int * argcPtr, char *** argvPtr);
54 static gboolean visit_url_cmd_cb( GtkWidget *widget, gpointer *data);
56 static gboolean check_at(GtkText *gtktext, gint from_pos);
57 static gchar *get_word_from_pos(GtkText* gtktext, gint pos, gint *pstart, gint *pend);
58 static gchar *get_curword(GtkText* gtktext, gint *pstart, gint *pend);
60 static void change_color(GtkText *gtktext, gint start, gint end, GdkColor *color);
62 static gboolean iswordsep(gchar c);
63 static gint is_url(gchar* word);
67 void gtkurl_attach(GtkText *gtktext)
69 gtk_signal_connect(GTK_OBJECT(gtktext), "insert-text",
70 GTK_SIGNAL_FUNC(entry_insert_cb), NULL);
71 gtk_signal_connect_after(GTK_OBJECT(gtktext), "delete-text",
72 GTK_SIGNAL_FUNC(entry_delete_cb), NULL);
73 gtk_signal_connect(GTK_OBJECT(gtktext), "button-press-event",
74 GTK_SIGNAL_FUNC(button_press_intercept_cb), NULL);
78 void gtkurl_detach(GtkText *gtktext)
80 gtk_signal_disconnect_by_func(GTK_OBJECT(gtktext),
81 GTK_SIGNAL_FUNC(entry_insert_cb), NULL);
82 gtk_signal_disconnect_by_func(GTK_OBJECT(gtktext),
83 GTK_SIGNAL_FUNC(entry_delete_cb), NULL);
84 gtk_signal_disconnect_by_func(GTK_OBJECT(gtktext),
85 GTK_SIGNAL_FUNC(button_press_intercept_cb), NULL);
87 gtkurl_uncheck_all(gtktext);
91 void gtkurl_check_all(GtkText *gtktext)
98 len = gtk_text_get_length(gtktext);
100 adj_value = gtktext->vadj->value;
101 gtk_text_freeze(gtktext);
102 origpos = gtk_editable_get_position(GTK_EDITABLE(gtktext));
106 while (pos < len && iswordsep(GTK_TEXT_INDEX(gtktext, pos)))
108 while (pos < len && !iswordsep(GTK_TEXT_INDEX(gtktext, pos)))
111 check_at(gtktext, pos-1);
114 gtk_text_thaw(gtktext);
115 gtk_editable_set_position(GTK_EDITABLE(gtktext), origpos);
119 void gtkurl_uncheck_all(GtkText *gtktext)
125 adj_value = gtktext->vadj->value;
126 gtk_text_freeze(gtktext);
127 origpos = gtk_editable_get_position(GTK_EDITABLE(gtktext));
128 text = gtk_editable_get_chars(GTK_EDITABLE(gtktext), 0, -1);
129 gtk_text_set_point(gtktext, 0);
130 gtk_text_forward_delete(gtktext, gtk_text_get_length(gtktext));
131 gtk_text_insert(gtktext, NULL, NULL, NULL, text, strlen(text));
132 gtk_text_thaw(gtktext);
134 gtk_editable_set_position(GTK_EDITABLE(gtktext), origpos);
135 gtk_adjustment_set_value(gtktext->vadj, adj_value);
139 static void entry_insert_cb(GtkText *gtktext, gchar *newtext, guint len, guint *ppos, gpointer d)
143 gtk_signal_handler_block_by_func(GTK_OBJECT(gtktext),
144 GTK_SIGNAL_FUNC(entry_insert_cb),
147 gtk_text_insert(GTK_TEXT(gtktext), NULL,
148 &(GTK_WIDGET(gtktext)->style->fg[0]), NULL, newtext, len);
150 gtk_signal_handler_unblock_by_func(GTK_OBJECT(gtktext),
151 GTK_SIGNAL_FUNC(entry_insert_cb),
154 gtk_signal_emit_stop_by_name(GTK_OBJECT(gtktext), "insert-text");
157 origpos = gtk_editable_get_position(GTK_EDITABLE(gtktext));
159 if (iswordsep(newtext[0]))
161 /* did we just end a word? */
162 if (*ppos >= 2) check_at(gtktext, *ppos-2);
164 /* did we just split a word? */
165 if (*ppos < gtk_text_get_length(gtktext))
166 check_at(gtktext, *ppos+1);
170 /* check as they type, *except* if they're typing at the end (the most
173 if (*ppos < gtk_text_get_length(gtktext) && !iswordsep(GTK_TEXT_INDEX(gtktext, *ppos)))
174 check_at(gtktext, *ppos-1);
177 gtk_editable_set_position(GTK_EDITABLE(gtktext), origpos);
178 gtk_editable_select_region(GTK_EDITABLE(gtktext), origpos, origpos);
182 static void entry_delete_cb(GtkText *gtktext, gint start, gint end, gpointer d)
186 origpos = gtk_editable_get_position(GTK_EDITABLE(gtktext));
187 check_at(gtktext, start-1);
188 gtk_editable_set_position(GTK_EDITABLE(gtktext), origpos);
189 gtk_editable_select_region(GTK_EDITABLE(gtktext), origpos, origpos);
190 /* this is to *UNDO* the selection, in case they were holding shift
191 * while hitting backspace. */
195 /* ok, this is pretty wacky:
196 * we need to let the right-mouse-click go through, so it moves the cursor,
197 * but we *can't* let it go through, because GtkText interprets rightclicks as
198 * weird selection modifiers.
200 * so what do we do? forge rightclicks as leftclicks, then popup the menu.
203 static gint button_press_intercept_cb(GtkText *gtktext, GdkEvent *e, gpointer d)
208 if (e->type != GDK_BUTTON_PRESS) return FALSE;
209 eb = (GdkEventButton*) e;
214 /* forge the leftclick */
217 gtk_signal_handler_block_by_func(GTK_OBJECT(gtktext),
218 GTK_SIGNAL_FUNC(button_press_intercept_cb), d);
219 gtk_signal_emit_by_name(GTK_OBJECT(gtktext), "button-press-event",
221 gtk_signal_handler_unblock_by_func(GTK_OBJECT(gtktext),
222 GTK_SIGNAL_FUNC(button_press_intercept_cb), d);
223 gtk_signal_emit_stop_by_name(GTK_OBJECT(gtktext), "button-press-event");
225 /* now do the menu wackiness */
226 popup_menu(gtktext, eb);
231 static void popup_menu(GtkText *gtktext, GdkEventButton *eb)
235 buf = get_curword(gtktext, NULL, NULL);
237 gtk_menu_popup(make_menu(buf), NULL, NULL, NULL, NULL,
238 eb->button, eb->time);
242 static GtkMenu *make_menu(gchar *url)
244 GtkWidget *menu, *item;
246 gchar *s = "http://";
249 switch( is_url(url) )
252 url = g_strdup_printf("%s", url);
255 url = g_strdup_printf("%s%s", s, url);
259 menu = gtk_menu_new();
261 caption = g_strdup_printf("%s", url);
262 item = gtk_menu_item_new_with_label(caption);
264 gtk_widget_set_sensitive( GTK_WIDGET(item), FALSE);
265 /* I'd like to make it so this item is never selectable, like
266 * the menu titles in the GNOME panel... unfortunately, the GNOME
267 * panel creates their own custom widget to do this! */
268 gtk_widget_show(item);
269 gtk_menu_append(GTK_MENU(menu), item);
271 item = gtk_menu_item_new();
272 gtk_widget_show(item);
273 gtk_menu_append(GTK_MENU(menu), item);
275 #ifdef GTKURL_USE_GNOME
276 item = gtk_menu_item_new_with_label(_("Open with GNOME URL Handler"));
277 gtk_signal_connect(GTK_OBJECT(item), "activate",
278 GTK_SIGNAL_FUNC(visit_url_gnome_cb), g_strdup(url) );
279 gtk_menu_append(GTK_MENU(menu), item);
280 gtk_widget_show(item);
281 #endif /* GTKURL_USE_GNOME */
283 item = gtk_menu_item_new_with_label(_("Open with Netscape (Existing)"));
284 cmd = g_strdup_printf("netscape -remote 'openURL(%s)'", url);
285 gtk_signal_connect(GTK_OBJECT(item), "activate",
286 GTK_SIGNAL_FUNC(visit_url_cmd_cb), g_strdup(cmd) );
288 gtk_menu_append(GTK_MENU(menu), item);
289 gtk_widget_show(item);
291 item = gtk_menu_item_new_with_label(_("Open with Netscape (New Window)"));
292 cmd = g_strdup_printf("netscape -remote 'openURL(%s,new-window)'", url);
293 gtk_signal_connect(GTK_OBJECT(item), "activate",
294 GTK_SIGNAL_FUNC(visit_url_cmd_cb), g_strdup(cmd) );
296 gtk_menu_append(GTK_MENU(menu), item);
297 gtk_widget_show(item);
299 item = gtk_menu_item_new_with_label(_("Open with Netscape (Run New)"));
300 cmd = g_strdup_printf("netscape %s", url);
301 gtk_signal_connect(GTK_OBJECT(item), "activate",
302 GTK_SIGNAL_FUNC(visit_url_cmd_cb), g_strdup(cmd) );
304 gtk_menu_append(GTK_MENU(menu), item);
305 gtk_widget_show(item);
309 return GTK_MENU(menu);
313 #ifdef GTKURL_USE_GNOME
314 static gboolean visit_url_gnome_cb( GtkWidget *widget, gpointer *data)
316 gnome_url_show((gchar *) data);
320 #endif /* GTKURL_USE_GNOME */
323 /* this is taken from gnome-libs 1.2.4 */
324 #define POPT_ARGV_ARRAY_GROW_DELTA 5
326 static int my_poptParseArgvString(const char * s, int * argcPtr, char *** argvPtr)
328 char * buf, * bufStart, * dst;
331 int argvAlloced = POPT_ARGV_ARRAY_GROW_DELTA;
332 char ** argv = malloc(sizeof(*argv) * argvAlloced);
337 buflen = strlen(s) + 1;
338 bufStart = buf = alloca(buflen);
339 memset(buf, '\0', buflen);
354 if (*src != quote) *buf++ = '\\';
357 } else if (isspace(*src)) {
360 if (argc == argvAlloced) {
361 argvAlloced += POPT_ARGV_ARRAY_GROW_DELTA;
362 argv = realloc(argv, sizeof(*argv) * argvAlloced);
366 } else switch (*src) {
385 if (strlen(argv[argc])) {
389 dst = malloc((argc + 1) * sizeof(*argv) + (buf - bufStart));
390 argv2 = (void *) dst;
391 dst += (argc + 1) * sizeof(*argv);
392 memcpy(argv2, argv, argc * sizeof(*argv));
394 memcpy(dst, bufStart, buf - bufStart);
396 for (i = 0; i < argc; i++) {
397 argv2[i] = dst + (argv[i] - bufStart);
402 *argvPtr = (char **)argv2; /* XXX don't change the API */
409 static gboolean visit_url_cmd_cb( GtkWidget *widget, gpointer *data)
415 if (my_poptParseArgvString ( (const char *)data, &argc, &argv) != 0)
423 execvp (argv[0], argv);
437 static gboolean check_at(GtkText *gtktext, gint from_pos)
442 if ( ! (buf = get_word_from_pos(gtktext, from_pos, &start, &end)) )
447 if (highlight.pixel == 0)
449 /* add an entry for the highlight in the color map. */
450 GdkColormap *gc = gtk_widget_get_colormap(GTK_WIDGET(gtktext));
451 gdk_colormap_alloc_color(gc, &highlight, FALSE, TRUE);;
453 change_color(gtktext, start, end, &highlight);
458 change_color(gtktext, start, end, &(GTK_WIDGET(gtktext)->style->fg[0]));
464 static gchar *get_word_from_pos(GtkText* gtktext, gint pos, gint *pstart, gint *pend)
466 GString *word = g_string_new("");
470 if (iswordsep(GTK_TEXT_INDEX(gtktext, pos)))
473 /* Get start and end position from the word */
474 for (start = pos; start >= 0; --start)
475 if (iswordsep(GTK_TEXT_INDEX(gtktext, start)))
479 for (end = pos; end < gtk_text_get_length(gtktext); end++)
480 if (iswordsep(GTK_TEXT_INDEX(gtktext, end)) )
483 /* Be sure to not include punctation marks etc. */
484 for ( ;end>start; end-- )
486 ch = GTK_TEXT_INDEX(gtktext, end-1);
487 if( isalpha(ch) || isdigit(ch) || ch == ':' )
491 /* Get the word (everyting between start and end */
492 for (pos = start; pos < end; pos++)
493 g_string_append_c( word, GTK_TEXT_INDEX(gtktext, pos) );
504 static gchar *get_curword(GtkText* gtktext, gint *pstart, gint *pend)
506 gint pos = gtk_editable_get_position(GTK_EDITABLE(gtktext));
507 return(get_word_from_pos(gtktext, pos, pstart, pend));
511 static void change_color(GtkText *gtktext, gint start, gint end, GdkColor *color)
515 /* So we don't need spaces at the very end of the text */
516 if ( end == gtk_text_get_length(GTK_TEXT(gtktext))+1 )
519 newtext = gtk_editable_get_chars(GTK_EDITABLE(gtktext), start, end);
521 gtk_text_freeze(gtktext);
522 gtk_signal_handler_block_by_func(GTK_OBJECT(gtktext),
523 GTK_SIGNAL_FUNC(entry_insert_cb), NULL);
525 gtk_text_set_point(gtktext, start);
526 gtk_text_forward_delete(gtktext, end-start);
528 if (newtext && end-start > 0)
529 gtk_text_insert(gtktext, NULL, color, NULL, newtext, end-start);
531 gtk_signal_handler_unblock_by_func(GTK_OBJECT(gtktext),
532 GTK_SIGNAL_FUNC(entry_insert_cb), NULL);
533 gtk_text_thaw(gtktext);
538 static gboolean iswordsep(gchar c)
540 /* return !isalpha(c) && c != '\''; */
541 return( isspace(c) );
545 static gint is_url(gchar* word)
549 return GTKURL_NO_URL;
553 if (!strncasecmp (word, "irc://", 6))
556 if (!strncasecmp (word, "irc.", 4))
559 if (!strncasecmp (word, "ftp.", 4))
562 if (!strncasecmp (word, "ftp:", 4))
565 if (!strncasecmp (word, "www.", 4))
568 if (!strncasecmp (word, "http:", 5))
571 if (!strncasecmp (word, "https:", 6))
574 if (!strncasecmp (word + len - 4, ".org", 4))
577 if (!strncasecmp (word + len - 4, ".net", 4))
580 if (!strncasecmp (word + len - 4, ".com", 4))
583 if (!strncasecmp (word + len - 4, ".edu", 4))
586 return GTKURL_NO_URL;