pidgin/gtkwebview.c

changeset 35714
bb2c91c3e2db
parent 35713
4423f463a782
child 35716
80bedd712883
equal deleted inserted replaced
35713:4423f463a782 35714:bb2c91c3e2db
83 83
84 gboolean (*activate)(PidginWebView *webview, const char *uri); 84 gboolean (*activate)(PidginWebView *webview, const char *uri);
85 gboolean (*context_menu)(PidginWebView *webview, WebKitDOMHTMLAnchorElement *link, GtkWidget *menu); 85 gboolean (*context_menu)(PidginWebView *webview, WebKitDOMHTMLAnchorElement *link, GtkWidget *menu);
86 } PidginWebViewProtocol; 86 } PidginWebViewProtocol;
87 87
88 struct _PidginWebViewSmiley {
89 gchar *smile;
90 gchar *file;
91 GdkPixbufAnimation *icon;
92 gboolean hidden;
93 GdkPixbufLoader *loader;
94 GSList *anchors;
95 PidginWebViewSmileyFlags flags;
96 PidginWebView *webview;
97 gpointer data;
98 gsize datasize;
99 };
100
101 typedef struct _GtkSmileyTree GtkSmileyTree;
102 struct _GtkSmileyTree {
103 GString *values;
104 GtkSmileyTree **children;
105 PidginWebViewSmiley *image;
106 };
107
108 typedef struct _PidginWebViewPriv { 88 typedef struct _PidginWebViewPriv {
109 /* Processing queues */ 89 /* Processing queues */
110 gboolean is_loading; 90 gboolean is_loading;
111 GQueue *load_queue; 91 GQueue *load_queue;
112 guint loader; 92 guint loader;
123 struct { 103 struct {
124 gboolean wbfo:1; /* Whole buffer formatting only. */ 104 gboolean wbfo:1; /* Whole buffer formatting only. */
125 gboolean block_changed:1; 105 gboolean block_changed:1;
126 } edit; 106 } edit;
127 107
128 /* Smileys */
129 char *protocol_name;
130 GHashTable *smiley_data;
131 GtkSmileyTree *default_smilies;
132
133 /* WebKit inspector */ 108 /* WebKit inspector */
134 WebKitWebView *inspector_view; 109 WebKitWebView *inspector_view;
135 GtkWindow *inspector_win; 110 GtkWindow *inspector_win;
136 } PidginWebViewPriv; 111 } PidginWebViewPriv;
137 112
139 * Globals 114 * Globals
140 *****************************************************************************/ 115 *****************************************************************************/
141 116
142 static WebKitWebViewClass *parent_class = NULL; 117 static WebKitWebViewClass *parent_class = NULL;
143 118
144 /******************************************************************************
145 * Smileys
146 *****************************************************************************/
147
148 const char *
149 pidgin_webview_get_protocol_name(PidginWebView *webview)
150 {
151 PidginWebViewPriv *priv;
152
153 g_return_val_if_fail(webview != NULL, NULL);
154
155 priv = PIDGIN_WEBVIEW_GET_PRIVATE(webview);
156 return priv->protocol_name;
157 }
158
159 void
160 pidgin_webview_set_protocol_name(PidginWebView *webview, const char *protocol_name)
161 {
162 PidginWebViewPriv *priv;
163
164 g_return_if_fail(webview != NULL);
165
166 priv = PIDGIN_WEBVIEW_GET_PRIVATE(webview);
167 priv->protocol_name = g_strdup(protocol_name);
168 }
169
170 static GtkSmileyTree *
171 gtk_smiley_tree_new(void)
172 {
173 return g_new0(GtkSmileyTree, 1);
174 }
175
176 static void
177 gtk_smiley_tree_insert(GtkSmileyTree *tree, PidginWebViewSmiley *smiley)
178 {
179 GtkSmileyTree *t = tree;
180 const char *x = smiley->smile;
181
182 if (!(*x))
183 return;
184
185 do {
186 char *pos;
187 gsize index;
188
189 if (!t->values)
190 t->values = g_string_new("");
191
192 pos = strchr(t->values->str, *x);
193 if (!pos) {
194 t->values = g_string_append_c(t->values, *x);
195 index = t->values->len - 1;
196 t->children = g_realloc(t->children, t->values->len * sizeof(GtkSmileyTree *));
197 t->children[index] = g_new0(GtkSmileyTree, 1);
198 } else
199 index = pos - t->values->str;
200
201 t = t->children[index];
202
203 x++;
204 } while (*x);
205
206 t->image = smiley;
207 }
208
209 static void
210 gtk_smiley_tree_destroy(GtkSmileyTree *tree)
211 {
212 GSList *list = g_slist_prepend(NULL, tree);
213
214 while (list) {
215 GtkSmileyTree *t = list->data;
216 gsize i;
217 list = g_slist_delete_link(list, list);
218 if (t && t->values) {
219 for (i = 0; i < t->values->len; i++)
220 list = g_slist_prepend(list, t->children[i]);
221 g_string_free(t->values, TRUE);
222 g_free(t->children);
223 }
224
225 g_free(t);
226 }
227 }
228
229 static void
230 gtk_smiley_tree_remove(GtkSmileyTree *tree, PidginWebViewSmiley *smiley)
231 {
232 GtkSmileyTree *t = tree;
233 const gchar *x = smiley->smile;
234 int len = 0;
235
236 while (*x) {
237 char *pos;
238
239 if (!t->values)
240 return;
241
242 pos = strchr(t->values->str, *x);
243 if (pos)
244 t = t->children[pos - t->values->str];
245 else
246 return;
247
248 x++; len++;
249 }
250
251 t->image = NULL;
252 }
253
254 #if 0
255 static int
256 gtk_smiley_tree_lookup(GtkSmileyTree *tree, const char *text)
257 {
258 GtkSmileyTree *t = tree;
259 const char *x = text;
260 int len = 0;
261 const char *amp;
262 int alen;
263
264 while (*x) {
265 char *pos;
266
267 if (!t->values)
268 break;
269
270 if (*x == '&' && (amp = purple_markup_unescape_entity(x, &alen))) {
271 gboolean matched = TRUE;
272 /* Make sure all chars of the unescaped value match */
273 while (*(amp + 1)) {
274 pos = strchr(t->values->str, *amp);
275 if (pos)
276 t = t->children[pos - t->values->str];
277 else {
278 matched = FALSE;
279 break;
280 }
281 amp++;
282 }
283 if (!matched)
284 break;
285
286 pos = strchr(t->values->str, *amp);
287 }
288 else if (*x == '<') /* Because we're all WYSIWYG now, a '<' char should
289 * only appear as the start of a tag. Perhaps a
290 * safer (but costlier) check would be to call
291 * pidgin_webview_is_tag on it */
292 break;
293 else {
294 alen = 1;
295 pos = strchr(t->values->str, *x);
296 }
297
298 if (pos)
299 t = t->children[pos - t->values->str];
300 else
301 break;
302
303 x += alen;
304 len += alen;
305 }
306
307 if (t->image)
308 return len;
309
310 return 0;
311 }
312 #endif
313
314 static void
315 pidgin_webview_disassociate_smiley_foreach(gpointer key, gpointer value,
316 gpointer user_data)
317 {
318 GtkSmileyTree *tree = (GtkSmileyTree *)value;
319 PidginWebViewSmiley *smiley = (PidginWebViewSmiley *)user_data;
320 gtk_smiley_tree_remove(tree, smiley);
321 }
322
323 static void
324 pidgin_webview_disconnect_smiley(PidginWebView *webview, PidginWebViewSmiley *smiley)
325 {
326 smiley->webview = NULL;
327 g_signal_handlers_disconnect_matched(webview, G_SIGNAL_MATCH_DATA, 0, 0,
328 NULL, NULL, smiley);
329 }
330
331 static void
332 pidgin_webview_disassociate_smiley(PidginWebViewSmiley *smiley)
333 {
334 if (smiley->webview) {
335 PidginWebViewPriv *priv = PIDGIN_WEBVIEW_GET_PRIVATE(smiley->webview);
336 gtk_smiley_tree_remove(priv->default_smilies, smiley);
337 g_hash_table_foreach(priv->smiley_data,
338 pidgin_webview_disassociate_smiley_foreach, smiley);
339 g_signal_handlers_disconnect_matched(smiley->webview,
340 G_SIGNAL_MATCH_DATA, 0, 0, NULL,
341 NULL, smiley);
342 smiley->webview = NULL;
343 }
344 }
345
346 void
347 pidgin_webview_associate_smiley(PidginWebView *webview, const char *sml,
348 PidginWebViewSmiley *smiley)
349 {
350 GtkSmileyTree *tree;
351 PidginWebViewPriv *priv;
352
353 g_return_if_fail(webview != NULL);
354 g_return_if_fail(PIDGIN_IS_WEBVIEW(webview));
355
356 priv = PIDGIN_WEBVIEW_GET_PRIVATE(webview);
357
358 if (sml == NULL)
359 tree = priv->default_smilies;
360 else if (!(tree = g_hash_table_lookup(priv->smiley_data, sml))) {
361 tree = gtk_smiley_tree_new();
362 g_hash_table_insert(priv->smiley_data, g_strdup(sml), tree);
363 }
364
365 /* need to disconnect old webview, if there is one */
366 if (smiley->webview) {
367 g_signal_handlers_disconnect_matched(smiley->webview,
368 G_SIGNAL_MATCH_DATA, 0, 0, NULL,
369 NULL, smiley);
370 }
371
372 smiley->webview = webview;
373
374 gtk_smiley_tree_insert(tree, smiley);
375
376 /* connect destroy signal for the webview */
377 g_signal_connect(webview, "destroy",
378 G_CALLBACK(pidgin_webview_disconnect_smiley), smiley);
379 }
380
381 #if 0
382 static gboolean
383 pidgin_webview_is_smiley(PidginWebViewPriv *priv, const char *sml, const char *text,
384 int *len)
385 {
386 GtkSmileyTree *tree;
387
388 if (!sml)
389 sml = priv->protocol_name;
390
391 if (!sml || !(tree = g_hash_table_lookup(priv->smiley_data, sml)))
392 tree = priv->default_smilies;
393
394 if (tree == NULL)
395 return FALSE;
396
397 *len = gtk_smiley_tree_lookup(tree, text);
398 return (*len > 0);
399 }
400 #endif
401
402 static PidginWebViewSmiley *
403 pidgin_webview_smiley_get_from_tree(GtkSmileyTree *t, const char *text)
404 {
405 const char *x = text;
406 char *pos;
407
408 if (t == NULL)
409 return NULL;
410
411 while (*x) {
412 if (!t->values)
413 return NULL;
414
415 pos = strchr(t->values->str, *x);
416 if (!pos)
417 return NULL;
418
419 t = t->children[pos - t->values->str];
420 x++;
421 }
422
423 return t->image;
424 }
425
426 PidginWebViewSmiley *
427 pidgin_webview_smiley_find(PidginWebView *webview, const char *sml, const char *text)
428 {
429 PidginWebViewPriv *priv;
430 PidginWebViewSmiley *ret;
431
432 g_return_val_if_fail(webview != NULL, NULL);
433
434 priv = PIDGIN_WEBVIEW_GET_PRIVATE(webview);
435
436 /* Look for custom smileys first */
437 if (sml != NULL) {
438 ret = pidgin_webview_smiley_get_from_tree(g_hash_table_lookup(priv->smiley_data, sml), text);
439 if (ret != NULL)
440 return ret;
441 }
442
443 /* Fall back to check for default smileys */
444 return pidgin_webview_smiley_get_from_tree(priv->default_smilies, text);
445 }
446
447 #if 0
448 static GdkPixbufAnimation *
449 gtk_smiley_get_image(PidginWebViewSmiley *smiley)
450 {
451 if (!smiley->icon) {
452 if (smiley->file) {
453 smiley->icon = gdk_pixbuf_animation_new_from_file(smiley->file, NULL);
454 } else if (smiley->loader) {
455 smiley->icon = gdk_pixbuf_loader_get_animation(smiley->loader);
456 if (smiley->icon)
457 g_object_ref(G_OBJECT(smiley->icon));
458 }
459 }
460
461 return smiley->icon;
462 }
463 #endif
464
465 static void
466 gtk_custom_smiley_allocated(GdkPixbufLoader *loader, gpointer user_data)
467 {
468 PidginWebViewSmiley *smiley;
469
470 smiley = (PidginWebViewSmiley *)user_data;
471 smiley->icon = gdk_pixbuf_loader_get_animation(loader);
472
473 if (smiley->icon)
474 g_object_ref(G_OBJECT(smiley->icon));
475 }
476
477 static void
478 gtk_custom_smiley_closed(GdkPixbufLoader *loader, gpointer user_data)
479 {
480 PidginWebViewSmiley *smiley;
481 GtkWidget *icon = NULL;
482 GtkTextChildAnchor *anchor = NULL;
483 GSList *current = NULL;
484
485 smiley = (PidginWebViewSmiley *)user_data;
486 if (!smiley->webview) {
487 g_object_unref(G_OBJECT(loader));
488 smiley->loader = NULL;
489 return;
490 }
491
492 for (current = smiley->anchors; current; current = g_slist_next(current)) {
493 anchor = GTK_TEXT_CHILD_ANCHOR(current->data);
494 if (gtk_text_child_anchor_get_deleted(anchor))
495 icon = NULL;
496 else
497 icon = gtk_image_new_from_animation(smiley->icon);
498
499 if (icon) {
500 GList *wids;
501 gtk_widget_show(icon);
502
503 wids = gtk_text_child_anchor_get_widgets(anchor);
504
505 #if 0
506 g_object_set_data_full(G_OBJECT(anchor), "gtkimhtml_plaintext",
507 purple_unescape_html(smiley->smile), g_free);
508 g_object_set_data_full(G_OBJECT(anchor), "gtkimhtml_htmltext",
509 g_strdup(smiley->smile), g_free);
510 #endif
511
512 if (smiley->webview) {
513 if (wids) {
514 GList *children = gtk_container_get_children(GTK_CONTAINER(wids->data));
515 g_list_foreach(children, (GFunc)gtk_widget_destroy, NULL);
516 g_list_free(children);
517 gtk_container_add(GTK_CONTAINER(wids->data), icon);
518 } else
519 gtk_text_view_add_child_at_anchor(GTK_TEXT_VIEW(smiley->webview), icon, anchor);
520 }
521 g_list_free(wids);
522 }
523 g_object_unref(anchor);
524 }
525
526 g_slist_free(smiley->anchors);
527 smiley->anchors = NULL;
528
529 g_object_unref(G_OBJECT(loader));
530 smiley->loader = NULL;
531 }
532
533 static void
534 gtk_custom_smiley_size_prepared(GdkPixbufLoader *loader, gint width, gint height, gpointer data)
535 {
536 if (purple_prefs_get_bool(PIDGIN_PREFS_ROOT "/conversations/resize_custom_smileys")) {
537 int custom_smileys_size = purple_prefs_get_int(PIDGIN_PREFS_ROOT "/conversations/custom_smileys_size");
538 if (width <= custom_smileys_size && height <= custom_smileys_size)
539 return;
540
541 if (width >= height) {
542 height = height * custom_smileys_size / width;
543 width = custom_smileys_size;
544 } else {
545 width = width * custom_smileys_size / height;
546 height = custom_smileys_size;
547 }
548 }
549 gdk_pixbuf_loader_set_size(loader, width, height);
550 }
551
552 PidginWebViewSmiley *
553 pidgin_webview_smiley_create(const char *file, const char *shortcut, gboolean hide,
554 PidginWebViewSmileyFlags flags)
555 {
556 PidginWebViewSmiley *smiley = g_new0(PidginWebViewSmiley, 1);
557 smiley->file = g_strdup(file);
558 smiley->smile = g_strdup(shortcut);
559 smiley->hidden = hide;
560 smiley->flags = flags;
561 smiley->webview = NULL;
562 pidgin_webview_smiley_reload(smiley);
563 return smiley;
564 }
565
566 void
567 pidgin_webview_smiley_reload(PidginWebViewSmiley *smiley)
568 {
569 if (smiley->icon)
570 g_object_unref(smiley->icon);
571 if (smiley->loader)
572 g_object_unref(smiley->loader);
573
574 smiley->icon = NULL;
575 smiley->loader = NULL;
576
577 if (smiley->file) {
578 /* We do not use the pixbuf loader for a smiley that can be loaded
579 * from a file. (e.g., local custom smileys)
580 */
581 return;
582 }
583
584 smiley->loader = gdk_pixbuf_loader_new();
585
586 g_signal_connect(smiley->loader, "area_prepared",
587 G_CALLBACK(gtk_custom_smiley_allocated), smiley);
588 g_signal_connect(smiley->loader, "closed",
589 G_CALLBACK(gtk_custom_smiley_closed), smiley);
590 g_signal_connect(smiley->loader, "size_prepared",
591 G_CALLBACK(gtk_custom_smiley_size_prepared), smiley);
592 }
593
594 const char *
595 pidgin_webview_smiley_get_smile(const PidginWebViewSmiley *smiley)
596 {
597 return smiley->smile;
598 }
599
600 const char *
601 pidgin_webview_smiley_get_file(const PidginWebViewSmiley *smiley)
602 {
603 return smiley->file;
604 }
605
606 gboolean
607 pidgin_webview_smiley_get_hidden(const PidginWebViewSmiley *smiley)
608 {
609 return smiley->hidden;
610 }
611
612 PidginWebViewSmileyFlags
613 pidgin_webview_smiley_get_flags(const PidginWebViewSmiley *smiley)
614 {
615 return smiley->flags;
616 }
617
618 void
619 pidgin_webview_smiley_destroy(PidginWebViewSmiley *smiley)
620 {
621 pidgin_webview_disassociate_smiley(smiley);
622 g_free(smiley->smile);
623 g_free(smiley->file);
624 if (smiley->icon)
625 g_object_unref(smiley->icon);
626 if (smiley->loader)
627 g_object_unref(smiley->loader);
628 g_free(smiley->data);
629 g_free(smiley);
630 }
631
632 void
633 pidgin_webview_remove_smileys(PidginWebView *webview)
634 {
635 PidginWebViewPriv *priv;
636
637 g_return_if_fail(webview != NULL);
638
639 priv = PIDGIN_WEBVIEW_GET_PRIVATE(webview);
640
641 g_hash_table_destroy(priv->smiley_data);
642 gtk_smiley_tree_destroy(priv->default_smilies);
643 priv->smiley_data = g_hash_table_new_full(g_str_hash, g_str_equal, g_free,
644 (GDestroyNotify)gtk_smiley_tree_destroy);
645 priv->default_smilies = gtk_smiley_tree_new();
646 }
647
648 void
649 pidgin_webview_insert_smiley(PidginWebView *webview, const char *sml,
650 const char *smiley)
651 {
652 PidginWebViewPriv *priv;
653 char *unescaped;
654 PidginWebViewSmiley *webview_smiley;
655
656 g_return_if_fail(webview != NULL);
657
658 priv = PIDGIN_WEBVIEW_GET_PRIVATE(webview);
659
660 unescaped = purple_unescape_html(smiley);
661 webview_smiley = pidgin_webview_smiley_find(webview, sml, unescaped);
662
663 #if 0
664 /* TODO */
665 if (priv->format_functions & PIDGIN_WEBVIEW_SMILEY) {
666 char *tmp;
667 /* TODO Better smiley insertion... */
668 tmp = g_strdup_printf("<img isEmoticon src='purple-smiley:%p' alt='%s'>",
669 webview_smiley, smiley);
670 pidgin_webview_append_html(webview, tmp);
671 g_free(tmp);
672 } else
673 #endif
674 {
675 pidgin_webview_append_html(webview, smiley);
676 }
677
678 g_free(unescaped);
679 }
680 119
681 /****************************************************************************** 120 /******************************************************************************
682 * Helpers 121 * Helpers
683 *****************************************************************************/ 122 *****************************************************************************/
684 123
1488 temp = g_queue_pop_head(priv->load_queue); 927 temp = g_queue_pop_head(priv->load_queue);
1489 temp = g_queue_pop_head(priv->load_queue); 928 temp = g_queue_pop_head(priv->load_queue);
1490 g_free(temp); 929 g_free(temp);
1491 } 930 }
1492 g_queue_free(priv->load_queue); 931 g_queue_free(priv->load_queue);
1493
1494 g_hash_table_destroy(priv->smiley_data);
1495 gtk_smiley_tree_destroy(priv->default_smilies);
1496 g_free(priv->protocol_name);
1497 932
1498 G_OBJECT_CLASS(parent_class)->finalize(G_OBJECT(webview)); 933 G_OBJECT_CLASS(parent_class)->finalize(G_OBJECT(webview));
1499 } 934 }
1500 935
1501 enum { 936 enum {
1637 PidginWebViewPriv *priv = PIDGIN_WEBVIEW_GET_PRIVATE(webview); 1072 PidginWebViewPriv *priv = PIDGIN_WEBVIEW_GET_PRIVATE(webview);
1638 WebKitWebInspector *inspector; 1073 WebKitWebInspector *inspector;
1639 1074
1640 priv->load_queue = g_queue_new(); 1075 priv->load_queue = g_queue_new();
1641 1076
1642 priv->smiley_data = g_hash_table_new_full(g_str_hash, g_str_equal, g_free,
1643 (GDestroyNotify)gtk_smiley_tree_destroy);
1644 priv->default_smilies = gtk_smiley_tree_new();
1645
1646 g_signal_connect(G_OBJECT(webview), "button-press-event", 1077 g_signal_connect(G_OBJECT(webview), "button-press-event",
1647 G_CALLBACK(webview_button_pressed), NULL); 1078 G_CALLBACK(webview_button_pressed), NULL);
1648 1079
1649 g_signal_connect(G_OBJECT(webview), "popup-menu", 1080 g_signal_connect(G_OBJECT(webview), "popup-menu",
1650 G_CALLBACK(webview_popup_menu), NULL); 1081 G_CALLBACK(webview_popup_menu), NULL);

mercurial