comparison pidgin/gtkimhtml.c @ 24396:38a2f78f80a7

Allow plugins to specify custom link types to the GtkIMHtml widget. Currently, the custom link types are added for all GtkIMHtml widgets. If we wanted, it should be possible to add custom links to particular widgets only too. If everything looks OK, I might merge this in before 2.6.0
author Sadrul Habib Chowdhury <imadil@gmail.com>
date Sun, 16 Nov 2008 09:58:48 +0000
parents 9ea84135db24
children 16811be2253f
comparison
equal deleted inserted replaced
24394:fae699fece1f 24396:38a2f78f80a7
86 struct im_image_data { 86 struct im_image_data {
87 int id; 87 int id;
88 GtkTextMark *mark; 88 GtkTextMark *mark;
89 }; 89 };
90 90
91 typedef struct _GtkIMHtmlProtocol
92 {
93 char *name;
94 int length;
95
96 gboolean (*activate)(GtkIMHtml *imhtml, const char *text);
97 gboolean (*context_menu)(GtkIMHtml *imhtml, const char *text, GtkWidget *menu);
98 } GtkIMHtmlProtocol;
99
91 static gboolean 100 static gboolean
92 gtk_text_view_drag_motion (GtkWidget *widget, 101 gtk_text_view_drag_motion (GtkWidget *widget,
93 GdkDragContext *context, 102 GdkDragContext *context,
94 gint x, 103 gint x,
95 gint y, 104 gint y,
113 static void imhtml_toggle_strike(GtkIMHtml *imhtml); 122 static void imhtml_toggle_strike(GtkIMHtml *imhtml);
114 static void imhtml_toggle_underline(GtkIMHtml *imhtml); 123 static void imhtml_toggle_underline(GtkIMHtml *imhtml);
115 static void imhtml_font_grow(GtkIMHtml *imhtml); 124 static void imhtml_font_grow(GtkIMHtml *imhtml);
116 static void imhtml_font_shrink(GtkIMHtml *imhtml); 125 static void imhtml_font_shrink(GtkIMHtml *imhtml);
117 static void imhtml_clear_formatting(GtkIMHtml *imhtml); 126 static void imhtml_clear_formatting(GtkIMHtml *imhtml);
127 static int gtk_imhtml_is_protocol(const char *text);
118 128
119 /* POINT_SIZE converts from AIM font sizes to a point size scale factor. */ 129 /* POINT_SIZE converts from AIM font sizes to a point size scale factor. */
120 #define MAX_FONT_SIZE 7 130 #define MAX_FONT_SIZE 7
121 #define POINT_SIZE(x) (_point_sizes [MIN ((x > 0 ? x : 1), MAX_FONT_SIZE) - 1]) 131 #define POINT_SIZE(x) (_point_sizes [MIN ((x > 0 ? x : 1), MAX_FONT_SIZE) - 1])
122 static const gdouble _point_sizes [] = { .85, .95, 1, 1.2, 1.44, 1.728, 2.0736}; 132 static const gdouble _point_sizes [] = { .85, .95, 1, 1.2, 1.44, 1.728, 2.0736};
1387 g_free(imhtml->protocol_name); 1397 g_free(imhtml->protocol_name);
1388 g_free(imhtml->search_string); 1398 g_free(imhtml->search_string);
1389 g_object_unref(imhtml->undo_manager); 1399 g_object_unref(imhtml->undo_manager);
1390 G_OBJECT_CLASS(parent_class)->finalize (object); 1400 G_OBJECT_CLASS(parent_class)->finalize (object);
1391 1401
1402 }
1403
1404 static GtkIMHtmlProtocol *
1405 imhtml_find_protocol(const char *url)
1406 {
1407 GtkIMHtmlClass *klass;
1408 GList *iter;
1409 GtkIMHtmlProtocol *proto = NULL;
1410
1411 klass = g_type_class_ref(GTK_TYPE_IMHTML);
1412 for (iter = klass->protocols; iter; iter = iter->next) {
1413 proto = iter->data;
1414 if (g_ascii_strncasecmp(url, proto->name, proto->length) == 0) {
1415 return proto;
1416 }
1417 }
1418 return NULL;
1419 }
1420
1421 static void
1422 imhtml_url_clicked(GtkIMHtml *imhtml, const char *url)
1423 {
1424 GtkIMHtmlProtocol *proto = imhtml_find_protocol(url);
1425 if (!proto)
1426 return;
1427 proto->activate(imhtml, url); /* XXX: Do something with the return value? */
1392 } 1428 }
1393 1429
1394 /* Boring GTK+ stuff */ 1430 /* Boring GTK+ stuff */
1395 static void gtk_imhtml_class_init (GtkIMHtmlClass *klass) 1431 static void gtk_imhtml_class_init (GtkIMHtmlClass *klass)
1396 { 1432 {
1473 1509
1474 1510
1475 klass->toggle_format = imhtml_toggle_format; 1511 klass->toggle_format = imhtml_toggle_format;
1476 klass->message_send = imhtml_message_send; 1512 klass->message_send = imhtml_message_send;
1477 klass->clear_format = imhtml_clear_formatting; 1513 klass->clear_format = imhtml_clear_formatting;
1514 klass->url_clicked = imhtml_url_clicked;
1478 klass->undo = gtk_imhtml_undo; 1515 klass->undo = gtk_imhtml_undo;
1479 klass->redo = gtk_imhtml_redo; 1516 klass->redo = gtk_imhtml_redo;
1480 1517
1481 gobject_class->finalize = gtk_imhtml_finalize; 1518 gobject_class->finalize = gtk_imhtml_finalize;
1482 widget_class->drag_motion = gtk_text_view_drag_motion; 1519 widget_class->drag_motion = gtk_text_view_drag_motion;
1743 g_object_set_data(G_OBJECT(tag), "visited", GINT_TO_POINTER(TRUE)); 1780 g_object_set_data(G_OBJECT(tag), "visited", GINT_TO_POINTER(TRUE));
1744 gtk_imhtml_set_link_color(GTK_IMHTML(imhtml), tag); 1781 gtk_imhtml_set_link_color(GTK_IMHTML(imhtml), tag);
1745 return FALSE; 1782 return FALSE;
1746 } else if(event_button->button == 3) { 1783 } else if(event_button->button == 3) {
1747 GtkWidget *img, *item, *menu; 1784 GtkWidget *img, *item, *menu;
1785 GtkIMHtmlProtocol *proto;
1748 struct url_data *tempdata = g_new(struct url_data, 1); 1786 struct url_data *tempdata = g_new(struct url_data, 1);
1749 tempdata->object = g_object_ref(imhtml); 1787 tempdata->object = g_object_ref(imhtml);
1750 tempdata->url = g_strdup(g_object_get_data(G_OBJECT(tag), "link_url")); 1788 tempdata->url = g_strdup(g_object_get_data(G_OBJECT(tag), "link_url"));
1751 tempdata->tag = g_object_ref(tag); 1789 tempdata->tag = g_object_ref(tag);
1752 1790
1764 else 1802 else
1765 gdk_window_set_cursor(event_button->window, GTK_IMHTML(imhtml)->arrow_cursor); 1803 gdk_window_set_cursor(event_button->window, GTK_IMHTML(imhtml)->arrow_cursor);
1766 menu = gtk_menu_new(); 1804 menu = gtk_menu_new();
1767 g_object_set_data_full(G_OBJECT(menu), "x-imhtml-url-data", tempdata, url_data_destroy); 1805 g_object_set_data_full(G_OBJECT(menu), "x-imhtml-url-data", tempdata, url_data_destroy);
1768 1806
1769 /* buttons and such */ 1807 proto = imhtml_find_protocol(tempdata->url);
1770 1808
1771 if (!strncmp(tempdata->url, "mailto:", 7)) 1809 if (!strncmp(tempdata->url, "mailto:", 7))
1772 { 1810 {
1773 /* Copy Email Address */ 1811 /* Copy Email Address */
1774 img = gtk_image_new_from_stock(GTK_STOCK_COPY, 1812 img = gtk_image_new_from_stock(GTK_STOCK_COPY,
1778 gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(item), img); 1816 gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(item), img);
1779 g_signal_connect(G_OBJECT(item), "activate", 1817 g_signal_connect(G_OBJECT(item), "activate",
1780 G_CALLBACK(url_copy), tempdata->url + 7); 1818 G_CALLBACK(url_copy), tempdata->url + 7);
1781 gtk_menu_shell_append(GTK_MENU_SHELL(menu), item); 1819 gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
1782 } 1820 }
1821 else if (proto && proto->context_menu)
1822 {
1823 GList *children;
1824 proto->context_menu(GTK_IMHTML(tempdata->object), tempdata->url, menu);
1825 children = gtk_container_get_children(GTK_CONTAINER(menu));
1826 if (!children) {
1827 item = gtk_menu_item_new_with_label(_("No actions available"));
1828 gtk_widget_show(item);
1829 gtk_widget_set_sensitive(item, FALSE);
1830 gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
1831 } else {
1832 g_list_free(children);
1833 }
1834 }
1783 else 1835 else
1784 { 1836 {
1785 /* Open Link in Browser */ 1837 /* Open Link in Browser */
1786 img = gtk_image_new_from_stock(GTK_STOCK_JUMP_TO, 1838 img = gtk_image_new_from_stock(GTK_STOCK_JUMP_TO,
1787 GTK_ICON_SIZE_MENU); 1839 GTK_ICON_SIZE_MENU);
1882 /* TODO: Is it really ok to change sd->data...? */ 1934 /* TODO: Is it really ok to change sd->data...? */
1883 purple_str_strip_char((char *)sd->data, '\r'); 1935 purple_str_strip_char((char *)sd->data, '\r');
1884 1936
1885 links = g_strsplit((char *)sd->data, "\n", 0); 1937 links = g_strsplit((char *)sd->data, "\n", 0);
1886 while((link = links[i]) != NULL){ 1938 while((link = links[i]) != NULL){
1887 if(purple_str_has_prefix(link, "http://") || 1939 if (gtk_imhtml_is_protocol(link)) {
1888 purple_str_has_prefix(link, "https://") ||
1889 purple_str_has_prefix(link, "ftp://"))
1890 {
1891 gchar *label; 1940 gchar *label;
1892 1941
1893 if(links[i + 1]) 1942 if(links[i + 1])
1894 i++; 1943 i++;
1895 1944
1896 label = links[i]; 1945 label = links[i];
1897 1946
1898 gtk_imhtml_insert_link(imhtml, mark, link, label); 1947 gtk_imhtml_insert_link(imhtml, mark, link, label);
1899 } else if (link=='\0') { 1948 } else if (*link == '\0') {
1900 /* Ignore blank lines */ 1949 /* Ignore blank lines */
1901 } else { 1950 } else {
1902 /* Special reasons, aka images being put in via other tag, etc. */ 1951 /* Special reasons, aka images being put in via other tag, etc. */
1903 /* ... don't pretend we handled it if we didn't */ 1952 /* ... don't pretend we handled it if we didn't */
1904 gtk_drag_finish(dc, FALSE, FALSE, t); 1953 gtk_drag_finish(dc, FALSE, FALSE, t);
2380 g_free(val); 2429 g_free(val);
2381 2430
2382 return g_string_free(ret, FALSE); 2431 return g_string_free(ret, FALSE);
2383 } 2432 }
2384 2433
2385 static const char *accepted_protocols[] = {
2386 "http://",
2387 "https://",
2388 "ftp://"
2389 };
2390
2391 static const int accepted_protocols_size = 3;
2392
2393 /* returns if the beginning of the text is a protocol. If it is the protocol, returns the length so 2434 /* returns if the beginning of the text is a protocol. If it is the protocol, returns the length so
2394 the caller knows how long the protocol string is. */ 2435 the caller knows how long the protocol string is. */
2395 static int gtk_imhtml_is_protocol(const char *text) 2436 static int gtk_imhtml_is_protocol(const char *text)
2396 { 2437 {
2397 gint i; 2438 GtkIMHtmlProtocol *proto = imhtml_find_protocol(text);
2398 2439 return proto ? proto->length : 0;
2399 for(i=0; i<accepted_protocols_size; i++){
2400 if( g_ascii_strncasecmp(text, accepted_protocols[i], strlen(accepted_protocols[i])) == 0 ){
2401 return strlen(accepted_protocols[i]);
2402 }
2403 }
2404 return 0;
2405 } 2440 }
2406 2441
2407 /* 2442 /*
2408 <KingAnt> marv: The two IM image functions in oscar are purple_odc_send_im and purple_odc_incoming 2443 <KingAnt> marv: The two IM image functions in oscar are purple_odc_send_im and purple_odc_incoming
2409 2444
3318 } 3353 }
3319 c++; 3354 c++;
3320 pos++; 3355 pos++;
3321 } else if ((len_protocol = gtk_imhtml_is_protocol(c)) > 0){ 3356 } else if ((len_protocol = gtk_imhtml_is_protocol(c)) > 0){
3322 br = FALSE; 3357 br = FALSE;
3358 if (wpos > 0) {
3359 gtk_text_buffer_insert(imhtml->text_buffer, iter, ws, wpos);
3360 ws[0] = '\0';
3361 wpos = 0;
3362 }
3323 while(len_protocol--){ 3363 while(len_protocol--){
3324 /* Skip the next len_protocol characters, but make sure they're 3364 /* Skip the next len_protocol characters, but make sure they're
3325 copied into the ws array. 3365 copied into the ws array.
3326 */ 3366 */
3327 ws [wpos++] = *c++; 3367 ws [wpos++] = *c++;
3328 pos++; 3368 pos++;
3369 }
3370 if (!imhtml->edit.link) {
3371 while (*c && *c != ' ') {
3372 ws [wpos++] = *c++;
3373 pos++;
3374 }
3375 ws[wpos] = '\0';
3376 gtk_imhtml_toggle_link(imhtml, ws);
3377 gtk_text_buffer_insert(imhtml->text_buffer, iter, ws, wpos);
3378 ws[0] = '\0'; wpos = 0;
3379 gtk_imhtml_toggle_link(imhtml, NULL);
3329 } 3380 }
3330 } else if (*c) { 3381 } else if (*c) {
3331 br = FALSE; 3382 br = FALSE;
3332 ws [wpos++] = *c++; 3383 ws [wpos++] = *c++;
3333 pos++; 3384 pos++;
5743 if (smiley->loader) 5794 if (smiley->loader)
5744 g_object_unref(smiley->loader); 5795 g_object_unref(smiley->loader);
5745 g_free(smiley); 5796 g_free(smiley);
5746 } 5797 }
5747 5798
5799 gboolean gtk_imhtml_class_register_protocol(const char *name,
5800 gboolean (*activate)(GtkIMHtml *imhtml, const char *text),
5801 gboolean (*context_menu)(GtkIMHtml *imhtml, const char *text, GtkWidget *menu))
5802 {
5803 GtkIMHtmlClass *klass;
5804 GtkIMHtmlProtocol *proto;
5805
5806 g_return_val_if_fail(name, FALSE);
5807
5808 klass = g_type_class_ref(GTK_TYPE_IMHTML);
5809 g_return_val_if_fail(klass, FALSE);
5810
5811 if ((proto = imhtml_find_protocol(name))) {
5812 g_return_val_if_fail(!activate, FALSE);
5813 g_free(proto->name);
5814 g_free(proto);
5815 klass->protocols = g_list_remove(klass->protocols, proto);
5816 return TRUE;
5817 } else {
5818 g_return_val_if_fail(activate, FALSE);
5819 }
5820
5821 proto = g_new0(GtkIMHtmlProtocol, 1);
5822 proto->name = g_strdup(name);
5823 proto->length = strlen(name);
5824 proto->activate = activate;
5825 proto->context_menu = context_menu;
5826 klass->protocols = g_list_prepend(klass->protocols, proto);
5827
5828 return TRUE;
5829 }
5830