| 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 |