comparison pidgin/gtkimhtml.c @ 16083:f2a4b05407d7

Patch from shlomil in ticket #78. This fixes RTL text support in MSN, and lays the framework so it could be supported in other prpls as well. As added pluses, shlomil removed some duplicate code and fixed some small related bugs. committer: Richard Laager <rlaager@wiktel.com>
author Shlomi Loubaton <shlomister@gmail.com>
date Fri, 13 Apr 2007 04:13:24 +0000
parents 66dff3dfdea6
children a5a831a5f186
comparison
equal deleted inserted replaced
16082:7a7377a86ad1 16083:f2a4b05407d7
98 static void preinsert_cb(GtkTextBuffer *buffer, GtkTextIter *iter, gchar *text, gint len, GtkIMHtml *imhtml); 98 static void preinsert_cb(GtkTextBuffer *buffer, GtkTextIter *iter, gchar *text, gint len, GtkIMHtml *imhtml);
99 static void insert_cb(GtkTextBuffer *buffer, GtkTextIter *iter, gchar *text, gint len, GtkIMHtml *imhtml); 99 static void insert_cb(GtkTextBuffer *buffer, GtkTextIter *iter, gchar *text, gint len, GtkIMHtml *imhtml);
100 static void delete_cb(GtkTextBuffer *buffer, GtkTextIter *iter, GtkTextIter *end, GtkIMHtml *imhtml); 100 static void delete_cb(GtkTextBuffer *buffer, GtkTextIter *iter, GtkTextIter *end, GtkIMHtml *imhtml);
101 static void insert_ca_cb(GtkTextBuffer *buffer, GtkTextIter *arg1, GtkTextChildAnchor *arg2, gpointer user_data); 101 static void insert_ca_cb(GtkTextBuffer *buffer, GtkTextIter *arg1, GtkTextChildAnchor *arg2, gpointer user_data);
102 static void gtk_imhtml_apply_tags_on_insert(GtkIMHtml *imhtml, GtkTextIter *start, GtkTextIter *end); 102 static void gtk_imhtml_apply_tags_on_insert(GtkIMHtml *imhtml, GtkTextIter *start, GtkTextIter *end);
103 static gboolean gtk_imhtml_is_amp_escape (const gchar *string, gchar **replace, gint *length);
104 void gtk_imhtml_close_tags(GtkIMHtml *imhtml, GtkTextIter *iter); 103 void gtk_imhtml_close_tags(GtkIMHtml *imhtml, GtkTextIter *iter);
105 static void gtk_imhtml_link_drop_cb(GtkWidget *widget, GdkDragContext *context, gint x, gint y, guint time, gpointer user_data); 104 static void gtk_imhtml_link_drop_cb(GtkWidget *widget, GdkDragContext *context, gint x, gint y, guint time, gpointer user_data);
106 static void gtk_imhtml_link_drag_rcv_cb(GtkWidget *widget, GdkDragContext *dc, guint x, guint y, GtkSelectionData *sd, guint info, guint t, GtkIMHtml *imhtml); 105 static void gtk_imhtml_link_drag_rcv_cb(GtkWidget *widget, GdkDragContext *dc, guint x, guint y, GtkSelectionData *sd, guint info, guint t, GtkIMHtml *imhtml);
107 static void mark_set_cb(GtkTextBuffer *buffer, GtkTextIter *arg1, GtkTextMark *mark, GtkIMHtml *imhtml); 106 static void mark_set_cb(GtkTextBuffer *buffer, GtkTextIter *arg1, GtkTextMark *mark, GtkIMHtml *imhtml);
108 static void hijack_menu_cb(GtkIMHtml *imhtml, GtkMenu *menu, gpointer data); 107 static void hijack_menu_cb(GtkIMHtml *imhtml, GtkMenu *menu, gpointer data);
1734 const gchar *text) 1733 const gchar *text)
1735 { 1734 {
1736 GtkSmileyTree *t = tree; 1735 GtkSmileyTree *t = tree;
1737 const gchar *x = text; 1736 const gchar *x = text;
1738 gint len = 0; 1737 gint len = 0;
1739 gchar *amp; 1738 const gchar *amp;
1740 gint alen; 1739 gint alen;
1741 1740
1742 while (*x) { 1741 while (*x) {
1743 gchar *pos; 1742 gchar *pos;
1744 1743
1745 if (!t->values) 1744 if (!t->values)
1746 break; 1745 break;
1747 1746
1748 if(*x == '&' && gtk_imhtml_is_amp_escape(x, &amp, &alen)) { 1747 if(*x == '&' && (amp = purple_markup_detect_entity(x, &alen))) {
1749 gboolean matched = TRUE; 1748 gboolean matched = TRUE;
1750 /* Make sure all chars of the unescaped value match */ 1749 /* Make sure all chars of the unescaped value match */
1751 while (*(amp + 1)) { 1750 while (*(amp + 1)) {
1752 pos = strchr (t->values->str, *amp); 1751 pos = strchr (t->values->str, *amp);
1753 if (pos) 1752 if (pos)
1924 return TRUE; \ 1923 return TRUE; \
1925 } \ 1924 } \
1926 } \ 1925 } \
1927 (*type)++ 1926 (*type)++
1928 1927
1929
1930 static gboolean
1931 gtk_imhtml_is_amp_escape (const gchar *string,
1932 gchar **replace,
1933 gint *length)
1934 {
1935 static char buf[7];
1936 g_return_val_if_fail (string != NULL, FALSE);
1937 g_return_val_if_fail (replace != NULL, FALSE);
1938 g_return_val_if_fail (length != NULL, FALSE);
1939
1940 if (!g_ascii_strncasecmp (string, "&amp;", 5)) {
1941 *replace = "&";
1942 *length = 5;
1943 } else if (!g_ascii_strncasecmp (string, "&lt;", 4)) {
1944 *replace = "<";
1945 *length = 4;
1946 } else if (!g_ascii_strncasecmp (string, "&gt;", 4)) {
1947 *replace = ">";
1948 *length = 4;
1949 } else if (!g_ascii_strncasecmp (string, "&nbsp;", 6)) {
1950 *replace = " ";
1951 *length = 6;
1952 } else if (!g_ascii_strncasecmp (string, "&copy;", 6)) {
1953 *replace = "©";
1954 *length = 6;
1955 } else if (!g_ascii_strncasecmp (string, "&quot;", 6)) {
1956 *replace = "\"";
1957 *length = 6;
1958 } else if (!g_ascii_strncasecmp (string, "&reg;", 5)) {
1959 *replace = "®";
1960 *length = 5;
1961 } else if (!g_ascii_strncasecmp (string, "&apos;", 6)) {
1962 *replace = "\'";
1963 *length = 6;
1964 } else if (*(string + 1) == '#') {
1965 guint pound = 0;
1966 if ((sscanf (string, "&#%u;", &pound) == 1) && pound != 0) {
1967 int buflen;
1968 if (*(string + 3 + (gint)log10 (pound)) != ';')
1969 return FALSE;
1970 buflen = g_unichar_to_utf8((gunichar)pound, buf);
1971 buf[buflen] = '\0';
1972 *replace = buf;
1973 *length = 2;
1974 while (isdigit ((gint) string [*length])) (*length)++;
1975 if (string [*length] == ';') (*length)++;
1976 } else {
1977 return FALSE;
1978 }
1979 } else {
1980 return FALSE;
1981 }
1982
1983 return TRUE;
1984 }
1985 1928
1986 static gboolean 1929 static gboolean
1987 gtk_imhtml_is_tag (const gchar *string, 1930 gtk_imhtml_is_tag (const gchar *string,
1988 gchar **tag, 1931 gchar **tag,
1989 gint *len, 1932 gint *len,
2082 { 2025 {
2083 gchar *t = tag; 2026 gchar *t = tag;
2084 gchar *e, *a; 2027 gchar *e, *a;
2085 gchar *val; 2028 gchar *val;
2086 gint len; 2029 gint len;
2087 gchar *c; 2030 const gchar *c;
2088 GString *ret; 2031 GString *ret;
2089 2032
2090 while (g_ascii_strncasecmp (t, opt, strlen (opt))) { 2033 while (g_ascii_strncasecmp (t, opt, strlen (opt))) {
2091 gboolean quote = FALSE; 2034 gboolean quote = FALSE;
2092 if (*t == '\0') break; 2035 if (*t == '\0') break;
2118 } 2061 }
2119 2062
2120 ret = g_string_new(""); 2063 ret = g_string_new("");
2121 e = val; 2064 e = val;
2122 while(*e) { 2065 while(*e) {
2123 if(gtk_imhtml_is_amp_escape(e, &c, &len)) { 2066 if((c = purple_markup_detect_entity(e, &len))) {
2124 ret = g_string_append(ret, c); 2067 ret = g_string_append(ret, c);
2125 e += len; 2068 e += len;
2126 } else { 2069 } else {
2127 ret = g_string_append_c(ret, *e); 2070 ret = g_string_append_c(ret, *e);
2128 e++; 2071 e++;
2129 } 2072 }
2130 } 2073 }
2131 2074
2132 g_free(val);
2133
2134 return g_string_free(ret, FALSE);
2135 }
2136
2137 /* Inline CSS Support - Douglas Thrift */
2138 static gchar*
2139 gtk_imhtml_get_css_opt (gchar *style,
2140 const gchar *opt)
2141 {
2142 gchar *t = style;
2143 gchar *e, *a;
2144 gchar *val;
2145 gint len;
2146 gchar *c;
2147 GString *ret;
2148
2149 while (g_ascii_strncasecmp (t, opt, strlen (opt))) {
2150 /* gboolean quote = FALSE; */
2151 if (*t == '\0') break;
2152 while (*t && !((*t == ' ') /*&& !quote*/)) {
2153 /* if (*t == '\"')
2154 quote = ! quote; */
2155 t++;
2156 }
2157 while (*t && (*t == ' ')) t++;
2158 }
2159
2160 if (!g_ascii_strncasecmp (t, opt, strlen (opt))) {
2161 t += strlen (opt);
2162 while (*t && (*t == ' ')) t++;
2163 if (!*t)
2164 return NULL;
2165 } else {
2166 return NULL;
2167 }
2168
2169 /* if ((*t == '\"') || (*t == '\'')) {
2170 e = a = ++t;
2171 while (*e && (*e != *(t - 1))) e++;
2172 if (*e == '\0') {
2173 return NULL;
2174 } else
2175 val = g_strndup(a, e - a);
2176 } else {
2177 e = a = t;
2178 while (*e && !isspace ((gint) *e)) e++;
2179 val = g_strndup(a, e - a);
2180 }*/
2181
2182 e = a = t;
2183 while (*e && *e != ';') e++;
2184 val = g_strndup(a, e - a);
2185
2186 ret = g_string_new("");
2187 e = val;
2188 while(*e) {
2189 if(gtk_imhtml_is_amp_escape(e, &c, &len)) {
2190 ret = g_string_append(ret, c);
2191 e += len;
2192 } else {
2193 ret = g_string_append_c(ret, *e);
2194 e++;
2195 }
2196 }
2197 g_free(val); 2075 g_free(val);
2198 2076
2199 return g_string_free(ret, FALSE); 2077 return g_string_free(ret, FALSE);
2200 } 2078 }
2201 2079
2367 gchar *bg = NULL; 2245 gchar *bg = NULL;
2368 gint len; 2246 gint len;
2369 gint tlen, smilelen, wpos=0; 2247 gint tlen, smilelen, wpos=0;
2370 gint type; 2248 gint type;
2371 const gchar *c; 2249 const gchar *c;
2372 gchar *amp; 2250 const gchar *amp;
2373 gint len_protocol; 2251 gint len_protocol;
2374 2252
2375 guint bold = 0, 2253 guint bold = 0,
2376 italics = 0, 2254 italics = 0,
2377 underline = 0, 2255 underline = 0,
2380 sup = 0, 2258 sup = 0,
2381 title = 0, 2259 title = 0,
2382 pre = 0; 2260 pre = 0;
2383 2261
2384 gboolean br = FALSE; 2262 gboolean br = FALSE;
2263 gboolean align_right = FALSE;
2264 gboolean rtl_direction = FALSE;
2265 gint align_line = 0;
2385 2266
2386 GSList *fonts = NULL; 2267 GSList *fonts = NULL;
2387 GObject *object; 2268 GObject *object;
2388 GtkIMHtmlScalable *scalable = NULL; 2269 GtkIMHtmlScalable *scalable = NULL;
2389 2270
2746 * background 2627 * background
2747 * font-family 2628 * font-family
2748 * font-size 2629 * font-size
2749 * text-decoration: underline 2630 * text-decoration: underline
2750 * font-weight: bold 2631 * font-weight: bold
2632 * direction: rtl
2633 * text-align: right
2751 * 2634 *
2752 * TODO: 2635 * TODO:
2753 * background-color 2636 * background-color
2754 * font-style 2637 * font-style
2755 */ 2638 */
2756 { 2639 {
2757 gchar *style, *color, *background, *family, *size; 2640 gchar *style, *color, *background, *family, *size, *direction, *alignment;
2758 gchar *textdec, *weight; 2641 gchar *textdec, *weight;
2759 GtkIMHtmlFontDetail *font, *oldfont = NULL; 2642 GtkIMHtmlFontDetail *font, *oldfont = NULL;
2760 style = gtk_imhtml_get_html_opt (tag, "style="); 2643 style = gtk_imhtml_get_html_opt (tag, "style=");
2761 2644
2762 if (!style) break; 2645 if (!style) break;
2763 2646
2764 color = gtk_imhtml_get_css_opt (style, "color:"); 2647 color = purple_markup_get_css_property (style, "color");
2765 background = gtk_imhtml_get_css_opt (style, "background:"); 2648 background = purple_markup_get_css_property (style, "background");
2766 family = gtk_imhtml_get_css_opt (style, 2649 family = purple_markup_get_css_property (style, "font-family");
2767 "font-family:"); 2650 size = purple_markup_get_css_property (style, "font-size");
2768 size = gtk_imhtml_get_css_opt (style, "font-size:"); 2651 textdec = purple_markup_get_css_property (style, "text-decoration");
2769 textdec = gtk_imhtml_get_css_opt (style, "text-decoration:"); 2652 weight = purple_markup_get_css_property (style, "font-weight");
2770 weight = gtk_imhtml_get_css_opt (style, "font-weight:"); 2653 direction = purple_markup_get_css_property (style, "direction");
2771 2654 alignment = purple_markup_get_css_property (style, "text-align");
2772 if (!(color || family || size || background || textdec || weight)) { 2655
2656
2657 if (!(color || family || size || background || textdec || weight || direction || alignment)) {
2773 g_free(style); 2658 g_free(style);
2774 break; 2659 break;
2775 } 2660 }
2776 2661
2777 2662
2778 gtk_text_buffer_insert(imhtml->text_buffer, iter, ws, wpos); 2663 gtk_text_buffer_insert(imhtml->text_buffer, iter, ws, wpos);
2779 ws[0] = '\0'; wpos = 0; 2664 ws[0] = '\0'; wpos = 0;
2780 /* NEW_BIT (NEW_TEXT_BIT); */ 2665 /* NEW_BIT (NEW_TEXT_BIT); */
2666
2667 /* Bi-Directional text support */
2668 if(direction && (!strncasecmp(direction, "RTL", 3))) {
2669 rtl_direction = TRUE;
2670 /* insert RLE character to set direction */
2671 ws[wpos++] = 0xE2;
2672 ws[wpos++] = 0x80;
2673 ws[wpos++] = 0xAB;
2674 ws[wpos] = '\0';
2675 gtk_text_buffer_insert(imhtml->text_buffer, iter, ws, wpos);
2676 ws[0] = '\0'; wpos = 0;
2677 }
2678 if(direction)
2679 g_free(direction);
2680
2681 if(alignment && (!strncasecmp(alignment, "RIGHT", 5))) {
2682 align_right = TRUE;
2683 align_line = gtk_text_iter_get_line(iter);
2684 }
2685 if(alignment)
2686 g_free(alignment);
2781 2687
2782 font = g_new0 (GtkIMHtmlFontDetail, 1); 2688 font = g_new0 (GtkIMHtmlFontDetail, 1);
2783 if (fonts) 2689 if (fonts)
2784 oldfont = fonts->data; 2690 oldfont = fonts->data;
2785 2691
2985 2891
2986 c += smilelen; 2892 c += smilelen;
2987 pos += smilelen; 2893 pos += smilelen;
2988 wpos = 0; 2894 wpos = 0;
2989 ws[0] = 0; 2895 ws[0] = 0;
2990 } else if (*c == '&' && gtk_imhtml_is_amp_escape (c, &amp, &tlen)) { 2896 } else if (*c == '&' && (amp = purple_markup_detect_entity(c, &tlen))) {
2991 while(*amp) { 2897 while(*amp) {
2992 ws [wpos++] = *amp++; 2898 ws [wpos++] = *amp++;
2993 } 2899 }
2994 c += tlen; 2900 c += tlen;
2995 pos += tlen; 2901 pos += tlen;
3033 gtk_text_buffer_insert(imhtml->text_buffer, iter, ws, wpos); 2939 gtk_text_buffer_insert(imhtml->text_buffer, iter, ws, wpos);
3034 ws[0] = '\0'; wpos = 0; 2940 ws[0] = '\0'; wpos = 0;
3035 2941
3036 /* NEW_BIT(NEW_TEXT_BIT); */ 2942 /* NEW_BIT(NEW_TEXT_BIT); */
3037 2943
2944 if(align_right) {
2945 /* insert RLM+LRM at beginning of the line to set alignment */
2946 GtkTextIter line_iter;
2947 line_iter = *iter;
2948 gtk_text_iter_set_line(&line_iter, align_line);
2949 /* insert RLM character to set alignment */
2950 ws[wpos++] = 0xE2;
2951 ws[wpos++] = 0x80;
2952 ws[wpos++] = 0x8F;
2953
2954 if(!rtl_direction)
2955 {
2956 /* insert LRM character to set direction */
2957 /* (alignment=right and direction=LTR) */
2958 ws[wpos++] = 0xE2;
2959 ws[wpos++] = 0x80;
2960 ws[wpos++] = 0x8E;
2961 }
2962
2963 ws[wpos] = '\0';
2964 gtk_text_buffer_insert(imhtml->text_buffer, &line_iter, ws, wpos);
2965 gtk_text_buffer_get_end_iter(gtk_text_iter_get_buffer(&line_iter), iter);
2966 ws[0] = '\0';
2967 wpos = 0;
2968 }
2969
3038 while (fonts) { 2970 while (fonts) {
3039 GtkIMHtmlFontDetail *font = fonts->data; 2971 GtkIMHtmlFontDetail *font = fonts->data;
3040 fonts = g_slist_remove (fonts, font); 2972 fonts = g_slist_remove (fonts, font);
3041 g_free (font->face); 2973 g_free (font->face);
3042 g_free (font->fore); 2974 g_free (font->fore);
4624 * Finally, replace <, >, &, and " with their HTML equivalent. 4556 * Finally, replace <, >, &, and " with their HTML equivalent.
4625 */ 4557 */
4626 char *gtk_imhtml_get_markup_range(GtkIMHtml *imhtml, GtkTextIter *start, GtkTextIter *end) 4558 char *gtk_imhtml_get_markup_range(GtkIMHtml *imhtml, GtkTextIter *start, GtkTextIter *end)
4627 { 4559 {
4628 gunichar c; 4560 gunichar c;
4629 GtkTextIter iter, nextiter; 4561 GtkTextIter iter, next_iter, non_neutral_iter;
4562 gboolean is_rtl_message = FALSE;
4630 GString *str = g_string_new(""); 4563 GString *str = g_string_new("");
4631 GSList *tags, *sl; 4564 GSList *tags, *sl;
4632 GQueue *q, *r; 4565 GQueue *q, *r;
4633 GtkTextTag *tag; 4566 GtkTextTag *tag;
4634 4567
4635 q = g_queue_new(); 4568 q = g_queue_new();
4636 r = g_queue_new(); 4569 r = g_queue_new();
4637 4570
4638 4571
4639 gtk_text_iter_order(start, end); 4572 gtk_text_iter_order(start, end);
4640 nextiter = iter = *start; 4573 non_neutral_iter = next_iter = iter = *start;
4641 gtk_text_iter_forward_char(&nextiter); 4574 gtk_text_iter_forward_char(&next_iter);
4575
4576 /* Bi-directional text support */
4577 /* Get to the first non-neutral character */
4578 while((PANGO_DIRECTION_NEUTRAL == pango_unichar_direction(gtk_text_iter_get_char(&non_neutral_iter)))
4579 && gtk_text_iter_forward_char(&non_neutral_iter));
4580 if(PANGO_DIRECTION_RTL == pango_unichar_direction(gtk_text_iter_get_char(&non_neutral_iter))) {
4581 is_rtl_message = TRUE;
4582 g_string_append(str, "<SPAN style=\"direction:rtl;text-align:right;\">");
4583 }
4642 4584
4643 /* First add the tags that are already in progress (we don't care about non-printing tags)*/ 4585 /* First add the tags that are already in progress (we don't care about non-printing tags)*/
4644 tags = gtk_text_iter_get_tags(start); 4586 tags = gtk_text_iter_get_tags(start);
4645 4587
4646 for (sl = tags; sl; sl = sl->next) { 4588 for (sl = tags; sl; sl = sl->next) {
4690 4632
4691 tags = g_slist_reverse(tags); 4633 tags = g_slist_reverse(tags);
4692 for (sl = tags; sl; sl = sl->next) { 4634 for (sl = tags; sl; sl = sl->next) {
4693 tag = sl->data; 4635 tag = sl->data;
4694 /** don't worry about non-printing tags ending */ 4636 /** don't worry about non-printing tags ending */
4695 if (tag_ends_here(tag, &iter, &nextiter) && strlen(tag_to_html_end(tag)) > 0) { 4637 if (tag_ends_here(tag, &iter, &next_iter) && strlen(tag_to_html_end(tag)) > 0) {
4696 4638
4697 GtkTextTag *tmp; 4639 GtkTextTag *tmp;
4698 4640
4699 while ((tmp = g_queue_pop_tail(q)) != tag) { 4641 while ((tmp = g_queue_pop_tail(q)) != tag) {
4700 if (tmp == NULL) 4642 if (tmp == NULL)
4701 break; 4643 break;
4702 4644
4703 if (!tag_ends_here(tmp, &iter, &nextiter) && strlen(tag_to_html_end(tmp)) > 0) 4645 if (!tag_ends_here(tmp, &iter, &next_iter) && strlen(tag_to_html_end(tmp)) > 0)
4704 g_queue_push_tail(r, tmp); 4646 g_queue_push_tail(r, tmp);
4705 g_string_append(str, tag_to_html_end(GTK_TEXT_TAG(tmp))); 4647 g_string_append(str, tag_to_html_end(GTK_TEXT_TAG(tmp)));
4706 } 4648 }
4707 4649
4708 if (tmp == NULL) 4650 if (tmp == NULL)
4717 } 4659 }
4718 } 4660 }
4719 4661
4720 g_slist_free(tags); 4662 g_slist_free(tags);
4721 gtk_text_iter_forward_char(&iter); 4663 gtk_text_iter_forward_char(&iter);
4722 gtk_text_iter_forward_char(&nextiter); 4664 gtk_text_iter_forward_char(&next_iter);
4723 } 4665 }
4724 4666
4725 while ((tag = g_queue_pop_tail(q))) 4667 while ((tag = g_queue_pop_tail(q)))
4726 g_string_append(str, tag_to_html_end(GTK_TEXT_TAG(tag))); 4668 g_string_append(str, tag_to_html_end(GTK_TEXT_TAG(tag)));
4669
4670 /* Bi-directional text support - close tags */
4671 if(is_rtl_message)
4672 g_string_append(str, "</SPAN>");
4727 4673
4728 g_queue_free(q); 4674 g_queue_free(q);
4729 g_queue_free(r); 4675 g_queue_free(r);
4730 return g_string_free(str, FALSE); 4676 return g_string_free(str, FALSE);
4731 } 4677 }