4 * Copyright (C) 2000, Eric Warmenhoven <warmenhoven@yahoo.com>
6 * This program is free software; you can redistribute it and/or modify
7 * 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.
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.
16 * You should have received a copy of the GNU General Public License
17 * along with this program; if not, write to the Free Software
18 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
25 #include "gtkimhtml.h"
27 #include <glib/gerror.h>
28 #include <gdk/gdkkeysyms.h>
34 #ifdef HAVE_LANGINFO_CODESET
41 # define _(x) gettext(x)
43 # define N_(String) gettext_noop (String)
45 # define N_(String) (String)
48 # define N_(String) (String)
52 #include <pango/pango-font.h>
54 /* GTK+ < 2.2.2 hack, see ui.h for details. */
55 #ifndef GTK_WRAP_WORD_CHAR
56 #define GTK_WRAP_WORD_CHAR GTK_WRAP_WORD
59 #define TOOLTIP_TIMEOUT 500
61 static gboolean
gtk_motion_event_notify(GtkWidget
*imhtml
, GdkEventMotion
*event
, gpointer user_data
);
62 static gboolean
gtk_leave_event_notify(GtkWidget
*imhtml
, GdkEventCrossing
*event
, gpointer user_data
);
64 static gboolean
gtk_size_allocate_cb(GtkIMHtml
*widget
, GtkAllocation
*alloc
, gpointer user_data
);
66 static gboolean
gaim_im_image_clicked(GtkWidget
*w
, GdkEvent
*event
, gaim_im_image
*image
);
68 static gint
gtk_imhtml_tip (gpointer data
);
71 /* POINT_SIZE converts from AIM font sizes to point sizes. It probably should be redone in such a
72 * way that it base the sizes off the default font size rather than using arbitrary font sizes. */
73 #define MAX_FONT_SIZE 7
74 #define POINT_SIZE(x) (_point_sizes [MIN ((x), MAX_FONT_SIZE) - 1])
75 static gint _point_sizes
[] = { 8, 10, 12, 14, 20, 30, 40 };
77 /* The four elements present in a <FONT> tag contained in a struct */
78 typedef struct _FontDetail FontDetail
;
87 struct _GtkSmileyTree
{
89 GtkSmileyTree
**children
;
90 GtkIMHtmlSmiley
*image
;
94 gtk_smiley_tree_new ()
96 return g_new0 (GtkSmileyTree
, 1);
100 gtk_smiley_tree_insert (GtkSmileyTree
*tree
,
101 GtkIMHtmlSmiley
*smiley
)
103 GtkSmileyTree
*t
= tree
;
104 const gchar
*x
= smiley
->smile
;
114 t
->values
= g_string_new ("");
116 pos
= strchr (t
->values
->str
, *x
);
118 t
->values
= g_string_append_c (t
->values
, *x
);
119 index
= t
->values
->len
- 1;
120 t
->children
= g_realloc (t
->children
, t
->values
->len
* sizeof (GtkSmileyTree
*));
121 t
->children
[index
] = g_new0 (GtkSmileyTree
, 1);
123 index
= (int) pos
- (int) t
->values
->str
;
125 t
= t
->children
[index
];
134 void gtk_smiley_tree_destroy (GtkSmileyTree
*tree
)
136 GSList
*list
= g_slist_append (NULL
, tree
);
139 GtkSmileyTree
*t
= list
->data
;
141 list
= g_slist_remove(list
, t
);
143 for (i
= 0; i
< t
->values
->len
; i
++)
144 list
= g_slist_append (list
, t
->children
[i
]);
145 g_string_free (t
->values
, TRUE
);
146 g_free (t
->children
);
153 static GtkTextViewClass
*parent_class
= NULL
;
156 /* GtkIMHtml has one signal--URL_CLICKED */
161 static guint signals
[LAST_SIGNAL
] = { 0 };
164 gtk_imhtml_finalize (GObject
*object
)
166 GtkIMHtml
*imhtml
= GTK_IMHTML(object
);
169 g_hash_table_destroy(imhtml
->smiley_data
);
170 gtk_smiley_tree_destroy(imhtml
->default_smilies
);
171 gdk_cursor_unref(imhtml
->hand_cursor
);
172 gdk_cursor_unref(imhtml
->arrow_cursor
);
173 if(imhtml
->tip_window
){
174 gtk_widget_destroy(imhtml
->tip_window
);
176 if(imhtml
->tip_timer
)
177 gtk_timeout_remove(imhtml
->tip_timer
);
179 for(scalables
= imhtml
->scalables
; scalables
; scalables
= scalables
->next
) {
180 GtkIMHtmlScalable
*scale
= GTK_IMHTML_SCALABLE(scalables
->data
);
184 g_list_free(imhtml
->scalables
);
185 G_OBJECT_CLASS(parent_class
)->finalize (object
);
188 /* Boring GTK stuff */
189 static void gtk_imhtml_class_init (GtkIMHtmlClass
*class)
191 GtkObjectClass
*object_class
;
192 GObjectClass
*gobject_class
;
193 object_class
= (GtkObjectClass
*) class;
194 gobject_class
= (GObjectClass
*) class;
195 parent_class
= gtk_type_class(GTK_TYPE_TEXT_VIEW
);
196 signals
[URL_CLICKED
] = g_signal_new("url_clicked",
197 G_TYPE_FROM_CLASS(gobject_class
),
199 G_STRUCT_OFFSET(GtkIMHtmlClass
, url_clicked
),
202 g_cclosure_marshal_VOID__POINTER
,
205 gobject_class
->finalize
= gtk_imhtml_finalize
;
208 static void gtk_imhtml_init (GtkIMHtml
*imhtml
)
211 imhtml
->text_buffer
= gtk_text_buffer_new(NULL
);
212 gtk_text_buffer_get_end_iter (imhtml
->text_buffer
, &iter
);
213 imhtml
->end
= gtk_text_buffer_create_mark(imhtml
->text_buffer
, NULL
, &iter
, FALSE
);
214 gtk_text_view_set_buffer(GTK_TEXT_VIEW(imhtml
), imhtml
->text_buffer
);
215 gtk_text_view_set_wrap_mode(GTK_TEXT_VIEW(imhtml
), GTK_WRAP_WORD_CHAR
);
216 gtk_text_view_set_editable(GTK_TEXT_VIEW(imhtml
), FALSE
);
217 gtk_text_view_set_pixels_below_lines(GTK_TEXT_VIEW(imhtml
), 5);
218 gtk_text_view_set_cursor_visible(GTK_TEXT_VIEW(imhtml
), FALSE
);
219 /*gtk_text_view_set_justification(GTK_TEXT_VIEW(imhtml), GTK_JUSTIFY_FILL);*/
221 /* These tags will be used often and can be reused--we create them on init and then apply them by name
222 * other tags (color, size, face, etc.) will have to be created and applied dynamically */
223 gtk_text_buffer_create_tag(imhtml
->text_buffer
, "BOLD", "weight", PANGO_WEIGHT_BOLD
, NULL
);
224 gtk_text_buffer_create_tag(imhtml
->text_buffer
, "ITALICS", "style", PANGO_STYLE_ITALIC
, NULL
);
225 gtk_text_buffer_create_tag(imhtml
->text_buffer
, "UNDERLINE", "underline", PANGO_UNDERLINE_SINGLE
, NULL
);
226 gtk_text_buffer_create_tag(imhtml
->text_buffer
, "STRIKE", "strikethrough", TRUE
, NULL
);
227 gtk_text_buffer_create_tag(imhtml
->text_buffer
, "SUB", "rise", -5000, NULL
);
228 gtk_text_buffer_create_tag(imhtml
->text_buffer
, "SUP", "rise", 5000, NULL
);
229 gtk_text_buffer_create_tag(imhtml
->text_buffer
, "PRE", "family", "Monospace", NULL
);
231 /* When hovering over a link, we show the hand cursor--elsewhere we show the plain ol' pointer cursor */
232 imhtml
->hand_cursor
= gdk_cursor_new (GDK_HAND2
);
233 imhtml
->arrow_cursor
= gdk_cursor_new (GDK_LEFT_PTR
);
235 imhtml
->show_smileys
= TRUE
;
236 imhtml
->show_comments
= TRUE
;
238 imhtml
->smiley_data
= g_hash_table_new_full(g_str_hash
, g_str_equal
,
239 g_free
, (GDestroyNotify
)gtk_smiley_tree_destroy
);
240 imhtml
->default_smilies
= gtk_smiley_tree_new();
242 g_signal_connect(G_OBJECT(imhtml
), "size-allocate", G_CALLBACK(gtk_size_allocate_cb
), NULL
);
243 g_signal_connect(G_OBJECT(imhtml
), "motion-notify-event", G_CALLBACK(gtk_motion_event_notify
), NULL
);
244 g_signal_connect(G_OBJECT(imhtml
), "leave-notify-event", G_CALLBACK(gtk_leave_event_notify
), NULL
);
245 gtk_widget_add_events(GTK_WIDGET(imhtml
), GDK_LEAVE_NOTIFY_MASK
);
248 imhtml
->tip_timer
= 0;
249 imhtml
->tip_window
= NULL
;
251 imhtml
->scalables
= NULL
;
254 GtkWidget
*gtk_imhtml_new(void *a
, void *b
)
256 return GTK_WIDGET(g_object_new(gtk_imhtml_get_type(), NULL
));
259 GType
gtk_imhtml_get_type()
261 static GType imhtml_type
= 0;
264 static const GTypeInfo imhtml_info
= {
265 sizeof(GtkIMHtmlClass
),
268 (GClassInitFunc
) gtk_imhtml_class_init
,
273 (GInstanceInitFunc
) gtk_imhtml_init
276 imhtml_type
= g_type_register_static(gtk_text_view_get_type(),
277 "GtkIMHtml", &imhtml_info
, 0);
288 static void url_open(GtkWidget
*w
, struct url_data
*data
) {
291 g_signal_emit(data
->object
, signals
[URL_CLICKED
], 0, data
->url
);
293 g_object_unref(data
->object
);
297 static void url_copy(GtkWidget
*w
, gchar
*url
) {
298 GtkClipboard
*clipboard
;
300 clipboard
= gtk_clipboard_get(GDK_SELECTION_CLIPBOARD
);
301 gtk_clipboard_set_text(clipboard
, url
, -1);
304 /* The callback for an event on a link tag. */
305 gboolean
tag_event(GtkTextTag
*tag
, GObject
*imhtml
, GdkEvent
*event
, GtkTextIter
*arg2
, char *url
) {
306 GdkEventButton
*event_button
= (GdkEventButton
*) event
;
308 if (event
->type
== GDK_BUTTON_RELEASE
) {
309 if (event_button
->button
== 1) {
310 GtkTextIter start
, end
;
311 /* we shouldn't open a URL if the user has selected something: */
312 gtk_text_buffer_get_selection_bounds(
313 gtk_text_iter_get_buffer(arg2
), &start
, &end
);
314 if(gtk_text_iter_get_offset(&start
) !=
315 gtk_text_iter_get_offset(&end
))
318 /* A link was clicked--we emit the "url_clicked" signal
319 * with the URL as the argument */
320 g_signal_emit(imhtml
, signals
[URL_CLICKED
], 0, url
);
322 } else if(event_button
->button
== 3) {
323 GtkWidget
*img
, *item
, *menu
;
324 struct url_data
*tempdata
= g_new(struct url_data
, 1);
325 tempdata
->object
= g_object_ref(imhtml
);
326 tempdata
->url
= g_strdup(url
);
328 /* Don't want the tooltip around if user right-clicked on link */
329 if (GTK_IMHTML(imhtml
)->tip_window
) {
330 gtk_widget_destroy(GTK_IMHTML(imhtml
)->tip_window
);
331 GTK_IMHTML(imhtml
)->tip_window
= NULL
;
333 if (GTK_IMHTML(imhtml
)->tip_timer
) {
334 g_source_remove(GTK_IMHTML(imhtml
)->tip_timer
);
335 GTK_IMHTML(imhtml
)->tip_timer
= 0;
337 gdk_window_set_cursor(event_button
->window
, GTK_IMHTML(imhtml
)->arrow_cursor
);
338 menu
= gtk_menu_new();
340 /* buttons and such */
341 img
= gtk_image_new_from_stock(GTK_STOCK_COPY
, GTK_ICON_SIZE_MENU
);
342 item
= gtk_image_menu_item_new_with_mnemonic(_("_Copy Link Location"));
343 gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(item
), img
);
344 g_signal_connect(G_OBJECT(item
), "activate", G_CALLBACK(url_copy
),
346 gtk_menu_shell_append(GTK_MENU_SHELL(menu
), item
);
348 img
= gtk_image_new_from_stock(GTK_STOCK_JUMP_TO
, GTK_ICON_SIZE_MENU
);
349 item
= gtk_image_menu_item_new_with_mnemonic(_("_Open Link in Browser"));
350 gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(item
), img
);
351 g_signal_connect(G_OBJECT(item
), "activate", G_CALLBACK(url_open
),
353 gtk_menu_shell_append(GTK_MENU_SHELL(menu
), item
);
355 gtk_widget_show_all(menu
);
356 gtk_menu_popup(GTK_MENU(menu
), NULL
, NULL
, NULL
, NULL
,
357 event_button
->button
, event_button
->time
);
362 if(event
->type
== GDK_BUTTON_PRESS
&& event_button
->button
== 3)
363 return TRUE
; /* Clicking the right mouse button on a link shouldn't
364 be caught by the regular GtkTextView menu */
366 return FALSE
; /* Let clicks go through if we didn't catch anything */
369 gboolean
gtk_motion_event_notify(GtkWidget
*imhtml
, GdkEventMotion
*event
, gpointer data
)
372 GdkWindow
*win
= event
->window
;
375 GSList
*tags
= NULL
, *templist
= NULL
;
376 gdk_window_get_pointer(GTK_WIDGET(imhtml
)->window
, NULL
, NULL
, NULL
);
377 gtk_text_view_window_to_buffer_coords(GTK_TEXT_VIEW(imhtml
), GTK_TEXT_WINDOW_WIDGET
,
378 event
->x
, event
->y
, &x
, &y
);
379 gtk_text_view_get_iter_at_location(GTK_TEXT_VIEW(imhtml
), &iter
, x
, y
);
380 tags
= gtk_text_iter_get_tags(&iter
);
384 GtkTextTag
*tag
= templist
->data
;
385 tip
= g_object_get_data(G_OBJECT(tag
), "link_url");
388 templist
= templist
->next
;
391 if (GTK_IMHTML(imhtml
)->tip
) {
392 if ((tip
== GTK_IMHTML(imhtml
)->tip
)) {
395 /* We've left the cell. Remove the timeout and create a new one below */
396 if (GTK_IMHTML(imhtml
)->tip_window
) {
397 gtk_widget_destroy(GTK_IMHTML(imhtml
)->tip_window
);
398 GTK_IMHTML(imhtml
)->tip_window
= NULL
;
400 gdk_window_set_cursor(win
, GTK_IMHTML(imhtml
)->arrow_cursor
);
401 if (GTK_IMHTML(imhtml
)->tip_timer
)
402 g_source_remove(GTK_IMHTML(imhtml
)->tip_timer
);
403 GTK_IMHTML(imhtml
)->tip_timer
= 0;
407 gdk_window_set_cursor(win
, GTK_IMHTML(imhtml
)->hand_cursor
);
408 GTK_IMHTML(imhtml
)->tip_timer
= g_timeout_add (TOOLTIP_TIMEOUT
,
409 gtk_imhtml_tip
, imhtml
);
412 GTK_IMHTML(imhtml
)->tip
= tip
;
417 gboolean
gtk_leave_event_notify(GtkWidget
*imhtml
, GdkEventCrossing
*event
, gpointer data
)
419 /* when leaving the widget, clear any current & pending tooltips and restore the cursor */
420 if (GTK_IMHTML(imhtml
)->tip_window
) {
421 gtk_widget_destroy(GTK_IMHTML(imhtml
)->tip_window
);
422 GTK_IMHTML(imhtml
)->tip_window
= NULL
;
424 if (GTK_IMHTML(imhtml
)->tip_timer
) {
425 g_source_remove(GTK_IMHTML(imhtml
)->tip_timer
);
426 GTK_IMHTML(imhtml
)->tip_timer
= 0;
428 gdk_window_set_cursor(event
->window
, GTK_IMHTML(imhtml
)->arrow_cursor
);
430 /* propogate the event normally */
434 /* this isn't used yet
436 gtk_smiley_tree_remove (GtkSmileyTree *tree,
437 GtkIMHtmlSmiley *smiley)
439 GtkSmileyTree *t = tree;
440 const gchar *x = smiley->smile;
449 pos = strchr (t->values->str, *x);
451 t = t->children [(int) pos - (int) t->values->str];
466 gtk_smiley_tree_lookup (GtkSmileyTree
*tree
,
469 GtkSmileyTree
*t
= tree
;
470 const gchar
*x
= text
;
479 pos
= strchr (t
->values
->str
, *x
);
481 t
= t
->children
[(int) pos
- (int) t
->values
->str
];
495 gtk_imhtml_associate_smiley (GtkIMHtml
*imhtml
,
497 GtkIMHtmlSmiley
*smiley
)
500 g_return_if_fail (imhtml
!= NULL
);
501 g_return_if_fail (GTK_IS_IMHTML (imhtml
));
504 tree
= imhtml
->default_smilies
;
505 else if ((tree
= g_hash_table_lookup(imhtml
->smiley_data
, sml
))) {
507 tree
= gtk_smiley_tree_new();
508 g_hash_table_insert(imhtml
->smiley_data
, g_strdup(sml
), tree
);
511 gtk_smiley_tree_insert (tree
, smiley
);
515 gtk_imhtml_is_smiley (GtkIMHtml
*imhtml
,
530 tree
= imhtml
->default_smilies
;
532 tree
= g_hash_table_lookup(imhtml
->smiley_data
, sml
);
537 *len
= gtk_smiley_tree_lookup (tree
, text
);
542 gtk_smiley_tree_image (GtkIMHtml
*imhtml
,
547 const gchar
*x
= text
;
549 t
= imhtml
->default_smilies
;
551 t
= g_hash_table_lookup(imhtml
->smiley_data
, sml
);
555 return sml
? gtk_smiley_tree_image(imhtml
, NULL
, text
) : NULL
;
561 return sml
? gtk_smiley_tree_image(imhtml
, NULL
, text
) : NULL
;
564 pos
= strchr (t
->values
->str
, *x
);
566 t
= t
->children
[(int) pos
- (int) t
->values
->str
];
568 return sml
? gtk_smiley_tree_image(imhtml
, NULL
, text
) : NULL
;
574 t
->image
->icon
= gdk_pixbuf_new_from_file(t
->image
->file
, NULL
);
576 return t
->image
->icon
;
578 #define VALID_TAG(x) if (!g_ascii_strncasecmp (string, x ">", strlen (x ">"))) { \
579 *tag = g_strndup (string, strlen (x)); \
580 *len = strlen (x) + 1; \
585 #define VALID_OPT_TAG(x) if (!g_ascii_strncasecmp (string, x " ", strlen (x " "))) { \
586 const gchar *c = string + strlen (x " "); \
588 gboolean quote = FALSE; \
590 if (*c == '"' || *c == '\'') { \
591 if (quote && (*c == e)) \
597 } else if (!quote && (*c == '>')) \
602 *tag = g_strndup (string, c - string); \
603 *len = c - string + 1; \
611 gtk_imhtml_is_amp_escape (const gchar
*string
,
615 g_return_val_if_fail (string
!= NULL
, FALSE
);
616 g_return_val_if_fail (replace
!= NULL
, FALSE
);
617 g_return_val_if_fail (length
!= NULL
, FALSE
);
619 if (!g_ascii_strncasecmp (string
, "&", 5)) {
622 } else if (!g_ascii_strncasecmp (string
, "<", 4)) {
625 } else if (!g_ascii_strncasecmp (string
, ">", 4)) {
628 } else if (!g_ascii_strncasecmp (string
, " ", 6)) {
631 } else if (!g_ascii_strncasecmp (string
, "©", 6)) {
632 *replace
= '©'; /* was: '©' */
634 } else if (!g_ascii_strncasecmp (string
, """, 6)) {
637 } else if (!g_ascii_strncasecmp (string
, "®", 5)) {
638 *replace
= '®'; /* was: '®' */
640 } else if (!g_ascii_strncasecmp (string
, "'", 6)) {
643 } else if (*(string
+ 1) == '#') {
645 if ((sscanf (string
, "&#%u;", £
) == 1) && pound
!= 0) {
646 if (*(string
+ 3 + (gint
)log10 (pound
)) != ';')
648 *replace
= (gchar
)pound
;
650 while (isdigit ((gint
) string
[*length
])) (*length
)++;
651 if (string
[*length
] == ';') (*length
)++;
663 gtk_imhtml_is_tag (const gchar
*string
,
670 if (!strchr (string
, '>'))
678 VALID_TAG ("ITALIC");
680 VALID_TAG ("/ITALIC");
682 VALID_TAG ("UNDERLINE");
684 VALID_TAG ("/UNDERLINE");
686 VALID_TAG ("STRIKE");
688 VALID_TAG ("/STRIKE");
696 VALID_TAG ("/TITLE");
712 VALID_TAG ("BINARY");
713 VALID_TAG ("/BINARY");
715 VALID_OPT_TAG ("HR");
716 VALID_OPT_TAG ("FONT");
717 VALID_OPT_TAG ("BODY");
719 VALID_OPT_TAG ("IMG");
721 VALID_OPT_TAG ("H3");
722 VALID_OPT_TAG ("HTML");
728 VALID_TAG ("STRONG");
729 VALID_TAG ("/STRONG");
731 VALID_OPT_TAG ("SPAN");
734 if (!g_ascii_strncasecmp(string
, "!--", strlen ("!--"))) {
735 gchar
*e
= strstr (string
+ strlen("!--"), "-->");
737 *len
= e
- string
+ strlen ("-->");
738 *tag
= g_strndup (string
+ strlen ("!--"), *len
- strlen ("!---->"));
747 gtk_imhtml_get_html_opt (gchar
*tag
,
753 while (g_ascii_strncasecmp (t
, opt
, strlen (opt
))) {
754 gboolean quote
= FALSE
;
755 if (*t
== '\0') break;
756 while (*t
&& !((*t
== ' ') && !quote
)) {
761 while (*t
&& (*t
== ' ')) t
++;
764 if (!g_ascii_strncasecmp (t
, opt
, strlen (opt
))) {
770 if ((*t
== '\"') || (*t
== '\'')) {
772 while (*e
&& (*e
!= *(t
- 1))) e
++;
776 return g_strndup (a
, e
- a
);
779 while (*e
&& !isspace ((gint
) *e
)) e
++;
780 return g_strndup (a
, e
- a
);
786 #define NEW_TEXT_BIT 0
787 #define NEW_COMMENT_BIT 2
788 #define NEW_SCALABLE_BIT 1
789 #define NEW_BIT(x) ws [wpos] = '\0'; \
790 mark2 = gtk_text_buffer_create_mark(imhtml->text_buffer, NULL, &iter, TRUE); \
791 gtk_text_buffer_insert(imhtml->text_buffer, &iter, ws, -1); \
792 gtk_text_buffer_get_end_iter(imhtml->text_buffer, &iter); \
793 gtk_text_buffer_get_iter_at_mark(imhtml->text_buffer, &siter, mark2); \
794 gtk_text_buffer_delete_mark(imhtml->text_buffer, mark2); \
796 gtk_text_buffer_apply_tag_by_name(imhtml->text_buffer, "BOLD", &siter, &iter); \
798 gtk_text_buffer_apply_tag_by_name(imhtml->text_buffer, "ITALICS", &siter, &iter); \
800 gtk_text_buffer_apply_tag_by_name(imhtml->text_buffer, "UNDERLINE", &siter, &iter); \
802 gtk_text_buffer_apply_tag_by_name(imhtml->text_buffer, "STRIKE", &siter, &iter); \
804 gtk_text_buffer_apply_tag_by_name(imhtml->text_buffer, "SUB", &siter, &iter); \
806 gtk_text_buffer_apply_tag_by_name(imhtml->text_buffer, "SUP", &siter, &iter); \
808 gtk_text_buffer_apply_tag_by_name(imhtml->text_buffer, "PRE", &siter, &iter); \
810 texttag = gtk_text_buffer_create_tag(imhtml->text_buffer, NULL, "background", bg, NULL); \
811 gtk_text_buffer_apply_tag(imhtml->text_buffer, texttag, &siter, &iter); \
814 FontDetail *fd = fonts->data; \
816 texttag = gtk_text_buffer_create_tag(imhtml->text_buffer, NULL, "foreground", fd->fore, NULL); \
817 gtk_text_buffer_apply_tag(imhtml->text_buffer, texttag, &siter, &iter); \
820 texttag = gtk_text_buffer_create_tag(imhtml->text_buffer, NULL, "background", fd->back, NULL); \
821 gtk_text_buffer_apply_tag(imhtml->text_buffer, texttag, &siter, &iter); \
824 texttag = gtk_text_buffer_create_tag(imhtml->text_buffer, NULL, "font", fd->face, NULL); \
825 gtk_text_buffer_apply_tag(imhtml->text_buffer, texttag, &siter, &iter); \
828 texttag = gtk_text_buffer_create_tag(imhtml->text_buffer, NULL, "size-points", (double)POINT_SIZE(fd->size), NULL); \
829 gtk_text_buffer_apply_tag(imhtml->text_buffer, texttag, &siter, &iter); \
833 texttag = gtk_text_buffer_create_tag(imhtml->text_buffer, NULL, "foreground", "blue", "underline", PANGO_UNDERLINE_SINGLE, NULL); \
834 g_signal_connect(G_OBJECT(texttag), "event", G_CALLBACK(tag_event), g_strdup(url)); \
835 gtk_text_buffer_apply_tag(imhtml->text_buffer, texttag, &siter, &iter); \
836 texttag = gtk_text_buffer_create_tag(imhtml->text_buffer, NULL, NULL); \
837 g_object_set_data(G_OBJECT(texttag), "link_url", g_strdup(url)); \
838 gtk_text_buffer_apply_tag(imhtml->text_buffer, texttag, &siter, &iter); \
842 gtk_text_buffer_get_end_iter(imhtml->text_buffer, &iter); \
843 if (x == NEW_SCALABLE_BIT) { \
845 gtk_text_view_get_visible_rect(GTK_TEXT_VIEW(imhtml), &rect); \
846 scalable->add_to(scalable, imhtml, &iter); \
847 scalable->scale(scalable, rect.width, rect.height); \
848 imhtml->scalables = g_list_append(imhtml->scalables, scalable); \
849 gtk_text_buffer_get_end_iter(imhtml->text_buffer, &iter); \
854 GString* gtk_imhtml_append_text (GtkIMHtml *imhtml,
857 GtkIMHtmlOptions options
)
861 GtkTextIter iter
, siter
;
862 GtkTextMark
*mark
, *mark2
;
868 gint tlen
, smilelen
, wpos
=0;
882 GSList
*fonts
= NULL
;
887 GtkIMHtmlScalable
*scalable
= NULL
;
889 g_return_val_if_fail (imhtml
!= NULL
, NULL
);
890 g_return_val_if_fail (GTK_IS_IMHTML (imhtml
), NULL
);
891 g_return_val_if_fail (text
!= NULL
, NULL
);
892 g_return_val_if_fail (len
!= 0, NULL
);
897 ws
= g_malloc(len
+ 1);
900 if (options
& GTK_IMHTML_RETURN_LOG
)
901 str
= g_string_new("");
903 gtk_text_buffer_get_end_iter(imhtml
->text_buffer
, &iter
);
904 mark
= gtk_text_buffer_create_mark (imhtml
->text_buffer
, NULL
, &iter
, /* right grav */ FALSE
);
906 gtk_text_view_get_visible_rect(GTK_TEXT_VIEW(imhtml
), &rect
);
907 gtk_text_view_get_line_yrange(GTK_TEXT_VIEW(imhtml
), &iter
, &y
, &height
);
909 if(((y
+ height
) - (rect
.y
+ rect
.height
)) > height
910 && gtk_text_buffer_get_char_count(imhtml
->text_buffer
)){
911 options
|= GTK_IMHTML_NO_SCROLL
;
915 if (*c
== '<' && gtk_imhtml_is_tag (c
+ 1, &tag
, &tlen
, &type
)) {
922 case 54: /* STRONG */
923 NEW_BIT (NEW_TEXT_BIT
);
928 case 55: /* /STRONG */
929 NEW_BIT (NEW_TEXT_BIT
);
936 NEW_BIT (NEW_TEXT_BIT
);
940 case 8: /* /ITALIC */
942 NEW_BIT (NEW_TEXT_BIT
);
947 case 10: /* UNDERLINE */
948 NEW_BIT (NEW_TEXT_BIT
);
952 case 12: /* /UNDERLINE */
953 NEW_BIT (NEW_TEXT_BIT
);
958 case 14: /* STRIKE */
959 NEW_BIT (NEW_TEXT_BIT
);
963 case 16: /* /STRIKE */
964 NEW_BIT (NEW_TEXT_BIT
);
969 NEW_BIT (NEW_TEXT_BIT
);
973 NEW_BIT (NEW_TEXT_BIT
);
978 NEW_BIT (NEW_TEXT_BIT
);
982 NEW_BIT (NEW_TEXT_BIT
);
987 NEW_BIT (NEW_TEXT_BIT
);
991 NEW_BIT (NEW_TEXT_BIT
);
996 NEW_BIT (NEW_TEXT_BIT
);
999 case 24: /* /TITLE */
1001 if (options
& GTK_IMHTML_NO_TITLE
) {
1011 NEW_BIT (NEW_TEXT_BIT
);
1014 case 42: /* HR (opt) */
1016 scalable
= gaim_hr_new();
1017 NEW_BIT(NEW_SCALABLE_BIT
);
1020 case 27: /* /FONT */
1022 FontDetail
*font
= fonts
->data
;
1023 NEW_BIT (NEW_TEXT_BIT
);
1024 fonts
= g_slist_remove (fonts
, font
);
1026 g_free (font
->face
);
1028 g_free (font
->fore
);
1030 g_free (font
->back
);
1038 NEW_BIT(NEW_TEXT_BIT
);
1048 case 34: /* /HTML */
1050 case 36: /* /BODY */
1053 case 39: /* /HEAD */
1055 case 40: /* BINARY */
1056 NEW_BIT (NEW_TEXT_BIT
);
1057 while (pos
< len
&& g_ascii_strncasecmp("</BINARY>", c
, strlen("</BINARY>"))) {
1061 c
= c
- tlen
; /* Because it will add this later */
1063 case 41: /* /BINARY */
1065 case 43: /* FONT (opt) */
1067 gchar
*color
, *back
, *face
, *size
, *sml
;
1068 FontDetail
*font
, *oldfont
= NULL
;
1069 color
= gtk_imhtml_get_html_opt (tag
, "COLOR=");
1070 back
= gtk_imhtml_get_html_opt (tag
, "BACK=");
1071 face
= gtk_imhtml_get_html_opt (tag
, "FACE=");
1072 size
= gtk_imhtml_get_html_opt (tag
, "SIZE=");
1073 sml
= gtk_imhtml_get_html_opt (tag
, "SML=");
1074 if (!(color
|| back
|| face
|| size
|| sml
))
1077 NEW_BIT (NEW_TEXT_BIT
);
1079 font
= g_new0 (FontDetail
, 1);
1081 oldfont
= fonts
->data
;
1083 if (color
&& !(options
& GTK_IMHTML_NO_COLOURS
))
1085 else if (oldfont
&& oldfont
->fore
)
1086 font
->fore
= g_strdup(oldfont
->fore
);
1088 if (back
&& !(options
& GTK_IMHTML_NO_COLOURS
))
1090 else if (oldfont
&& oldfont
->back
)
1091 font
->back
= g_strdup(oldfont
->back
);
1093 if (face
&& !(options
& GTK_IMHTML_NO_FONTS
))
1095 else if (oldfont
&& oldfont
->face
)
1096 font
->face
= g_strdup(oldfont
->face
);
1097 if (font
->face
&& (atoi(font
->face
) > 100)) {
1099 font
->face
= g_strdup("100");
1104 else if (oldfont
&& oldfont
->sml
)
1105 font
->sml
= g_strdup(oldfont
->sml
);
1107 if (size
&& !(options
& GTK_IMHTML_NO_SIZES
)) {
1109 sscanf (size
+ 1, "%hd", &font
->size
);
1111 } else if (*size
== '-') {
1112 sscanf (size
+ 1, "%hd", &font
->size
);
1113 font
->size
= MAX (0, 3 - font
->size
);
1114 } else if (isdigit (*size
)) {
1115 sscanf (size
, "%hd", &font
->size
);
1118 font
->size
= oldfont
->size
;
1120 fonts
= g_slist_prepend (fonts
, font
);
1123 case 44: /* BODY (opt) */
1124 if (!(options
& GTK_IMHTML_NO_COLOURS
)) {
1125 char *bgcolor
= gtk_imhtml_get_html_opt (tag
, "BGCOLOR=");
1127 NEW_BIT(NEW_TEXT_BIT
);
1134 case 45: /* A (opt) */
1136 gchar
*href
= gtk_imhtml_get_html_opt (tag
, "HREF=");
1138 NEW_BIT (NEW_TEXT_BIT
);
1145 case 46: /* IMG (opt) */
1147 gchar
*src
= gtk_imhtml_get_html_opt (tag
, "SRC=");
1148 gchar
*id
= gtk_imhtml_get_html_opt (tag
, "ID=");
1149 gchar
*datasize
= gtk_imhtml_get_html_opt (tag
, "DATASIZE=");
1150 gint im_len
= datasize
?atoi(datasize
):0;
1152 if (src
&& id
&& im_len
&& im_len
<= len
- pos
) {
1153 /* This is an embedded IM image, or is it? */
1155 const char *alltext
;
1156 guchar
*imagedata
= NULL
;
1158 GdkPixbufLoader
*load
;
1159 GdkPixbuf
*imagepb
= NULL
;
1160 GError
*error
= NULL
;
1162 tmp
= g_strdup_printf("<DATA ID=\"%s\" SIZE=\"%s\">", id
, datasize
);
1163 alltext
= strstr(c
, tmp
);
1164 imagedata
= g_memdup(alltext
+ strlen(tmp
), im_len
);
1168 load
= gdk_pixbuf_loader_new();
1169 if (!gdk_pixbuf_loader_write(load
, imagedata
, im_len
, &error
)){
1170 fprintf(stderr
, "IM Image corrupted or unreadable.: %s\n", error
->message
);
1172 imagepb
= gdk_pixbuf_loader_get_pixbuf(load
);
1174 scalable
= gaim_im_image_new(imagepb
, g_strdup(src
));
1175 NEW_BIT(NEW_SCALABLE_BIT
);
1176 g_object_unref(imagepb
);
1180 gdk_pixbuf_loader_close(load
, NULL
);
1194 case 47: /* P (opt) */
1195 case 48: /* H3 (opt) */
1196 case 49: /* HTML (opt) */
1198 case 51: /* /CITE */
1200 case 57: /* /SPAN */
1202 case 58: /* comment */
1203 NEW_BIT (NEW_TEXT_BIT
);
1204 if (imhtml
->show_comments
)
1205 wpos
= g_snprintf (ws
, len
, "%s", tag
);
1206 NEW_BIT (NEW_COMMENT_BIT
);
1214 g_free(tag
); /* This was allocated back in VALID_TAG() */
1215 } else if (*c
== '&' && gtk_imhtml_is_amp_escape (c
, &
, &tlen
)) {
1219 } else if (*c
== '\n') {
1220 if (!(options
& GTK_IMHTML_NO_NEWLINE
)) {
1223 NEW_BIT (NEW_TEXT_BIT
);
1227 } else if (imhtml
->show_smileys
&& (gtk_imhtml_is_smiley (imhtml
, fonts
, c
, &smilelen
) || gtk_imhtml_is_smiley(imhtml
, NULL
, c
, &smilelen
))) {
1234 NEW_BIT (NEW_TEXT_BIT
);
1235 wpos
= g_snprintf (ws
, smilelen
+ 1, "%s", c
);
1236 gtk_text_buffer_insert_pixbuf(imhtml
->text_buffer
, &iter
, gtk_smiley_tree_image (imhtml
, sml
, ws
));
1249 NEW_BIT(NEW_TEXT_BIT
);
1253 str
= g_string_append (str
, "</A>");
1257 FontDetail
*font
= fonts
->data
;
1258 fonts
= g_slist_remove (fonts
, font
);
1260 g_free (font
->face
);
1262 g_free (font
->fore
);
1264 g_free (font
->back
);
1269 str
= g_string_append (str
, "</FONT>");
1274 str
= g_string_append (str
, "</B>");
1278 str
= g_string_append (str
, "</I>");
1282 str
= g_string_append (str
, "</U>");
1286 str
= g_string_append (str
, "</S>");
1290 str
= g_string_append (str
, "</SUB>");
1294 str
= g_string_append (str
, "</SUP>");
1298 str
= g_string_append (str
, "</TITLE>");
1302 str
= g_string_append (str
, "</PRE>");
1309 if (!(options
& GTK_IMHTML_NO_SCROLL
))
1310 gtk_text_view_scroll_to_mark (GTK_TEXT_VIEW (imhtml
), mark
,
1312 gtk_text_buffer_delete_mark (imhtml
->text_buffer
, mark
);
1316 void gtk_imhtml_remove_smileys(GtkIMHtml
*imhtml
)
1318 g_hash_table_destroy(imhtml
->smiley_data
);
1319 gtk_smiley_tree_destroy(imhtml
->default_smilies
);
1320 imhtml
->smiley_data
= g_hash_table_new_full(g_str_hash
, g_str_equal
,
1321 g_free
, (GDestroyNotify
)gtk_smiley_tree_destroy
);
1322 imhtml
->default_smilies
= gtk_smiley_tree_new();
1324 void gtk_imhtml_show_smileys (GtkIMHtml
*imhtml
,
1327 imhtml
->show_smileys
= show
;
1330 void gtk_imhtml_show_comments (GtkIMHtml
*imhtml
,
1333 imhtml
->show_comments
= show
;
1337 gtk_imhtml_clear (GtkIMHtml
*imhtml
)
1339 GtkTextIter start
, end
;
1341 gtk_text_buffer_get_start_iter(imhtml
->text_buffer
, &start
);
1342 gtk_text_buffer_get_end_iter(imhtml
->text_buffer
, &end
);
1343 gtk_text_buffer_delete(imhtml
->text_buffer
, &start
, &end
);
1346 void gtk_imhtml_page_up (GtkIMHtml
*imhtml
)
1350 void gtk_imhtml_page_down (GtkIMHtml
*imhtml
){}
1353 gtk_imhtml_tip_paint (GtkIMHtml
*imhtml
)
1355 PangoLayout
*layout
;
1357 g_return_val_if_fail(GTK_IS_IMHTML(imhtml
), FALSE
);
1359 layout
= gtk_widget_create_pango_layout(imhtml
->tip_window
, imhtml
->tip
);
1361 gtk_paint_flat_box (imhtml
->tip_window
->style
, imhtml
->tip_window
->window
,
1362 GTK_STATE_NORMAL
, GTK_SHADOW_OUT
, NULL
, imhtml
->tip_window
,
1363 "tooltip", 0, 0, -1, -1);
1365 gtk_paint_layout (imhtml
->tip_window
->style
, imhtml
->tip_window
->window
, GTK_STATE_NORMAL
,
1366 FALSE
, NULL
, imhtml
->tip_window
, NULL
, 4, 4, layout
);
1368 g_object_unref(layout
);
1373 gtk_imhtml_tip (gpointer data
)
1375 GtkIMHtml
*imhtml
= data
;
1376 PangoFontMetrics
*font
;
1377 PangoLayout
*layout
;
1379 gint gap
, x
, y
, h
, w
, scr_w
, baseline_skip
;
1381 g_return_val_if_fail(GTK_IS_IMHTML(imhtml
), FALSE
);
1383 if (!imhtml
->tip
|| !GTK_WIDGET_DRAWABLE (GTK_WIDGET(imhtml
))) {
1384 imhtml
->tip_timer
= 0;
1388 if (imhtml
->tip_window
){
1389 gtk_widget_destroy (imhtml
->tip_window
);
1390 imhtml
->tip_window
= NULL
;
1393 imhtml
->tip_timer
= 0;
1394 imhtml
->tip_window
= gtk_window_new (GTK_WINDOW_POPUP
);
1395 gtk_widget_set_app_paintable (imhtml
->tip_window
, TRUE
);
1396 gtk_window_set_resizable (GTK_WINDOW (imhtml
->tip_window
), FALSE
);
1397 gtk_widget_set_name (imhtml
->tip_window
, "gtk-tooltips");
1398 g_signal_connect_swapped (G_OBJECT (imhtml
->tip_window
), "expose_event",
1399 G_CALLBACK (gtk_imhtml_tip_paint
), imhtml
);
1401 gtk_widget_ensure_style (imhtml
->tip_window
);
1402 layout
= gtk_widget_create_pango_layout(imhtml
->tip_window
, imhtml
->tip
);
1403 font
= pango_font_get_metrics(pango_context_load_font(pango_layout_get_context(layout
),
1404 imhtml
->tip_window
->style
->font_desc
),
1408 pango_layout_get_pixel_size(layout
, &scr_w
, NULL
);
1409 gap
= PANGO_PIXELS((pango_font_metrics_get_ascent(font
) +
1410 pango_font_metrics_get_descent(font
))/ 4);
1414 baseline_skip
= PANGO_PIXELS(pango_font_metrics_get_ascent(font
) +
1415 pango_font_metrics_get_descent(font
));
1417 h
= 8 + baseline_skip
;
1419 gdk_window_get_pointer (NULL
, &x
, &y
, NULL
);
1420 if (GTK_WIDGET_NO_WINDOW (GTK_WIDGET(imhtml
)))
1421 y
+= GTK_WIDGET(imhtml
)->allocation
.y
;
1423 scr_w
= gdk_screen_width();
1425 x
-= ((w
>> 1) + 4);
1427 if ((x
+ w
) > scr_w
)
1428 x
-= (x
+ w
) - scr_w
;
1432 y
= y
+ PANGO_PIXELS(pango_font_metrics_get_ascent(font
) +
1433 pango_font_metrics_get_descent(font
));
1435 gtk_widget_set_size_request (imhtml
->tip_window
, w
, h
);
1436 gtk_widget_show (imhtml
->tip_window
);
1437 gtk_window_move (GTK_WINDOW(imhtml
->tip_window
), x
, y
);
1439 pango_font_metrics_unref(font
);
1440 g_object_unref(layout
);
1445 static gboolean
gtk_size_allocate_cb(GtkIMHtml
*widget
, GtkAllocation
*alloc
, gpointer user_data
)
1449 gtk_text_view_get_visible_rect(GTK_TEXT_VIEW(widget
), &rect
);
1450 if(widget
->old_rect
.width
!= rect
.width
|| widget
->old_rect
.height
!= rect
.height
){
1451 GList
*iter
= GTK_IMHTML(widget
)->scalables
;
1454 GtkIMHtmlScalable
*scale
= GTK_IMHTML_SCALABLE(iter
->data
);
1455 scale
->scale(scale
, rect
.width
, rect
.height
);
1461 widget
->old_rect
= rect
;
1465 /* GtkIMHtmlScalable, gaim_im_image, gaim_hr */
1466 GtkIMHtmlScalable
*gaim_im_image_new(GdkPixbuf
*img
, gchar
*filename
)
1468 gaim_im_image
*im_image
= g_malloc(sizeof(gaim_im_image
));
1469 GtkImage
*image
= GTK_IMAGE(gtk_image_new_from_pixbuf(img
));
1471 GTK_IMHTML_SCALABLE(im_image
)->scale
= gaim_im_image_scale
;
1472 GTK_IMHTML_SCALABLE(im_image
)->add_to
= gaim_im_image_add_to
;
1473 GTK_IMHTML_SCALABLE(im_image
)->free
= gaim_im_image_free
;
1475 im_image
->pixbuf
= img
;
1476 im_image
->image
= image
;
1477 im_image
->width
= gdk_pixbuf_get_width(img
);
1478 im_image
->height
= gdk_pixbuf_get_height(img
);
1479 im_image
->mark
= NULL
;
1480 im_image
->filename
= filename
;
1483 return GTK_IMHTML_SCALABLE(im_image
);
1486 void gaim_im_image_scale(GtkIMHtmlScalable
*scale
, int width
, int height
)
1488 gaim_im_image
*image
= (gaim_im_image
*)scale
;
1490 if(image
->width
> width
|| image
->height
> height
){
1491 GdkPixbuf
*new_image
= NULL
;
1493 int new_width
= image
->width
, new_height
= image
->height
;
1495 if(image
->width
> width
){
1496 factor
= (float)(width
)/image
->width
;
1498 new_height
= image
->height
* factor
;
1500 if(new_height
> height
){
1501 factor
= (float)(height
)/new_height
;
1502 new_height
= height
;
1503 new_width
= new_width
* factor
;
1506 new_image
= gdk_pixbuf_scale_simple(image
->pixbuf
, new_width
, new_height
, GDK_INTERP_BILINEAR
);
1507 gtk_image_set_from_pixbuf(image
->image
, new_image
);
1508 g_object_unref(G_OBJECT(new_image
));
1512 void gaim_im_image_free(GtkIMHtmlScalable
*scale
)
1514 gaim_im_image
*image
= (gaim_im_image
*)scale
;
1516 g_object_unref(image
->pixbuf
);
1517 g_free(image
->filename
);
1521 void gaim_im_image_add_to(GtkIMHtmlScalable
*scale
, GtkIMHtml
*imhtml
, GtkTextIter
*iter
)
1523 gaim_im_image
*image
= (gaim_im_image
*)scale
;
1524 GtkWidget
*box
= gtk_event_box_new();
1525 GtkTextChildAnchor
*anchor
= gtk_text_buffer_create_child_anchor(imhtml
->text_buffer
, iter
);
1527 gtk_container_add(GTK_CONTAINER(box
), GTK_WIDGET(image
->image
));
1529 gtk_widget_show(GTK_WIDGET(image
->image
));
1530 gtk_widget_show(box
);
1532 gtk_text_view_add_child_at_anchor(GTK_TEXT_VIEW(imhtml
), box
, anchor
);
1533 g_signal_connect(G_OBJECT(box
), "event", G_CALLBACK(gaim_im_image_clicked
), image
);
1536 GtkIMHtmlScalable
*gaim_hr_new()
1538 gaim_hr
*hr
= g_malloc(sizeof(gaim_hr
));
1540 GTK_IMHTML_SCALABLE(hr
)->scale
= gaim_hr_scale
;
1541 GTK_IMHTML_SCALABLE(hr
)->add_to
= gaim_hr_add_to
;
1542 GTK_IMHTML_SCALABLE(hr
)->free
= gaim_hr_free
;
1544 hr
->sep
= gtk_hseparator_new();
1545 gtk_widget_set_size_request(hr
->sep
, 5000, 2);
1546 gtk_widget_show(hr
->sep
);
1548 return GTK_IMHTML_SCALABLE(hr
);
1551 void gaim_hr_scale(GtkIMHtmlScalable
*scale
, int width
, int height
)
1553 gtk_widget_set_size_request(((gaim_hr
*)scale
)->sep
, width
, 2);
1556 void gaim_hr_add_to(GtkIMHtmlScalable
*scale
, GtkIMHtml
*imhtml
, GtkTextIter
*iter
)
1558 gaim_hr
*hr
= (gaim_hr
*)scale
;
1559 GtkTextChildAnchor
*anchor
= gtk_text_buffer_create_child_anchor(imhtml
->text_buffer
, iter
);
1561 gtk_text_view_add_child_at_anchor(GTK_TEXT_VIEW(imhtml
), hr
->sep
, anchor
);
1564 void gaim_hr_free(GtkIMHtmlScalable
*scale
)
1566 /* gtk_widget_destroy(((gaim_hr *)scale)->sep); */
1570 static void write_img_to_file(GtkWidget
*w
, GtkFileSelection
*sel
)
1572 const gchar
*filename
= gtk_file_selection_get_filename(sel
);
1573 gaim_im_image
*image
= g_object_get_data(G_OBJECT(sel
), "gaim_im_image");
1575 GError
*error
= NULL
;
1576 #if GTK_CHECK_VERSION(2,2,0)
1577 GSList
*formats
= gdk_pixbuf_get_formats();
1580 GdkPixbufFormat
*format
= formats
->data
;
1581 gchar
**extensions
= gdk_pixbuf_format_get_extensions(format
);
1582 gpointer p
= extensions
;
1584 while(gdk_pixbuf_format_is_writable(format
) && extensions
&& extensions
[0]){
1585 gchar
*fmt_ext
= extensions
[0];
1586 const gchar
* file_ext
= filename
+ strlen(filename
) - strlen(fmt_ext
);
1588 if(!strcmp(fmt_ext
, file_ext
)){
1589 type
= gdk_pixbuf_format_get_name(format
);
1601 formats
= formats
->next
;
1604 g_slist_free(formats
);
1606 /* this is really ugly code, but I think it will work */
1607 char *basename
= g_path_get_basename(filename
);
1608 char *ext
= strrchr(basename
, '.');
1612 if(!g_ascii_strcasecmp(ext
, "jpeg") || !g_ascii_strcasecmp(ext
, "jpg"))
1613 type
= g_strdup("jpeg");
1614 else if(!g_ascii_strcasecmp(ext
, "png"))
1615 type
= g_strdup("png");
1621 /* If I can't find a valid type, I will just tell the user about it and then assume
1624 gtk_message_dialog_new(NULL
, 0, GTK_MESSAGE_ERROR
, GTK_BUTTONS_OK
,
1625 _("Gaim was unable to guess the image type base on the file extension supplied. Defaulting to PNG."));
1626 type
= g_strdup("png");
1629 gdk_pixbuf_save(image
->pixbuf
, filename
, type
, &error
, NULL
);
1632 gtk_message_dialog_new(NULL
, 0, GTK_MESSAGE_ERROR
, GTK_BUTTONS_OK
,
1633 _("Error saving image: %s"), error
->message
);
1634 g_error_free(error
);
1640 static void gaim_im_image_save(GtkWidget
*w
, gaim_im_image
*image
)
1642 GtkWidget
*sel
= gtk_file_selection_new(_("Gaim - Save Image"));
1644 gtk_file_selection_set_filename(GTK_FILE_SELECTION(sel
), image
->filename
);
1645 g_object_set_data(G_OBJECT(sel
), "gaim_im_image", image
);
1646 g_signal_connect(G_OBJECT(GTK_FILE_SELECTION(sel
)->ok_button
), "clicked",
1647 G_CALLBACK(write_img_to_file
), sel
);
1649 g_signal_connect_swapped(G_OBJECT(GTK_FILE_SELECTION(sel
)->ok_button
), "clicked",
1650 G_CALLBACK(gtk_widget_destroy
), sel
);
1651 g_signal_connect_swapped(G_OBJECT(GTK_FILE_SELECTION(sel
)->cancel_button
), "clicked",
1652 G_CALLBACK(gtk_widget_destroy
), sel
);
1654 gtk_widget_show(sel
);
1657 static gboolean
gaim_im_image_clicked(GtkWidget
*w
, GdkEvent
*event
, gaim_im_image
*image
)
1659 GdkEventButton
*event_button
= (GdkEventButton
*) event
;
1661 if (event
->type
== GDK_BUTTON_RELEASE
) {
1662 if(event_button
->button
== 3) {
1663 GtkWidget
*img
, *item
, *menu
;
1664 gchar
*text
= g_strdup_printf(_("_Save Image..."));
1665 menu
= gtk_menu_new();
1667 /* buttons and such */
1668 img
= gtk_image_new_from_stock(GTK_STOCK_SAVE
, GTK_ICON_SIZE_MENU
);
1669 item
= gtk_image_menu_item_new_with_mnemonic(text
);
1670 gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(item
), img
);
1671 g_signal_connect(G_OBJECT(item
), "activate", G_CALLBACK(gaim_im_image_save
), image
);
1672 gtk_menu_shell_append(GTK_MENU_SHELL(menu
), item
);
1674 gtk_widget_show_all(menu
);
1675 gtk_menu_popup(GTK_MENU(menu
), NULL
, NULL
, NULL
, NULL
,
1676 event_button
->button
, event_button
->time
);
1682 if(event
->type
== GDK_BUTTON_PRESS
&& event_button
->button
== 3)
1683 return TRUE
; /* Clicking the right mouse button on a link shouldn't
1684 be caught by the regular GtkTextView menu */
1686 return FALSE
; /* Let clicks go through if we didn't catch anything */