Mercurial > pidgin.yaz
changeset 32576:7c346c5a05d4
propagate from branch 'im.pidgin.pidgin' (head 1bcd0bb175fd417fd165aa7b52e219c97b0e30c6)
to branch 'im.pidgin.soc.2009.webkitmessageview' (head 75ffceeee02a5b5f52aac4008a35ac04f5f5a5f4)
author | tdrhq@soc.pidgin.im |
---|---|
date | Wed, 14 Oct 2009 02:41:11 +0000 |
parents | 5550823608cb (current diff) 4b53d875529f (diff) |
children | 994e8d214754 |
files | configure.ac pidgin/Makefile.am pidgin/gtkconv.c pidgin/gtkdialogs.c pidgin/plugins/Makefile.am |
diffstat | 20 files changed, 2335 insertions(+), 554 deletions(-) [+] |
line wrap: on
line diff
--- a/configure.ac Tue Oct 13 19:52:35 2009 +0000 +++ b/configure.ac Wed Oct 14 02:41:11 2009 +0000 @@ -703,6 +703,8 @@ #AC_CHECK_FUNC(wcwidth, [AC_DEFINE([HAVE_WCWIDTH], [1], [Define to 1 if you have wcwidth function.])]) +PKG_CHECK_MODULES(WEBKIT, [webkit-1.0 >= 1.1.1]); + dnl ####################################################################### dnl # Check for LibXML2 (required) dnl ####################################################################### @@ -2500,6 +2502,7 @@ pidgin/pixmaps/emotes/none/Makefile pidgin/pixmaps/emotes/small/16/Makefile pidgin/plugins/Makefile + pidgin/plugins/adiumthemes/Makefile pidgin/plugins/cap/Makefile pidgin/plugins/disco/Makefile pidgin/plugins/gestures/Makefile
--- a/pidgin/Makefile.am Tue Oct 13 19:52:35 2009 +0000 +++ b/pidgin/Makefile.am Wed Oct 14 02:41:11 2009 +0000 @@ -125,9 +125,11 @@ gtkstatusbox.c \ gtkthemes.c \ gtkutils.c \ + gtkwebview.c \ gtkwhiteboard.c \ minidialog.c \ - pidgintooltip.c + pidgintooltip.c \ + smileyparser.c pidgin_headers = \ eggtrayicon.h \ @@ -183,10 +185,12 @@ pidginstock.h \ gtkthemes.h \ gtkutils.h \ + gtkwebview.h \ gtkwhiteboard.h \ minidialog.h \ pidgintooltip.h \ - pidgin.h + pidgin.h \ + smileyparser.h pidginincludedir=$(includedir)/pidgin pidgininclude_HEADERS = \ @@ -206,6 +210,7 @@ $(GTKSPELL_LIBS) \ $(STARTUP_NOTIFICATION_LIBS) \ $(LIBXML_LIBS) \ + $(WEBKIT_LIBS) \ $(GTK_LIBS) \ $(top_builddir)/libpurple/libpurple.la @@ -230,5 +235,6 @@ $(GTKSPELL_CFLAGS) \ $(STARTUP_NOTIFICATION_CFLAGS) \ $(LIBXML_CFLAGS) \ + $(WEBKIT_CFLAGS) \ $(INTGG_CFLAGS) endif # ENABLE_GTK
--- a/pidgin/gtkconv.c Tue Oct 13 19:52:35 2009 +0000 +++ b/pidgin/gtkconv.c Wed Oct 14 02:41:11 2009 +0000 @@ -69,6 +69,7 @@ #include "gtkprivacy.h" #include "gtkthemes.h" #include "gtkutils.h" +#include "gtkwebview.h" #include "pidginstock.h" #include "pidgintooltip.h" @@ -164,7 +165,6 @@ gboolean pidgin_conv_has_focus(PurpleConversation *conv); static GdkColor* generate_nick_colors(guint *numcolors, GdkColor background); static gboolean color_is_visible(GdkColor foreground, GdkColor background, int color_contrast, int brightness_contrast); -static GtkTextTag *get_buddy_tag(PurpleConversation *conv, const char *who, PurpleMessageFlags flag, gboolean create); static void pidgin_conv_update_fields(PurpleConversation *conv, PidginConvFields fields); static void focus_out_from_menubar(GtkWidget *wid, PidginWindow *win); static void pidgin_conv_tab_pack(PidginWindow *win, PidginConversation *gtkconv); @@ -178,7 +178,7 @@ static const GdkColor *get_nick_color(PidginConversation *gtkconv, const char *name) { static GdkColor col; - GtkStyle *style = gtk_widget_get_style(gtkconv->imhtml); + GtkStyle *style = gtk_widget_get_style(gtkconv->webview); float scale; col = nick_colors[g_str_hash(name) % nbr_nick_colors]; @@ -373,7 +373,7 @@ gtkconv = PIDGIN_CONVERSATION(conv); - gtk_imhtml_clear(GTK_IMHTML(gtkconv->imhtml)); + webkit_web_view_load_html_string (WEBKIT_WEB_VIEW (gtkconv->webview), "", ""); for (iter = gtkconv->convs; iter; iter = iter->next) purple_conversation_clear_message_history(iter->data); } @@ -987,32 +987,7 @@ static void savelog_writefile_cb(void *user_data, const char *filename) { - PurpleConversation *conv = (PurpleConversation *)user_data; - FILE *fp; - const char *name; - char **lines; - gchar *text; - - if ((fp = g_fopen(filename, "w+")) == NULL) { - purple_notify_error(PIDGIN_CONVERSATION(conv), NULL, _("Unable to open file."), NULL); - return; - } - - name = purple_conversation_get_name(conv); - fprintf(fp, "<html>\n<head>\n"); - fprintf(fp, "<meta http-equiv=\"content-type\" content=\"text/html; charset=UTF-8\">\n"); - fprintf(fp, "<title>%s</title>\n</head>\n<body>\n", name); - fprintf(fp, _("<h1>Conversation with %s</h1>\n"), name); - - lines = gtk_imhtml_get_markup_lines( - GTK_IMHTML(PIDGIN_CONVERSATION(conv)->imhtml)); - text = g_strjoinv("<br>\n", lines); - fprintf(fp, "%s", text); - g_free(text); - g_strfreev(lines); - - fprintf(fp, "\n</body>\n</html>\n"); - fclose(fp); + /* TODO: I don't know how to support this using webkit yet. */ } /* @@ -1142,12 +1117,16 @@ if (gtkconv != gtk_active_conv) { - gtk_imhtml_search_clear(GTK_IMHTML(gtkconv->imhtml)); + webkit_web_view_unmark_text_matches (WEBKIT_WEB_VIEW (gtkconv->webview)); } else { - gtk_imhtml_search_find(GTK_IMHTML(gtk_active_conv->imhtml), - gtk_entry_get_text(GTK_ENTRY(s->entry))); + webkit_web_view_mark_text_matches (WEBKIT_WEB_VIEW (gtkconv->webview), + gtk_entry_get_text (GTK_ENTRY(s->entry)), TRUE, 0); + webkit_web_view_set_highlight_text_matches (WEBKIT_WEB_VIEW (gtkconv->webview), + true); + webkit_web_view_search_text (WEBKIT_WEB_VIEW (gtkconv->webview), + gtk_entry_get_text (GTK_ENTRY(s->entry)), FALSE, TRUE, FALSE); } } break; @@ -1158,7 +1137,7 @@ for (iter = pidgin_conv_window_get_gtkconvs(s->gtkwin); iter; iter=iter->next) { PidginConversation *gconv = iter->data; - gtk_imhtml_search_clear(GTK_IMHTML(gconv->imhtml)); + webkit_web_view_unmark_text_matches (WEBKIT_WEB_VIEW(gconv->webview)); } gtk_widget_destroy(s->gtkwin->dialogs.search); @@ -1661,30 +1640,11 @@ gtk_widget_grab_focus(PIDGIN_CONVERSATION(conv)->entry); } -static GtkTextMark * -get_mark_for_user(PidginConversation *gtkconv, const char *who) -{ - GtkTextBuffer *buf = gtk_text_view_get_buffer(GTK_TEXT_VIEW(gtkconv->imhtml)); - char *tmp = g_strconcat("user:", who, NULL); - GtkTextMark *mark = gtk_text_buffer_get_mark(buf, tmp); - - g_free(tmp); - return mark; -} - static void menu_last_said_cb(GtkWidget *w, PidginConversation *gtkconv) { - GtkTextMark *mark; - const char *who; - - who = g_object_get_data(G_OBJECT(w), "user_data"); - mark = get_mark_for_user(gtkconv, who); - - if (mark != NULL) - gtk_text_view_scroll_to_mark(GTK_TEXT_VIEW(gtkconv->imhtml), mark, 0.1, FALSE, 0, 0); - else - g_return_if_reached(); + /* I don't know what this is! */ + return; } static GtkWidget * @@ -1799,8 +1759,6 @@ button = pidgin_new_item_from_stock(menu, _("Last Said"), GTK_STOCK_INDEX, G_CALLBACK(menu_last_said_cb), PIDGIN_CONVERSATION(conv), 0, 0, NULL); g_object_set_data_full(G_OBJECT(button), "user_data", g_strdup(who), g_free); - if (!get_mark_for_user(PIDGIN_CONVERSATION(conv), who)) - gtk_widget_set_sensitive(button, FALSE); if (buddy != NULL) { @@ -1890,10 +1848,10 @@ chat_do_im(gtkconv, who); } else if (event->button == 2 && event->type == GDK_BUTTON_PRESS) { /* Move to user's anchor */ - GtkTextMark *mark = get_mark_for_user(gtkconv, who); - - if(mark != NULL) - gtk_text_view_scroll_to_mark(GTK_TEXT_VIEW(gtkconv->imhtml), mark, 0.1, FALSE, 0, 0); + //GtkTextMark *mark = get_mark_for_user(gtkconv, who); + + //if(mark != NULL) + // gtk_text_view_scroll_to_mark(GTK_TEXT_VIEW(gtkconv->imhtml), mark, 0.1, FALSE, 0, 0); } else if (event->button == 3 && event->type == GDK_BUTTON_PRESS) { GtkWidget *menu = create_chat_menu (conv, who, gc); gtk_menu_popup(GTK_MENU(menu), NULL, NULL, NULL, NULL, @@ -1969,8 +1927,8 @@ GtkWidget *from; GtkWidget *to; } transitions[] = { - {gtkconv->entry, gtkconv->imhtml}, - {gtkconv->imhtml, chat ? gtkconv->u.chat->list : gtkconv->entry}, + {gtkconv->entry, gtkconv->webview}, + {gtkconv->webview, chat ? gtkconv->u.chat->list : gtkconv->entry}, {chat ? gtkconv->u.chat->list : NULL, gtkconv->entry}, {NULL, NULL} }, *ptr; @@ -2226,12 +2184,12 @@ break; case GDK_Page_Up: - gtk_imhtml_page_up(GTK_IMHTML(gtkconv->imhtml)); + //gtk_imhtml_page_up(GTK_IMHTML(gtkconv->imhtml)); return TRUE; break; case GDK_Page_Down: - gtk_imhtml_page_down(GTK_IMHTML(gtkconv->imhtml)); + //gtk_imhtml_page_down(GTK_IMHTML(gtkconv->imhtml)); return TRUE; break; @@ -2334,7 +2292,7 @@ entry = GTK_IMHTML(gtkconv->entry); protocol_name = purple_account_get_protocol_name(conv->account); gtk_imhtml_set_protocol_name(entry, protocol_name); - gtk_imhtml_set_protocol_name(GTK_IMHTML(gtkconv->imhtml), protocol_name); + //gtk_imhtml_set_protocol_name(GTK_IMHTML(gtkconv->imhtml), protocol_name); if (!(conv->features & PURPLE_CONNECTION_HTML)) gtk_imhtml_clear_formatting(GTK_IMHTML(gtkconv->entry)); @@ -3278,11 +3236,11 @@ if (purple_conversation_get_type(conv) == PURPLE_CONV_TYPE_CHAT) { chat = purple_blist_find_chat(conv->account, conv->name); - if ((chat == NULL) && (gtkconv->imhtml != NULL)) { - chat = g_object_get_data(G_OBJECT(gtkconv->imhtml), "transient_chat"); - } - - if ((chat == NULL) && (gtkconv->imhtml != NULL)) { + if ((chat == NULL) && (gtkconv->webview != NULL)) { + chat = g_object_get_data(G_OBJECT(gtkconv->webview), "transient_chat"); + } + + if ((chat == NULL) && (gtkconv->webview != NULL)) { GHashTable *components; PurpleAccount *account = purple_conversation_get_account(conv); PurplePlugin *prpl = purple_find_prpl(purple_account_get_protocol_id(account)); @@ -3300,7 +3258,7 @@ chat = purple_chat_new(conv->account, NULL, components); purple_blist_node_set_flags((PurpleBlistNode *)chat, PURPLE_BLIST_NODE_FLAG_NO_SAVE); - g_object_set_data_full(G_OBJECT(gtkconv->imhtml), "transient_chat", + g_object_set_data_full(G_OBJECT(gtkconv->webview), "transient_chat", chat, (GDestroyNotify)purple_blist_remove_chat); } } else { @@ -3312,15 +3270,15 @@ /* gotta remain bug-compatible :( libpurple < 2.0.2 didn't handle * removing "isolated" buddy nodes well */ if (purple_version_check(2, 0, 2) == NULL) { - if ((buddy == NULL) && (gtkconv->imhtml != NULL)) { - buddy = g_object_get_data(G_OBJECT(gtkconv->imhtml), "transient_buddy"); + if ((buddy == NULL) && (gtkconv->webview != NULL)) { + buddy = g_object_get_data(G_OBJECT(gtkconv->webview), "transient_buddy"); } - if ((buddy == NULL) && (gtkconv->imhtml != NULL)) { + if ((buddy == NULL) && (gtkconv->webview != NULL)) { buddy = purple_buddy_new(conv->account, conv->name, NULL); purple_blist_node_set_flags((PurpleBlistNode *)buddy, PURPLE_BLIST_NODE_FLAG_NO_SAVE); - g_object_set_data_full(G_OBJECT(gtkconv->imhtml), "transient_buddy", + g_object_set_data_full(G_OBJECT(gtkconv->webview), "transient_buddy", buddy, (GDestroyNotify)purple_buddy_destroy); } } @@ -3728,38 +3686,7 @@ static void update_typing_message(PidginConversation *gtkconv, const char *message) { - GtkTextBuffer *buffer; - GtkTextMark *stmark, *enmark; - - if (g_object_get_data(G_OBJECT(gtkconv->imhtml), "disable-typing-notification")) - return; - - buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(gtkconv->imhtml)); - stmark = gtk_text_buffer_get_mark(buffer, "typing-notification-start"); - enmark = gtk_text_buffer_get_mark(buffer, "typing-notification-end"); - if (stmark && enmark) { - GtkTextIter start, end; - gtk_text_buffer_get_iter_at_mark(buffer, &start, stmark); - gtk_text_buffer_get_iter_at_mark(buffer, &end, enmark); - gtk_text_buffer_delete_mark(buffer, stmark); - gtk_text_buffer_delete_mark(buffer, enmark); - gtk_text_buffer_delete(buffer, &start, &end); - } else if (message && *message == '\n' && message[1] == ' ' && message[2] == '\0') - message = NULL; - -#ifdef RESERVE_LINE - if (!message) - message = "\n "; /* The blank space is required to avoid a GTK+/Pango bug */ -#endif - - if (message) { - GtkTextIter iter; - gtk_text_buffer_get_end_iter(buffer, &iter); - gtk_text_buffer_create_mark(buffer, "typing-notification-start", &iter, TRUE); - gtk_text_buffer_insert_with_tags_by_name(buffer, &iter, message, -1, "TYPING-NOTIFICATION", NULL); - gtk_text_buffer_get_end_iter(buffer, &iter); - gtk_text_buffer_create_mark(buffer, "typing-notification-end", &iter, TRUE); - } + /* this is not handled at all */ } static void @@ -4076,7 +4003,6 @@ gboolean is_buddy; gchar *tmp, *alias_key, *name, *alias; int flags; - GdkColor *color = NULL; alias = cb->alias; name = cb->name; @@ -4103,20 +4029,6 @@ alias_key = g_utf8_collate_key(tmp, -1); g_free(tmp); - if (is_me) { - GtkTextTag *tag = gtk_text_tag_table_lookup( - gtk_text_buffer_get_tag_table(GTK_IMHTML(gtkconv->imhtml)->text_buffer), - "send-name"); - g_object_get(tag, "foreground-gdk", &color, NULL); - } else { - GtkTextTag *tag; - if ((tag = get_buddy_tag(conv, name, 0, FALSE))) - g_object_set(G_OBJECT(tag), "style", PANGO_STYLE_NORMAL, NULL); - if ((tag = get_buddy_tag(conv, name, PURPLE_MESSAGE_NICK, FALSE))) - g_object_set(G_OBJECT(tag), "style", PANGO_STYLE_NORMAL, NULL); - color = (GdkColor*)get_nick_color(gtkconv, name); - } - #if GTK_CHECK_VERSION(2,6,0) gtk_list_store_insert_with_values(ls, &iter, /* @@ -4132,7 +4044,6 @@ CHAT_USERS_ALIAS_KEY_COLUMN, alias_key, CHAT_USERS_NAME_COLUMN, name, CHAT_USERS_FLAGS_COLUMN, flags, - CHAT_USERS_COLOR_COLUMN, color, CHAT_USERS_WEIGHT_COLUMN, is_buddy ? PANGO_WEIGHT_BOLD : PANGO_WEIGHT_NORMAL, -1); #else @@ -4143,13 +4054,10 @@ CHAT_USERS_ALIAS_KEY_COLUMN, alias_key, CHAT_USERS_NAME_COLUMN, name, CHAT_USERS_FLAGS_COLUMN, flags, - CHAT_USERS_COLOR_COLUMN, color, CHAT_USERS_WEIGHT_COLUMN, is_buddy ? PANGO_WEIGHT_BOLD : PANGO_WEIGHT_NORMAL, -1); #endif - if (is_me && color) - gdk_color_free(color); g_free(alias_key); } @@ -4547,7 +4455,6 @@ GtkTreeModel *model; char *normalized_name; GtkTreeIter iter; - GtkTextTag *texttag; int f; g_return_if_fail(buddy != NULL); @@ -4586,10 +4493,6 @@ blist_node_aliased_cb((PurpleBlistNode *)buddy, NULL, conv); - texttag = get_buddy_tag(conv, purple_buddy_get_name(buddy), 0, FALSE); /* XXX: do we want the normalized name? */ - if (texttag) { - g_object_set(texttag, "weight", is_buddy ? PANGO_WEIGHT_BOLD : PANGO_WEIGHT_NORMAL, NULL); - } } static void @@ -4621,7 +4524,7 @@ } static void -entry_popup_menu_cb(GtkIMHtml *imhtml, GtkMenu *menu, gpointer data) +entry_popup_menu_cb(GtkIMHtml *imhtml /* this is for ->entry, fine! */, GtkMenu *menu, gpointer data) { GtkWidget *menuitem; PidginConversation *gtkconv = data; @@ -4649,7 +4552,7 @@ GdkRectangle oneline; int height, diff; int pad_top, pad_inside, pad_bottom; - int total_height = (gtkconv->imhtml->allocation.height + gtkconv->entry->allocation.height); + int total_height = (gtkconv->webview->allocation.height + gtkconv->entry->allocation.height); int max_height = total_height / 2; int min_lines = purple_prefs_get_int(PIDGIN_PREFS_ROOT "/conversations/minimum_entry_lines"); int min_height; @@ -4888,13 +4791,13 @@ if (purple_conversation_get_type(conv) == PURPLE_CONV_TYPE_CHAT) { node = (PurpleBlistNode*)(purple_blist_find_chat(conv->account, conv->name)); if (!node) - node = g_object_get_data(G_OBJECT(gtkconv->imhtml), "transient_chat"); + node = g_object_get_data(G_OBJECT(gtkconv->webview), "transient_chat"); } else { node = (PurpleBlistNode*)(purple_find_buddy(conv->account, conv->name)); #if 0 /* Using the transient blist nodes to show the tooltip doesn't quite work yet. */ if (!node) - node = g_object_get_data(G_OBJECT(gtkconv->imhtml), "transient_buddy"); + node = g_object_get_data(G_OBJECT(gtkconv->webview), "transient_buddy"); #endif } @@ -4906,13 +4809,13 @@ static GtkWidget * setup_common_pane(PidginConversation *gtkconv) { - GtkWidget *vbox, *frame, *imhtml_sw, *event_box; + GtkWidget *vbox, *frame, *webview_sw, *event_box; GtkCellRenderer *rend; GtkTreePath *path; PurpleConversation *conv = gtkconv->active_conv; PurpleBuddy *buddy; gboolean chat = (conv->type == PURPLE_CONV_TYPE_CHAT); - GtkPolicyType imhtml_sw_hscroll; + GtkPolicyType webview_sw_hscroll; int buddyicon_size = 0; /* Setup the top part of the pane */ @@ -5008,8 +4911,15 @@ g_object_set(rend, "xalign", 0.0, "xpad", 6, "ypad", 0, NULL); /* Setup the gtkimhtml widget */ - frame = pidgin_create_imhtml(FALSE, >kconv->imhtml, NULL, &imhtml_sw); - gtk_widget_set_size_request(gtkconv->imhtml, -1, 0); + webview_sw = gtk_scrolled_window_new (NULL, NULL); + gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(webview_sw), GTK_SHADOW_IN); + gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (webview_sw), GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC); + + gtkconv->webview = gtk_webview_new (); + gtk_container_add (GTK_CONTAINER (webview_sw), gtkconv->webview); + + gtk_widget_set_size_request(gtkconv->webview, -1, 0); + if (chat) { GtkWidget *hpaned; @@ -5020,29 +4930,28 @@ hpaned = gtk_hpaned_new(); gtk_box_pack_start(GTK_BOX(vbox), hpaned, TRUE, TRUE, 0); gtk_widget_show(hpaned); - gtk_paned_pack1(GTK_PANED(hpaned), frame, TRUE, TRUE); + gtk_paned_pack1(GTK_PANED(hpaned), webview_sw, TRUE, TRUE); /* Now add the userlist */ setup_chat_userlist(gtkconv, hpaned); } else { - gtk_box_pack_start(GTK_BOX(vbox), frame, TRUE, TRUE, 0); - } - gtk_widget_show(frame); - - gtk_widget_set_name(gtkconv->imhtml, "pidgin_conv_imhtml"); - gtk_imhtml_show_comments(GTK_IMHTML(gtkconv->imhtml),TRUE); - g_object_set_data(G_OBJECT(gtkconv->imhtml), "gtkconv", gtkconv); - - gtk_scrolled_window_get_policy(GTK_SCROLLED_WINDOW(imhtml_sw), - &imhtml_sw_hscroll, NULL); - gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(imhtml_sw), - imhtml_sw_hscroll, GTK_POLICY_ALWAYS); - - g_signal_connect_after(G_OBJECT(gtkconv->imhtml), "button_press_event", + gtk_box_pack_start(GTK_BOX(vbox), webview_sw, TRUE, TRUE, 0); + } + gtk_widget_show_all(webview_sw); + + gtk_widget_set_name(gtkconv->webview, "pidgin_conv_webview"); + g_object_set_data(G_OBJECT(gtkconv->webview), "gtkconv", gtkconv); + + gtk_scrolled_window_get_policy(GTK_SCROLLED_WINDOW(webview_sw), + &webview_sw_hscroll, NULL); + gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(webview_sw), + webview_sw_hscroll, GTK_POLICY_ALWAYS); + + g_signal_connect_after(G_OBJECT(gtkconv->webview), "button_press_event", G_CALLBACK(entry_stop_rclick_cb), NULL); - g_signal_connect(G_OBJECT(gtkconv->imhtml), "key_press_event", + g_signal_connect(G_OBJECT(gtkconv->webview), "key_press_event", G_CALLBACK(refocus_entry_cb), gtkconv); - g_signal_connect(G_OBJECT(gtkconv->imhtml), "key_release_event", + g_signal_connect(G_OBJECT(gtkconv->webview), "key_release_event", G_CALLBACK(refocus_entry_cb), gtkconv); gtkconv->lower_hbox = gtk_hbox_new(FALSE, PIDGIN_HIG_BOX_SPACE); @@ -5281,36 +5190,6 @@ static void set_typing_font(GtkWidget *widget, GtkStyle *style, PidginConversation *gtkconv) { - static PangoFontDescription *font_desc = NULL; - static GdkColor *color = NULL; - static gboolean enable = TRUE; - - if (font_desc == NULL) { - char *string = NULL; - gtk_widget_style_get(widget, - "typing-notification-font", &string, - "typing-notification-color", &color, - "typing-notification-enable", &enable, - NULL); - font_desc = pango_font_description_from_string(string); - g_free(string); - if (color == NULL) { - GdkColor def = {0, 0x8888, 0x8888, 0x8888}; - color = gdk_color_copy(&def); - } - } - - gtk_text_buffer_create_tag(GTK_IMHTML(widget)->text_buffer, "TYPING-NOTIFICATION", - "foreground-gdk", color, - "font-desc", font_desc, - NULL); - - if (!enable) { - g_object_set_data(G_OBJECT(widget), "disable-typing-notification", GINT_TO_POINTER(TRUE)); - /* or may be 'gtkconv->disable_typing = TRUE;' instead? */ - } - - g_signal_handlers_disconnect_by_func(G_OBJECT(widget), set_typing_font, gtkconv); } /************************************************************************** @@ -5352,9 +5231,6 @@ } pane = setup_common_pane(gtkconv); - gtk_imhtml_set_format_functions(GTK_IMHTML(gtkconv->imhtml), - gtk_imhtml_get_format_functions(GTK_IMHTML(gtkconv->imhtml)) | GTK_IMHTML_IMAGE); - if (pane == NULL) { if (conv_type == PURPLE_CONV_TYPE_CHAT) g_free(gtkconv->u.chat); @@ -5377,7 +5253,7 @@ GTK_DEST_DEFAULT_DROP, te, sizeof(te) / sizeof(GtkTargetEntry), GDK_ACTION_COPY); - gtk_drag_dest_set(gtkconv->imhtml, 0, + gtk_drag_dest_set(gtkconv->webview, 0, te, sizeof(te) / sizeof(GtkTargetEntry), GDK_ACTION_COPY); @@ -5389,12 +5265,12 @@ G_CALLBACK(ignore_middle_click), NULL); g_signal_connect(G_OBJECT(pane), "drag_data_received", G_CALLBACK(conv_dnd_recv), gtkconv); - g_signal_connect(G_OBJECT(gtkconv->imhtml), "drag_data_received", + g_signal_connect(G_OBJECT(gtkconv->webview), "drag_data_received", G_CALLBACK(conv_dnd_recv), gtkconv); g_signal_connect(G_OBJECT(gtkconv->entry), "drag_data_received", G_CALLBACK(conv_dnd_recv), gtkconv); - g_signal_connect(gtkconv->imhtml, "style-set", G_CALLBACK(set_typing_font), gtkconv); + g_signal_connect(gtkconv->webview, "style-set", G_CALLBACK(set_typing_font), gtkconv); /* Setup the container for the tab. */ gtkconv->tab_cont = tab_cont = gtk_vbox_new(FALSE, PIDGIN_HIG_BOX_SPACE); @@ -5424,10 +5300,6 @@ else gtk_widget_hide(gtkconv->infopane_hbox); - gtk_imhtml_show_comments(GTK_IMHTML(gtkconv->imhtml), - purple_prefs_get_bool(PIDGIN_PREFS_ROOT "/conversations/show_timestamps")); - gtk_imhtml_set_protocol_name(GTK_IMHTML(gtkconv->imhtml), - purple_account_get_protocol_name(conv->account)); g_signal_connect_swapped(G_OBJECT(pane), "focus", G_CALLBACK(gtk_widget_grab_focus), @@ -5440,7 +5312,7 @@ if (nick_colors == NULL) { nbr_nick_colors = NUM_NICK_COLORS; - nick_colors = generate_nick_colors(&nbr_nick_colors, gtk_widget_get_style(gtkconv->imhtml)->base[GTK_STATE_NORMAL]); + nick_colors = generate_nick_colors(&nbr_nick_colors, gtk_widget_get_style(gtkconv->webview)->base[GTK_STATE_NORMAL]); } if (conv->features & PURPLE_CONNECTION_ALLOW_CUSTOM_SMILEY) @@ -5647,43 +5519,6 @@ return FALSE; } -static GtkTextTag *get_buddy_tag(PurpleConversation *conv, const char *who, PurpleMessageFlags flag, - gboolean create) -{ - PidginConversation *gtkconv = PIDGIN_CONVERSATION(conv); - GtkTextTag *buddytag; - gchar *str; - gboolean highlight = (flag & PURPLE_MESSAGE_NICK); - GtkTextBuffer *buffer = GTK_IMHTML(gtkconv->imhtml)->text_buffer; - - str = g_strdup_printf(highlight ? "HILIT %s" : "BUDDY %s", who); - - buddytag = gtk_text_tag_table_lookup( - gtk_text_buffer_get_tag_table(buffer), str); - - if (buddytag == NULL && create) { - if (highlight) - buddytag = gtk_text_buffer_create_tag(buffer, str, - "foreground", get_text_tag_color(gtk_text_tag_table_lookup( - gtk_text_buffer_get_tag_table(buffer), "highlight-name")), - "weight", PANGO_WEIGHT_BOLD, - NULL); - else - buddytag = gtk_text_buffer_create_tag( - buffer, str, - "foreground-gdk", get_nick_color(gtkconv, who), - "weight", purple_find_buddy(purple_conversation_get_account(conv), who) ? PANGO_WEIGHT_BOLD : PANGO_WEIGHT_NORMAL, - NULL); - - g_signal_connect(G_OBJECT(buddytag), "event", - G_CALLBACK(buddytag_event), conv); - } - - g_free(str); - - return buddytag; -} - static void pidgin_conv_calculate_newday(PidginConversation *gtkconv, time_t mtime) { struct tm *tm = localtime(&mtime); @@ -5740,10 +5575,6 @@ PurpleConnection *gc; PurpleAccount *account; PurplePluginProtocolInfo *prpl_info; - int gtk_font_options = 0; - int gtk_font_options_all = 0; - int max_scrollback_lines; - int line_count; char buf2[BUF_LONG]; gboolean show_date; char *mdate; @@ -5754,8 +5585,6 @@ PurpleConversationType type; char *displaying; gboolean plugin_return; - char *bracket; - int tag_count = 0; gboolean is_rtl_message = FALSE; g_return_if_fail(conv != NULL); @@ -5813,59 +5642,13 @@ } length = strlen(displaying) + 1; - /* Awful hack to work around GtkIMHtml's inefficient rendering of messages with lots of formatting changes. - * If a message has over 100 '<' characters, strip formatting before appending it. Hopefully nobody actually - * needs that much formatting, anyway. - */ - for (bracket = strchr(displaying, '<'); bracket && *(bracket + 1); bracket = strchr(bracket + 1, '<')) - tag_count++; - - if (tag_count > 100) { - char *tmp = displaying; - displaying = purple_markup_strip_html(tmp); - g_free(tmp); - } win = gtkconv->win; prpl_info = gc ? PURPLE_PLUGIN_PROTOCOL_INFO(gc->prpl) : NULL; - line_count = gtk_text_buffer_get_line_count( - gtk_text_view_get_buffer(GTK_TEXT_VIEW( - gtkconv->imhtml))); - - max_scrollback_lines = purple_prefs_get_int( - PIDGIN_PREFS_ROOT "/conversations/scrollback_lines"); - /* If we're sitting at more than 100 lines more than the - max scrollback, trim down to max scrollback */ - if (max_scrollback_lines > 0 - && line_count > (max_scrollback_lines + 100)) { - GtkTextBuffer *text_buffer = gtk_text_view_get_buffer( - GTK_TEXT_VIEW(gtkconv->imhtml)); - GtkTextIter start, end; - - gtk_text_buffer_get_start_iter(text_buffer, &start); - gtk_text_buffer_get_iter_at_line(text_buffer, &end, - (line_count - max_scrollback_lines)); - gtk_imhtml_delete(GTK_IMHTML(gtkconv->imhtml), &start, &end); - } - - if (type == PURPLE_CONV_TYPE_CHAT) - { - /* Create anchor for user */ - GtkTextIter iter; - char *tmp = g_strconcat("user:", name, NULL); - - gtk_text_buffer_get_end_iter(gtk_text_view_get_buffer(GTK_TEXT_VIEW(gtkconv->imhtml)), &iter); - gtk_text_buffer_create_mark(gtk_text_view_get_buffer(GTK_TEXT_VIEW(gtkconv->imhtml)), - tmp, &iter, TRUE); - g_free(tmp); - } - - if (purple_prefs_get_bool(PIDGIN_PREFS_ROOT "/conversations/use_smooth_scrolling")) - gtk_font_options_all |= GTK_IMHTML_USE_SMOOTHSCROLLING; - - if (gtk_text_buffer_get_char_count(gtk_text_view_get_buffer(GTK_TEXT_VIEW(gtkconv->imhtml)))) - gtk_imhtml_append_text(GTK_IMHTML(gtkconv->imhtml), "<BR>", gtk_font_options_all | GTK_IMHTML_NO_SCROLL); + /* if the buffer is not empty add a <br> */ + if (!gtk_webview_is_empty (GTK_WEBVIEW(gtkconv->webview))) + gtk_webview_append_html (GTK_WEBVIEW(gtkconv->webview), "<br />"); /* First message in a conversation. */ if (gtkconv->newday == 0) @@ -5901,47 +5684,29 @@ sml_attrib = g_strdup_printf("sml=\"%s\"", purple_account_get_protocol_name(account)); - gtk_font_options |= GTK_IMHTML_NO_COMMENTS; - - if ((flags & PURPLE_MESSAGE_RECV) && - !purple_prefs_get_bool(PIDGIN_PREFS_ROOT "/conversations/show_incoming_formatting")) - gtk_font_options |= GTK_IMHTML_NO_COLOURS | GTK_IMHTML_NO_FONTS | GTK_IMHTML_NO_SIZES | GTK_IMHTML_NO_FORMATTING; - - /* this is gonna crash one day, I can feel it. */ - if (PURPLE_PLUGIN_PROTOCOL_INFO(purple_find_prpl(purple_account_get_protocol_id(conv->account)))->options & - OPT_PROTO_USE_POINTSIZE) { - gtk_font_options |= GTK_IMHTML_USE_POINTSIZE; - } - - if (!(flags & PURPLE_MESSAGE_RECV) && (conv->features & PURPLE_CONNECTION_ALLOW_CUSTOM_SMILEY)) - { - /* We want to see our own smileys. Need to revert it after send*/ - pidgin_themes_smiley_themeize_custom(gtkconv->imhtml); - } - /* TODO: These colors should not be hardcoded so log.c can use them */ if (flags & PURPLE_MESSAGE_RAW) { - gtk_imhtml_append_text(GTK_IMHTML(gtkconv->imhtml), message, gtk_font_options_all); + gtk_webview_append_html (GTK_WEBVIEW(gtkconv->webview), message); } else if (flags & PURPLE_MESSAGE_SYSTEM) { g_snprintf(buf2, sizeof(buf2), - "<FONT %s><FONT SIZE=\"2\"><!--%s --></FONT><B>%s</B></FONT>", + "<font %s><font size=\"2\"><span class='timestamp'>%s</span></font><b>%s</b></font>", sml_attrib ? sml_attrib : "", mdate, displaying); - gtk_imhtml_append_text(GTK_IMHTML(gtkconv->imhtml), buf2, gtk_font_options_all); + gtk_webview_append_html(GTK_WEBVIEW(gtkconv->webview), buf2); } else if (flags & PURPLE_MESSAGE_ERROR) { g_snprintf(buf2, sizeof(buf2), - "<FONT COLOR=\"#ff0000\"><FONT %s><FONT SIZE=\"2\"><!--%s --></FONT><B>%s</B></FONT></FONT>", + "<font color=\"#ff0000\"><font %s><font size=\"2\"><span class='timestamp'>%s</span> </font><b>%s</b></font></font>", sml_attrib ? sml_attrib : "", mdate, displaying); - gtk_imhtml_append_text(GTK_IMHTML(gtkconv->imhtml), buf2, gtk_font_options_all); + gtk_webview_append_html(GTK_WEBVIEW(gtkconv->webview), buf2); } else if (flags & PURPLE_MESSAGE_NO_LOG) { g_snprintf(buf2, BUF_LONG, - "<B><FONT %s COLOR=\"#777777\">%s</FONT></B>", + "<b><font %s color=\"#777777\">%s</font></b>", sml_attrib ? sml_attrib : "", displaying); - gtk_imhtml_append_text(GTK_IMHTML(gtkconv->imhtml), buf2, gtk_font_options_all); + gtk_webview_append_html(GTK_WEBVIEW(gtkconv->webview), buf2); } else { char *new_message = g_memdup(displaying, length); char *alias_escaped = (alias ? g_markup_escape_text(alias, strlen(alias)) : g_strdup("")); @@ -5951,11 +5716,6 @@ int tag_end_offset = 0; const char *tagname = NULL; - GtkTextIter start, end; - GtkTextMark *mark; - GtkTextTag *tag; - GtkTextBuffer *buffer = GTK_IMHTML(gtkconv->imhtml)->text_buffer; - /* Enforce direction on alias */ if (is_rtl_message) str_embed_direction_chars(&alias_escaped); @@ -6016,55 +5776,27 @@ g_free(alias_escaped); - if (tagname) - tag = gtk_text_tag_table_lookup(gtk_text_buffer_get_tag_table(buffer), tagname); - else - tag = get_buddy_tag(conv, name, flags, TRUE); - - if (GTK_IMHTML(gtkconv->imhtml)->show_comments) { - /* The color for the timestamp has to be set in the font-tags, unfortunately. - * Applying the nick-tag to timestamps would work, but that can make it - * bold. I thought applying the "comment" tag again, which has "weight" set - * to PANGO_WEIGHT_NORMAL, would remove the boldness. But it doesn't. So - * this will have to do. I don't terribly like it. -- sadrul */ - const char *color = get_text_tag_color(tag); - g_snprintf(buf2, BUF_LONG, "<FONT %s%s%s SIZE=\"2\"><!--%s --></FONT>", - color ? "COLOR=\"" : "", color ? color : "", color ? "\"" : "", mdate); - gtk_imhtml_append_text(GTK_IMHTML(gtkconv->imhtml), buf2, gtk_font_options_all | GTK_IMHTML_NO_SCROLL); - } - - gtk_text_buffer_get_end_iter(buffer, &end); - mark = gtk_text_buffer_create_mark(buffer, NULL, &end, TRUE); - - g_snprintf(buf2, BUF_LONG, "<FONT %s>%s</FONT> ", sml_attrib ? sml_attrib : "", str); - gtk_imhtml_append_text(GTK_IMHTML(gtkconv->imhtml), buf2, gtk_font_options_all | GTK_IMHTML_NO_SCROLL); - - gtk_text_buffer_get_end_iter(buffer, &end); - gtk_text_buffer_get_iter_at_mark(buffer, &start, mark); - gtk_text_buffer_apply_tag(buffer, tag, &start, &end); - gtk_text_buffer_delete_mark(buffer, mark); + /* timestamp */ + { + g_snprintf (buf2, BUF_LONG, "<font size='2'>%s </font>", mdate); + + gtk_webview_append_html (GTK_WEBVIEW(gtkconv->webview), buf2); + } + g_snprintf(buf2, BUF_LONG, "<font %s>%s</font> ", sml_attrib ? sml_attrib : "", str); + gtk_webview_append_html(GTK_WEBVIEW(gtkconv->webview), buf2); g_free(str); if(gc){ char *pre = g_strdup_printf("<font %s>", sml_attrib ? sml_attrib : ""); char *post = "</font>"; - int pre_len = strlen(pre); - int post_len = strlen(post); - - with_font_tag = g_malloc(length + pre_len + post_len + 1); - - strcpy(with_font_tag, pre); - memcpy(with_font_tag + pre_len, new_message, length); - strcpy(with_font_tag + pre_len + length, post); - - length += pre_len + post_len; + with_font_tag = g_strdup_printf ("%s%s%s", pre, new_message, post); g_free(pre); } else with_font_tag = g_memdup(new_message, length); - gtk_imhtml_append_text(GTK_IMHTML(gtkconv->imhtml), - with_font_tag, gtk_font_options | gtk_font_options_all); + gtk_webview_append_html(GTK_WEBVIEW(gtkconv->webview), + with_font_tag); g_free(with_font_tag); g_free(new_message); @@ -6091,12 +5823,6 @@ gtkconv_set_unseen(gtkconv, unseen); } - if (!(flags & PURPLE_MESSAGE_RECV) && (conv->features & PURPLE_CONNECTION_ALLOW_CUSTOM_SMILEY)) - { - /* Restore the smiley-data */ - pidgin_themes_smiley_themeize(gtkconv->imhtml); - } - purple_signal_emit(pidgin_conversations_get_handle(), (type == PURPLE_CONV_TYPE_IM ? "displayed-im-msg" : "displayed-chat-msg"), account, name, displaying, conv, flags); @@ -6188,10 +5914,6 @@ g_free(val); } - if ((tag = get_buddy_tag(conv, old_name, 0, FALSE))) - g_object_set(G_OBJECT(tag), "style", PANGO_STYLE_ITALIC, NULL); - if ((tag = get_buddy_tag(conv, old_name, PURPLE_MESSAGE_NICK, FALSE))) - g_object_set(G_OBJECT(tag), "style", PANGO_STYLE_ITALIC, NULL); if (!purple_conv_chat_find_user(chat, old_name)) return; @@ -6249,10 +5971,7 @@ g_free(val); } while (f); - if ((tag = get_buddy_tag(conv, l->data, 0, FALSE))) - g_object_set(G_OBJECT(tag), "style", PANGO_STYLE_ITALIC, NULL); - if ((tag = get_buddy_tag(conv, l->data, PURPLE_MESSAGE_NICK, FALSE))) - g_object_set(G_OBJECT(tag), "style", PANGO_STYLE_ITALIC, NULL); + } g_snprintf(tmp, sizeof(tmp), @@ -6356,99 +6075,17 @@ return TRUE; } -static gboolean -pidgin_conv_custom_smiley_add(PurpleConversation *conv, const char *smile, gboolean remote) -{ - PidginConversation *gtkconv; - struct smiley_list *list; - const char *sml = NULL, *conv_sml; - - if (!conv || !smile || !*smile) { - return FALSE; - } - - /* If smileys are off, return false */ - if (pidgin_themes_smileys_disabled()) - return FALSE; - - /* If possible add this smiley to the current theme. - * The addition is only temporary: custom smilies aren't saved to disk. */ - conv_sml = purple_account_get_protocol_name(conv->account); - gtkconv = PIDGIN_CONVERSATION(conv); - - for (list = (struct smiley_list *)current_smiley_theme->list; list; list = list->next) { - if (!strcmp(list->sml, conv_sml)) { - sml = list->sml; - break; - } - } - - if (!add_custom_smiley_for_imhtml(GTK_IMHTML(gtkconv->imhtml), sml, smile)) - return FALSE; - - if (!remote) /* If it's a local custom smiley, then add it for the entry */ - if (!add_custom_smiley_for_imhtml(GTK_IMHTML(gtkconv->entry), sml, smile)) - return FALSE; - - return TRUE; -} - static void pidgin_conv_custom_smiley_write(PurpleConversation *conv, const char *smile, const guchar *data, gsize size) { - PidginConversation *gtkconv; - GtkIMHtmlSmiley *smiley; - GdkPixbufLoader *loader; - const char *sml; - - sml = purple_account_get_protocol_name(conv->account); - gtkconv = PIDGIN_CONVERSATION(conv); - smiley = gtk_imhtml_smiley_get(GTK_IMHTML(gtkconv->imhtml), sml, smile); - - if (!smiley) - return; - - smiley->data = g_realloc(smiley->data, smiley->datasize + size); - g_memmove((guchar *)smiley->data + smiley->datasize, data, size); - smiley->datasize += size; - - loader = smiley->loader; - if (!loader) - return; - - gdk_pixbuf_loader_write(loader, data, size, NULL); + return; } static void pidgin_conv_custom_smiley_close(PurpleConversation *conv, const char *smile) { - PidginConversation *gtkconv; - GtkIMHtmlSmiley *smiley; - GdkPixbufLoader *loader; - const char *sml; - - g_return_if_fail(conv != NULL); - g_return_if_fail(smile != NULL); - - sml = purple_account_get_protocol_name(conv->account); - gtkconv = PIDGIN_CONVERSATION(conv); - smiley = gtk_imhtml_smiley_get(GTK_IMHTML(gtkconv->imhtml), sml, smile); - - if (!smiley) - return; - - loader = smiley->loader; - - if (!loader) - return; - - - - purple_debug_info("gtkconv", "About to close the smiley pixbuf\n"); - - gdk_pixbuf_loader_close(loader, NULL); - + return; } static void @@ -6716,8 +6353,6 @@ } } - if (fields & PIDGIN_CONV_SMILEY_THEME) - pidgin_themes_smiley_themeize(PIDGIN_CONVERSATION(conv)->imhtml); if ((fields & PIDGIN_CONV_COLORIZE_TITLE) || (fields & PIDGIN_CONV_SET_TITLE) || @@ -6942,7 +6577,7 @@ pidgin_conv_chat_update_user, /* chat_update_user */ pidgin_conv_present_conversation, /* present */ pidgin_conv_has_focus, /* has_focus */ - pidgin_conv_custom_smiley_add, /* custom_smiley_add */ + NULL, /* custom_smiley_add */ pidgin_conv_custom_smiley_write, /* custom_smiley_write */ pidgin_conv_custom_smiley_close, /* custom_smiley_close */ pidgin_conv_send_confirm, /* send_confirm */ @@ -7349,8 +6984,6 @@ GTK_CHECK_MENU_ITEM(win->menu.show_timestamps), (gboolean)GPOINTER_TO_INT(value)); - gtk_imhtml_show_comments(GTK_IMHTML(gtkconv->imhtml), - (gboolean)GPOINTER_TO_INT(value)); } } @@ -7745,7 +7378,7 @@ while (gtkconv->attach.current && count < 100) { /* XXX: 100 is a random value here */ PurpleConvMessage *msg = gtkconv->attach.current->data; if (!im && when && when < msg->when) { - gtk_imhtml_append_text(GTK_IMHTML(gtkconv->imhtml), "<BR><HR>", 0); + gtk_webview_append_html(GTK_WEBVIEW(gtkconv->webview), "<BR><HR>"); g_object_set_data(G_OBJECT(gtkconv->entry), "attach-start-time", NULL); } pidgin_conv_write_conv(msg->conv, msg->who, msg->alias, msg->what, msg->flags, msg->when); @@ -7780,7 +7413,7 @@ PurpleConvMessage *msg = msgs->data; pidgin_conv_write_conv(msg->conv, msg->who, msg->alias, msg->what, msg->flags, msg->when); } - gtk_imhtml_append_text(GTK_IMHTML(gtkconv->imhtml), "<BR><HR>", 0); + gtk_webview_append_html(GTK_WEBVIEW(gtkconv->webview), "<BR><HR>"); g_object_set_data(G_OBJECT(gtkconv->entry), "attach-start-time", NULL); }
--- a/pidgin/gtkconv.h Tue Oct 13 19:52:35 2009 +0000 +++ b/pidgin/gtkconv.h Wed Oct 14 02:41:11 2009 +0000 @@ -128,7 +128,7 @@ GtkWidget *tabby; GtkWidget *menu_tabby; - GtkWidget *imhtml; + GtkWidget *webview; GtkTextBuffer *entry_buffer; GtkWidget *entry; gboolean auto_resize; /* this is set to TRUE if the conversation
--- a/pidgin/gtkdialogs.c Tue Oct 13 19:52:35 2009 +0000 +++ b/pidgin/gtkdialogs.c Wed Oct 14 02:41:11 2009 +0000 @@ -38,12 +38,13 @@ #include "gtkblist.h" #include "gtkdialogs.h" -#include "gtkimhtml.h" -#include "gtkimhtmltoolbar.h" #include "gtklog.h" #include "gtkutils.h" +#include "gtkwebview.h" #include "pidginstock.h" + + static GList *dialogwindows = NULL; static GtkWidget *about = NULL; @@ -402,10 +403,10 @@ { GtkWidget *vbox; GtkWidget *logo; - GtkWidget *frame; - GtkWidget *text; + GtkWidget *scrolled_window; GtkWidget *button; - GtkTextIter iter; + GtkWidget *web_view; + GString *str; AtkObject *obj; char* filename, *tmp; @@ -443,9 +444,14 @@ g_free(tmp); gtk_box_pack_start(GTK_BOX(vbox), logo, FALSE, FALSE, 0); - frame = pidgin_create_imhtml(FALSE, &text, NULL, NULL); - gtk_imhtml_set_format_functions(GTK_IMHTML(text), GTK_IMHTML_ALL ^ GTK_IMHTML_SMILEY); - gtk_box_pack_start(GTK_BOX(vbox), frame, TRUE, TRUE, 0); + scrolled_window = gtk_scrolled_window_new (NULL, NULL); + gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(scrolled_window), GTK_SHADOW_IN); + gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scrolled_window), GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC); + + web_view = gtk_webview_new (); + gtk_container_add (GTK_CONTAINER (scrolled_window), web_view); + + gtk_box_pack_start(GTK_BOX(vbox), scrolled_window, TRUE, TRUE, 0); str = g_string_sized_new(4096); @@ -698,11 +704,9 @@ /* End of not to be translated section */ - gtk_imhtml_append_text(GTK_IMHTML(text), str->str, GTK_IMHTML_NO_SCROLL); + webkit_web_view_load_html_string (WEBKIT_WEB_VIEW(web_view), str->str, ""); g_string_free(str, TRUE); - gtk_text_buffer_get_start_iter(gtk_text_view_get_buffer(GTK_TEXT_VIEW(text)), &iter); - gtk_text_buffer_place_cursor(gtk_text_view_get_buffer(GTK_TEXT_VIEW(text)), &iter); /* Close Button */ button = pidgin_dialog_add_button(GTK_DIALOG(about), GTK_STOCK_CLOSE,
--- a/pidgin/gtklog.c Tue Oct 13 19:52:35 2009 +0000 +++ b/pidgin/gtklog.c Wed Oct 14 02:41:11 2009 +0000 @@ -35,9 +35,9 @@ #include "pidginstock.h" #include "gtkblist.h" -#include "gtkimhtml.h" #include "gtklog.h" #include "gtkutils.h" +#include "gtkwebview.h" static GHashTable *log_viewers = NULL; static void populate_log_tree(PidginLogViewer *lv); @@ -130,7 +130,7 @@ populate_log_tree(lv); g_free(lv->search); lv->search = NULL; - gtk_imhtml_search_clear(GTK_IMHTML(lv->imhtml)); + webkit_web_view_unmark_text_matches(WEBKIT_WEB_VIEW(lv->web_view)); select_first_log(lv); return; } @@ -138,7 +138,7 @@ if (lv->search != NULL && !strcmp(lv->search, search_term)) { /* Searching for the same term acts as "Find Next" */ - gtk_imhtml_search_find(GTK_IMHTML(lv->imhtml), lv->search); + webkit_web_view_search_text (WEBKIT_WEB_VIEW(lv->web_view), lv->search, FALSE, TRUE, TRUE); return; } @@ -148,7 +148,7 @@ lv->search = g_strdup(search_term); gtk_tree_store_clear(lv->treestore); - gtk_imhtml_clear(GTK_IMHTML(lv->imhtml)); + webkit_web_view_open (WEBKIT_WEB_VIEW (lv->web_view), "about:blank"); /* clear the view */ for (logs = lv->logs; logs != NULL; logs = logs->next) { char *read = purple_log_read((PurpleLog*)logs->data, NULL); @@ -422,7 +422,9 @@ static gboolean search_find_cb(gpointer data) { PidginLogViewer *viewer = data; - gtk_imhtml_search_find(GTK_IMHTML(viewer->imhtml), viewer->search); + webkit_web_view_mark_text_matches (WEBKIT_WEB_VIEW (viewer->web_view), viewer->search, FALSE, 0); + webkit_web_view_set_highlight_text_matches (WEBKIT_WEB_VIEW (viewer->web_view), TRUE); + webkit_web_view_search_text (WEBKIT_WEB_VIEW (viewer->web_view), viewer->search, FALSE, TRUE, TRUE); return FALSE; } @@ -463,19 +465,15 @@ read = purple_log_read(log, &flags); viewer->flags = flags; - gtk_imhtml_clear(GTK_IMHTML(viewer->imhtml)); - gtk_imhtml_set_protocol_name(GTK_IMHTML(viewer->imhtml), - purple_account_get_protocol_name(log->account)); + webkit_web_view_open (WEBKIT_WEB_VIEW(viewer->web_view), "about:blank"); purple_signal_emit(pidgin_log_get_handle(), "log-displaying", viewer, log); - gtk_imhtml_append_text(GTK_IMHTML(viewer->imhtml), read, - GTK_IMHTML_NO_COMMENTS | GTK_IMHTML_NO_TITLE | GTK_IMHTML_NO_SCROLL | - ((flags & PURPLE_LOG_READ_NO_NEWLINE) ? GTK_IMHTML_NO_NEWLINE : 0)); + webkit_web_view_load_html_string (WEBKIT_WEB_VIEW(viewer->web_view), read, ""); g_free(read); if (viewer->search != NULL) { - gtk_imhtml_search_clear(GTK_IMHTML(viewer->imhtml)); + webkit_web_view_unmark_text_matches(WEBKIT_WEB_VIEW(viewer->web_view)); g_idle_add(search_find_cb, viewer); } @@ -658,11 +656,16 @@ gtk_paned_add2(GTK_PANED(pane), vbox); /* Viewer ************/ - frame = pidgin_create_imhtml(FALSE, &lv->imhtml, NULL, NULL); - gtk_widget_set_name(lv->imhtml, "pidgin_log_imhtml"); - gtk_widget_set_size_request(lv->imhtml, 320, 200); - gtk_box_pack_start(GTK_BOX(vbox), frame, TRUE, TRUE, 0); - gtk_widget_show(frame); + sw = gtk_scrolled_window_new (NULL, NULL); + gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (sw), GTK_SHADOW_IN); + gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(sw), GTK_POLICY_NEVER, GTK_POLICY_ALWAYS); + + lv->web_view = gtk_webview_new (); + gtk_container_add (GTK_CONTAINER (sw), lv->web_view); + gtk_widget_set_name(lv->web_view, "pidgin_log_web_view"); + gtk_widget_set_size_request(lv->web_view, 320, 200); + gtk_box_pack_start(GTK_BOX(vbox), sw, TRUE, TRUE, 0); + gtk_widget_show(sw); /* Search box **********/ hbox = gtk_hbox_new(FALSE, PIDGIN_HIG_BOX_SPACE);
--- a/pidgin/gtklog.h Tue Oct 13 19:52:35 2009 +0000 +++ b/pidgin/gtklog.h Wed Oct 14 02:41:11 2009 +0000 @@ -43,7 +43,7 @@ GtkWidget *window; /**< The viewer's window */ GtkTreeStore *treestore; /**< The treestore containing said logs */ GtkWidget *treeview; /**< The treeview representing said treestore */ - GtkWidget *imhtml; /**< The imhtml to display said logs */ + GtkWidget *web_view; /**< The webkit web view to display said logs */ GtkWidget *entry; /**< The search entry, in which search terms * are entered */ PurpleLogReadFlags flags; /**< The most recently used log flags */
--- a/pidgin/gtknotify.c Tue Oct 13 19:52:35 2009 +0000 +++ b/pidgin/gtknotify.c Wed Oct 14 02:41:11 2009 +0000 @@ -36,10 +36,10 @@ #include "util.h" #include "gtkblist.h" -#include "gtkimhtml.h" #include "gtknotify.h" #include "gtkpounce.h" #include "gtkutils.h" +#include "gtkwebview.h" typedef struct { @@ -815,21 +815,6 @@ return FALSE; } -static GtkIMHtmlOptions -notify_imhtml_options(void) -{ - GtkIMHtmlOptions options = 0; - - if (!purple_prefs_get_bool(PIDGIN_PREFS_ROOT "/conversations/show_incoming_formatting")) - options |= GTK_IMHTML_NO_COLOURS | GTK_IMHTML_NO_FONTS | GTK_IMHTML_NO_SIZES; - - options |= GTK_IMHTML_NO_COMMENTS; - options |= GTK_IMHTML_NO_TITLE; - options |= GTK_IMHTML_NO_NEWLINE; - options |= GTK_IMHTML_NO_SCROLL; - return options; -} - static void * pidgin_notify_formatted(const char *title, const char *primary, const char *secondary, const char *text) @@ -838,8 +823,8 @@ GtkWidget *vbox; GtkWidget *label; GtkWidget *button; - GtkWidget *imhtml; - GtkWidget *frame; + GtkWidget *web_view; + GtkWidget *scrolled_window; char label_text[2048]; char *linked_text, *primary_esc, *secondary_esc; @@ -874,14 +859,18 @@ gtk_box_pack_start(GTK_BOX(vbox), label, FALSE, FALSE, 0); gtk_widget_show(label); - /* Add the imhtml */ - frame = pidgin_create_imhtml(FALSE, &imhtml, NULL, NULL); - gtk_widget_set_name(imhtml, "pidgin_notify_imhtml"); - gtk_imhtml_set_format_functions(GTK_IMHTML(imhtml), - gtk_imhtml_get_format_functions(GTK_IMHTML(imhtml)) | GTK_IMHTML_IMAGE); - gtk_widget_set_size_request(imhtml, 300, 250); - gtk_box_pack_start(GTK_BOX(vbox), frame, TRUE, TRUE, 0); - gtk_widget_show(frame); + /* Add the webview */ + scrolled_window = gtk_scrolled_window_new (NULL, NULL); + gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(scrolled_window), GTK_SHADOW_IN); + gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scrolled_window), GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC); + + web_view = gtk_webview_new (); + gtk_container_add (GTK_CONTAINER (scrolled_window), web_view); + + gtk_widget_set_name(web_view, "pidgin_notify_webview"); + gtk_widget_set_size_request(web_view, 300, 250); + gtk_box_pack_start(GTK_BOX(vbox), scrolled_window, TRUE, TRUE, 0); + gtk_widget_show_all(scrolled_window); /* Add the Close button. */ button = gtk_dialog_add_button(GTK_DIALOG(window), GTK_STOCK_CLOSE, GTK_RESPONSE_CLOSE); @@ -894,10 +883,10 @@ /* Make sure URLs are clickable */ linked_text = purple_markup_linkify(text); - gtk_imhtml_append_text(GTK_IMHTML(imhtml), linked_text, notify_imhtml_options()); + webkit_web_view_load_html_string (WEBKIT_WEB_VIEW (web_view), linked_text, ""); g_free(linked_text); - g_object_set_data(G_OBJECT(window), "info-widget", imhtml); + g_object_set_data(G_OBJECT(window), "webview-widget", web_view); /* Show the window */ pidgin_auto_parent_window(window); @@ -1156,10 +1145,11 @@ info = purple_notify_user_info_get_text_with_newline(user_info, "<br />"); pinfo = g_hash_table_lookup(userinfo, key); if (pinfo != NULL) { - GtkIMHtml *imhtml = g_object_get_data(G_OBJECT(pinfo->window), "info-widget"); + GtkWidget *webview = g_object_get_data(G_OBJECT(pinfo->window), "webview-widget"); char *linked_text = purple_markup_linkify(info); - gtk_imhtml_clear(imhtml); - gtk_imhtml_append_text(imhtml, linked_text, notify_imhtml_options()); + g_assert (webview); + printf ("%s\n", linked_text); + gtk_webview_load_html_string_with_imgstore (GTK_WEBVIEW (webview), linked_text); g_free(linked_text); g_free(key); ui_handle = pinfo->window;
--- a/pidgin/gtkthemes.c Tue Oct 13 19:52:35 2009 +0000 +++ b/pidgin/gtkthemes.c Wed Oct 14 02:41:11 2009 +0000 @@ -271,6 +271,8 @@ if (*i == '[' && strchr(i, ']') && load) { struct smiley_list *child = g_new0(struct smiley_list, 1); child->sml = g_strndup(i+1, strchr(i, ']') - i - 1); + child->files = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free); + if (theme->list) list->next = child; else @@ -321,6 +323,7 @@ } else { GtkIMHtmlSmiley *smiley = gtk_imhtml_smiley_create(sfile, l, hidden, 0); list->smileys = g_slist_prepend(list->smileys, smiley); + g_hash_table_insert (list->files, g_strdup(l), g_strdup(sfile)); } while (isspace(*i)) i++; @@ -359,7 +362,6 @@ if (PIDGIN_IS_PIDGIN_CONVERSATION(conv)) { /* We want to see our custom smileys on our entry if we write the shortcut */ - pidgin_themes_smiley_themeize(PIDGIN_CONVERSATION(conv)->imhtml); pidgin_themes_smiley_themeize_custom(PIDGIN_CONVERSATION(conv)->entry); } }
--- a/pidgin/gtkthemes.h Tue Oct 13 19:52:35 2009 +0000 +++ b/pidgin/gtkthemes.h Wed Oct 14 02:41:11 2009 +0000 @@ -29,6 +29,7 @@ struct smiley_list { char *sml; GSList *smileys; + GHashTable *files; /**< map from smiley shortcut to filename */ struct smiley_list *next; };
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/pidgin/gtkwebview.c Wed Oct 14 02:41:11 2009 +0000 @@ -0,0 +1,336 @@ +/* + * @file gtkwebview.c GTK+ WebKitWebView wrapper class. + * @ingroup pidgin + */ + +/* pidgin + * + * Pidgin is the legal property of its developers, whose names are too numerous + * to list here. Please refer to the COPYRIGHT file distributed with this + * source distribution. + * + * This program is free software; you can redistribute it and/or modify + * under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA + * + */ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <ctype.h> +#include <string.h> +#include <glib.h> +#include <glib/gstdio.h> +#include <JavaScriptCore/JavaScript.h> + +#include "util.h" +#include "gtkwebview.h" +#include "imgstore.h" + +static WebKitWebViewClass *parent_class = NULL; + +struct GtkWebViewPriv { + GHashTable *images; /**< a map from id to temporary file for the image */ + gboolean empty; /**< whether anything has been appended **/ + + /* JS execute queue */ + GQueue *js_queue; + gboolean is_loading; +}; + +GtkWidget* gtk_webview_new (void) +{ + GtkWebView* ret = GTK_WEBVIEW (g_object_new(gtk_webview_get_type(), NULL)); + return GTK_WIDGET (ret); +} + +static char* +get_image_filename_from_id (GtkWebView* view, int id) +{ + char *filename = NULL; + FILE *file; + PurpleStoredImage* img; + + if (!view->priv->images) + view->priv->images = g_hash_table_new_full (g_direct_hash, g_direct_equal, NULL, g_free); + + filename = (char*) g_hash_table_lookup (view->priv->images, GINT_TO_POINTER (id)); + if (filename) return filename; + + /* else get from img store */ + file = purple_mkstemp (&filename, TRUE); + + img = purple_imgstore_find_by_id (id); + + fwrite (purple_imgstore_get_data (img), purple_imgstore_get_size (img), 1, file); + g_hash_table_insert (view->priv->images, GINT_TO_POINTER (id), filename); + fclose (file); + return filename; +} + +static void +clear_single_image (gpointer key, gpointer value, gpointer userdata) +{ + g_unlink ((char*) value); +} + +static void +clear_images (GtkWebView* view) +{ + if (!view->priv->images) return; + g_hash_table_foreach (view->priv->images, clear_single_image, NULL); + g_hash_table_unref (view->priv->images); +} + +/* + * Replace all <img id=""> tags with <img src="">. I hoped to never + * write any HTML parsing code, but I'm forced to do this, until + * purple changes the way it works. + */ +static char* +replace_img_id_with_src (GtkWebView *view, const char* html) +{ + GString *buffer = g_string_sized_new (strlen (html)); + const char* cur = html; + char *id; + int nid; + + while (*cur) { + const char* img = strstr (cur, "<img"); + if (!img) { + g_string_append (buffer, cur); + break; + } else + g_string_append_len (buffer, cur, img - cur); + + cur = strstr (img, "/>"); + if (!cur) + cur = strstr (img, ">"); + + if (!cur) { /* invalid html? */ + g_string_printf (buffer, "%s", html); + break; + } + + if (strstr (img, "src=") || !strstr (img, "id=")) { + g_string_printf (buffer, "%s", html); + break; + } + + /* + * if this is valid HTML, then I can be sure that it + * has an id= and does not have an src=, since + * '=' cannot appear in parameters. + */ + + id = strstr (img, "id=") + 3; + + /* *id can't be \0, since a ">" appears after this */ + if (isdigit (*id)) + nid = atoi (id); + else + nid = atoi (id+1); + + /* let's dump this, tag and then dump the src information */ + g_string_append_len (buffer, img, cur - img); + + g_string_append_printf (buffer, " src='file://%s' ", get_image_filename_from_id (view, nid)); + } + + return g_string_free (buffer, FALSE); +} + +static void +gtk_webview_finalize (GObject *view) +{ + gpointer temp; + + while ((temp = g_queue_pop_head (GTK_WEBVIEW(view)->priv->js_queue))) + g_free (temp); + g_queue_free (GTK_WEBVIEW(view)->priv->js_queue); + + clear_images (GTK_WEBVIEW (view)); + g_free (GTK_WEBVIEW(view)->priv); + G_OBJECT_CLASS (parent_class)->finalize (G_OBJECT(view)); +} + +static void +gtk_webview_class_init (GtkWebViewClass *klass, gpointer userdata) +{ + parent_class = g_type_class_ref (webkit_web_view_get_type ()); + G_OBJECT_CLASS (klass)->finalize = gtk_webview_finalize; +} + +static gboolean +webview_link_clicked (WebKitWebView *view, + WebKitWebFrame *frame, + WebKitNetworkRequest *request, + WebKitWebNavigationAction *navigation_action, + WebKitWebPolicyDecision *policy_decision) +{ + const gchar *uri; + + uri = webkit_network_request_get_uri (request); + + /* the gtk imhtml way was to create an idle cb, not sure + * why, so right now just using purple_notify_uri directly */ + purple_notify_uri (NULL, uri); + return TRUE; +} + +static gboolean +process_js_script_queue (GtkWebView *view) +{ + char *script; + if (view->priv->is_loading) return FALSE; /* we will be called when loaded */ + if (!view->priv->js_queue || g_queue_is_empty (view->priv->js_queue)) + return FALSE; /* nothing to do! */ + + script = g_queue_pop_head (view->priv->js_queue); + webkit_web_view_execute_script (WEBKIT_WEB_VIEW(view), script); + g_free (script); + + return TRUE; /* there may be more for now */ +} + +static void +webview_load_started (WebKitWebView *view, + WebKitWebFrame *frame, + gpointer userdata) +{ + /* is there a better way to test for is_loading? */ + GTK_WEBVIEW(view)->priv->is_loading = true; +} + +static void +webview_load_finished (WebKitWebView *view, + WebKitWebFrame *frame, + gpointer userdata) +{ + GTK_WEBVIEW(view)->priv->is_loading = false; + g_idle_add ((GSourceFunc) process_js_script_queue, view); +} + +void +gtk_webview_safe_execute_script (GtkWebView *view, const char* script) +{ + g_queue_push_tail (view->priv->js_queue, g_strdup (script)); + g_idle_add ((GSourceFunc)process_js_script_queue, view); +} + +static void +gtk_webview_init (GtkWebView *view, gpointer userdata) +{ + view->priv = g_new0 (struct GtkWebViewPriv, 1); + g_signal_connect (view, "navigation-policy-decision-requested", + G_CALLBACK (webview_link_clicked), + view); + + g_signal_connect (view, "load-started", + G_CALLBACK (webview_load_started), + view); + + g_signal_connect (view, "load-finished", + G_CALLBACK (webview_load_finished), + view); + + view->priv->empty = TRUE; + view->priv->js_queue = g_queue_new (); +} + + +void +gtk_webview_load_html_string_with_imgstore (GtkWebView* view, const char* html) +{ + char* html_imged; + + clear_images (view); + html_imged = replace_img_id_with_src (view, html); + printf ("%s\n", html_imged); + webkit_web_view_load_html_string (WEBKIT_WEB_VIEW (view), html_imged, "file:///"); + g_free (html_imged); +} + +char *gtk_webview_quote_js_string(const char *text) +{ + GString *str = g_string_new("\""); + const char *cur = text; + + while (cur && *cur) { + switch (*cur) { + case '\\': + g_string_append(str, "\\\\"); + break; + case '\"': + g_string_append(str, "\\\""); + break; + case '\r': + g_string_append(str, "<br/>"); + break; + case '\n': + break; + default: + g_string_append_c(str, *cur); + } + cur ++; + } + g_string_append_c (str, '"'); + return g_string_free (str, FALSE); +} + + +/* this is a "hack", my plan is to eventually handle this + * correctly using a signals and a plugin: the plugin will have + * the information as to what javascript function to call. It seems + * wrong to hardcode that here. + */ +void +gtk_webview_append_html (GtkWebView* view, const char* html) +{ + char* escaped = gtk_webview_quote_js_string (html); + char* script = g_strdup_printf ("document.write(%s)", escaped); + printf ("script: %s\n", script); + webkit_web_view_execute_script (WEBKIT_WEB_VIEW (view), script); + view->priv->empty = FALSE; + g_free (script); + g_free (escaped); +} + +gboolean gtk_webview_is_empty (GtkWebView *view) +{ + return view->priv->empty; +} + +GType gtk_webview_get_type (void) +{ + static GType mview_type = 0; + if (G_UNLIKELY (mview_type == 0)) { + static const GTypeInfo mview_info = { + sizeof (GtkWebViewClass), + NULL, + NULL, + (GClassInitFunc) gtk_webview_class_init, + NULL, + NULL, + sizeof (GtkWebView), + 0, + (GInstanceInitFunc) gtk_webview_init, + NULL + }; + mview_type = g_type_register_static(webkit_web_view_get_type (), + "GtkWebView", &mview_info, 0); + } + return mview_type; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/pidgin/gtkwebview.h Wed Oct 14 02:41:11 2009 +0000 @@ -0,0 +1,127 @@ +/** + * @file gtkwebview.h Wrapper over the Gtk WebKitWebView component + * @ingroup pidgin + */ + +/* Pidgin is the legal property of its developers, whose names are too numerous + * to list here. Please refer to the COPYRIGHT file distributed with this + * source distribution. + * + * This program is free software; you can redistribute it and/or modify + * under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA + */ + +#ifndef _PIDGIN_WEBVIEW_H_ +#define _PIDGIN_WEBVIEW_H_ + +#include <glib.h> +#include <gtk/gtk.h> +#include <webkit/webkit.h> + +#include "notify.h" + +#define GTK_TYPE_WEBVIEW (gtk_webview_get_type()) +#define GTK_WEBVIEW(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), GTK_TYPE_WEBVIEW, GtkWebView)) +#define GTK_WEBVIEW_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), GTK_TYPE_WEBVIEW, GtkWebViewClass)) +#define GTK_IS_WEBVIEW(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), GTK_TYPE_WEBVIEW)) +#define GTK_IS_WEBVIEW_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), GTK_TYPE_WEBVIEW)) + + +struct GtkWebViewPriv; + +struct _GtkWebView +{ + WebKitWebView webkit_web_view; + + /*< private >*/ + struct GtkWebViewPriv* priv; +}; + +typedef struct _GtkWebView GtkWebView; + +struct _GtkWebViewClass +{ + WebKitWebViewClass parent; +}; + +typedef struct _GtkWebViewClass GtkWebViewClass; + + +/** + * Returns the GType for a GtkWebView widget + * + * @return the GType for GtkWebView widget + */ +GType gtk_webview_get_type (void); + +/** + * Create a new GtkWebView object + * + * @return a GtkWidget corresponding to the GtkWebView object + */ +GtkWidget* gtk_webview_new (void); + +/** + * A very basic routine to append html, which can be considered + * equivalent to a "document.write" using JavaScript. + * + * @param webview The GtkWebView object + * @param markup The html markup to append + */ +void gtk_webview_append_html (GtkWebView *webview, const char* markup); + +/** + * Rather than use webkit_webview_load_string, this routine + * parses and displays the <img id=?> tags that make use of the + * Pidgin imgstore. + * + * @param webview The GtkWebView object + * @param html The HTML content to load + */ +void gtk_webview_load_html_string_with_imgstore (GtkWebView* webview, const char* html); + +/** + * (To be changed, right now it just tests whether an append has been + * called since the last clear or since the Widget was created. So it + * does not test for load_string's called in between. + * + * @param webview The GtkWebView object + * + * @return gboolean indicating whether the webview is empty. + */ +gboolean gtk_webview_is_empty (GtkWebView *webview); + +/** + * Execute the JavaScript only after the webkit_webview_load_string + * loads completely. We also guarantee that the scripts are executed + * in the order they are called here.This is useful to avoid race + * conditions when calls JS functions immediately after opening the + * page. + * + * @param webview the GtkWebView object + * @param script the script to execute + */ +void gtk_webview_safe_execute_script (GtkWebView *webview, const char* script); + +/** + * A convenience routine to quote a string for use as a JavaScript + * string. For instance, "hello 'world'" becomes "'hello \\'world\\''" + * + * @param str The string to escape and quote + * + * @return the quoted string. + */ +char* gtk_webview_quote_js_string (const char* str); + +#endif /* _PIDGIN_WEBVIEW_H_ */
--- a/pidgin/plugins/Makefile.am Tue Oct 13 19:52:35 2009 +0000 +++ b/pidgin/plugins/Makefile.am Wed Oct 14 02:41:11 2009 +0000 @@ -1,4 +1,4 @@ -DIST_SUBDIRS = cap disco gestures gevolution musicmessaging perl ticker +DIST_SUBDIRS = adiumthemes cap disco gestures gevolution musicmessaging perl ticker if BUILD_GEVOLUTION GEVOLUTION_DIR = gevolution @@ -16,9 +16,6 @@ PERL_DIR = perl endif -if ENABLE_GESTURES -GESTURE_DIR = gestures -endif SUBDIRS = \ $(CAP_DIR) \ @@ -27,7 +24,8 @@ $(MUSICMESSAGING_DIR) \ $(PERL_DIR) \ disco \ - ticker + ticker \ + adiumthemes plugindir = $(libdir)/pidgin @@ -36,17 +34,12 @@ extplacement_la_LDFLAGS = -module -avoid-version gtk_signals_test_la_LDFLAGS = -module -avoid-version gtkbuddynote_la_LDFLAGS = -module -avoid-version -history_la_LDFLAGS = -module -avoid-version iconaway_la_LDFLAGS = -module -avoid-version -markerline_la_LDFLAGS = -module -avoid-version -notify_la_LDFLAGS = -module -avoid-version pidginrc_la_LDFLAGS = -module -avoid-version relnot_la_LDFLAGS = -module -avoid-version sendbutton_la_LDFLAGS = -module -avoid-version spellchk_la_LDFLAGS = -module -avoid-version themeedit_la_LDFLAGS = -module -avoid-version -timestamp_la_LDFLAGS = -module -avoid-version -timestamp_format_la_LDFLAGS = -module -avoid-version vvconfig_la_LDFLAGS = -module -avoid-version xmppconsole_la_LDFLAGS = -module -avoid-version @@ -56,17 +49,12 @@ convcolors.la \ extplacement.la \ gtkbuddynote.la \ - history.la \ iconaway.la \ - markerline.la \ - notify.la \ pidginrc.la \ relnot.la \ sendbutton.la \ spellchk.la \ themeedit.la \ - timestamp.la \ - timestamp_format.la \ xmppconsole.la if USE_VV @@ -82,18 +70,12 @@ extplacement_la_SOURCES = extplacement.c gtk_signals_test_la_SOURCES = gtk-signals-test.c gtkbuddynote_la_SOURCES = gtkbuddynote.c -history_la_SOURCES = history.c iconaway_la_SOURCES = iconaway.c -markerline_la_SOURCES = markerline.c -notify_la_SOURCES = notify.c pidginrc_la_SOURCES = pidginrc.c relnot_la_SOURCES = relnot.c sendbutton_la_SOURCES = sendbutton.c spellchk_la_SOURCES = spellchk.c themeedit_la_SOURCES = themeedit.c themeedit-icon.c themeedit-icon.h -timestamp_la_SOURCES = timestamp.c -timestamp_format_la_SOURCES = timestamp_format.c -vvconfig_la_SOURCES = vvconfig.c xmppconsole_la_SOURCES = xmppconsole.c convcolors_la_LIBADD = $(GTK_LIBS) @@ -101,18 +83,12 @@ extplacement_la_LIBADD = $(GTK_LIBS) gtk_signals_test_la_LIBADD = $(GTK_LIBS) gtkbuddynote_la_LIBADD = $(GTK_LIBS) -history_la_LIBADD = $(GTK_LIBS) iconaway_la_LIBADD = $(GTK_LIBS) -markerline_la_LIBADD = $(GTK_LIBS) -notify_la_LIBADD = $(GTK_LIBS) pidginrc_la_LIBADD = $(GTK_LIBS) relnot_la_LIBADD = $(GLIB_LIBS) sendbutton_la_LIBADD = $(GTK_LIBS) spellchk_la_LIBADD = $(GTK_LIBS) themeedit_la_LIBADD = $(GTK_LIBS) -timestamp_la_LIBADD = $(GTK_LIBS) -timestamp_format_la_LIBADD = $(GTK_LIBS) -vvconfig_la_LIBADD = $(GTK_LIBS) $(GSTREAMER_LIBS) xmppconsole_la_LIBADD = $(GTK_LIBS) endif # PLUGINS
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/pidgin/plugins/adiumthemes/Makefile.am Wed Oct 14 02:41:11 2009 +0000 @@ -0,0 +1,30 @@ + +webkittemplatedir = $(datadir)/pidgin/webkit +webkittemplate_DATA = Template.html + +webkitdir = $(libdir)/pidgin + +webkit_la_LDFLAGS = -module -avoid-version + +EXTRA_DIST = $(webkittemplate_DATA) + +if PLUGINS + +webkit_LTLIBRARIES = webkit.la + +webkit_la_SOURCES = webkit.c \ + message-style.h \ + message-style.c + +endif + +webkit_la_LIBADD = $(GTK_LIBS) $(WEBKIT_LIBS) + +AM_CPPFLAGS = \ + -DDATADIR=\"$(datadir)\" \ + -I$(top_srcdir)/libpurple \ + -I$(top_builddir)/libpurple \ + -I$(top_srcdir)/pidgin \ + $(DEBUG_CFLAGS) \ + $(GTK_CFLAGS) \ + $(WEBKIT_CFLAGS)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/pidgin/plugins/adiumthemes/Template.html Wed Oct 14 02:41:11 2009 +0000 @@ -0,0 +1,164 @@ +<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd"> +<html> +<head> + <meta http-equiv="content-type" content="text/html; charset=utf-8" /> + <base href="%@"> + <script type="text/ecmascript" defer="defer"> + + //Appending new content to the message view + function appendMessage(html) { + shouldScroll = nearBottom(); + + //Remove any existing insertion point + insert = document.getElementById("insert"); + if(insert) insert.parentNode.removeChild(insert); + + //Append the new message to the bottom of our chat block + chat = document.getElementById("Chat"); + range = document.createRange(); + range.selectNode(chat); + documentFragment = range.createContextualFragment(html); + chat.appendChild(documentFragment); + + alignChat(shouldScroll); + } + function appendMessageNoScroll(html) { + //Remove any existing insertion point + insert = document.getElementById("insert"); + if(insert) insert.parentNode.removeChild(insert); + + //Append the new message to the bottom of our chat block + chat = document.getElementById("Chat"); + range = document.createRange(); + range.selectNode(chat); + documentFragment = range.createContextualFragment(html); + chat.appendChild(documentFragment); + } + function appendNextMessage(html){ + shouldScroll = nearBottom(); + + //Locate the insertion point + insert = document.getElementById("insert"); + + //make new node + range = document.createRange(); + range.selectNode(insert.parentNode); + newNode = range.createContextualFragment(html); + + //swap + insert.parentNode.replaceChild(newNode,insert); + + alignChat(shouldScroll); + } + function appendNextMessageNoScroll(html){ + //Locate the insertion point + insert = document.getElementById("insert"); + + //make new node + range = document.createRange(); + range.selectNode(insert.parentNode); + newNode = range.createContextualFragment(html); + + //swap + insert.parentNode.replaceChild(newNode,insert); + } + + //Auto-scroll to bottom. Use nearBottom to determine if a scrollToBottom is desired. + function nearBottom() { + return ( document.body.scrollTop >= ( document.body.offsetHeight - ( window.innerHeight * 1.2 ) ) ); + } + function scrollToBottom() { + document.body.scrollTop = document.body.offsetHeight; + } + + //Dynamically exchange the active stylesheet + function setStylesheet( id, url ) { + code = "<style id=\"" + id + "\" type=\"text/css\" media=\"screen,print\">"; + if( url.length ) code += "@import url( \"" + url + "\" );"; + code += "</style>"; + range = document.createRange(); + head = document.getElementsByTagName( "head" ).item(0); + range.selectNode( head ); + documentFragment = range.createContextualFragment( code ); + head.removeChild( document.getElementById( id ) ); + head.appendChild( documentFragment ); + } + + //Swap an image with its alt-tag text on click, or expand/unexpand an attached image + document.onclick = imageCheck; + function imageCheck() { + node = event.target; + if(node.tagName == 'IMG' && !client.zoomImage(node) && node.alt) { + a = document.createElement('a'); + a.setAttribute('onclick', 'imageSwap(this)'); + a.setAttribute('src', node.getAttribute('src')); + a.className = node.className; + text = document.createTextNode(node.alt); + a.appendChild(text); + node.parentNode.replaceChild(a, node); + } + } + + function imageSwap(node) { + shouldScroll = nearBottom(); + + //Swap the image/text + img = document.createElement('img'); + img.setAttribute('src', node.getAttribute('src')); + img.setAttribute('alt', node.firstChild.nodeValue); + img.className = node.className; + node.parentNode.replaceChild(img, node); + + alignChat(shouldScroll); + } + + //Align our chat to the bottom of the window. If true is passed, view will also be scrolled down + function alignChat(shouldScroll) { + var windowHeight = window.innerHeight; + + if (windowHeight > 0) { + var contentElement = document.getElementById('Chat'); + var contentHeight = contentElement.offsetHeight; + if (windowHeight - contentHeight > 0) { + contentElement.style.position = 'relative'; + contentElement.style.top = (windowHeight - contentHeight) + 'px'; + } else { + contentElement.style.position = 'static'; + } + } + + if (shouldScroll) scrollToBottom(); + } + + function windowDidResize(){ + alignChat(true/*nearBottom()*/); //nearBottom buggy with inactive tabs + } + + window.onresize = windowDidResize; + </script> + + <style type="text/css"> + .actionMessageUserName:before { content:"*"; } + .actionMessageBody:after { content:"*"; } + *{ word-wrap:break-word; } + img.scaledToFitImage { height:auto; width:100%; } + </style> + + <!-- This style is shared by all variants. !--> + <style id="baseStyle" type="text/css" media="screen,print"> + %@ + </style> + + <!-- Although we call this mainStyle for legacy reasons, it's actually the variant style !--> + <style id="mainStyle" type="text/css" media="screen,print"> + @import url( "%@" ); + </style> + +</head> +<body onload="alignChat(true);" style="==bodyBackground=="> +%@ +<div id="Chat"> +</div> +%@ +</body> +</html>
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/pidgin/plugins/adiumthemes/message-style.c Wed Oct 14 02:41:11 2009 +0000 @@ -0,0 +1,448 @@ +/* + * Adium Message Styles + * Copyright (C) 2009 Arnold Noronha <arnstein87@gmail.com> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + */ + +#include "message-style.h" + +#include <string.h> + +#include <glib.h> + +#include <debug.h> +#include <util.h> + +static void +glist_free_all_string (GList *list) +{ + GList *first = list; + for (; list; list = g_list_next (list)) + g_free (list->data); + g_list_free (first); +} + +static +PidginMessageStyle* pidgin_message_style_new (const char* styledir) +{ + PidginMessageStyle* ret = g_new0 (PidginMessageStyle, 1); + + ret->ref_counter = 1; + ret->style_dir = g_strdup (styledir); + + return ret; +} + +/** + * deallocate any memory used for info.plist options + */ +static void +pidgin_message_style_unset_info_plist (PidginMessageStyle *style) +{ + style->message_view_version = 0; + g_free (style->cf_bundle_name); + style->cf_bundle_name = NULL; + + g_free (style->cf_bundle_identifier); + style->cf_bundle_identifier = NULL; + + g_free (style->cf_bundle_get_info_string); + style->cf_bundle_get_info_string = NULL; + + g_free (style->default_font_family); + style->default_font_family = NULL; + + style->default_font_size = 0; + style->shows_user_icons = TRUE; + style->disable_combine_consecutive = FALSE; + style->default_background_is_transparent = FALSE; + style->disable_custom_background = FALSE; + + g_free (style->default_background_color); + style->default_background_color = NULL; + + style->allow_text_colors = TRUE; + + g_free (style->image_mask); + style->image_mask = NULL; + g_free (style->default_variant); + style->default_variant = NULL; +} + + +void pidgin_message_style_unref (PidginMessageStyle *style) +{ + if (!style) return; + g_assert (style->ref_counter > 0); + + style->ref_counter--; + if (style->ref_counter) return; + + g_free (style->style_dir); + g_free (style->template_path); + + g_free (style->template_html); + g_free (style->incoming_content_html); + g_free (style->outgoing_content_html); + g_free (style->outgoing_next_content_html); + g_free (style->status_html); + g_free (style->basestyle_css); + + g_free (style); + + pidgin_message_style_unset_info_plist (style); +} + +void +pidgin_message_style_save_state (const PidginMessageStyle *style) +{ + char *prefname = g_strdup_printf ("/plugins/gtk/adiumthemes/%s", style->cf_bundle_identifier); + char *variant = g_strdup_printf ("%s/variant", prefname); + + purple_debug_info ("webkit", "saving state with variant %s\n", style->variant); + purple_prefs_add_none (prefname); + purple_prefs_add_string (variant, ""); + purple_prefs_set_string (variant, style->variant); + + g_free (prefname); + g_free (variant); +} + +static void +pidgin_message_style_load_state (PidginMessageStyle *style) +{ + char *prefname = g_strdup_printf ("/plugins/gtk/adiumthemes/%s", style->cf_bundle_identifier); + char *variant = g_strdup_printf ("%s/variant", prefname); + + const char* value = purple_prefs_get_string (variant); + gboolean changed = !style->variant || !g_str_equal (style->variant, value); + + g_free (style->variant); + style->variant = g_strdup (value); + + if (changed) pidgin_message_style_read_info_plist (style, style->variant); + + g_free (prefname); + g_free(variant); +} + + +static gboolean +parse_info_plist_key_value (xmlnode* key, gpointer destination, const char* expected) +{ + xmlnode *val = key->next; + + for (; val && val->type != XMLNODE_TYPE_TAG; val = val->next); + if (!val) return FALSE; + + if (expected == NULL || g_str_equal (expected, "string")) { + char** dest = (char**) destination; + if (!g_str_equal (val->name, "string")) return FALSE; + if (*dest) g_free (*dest); + *dest = xmlnode_get_data_unescaped (val); + } else if (g_str_equal (expected, "integer")) { + int* dest = (int*) destination; + char* value = xmlnode_get_data_unescaped (val); + + if (!g_str_equal (val->name, "integer")) return FALSE; + *dest = atoi (value); + g_free (value); + } else if (g_str_equal (expected, "boolean")) { + gboolean *dest = (gboolean*) destination; + if (g_str_equal (val->name, "true")) *dest = TRUE; + else if (g_str_equal (val->name, "false")) *dest = FALSE; + else return FALSE; + } else return FALSE; + + return TRUE; +} + +static +gboolean str_for_key (const char *key, const char *found, const char *variant){ + if (g_str_equal (key, found)) return TRUE; + if (!variant) return FALSE; + return (g_str_has_prefix (found, key) + && g_str_has_suffix (found, variant) + && strlen (found) == strlen (key) + strlen (variant) + 1); +} + +/** + * Info.plist should be re-read every time the variant changes, this is because + * the keys that take precedence depend on the value of the current variant. + */ +void +pidgin_message_style_read_info_plist (PidginMessageStyle *style, const char* variant) +{ + /* note that if a variant is used the option:VARIANTNAME takes precedence */ + char *contents = g_build_filename (style->style_dir, "Contents", NULL); + xmlnode *plist = xmlnode_from_file (contents, "Info.plist", "Info.plist", "webkit"), *iter; + xmlnode *dict = xmlnode_get_child (plist, "dict"); + + g_assert (dict); + for (iter = xmlnode_get_child (dict, "key"); iter; iter = xmlnode_get_next_twin (iter)) { + char* key = xmlnode_get_data_unescaped (iter); + gboolean pr = TRUE; + + if (g_str_equal ("MessageViewVersion", key)) + pr = parse_info_plist_key_value (iter, &style->message_view_version, "integer"); + else if (g_str_equal ("CFBundleName", key)) + pr = parse_info_plist_key_value (iter, &style->cf_bundle_name, "string"); + else if (g_str_equal ("CFBundleIdentifier", key)) + pr = parse_info_plist_key_value (iter, &style->cf_bundle_identifier, "string"); + else if (g_str_equal ("CFBundleGetInfoString", key)) + pr = parse_info_plist_key_value (iter, &style->cf_bundle_get_info_string, "string"); + else if (str_for_key ("DefaultFontFamily", key, variant)) + pr = parse_info_plist_key_value (iter, &style->default_font_family, "string"); + else if (str_for_key ("DefaultFontSize", key, variant)) + pr = parse_info_plist_key_value (iter, &style->default_font_size, "integer"); + else if (str_for_key ("ShowsUserIcons", key, variant)) + pr = parse_info_plist_key_value (iter, &style->shows_user_icons, "boolean"); + else if (str_for_key ("DisableCombineConsecutive", key, variant)) + pr = parse_info_plist_key_value (iter, &style->disable_combine_consecutive, "boolean"); + else if (str_for_key ("DefaultBackgroundIsTransparent", key, variant)) + pr = parse_info_plist_key_value (iter, &style->default_background_is_transparent, "boolean"); + else if (str_for_key ("DisableCustomBackground", key, variant)) + pr = parse_info_plist_key_value (iter, &style->disable_custom_background, "boolean"); + else if (str_for_key ("DefaultBackgroundColor", key, variant)) + pr = parse_info_plist_key_value (iter, &style->default_background_color, "string"); + else if (str_for_key ("AllowTextColors", key, variant)) + pr = parse_info_plist_key_value (iter, &style->allow_text_colors, "integer"); + else if (str_for_key ("ImageMask", key, variant)) + pr = parse_info_plist_key_value (iter, &style->image_mask, "string"); + + if (!pr) + purple_debug_warning ("webkit", "Failed to parse key %s\n", key); + g_free (key); + } + + xmlnode_free (plist); +} + +PidginMessageStyle* +pidgin_message_style_load (const char* styledir) +{ + /* + * the loading process described: + * + * First we load all the style .html files, etc. + * The we load any config options that have been stored for + * this variant. + * Then we load the Info.plist, for the currently decided variant. + * At this point, if we find that variants exist, yet + * we don't have a variant selected, we choose DefaultVariant + * and if that does not exist, we choose the first one in the + * directory. + */ + + /* is this style already loaded? */ + char *file; /* temporary variable */ + PidginMessageStyle *style = NULL; + + /* else we need to load it */ + style = pidgin_message_style_new (styledir); + + /* load all other files */ + + /* The template path can either come from the theme, or can + * be stock Template.html that comes with the plugin */ + style->template_path = g_build_filename(styledir, "Contents", "Resources", "Template.html", NULL); + + if (!g_file_test(style->template_path, G_FILE_TEST_EXISTS)) { + g_free (style->template_path); + style->template_path = g_build_filename(DATADIR, "pidgin", "webkit", "Template.html", NULL); + } + + if (!g_file_get_contents(style->template_path, &style->template_html, NULL, NULL)) { + purple_debug_error ("webkit", "Could not locate a Template.html (%s)\n", style->template_path); + pidgin_message_style_unref (style); + return NULL; + } + + file = g_build_filename(styledir, "Contents", "Resources", "Status.html", NULL); + if (!g_file_get_contents(file, &style->status_html, NULL, NULL)) { + purple_debug_info ("webkit", "%s could not find Resources/Status.html", styledir); + pidgin_message_style_unref (style); + g_free (file); + return NULL; + } + g_free (file); + + file = g_build_filename(styledir, "Contents", "Resources", "main.css", NULL); + if (!g_file_get_contents(file, &style->basestyle_css, NULL, NULL)) + style->basestyle_css = g_strdup (""); + g_free (file); + + file = g_build_filename(styledir, "Contents", "Resources", "Header.html", NULL); + if (!g_file_get_contents(file, &style->header_html, NULL, NULL)) + style->header_html = g_strdup (""); + g_free (file); + + file = g_build_filename(styledir, "Contents", "Resources", "Footer.html", NULL); + if (!g_file_get_contents(file, &style->footer_html, NULL, NULL)) + style->footer_html = g_strdup (""); + g_free (file); + + file = g_build_filename(styledir, "Contents", "Resources", "Incoming", "Content.html", NULL); + if (!g_file_get_contents(file, &style->incoming_content_html, NULL, NULL)) { + purple_debug_info ("webkit", "%s did not have a Incoming/Content.html\n", styledir); + pidgin_message_style_unref (style); + g_free (file); + return NULL; + } + g_free (file); + + + /* according to the spec, the following are optional files */ + file = g_build_filename(styledir, "Contents", "Resources", "Incoming", "NextContent.html", NULL); + if (!g_file_get_contents(file, &style->incoming_next_content_html, NULL, NULL)) { + style->incoming_next_content_html = g_strdup (style->incoming_content_html); + } + g_free (file); + + file = g_build_filename(styledir, "Contents", "Resources", "Outgoing", "Content.html", NULL); + if (!g_file_get_contents(file, &style->outgoing_content_html, NULL, NULL)) { + style->outgoing_content_html = g_strdup(style->incoming_content_html); + } + g_free (file); + + file = g_build_filename(styledir, "Contents", "Resources", "Outgoing", "NextContent.html", NULL); + if (!g_file_get_contents(file, &style->outgoing_next_content_html, NULL, NULL)) { + style->outgoing_next_content_html = g_strdup (style->outgoing_content_html); + } + + pidgin_message_style_read_info_plist (style, NULL); + pidgin_message_style_load_state (style); + + /* non variant dependent Info.plist checks */ + if (style->message_view_version < 3) { + purple_debug_info ("webkit", "%s is a legacy style (version %d) and will not be loaded\n", style->cf_bundle_name, style->message_view_version); + pidgin_message_style_unref (style); + return NULL; + } + + if (!style->variant) + { + GList *variants = pidgin_message_style_get_variants (style); + + if (variants) + pidgin_message_style_set_variant (style, variants->data); + + glist_free_all_string (variants); + } + + return style; +} + +PidginMessageStyle* +pidgin_message_style_copy (const PidginMessageStyle *style) +{ + /* it's at times like this that I miss C++ */ + PidginMessageStyle *ret = pidgin_message_style_new (style->style_dir); + + ret->variant = g_strdup (style->variant); + ret->message_view_version = style->message_view_version; + ret->cf_bundle_name = g_strdup (style->cf_bundle_name); + ret->cf_bundle_identifier = g_strdup (style->cf_bundle_identifier); + ret->cf_bundle_get_info_string = g_strdup (style->cf_bundle_get_info_string); + ret->default_font_family = g_strdup (style->default_font_family); + ret->default_font_size = style->default_font_size; + ret->shows_user_icons = style->shows_user_icons; + ret->disable_combine_consecutive = style->disable_combine_consecutive; + ret->default_background_is_transparent = style->default_background_is_transparent; + ret->disable_custom_background = style->disable_custom_background; + ret->default_background_color = g_strdup (style->default_background_color); + ret->allow_text_colors = style->allow_text_colors; + ret->image_mask = g_strdup (style->image_mask); + ret->default_variant = g_strdup (style->default_variant); + + ret->template_path = g_strdup (style->template_path); + ret->template_html = g_strdup (style->template_html); + ret->header_html = g_strdup (style->header_html); + ret->footer_html = g_strdup (style->footer_html); + ret->incoming_content_html = g_strdup (style->incoming_content_html); + ret->outgoing_content_html = g_strdup (style->outgoing_content_html); + ret->incoming_next_content_html = g_strdup (style->incoming_next_content_html); + ret->outgoing_next_content_html = g_strdup (style->outgoing_next_content_html); + ret->status_html = g_strdup (style->status_html); + ret->basestyle_css = g_strdup (style->basestyle_css); + return ret; +} + +void +pidgin_message_style_set_variant (PidginMessageStyle *style, const char *variant) +{ + /* I'm not going to test whether this variant is valid! */ + g_free (style->variant); + style->variant = g_strdup (variant); + + pidgin_message_style_read_info_plist (style, variant); + + /* todo, the style has "changed". Ideally, I would like to use signals at this point. */ +} + +char* pidgin_message_style_get_variant (PidginMessageStyle *style) +{ + return g_strdup (style->variant); +} + +/** + * Get a list of variants supported by the style. + */ +GList* +pidgin_message_style_get_variants (PidginMessageStyle *style) +{ + GList *ret = NULL; + GDir *variants; + const char *css_file; + char *css; + char *variant_dir; + + g_assert (style->style_dir); + variant_dir = g_build_filename(style->style_dir, "Contents", "Resources", "Variants", NULL); + + variants = g_dir_open(variant_dir, 0, NULL); + if (!variants) return NULL; + + while ((css_file = g_dir_read_name(variants)) != NULL) { + if (!g_str_has_suffix (css_file, ".css")) + continue; + + css = g_strndup (css_file, strlen (css_file) - 4); + ret = g_list_append(ret, css); + } + + g_dir_close(variants); + g_free(variant_dir); + + ret = g_list_sort (ret, (GCompareFunc)g_strcmp0); + return ret; +} + + +char* pidgin_message_style_get_css (PidginMessageStyle *style) +{ + if (!style->variant) { + return g_build_filename (style->style_dir, "Contents", "Resources", "main.css", NULL); + } else { + char *file = g_strdup_printf ("%s.css", style->variant); + char *ret = g_build_filename (style->style_dir, "Contents", "Resources", "Variants", file, NULL); + g_free (file); + return ret; + } +} + +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/pidgin/plugins/adiumthemes/message-style.h Wed Oct 14 02:41:11 2009 +0000 @@ -0,0 +1,59 @@ + +#include <glib.h> + +/* + * I'm going to allow a different style for each PidginConversation. + * This way I can do two things: 1) change the theme on the fly and not + * change existing themes, and 2) Use a different theme for IMs and + * chats. + */ +typedef struct _PidginMessageStyle { + int ref_counter; + + /* current config options */ + char *variant; /* allowed to be NULL if there are no variants */ + + /* Info.plist keys that change with Variant */ + + /* Static Info.plist keys */ + int message_view_version; + char *cf_bundle_name; + char *cf_bundle_identifier; + char *cf_bundle_get_info_string; + char *default_font_family; + int default_font_size; + gboolean shows_user_icons; + gboolean disable_combine_consecutive; + gboolean default_background_is_transparent; + gboolean disable_custom_background; + char *default_background_color; + gboolean allow_text_colors; + char *image_mask; + char *default_variant; + + /* paths */ + char *style_dir; + char *template_path; + + /* caches */ + char *template_html; + char *header_html; + char *footer_html; + char *incoming_content_html; + char *outgoing_content_html; + char *incoming_next_content_html; + char *outgoing_next_content_html; + char *status_html; + char *basestyle_css; +} PidginMessageStyle; + +PidginMessageStyle* pidgin_message_style_load (const char* styledir); +PidginMessageStyle* pidgin_message_style_copy (const PidginMessageStyle *style); +void pidgin_message_style_save_state (const PidginMessageStyle *style); +void pidgin_message_style_unref (PidginMessageStyle *style); +void pidgin_message_style_read_info_plist (PidginMessageStyle *style, const char* variant); +char* pidgin_message_style_get_variant (PidginMessageStyle *style); +GList* pidgin_message_style_get_variants (PidginMessageStyle *style); +void pidgin_message_style_set_variant (PidginMessageStyle *style, const char *variant); + +char* pidgin_message_style_get_css (PidginMessageStyle *style);
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/pidgin/plugins/adiumthemes/webkit.c Wed Oct 14 02:41:11 2009 +0000 @@ -0,0 +1,852 @@ +/* + * Adium Message Styles + * Copyright (C) 2009 Arnold Noronha <arnstein87@gmail.com> + * Copyright (C) 2007 + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + */ + +#define PLUGIN_ID "gtk-webview-adium-ims" +#define PLUGIN_NAME "webview-adium-ims" + +/* + * A lot of this was originally written by Sean Egan, but I think I've + * rewrote enough to replace the author for now. + */ +#define PLUGIN_AUTHOR "Arnold Noronha <arnstein87@gmail.com>" +#define PURPLE_PLUGINS "Hell yeah" + +/* System headers */ +#include <string.h> +#include <gdk/gdk.h> +#include <gtk/gtk.h> + +#include <webkit/webkit.h> + +/* Purple headers */ +#include <conversation.h> +#include <debug.h> +#include <notify.h> +#include <util.h> +#include <version.h> + +/* Pidgin headers */ +#include <gtkconv.h> +#include <gtkplugin.h> +#include <gtkwebview.h> +#include <smileyparser.h> + +#include <libxml/xmlreader.h> + +#include "message-style.h" +/* GObject data keys */ +#define MESSAGE_STYLE_KEY "message-style" + +static char *cur_style_dir = NULL; +static void *handle = NULL; + +static inline char* get_absolute_path (const char *path) +{ + if (g_path_is_absolute (path)) return g_strdup (path); + else { + char* cwd = g_get_current_dir (), *ret; + ret = g_build_filename (cwd, path, NULL); + g_free (cwd); + return ret; + } +} + +static void +glist_free_all_string (GList *list) +{ + GList *first = list; + for (; list; list = g_list_next (list)) + g_free (list->data); + g_list_free (first); +} + +static void webkit_on_webview_destroy (GtkObject* obj, gpointer data); + +static void* webkit_plugin_get_handle () +{ + if (handle) return handle; + else return (handle = g_malloc (1)); +} + +static void webkit_plugin_free_handle () +{ + purple_signals_disconnect_by_handle (handle); + g_free (handle); +} + +static char * +replace_message_tokens( + const char *text, + gsize len, + PurpleConversation *conv, + const char *name, + const char *alias, + const char *message, + PurpleMessageFlags flags, + time_t mtime) +{ + GString *str = g_string_new_len(NULL, len); + const char *cur = text; + const char *prev = cur; + + while ((cur = strchr(cur, '%'))) { + const char *replace = NULL; + char *fin = NULL; + + if (!strncmp(cur, "%message%", strlen("%message%"))) { + replace = message; + } else if (!strncmp(cur, "%messageClasses%", strlen("%messageClasses%"))) { + replace = flags & PURPLE_MESSAGE_SEND ? "outgoing" : + flags & PURPLE_MESSAGE_RECV ? "incoming" : "event"; + } else if (!strncmp(cur, "%time", strlen("%time"))) { + char *format = NULL; + if (*(cur + strlen("%time")) == '{') { + const char *start = cur + strlen("%time") + 1; + char *end = strstr(start, "}%"); + if (!end) /* Invalid string */ + continue; + format = g_strndup(start, end - start); + fin = end + 1; + } + replace = purple_utf8_strftime(format ? format : "%X", NULL); + g_free(format); + } else if (!strncmp(cur, "%userIconPath%", strlen("%userIconPath%"))) { + if (flags & PURPLE_MESSAGE_SEND) { + if (purple_account_get_bool(conv->account, "use-global-buddyicon", TRUE)) { + replace = purple_prefs_get_path(PIDGIN_PREFS_ROOT "/accounts/buddyicon"); + } else { + PurpleStoredImage *img = purple_buddy_icons_find_account_icon(conv->account); + replace = purple_imgstore_get_filename(img); + } + if (replace == NULL || !g_file_test(replace, G_FILE_TEST_EXISTS)) { + replace = g_build_filename("Outgoing", "buddy_icon.png", NULL); + } + } else if (flags & PURPLE_MESSAGE_RECV) { + PurpleBuddyIcon *icon = purple_conv_im_get_icon(PURPLE_CONV_IM(conv)); + replace = purple_buddy_icon_get_full_path(icon); + if (replace == NULL || !g_file_test(replace, G_FILE_TEST_EXISTS)) { + replace = g_build_filename("Incoming", "buddy_icon.png", NULL); + } + } + + } else if (!strncmp(cur, "%senderScreenName%", strlen("%senderScreenName%"))) { + replace = name; + } else if (!strncmp(cur, "%sender%", strlen("%sender%"))) { + replace = alias; + } else if (!strncmp(cur, "%service%", strlen("%service%"))) { + replace = purple_account_get_protocol_name(conv->account); + } else { + cur++; + continue; + } + + /* Here we have a replacement to make */ + g_string_append_len(str, prev, cur - prev); + g_string_append(str, replace); + + /* And update the pointers */ + if (fin) { + prev = cur = fin + 1; + } else { + prev = cur = strchr(cur + 1, '%') + 1; + } + + } + + /* And wrap it up */ + g_string_append(str, prev); + return g_string_free(str, FALSE); +} + +static char * +replace_header_tokens(char *text, gsize len, PurpleConversation *conv) +{ + GString *str = g_string_new_len(NULL, len); + char *cur = text; + char *prev = cur; + + if (text == NULL) + return NULL; + + while ((cur = strchr(cur, '%'))) { + const char *replace = NULL; + char *fin = NULL; + + if (!strncmp(cur, "%chatName%", strlen("%chatName%"))) { + replace = conv->name; + } else if (!strncmp(cur, "%sourceName%", strlen("%sourceName%"))) { + replace = purple_account_get_alias(conv->account); + if (replace == NULL) + replace = purple_account_get_username(conv->account); + } else if (!strncmp(cur, "%destinationName%", strlen("%destinationName%"))) { + PurpleBuddy *buddy = purple_find_buddy(conv->account, conv->name); + if (buddy) { + replace = purple_buddy_get_alias(buddy); + } else { + replace = conv->name; + } + } else if (!strncmp(cur, "%incomingIconPath%", strlen("%incomingIconPath%"))) { + PurpleBuddyIcon *icon = purple_conv_im_get_icon(PURPLE_CONV_IM(conv)); + replace = purple_buddy_icon_get_full_path(icon); + } else if (!strncmp(cur, "%outgoingIconPath%", strlen("%outgoingIconPath%"))) { + } else if (!strncmp(cur, "%timeOpened", strlen("%timeOpened"))) { + char *format = NULL; + if (*(cur + strlen("%timeOpened")) == '{') { + char *start = cur + strlen("%timeOpened") + 1; + char *end = strstr(start, "}%"); + if (!end) /* Invalid string */ + continue; + format = g_strndup(start, end - start); + fin = end + 1; + } + replace = purple_utf8_strftime(format ? format : "%X", NULL); + g_free(format); + } else { + continue; + } + + /* Here we have a replacement to make */ + g_string_append_len(str, prev, cur - prev); + g_string_append(str, replace); + + /* And update the pointers */ + if (fin) { + prev = cur = fin + 1; + } else { + prev = cur = strchr(cur + 1, '%') + 1; + } + } + + /* And wrap it up */ + g_string_append(str, prev); + return g_string_free(str, FALSE); +} + +static char * +replace_template_tokens(PidginMessageStyle *style, char *text, int len, char *header, char *footer) { + GString *str = g_string_new_len(NULL, len); + + char **ms = g_strsplit(text, "%@", 6); + char *base = NULL; + char *csspath = pidgin_message_style_get_css (style); + if (ms[0] == NULL || ms[1] == NULL || ms[2] == NULL || ms[3] == NULL || ms[4] == NULL || ms[5] == NULL) { + g_strfreev(ms); + g_string_free(str, TRUE); + return NULL; + } + + g_string_append(str, ms[0]); + g_string_append(str, "file://"); + base = g_build_filename (style->style_dir, "Contents", "Resources", "Template.html", NULL); + g_string_append(str, base); + g_free (base); + + g_string_append(str, ms[1]); + + g_string_append(str, style->basestyle_css); + + g_string_append(str, ms[2]); + + g_string_append(str, "file://"); + g_string_append(str, csspath); + + + + g_string_append(str, ms[3]); + if (header) + g_string_append(str, header); + g_string_append(str, ms[4]); + if (footer) + g_string_append(str, footer); + g_string_append(str, ms[5]); + + g_strfreev(ms); + g_free (csspath); + return g_string_free (str, FALSE); +} + +static GtkWidget * +get_webkit(PurpleConversation *conv) +{ + PidginConversation *gtkconv; + gtkconv = PIDGIN_CONVERSATION(conv); + if (!gtkconv) + return NULL; + else + return gtkconv->webview; +} + +static void set_theme_webkit_settings (WebKitWebView *webview, PidginMessageStyle *style) +{ + WebKitWebSettings *settings; + + g_object_get (G_OBJECT(webview), "settings", &settings, NULL); + if (style->default_font_family) + g_object_set (G_OBJECT (settings), "default-font-family", style->default_font_family, NULL); + + if (style->default_font_size) + g_object_set (G_OBJECT (settings), "default-font-size", GINT_TO_POINTER (style->default_font_size), NULL); + + /* this does not work :( */ + webkit_web_view_set_transparent (webview, style->default_background_is_transparent); +} + +/* + * The style specification says that if the conversation is a group + * chat then the <div id="Chat"> element will be given a class + * 'groupchat'. I can't add another '%@' in Template.html because + * that breaks style-specific Template.html's. I have to either use libxml + * or conveniently play with WebKit's javascript engine. The javascript + * engine should work, but it's not an identical behavior. + */ +static void +webkit_set_groupchat (GtkWebView *webview) +{ + gtk_webview_safe_execute_script (webview, "document.getElementById('Chat').className = 'groupchat'"); +} + + +/** + * Called when either a new PurpleConversation is created + * or when a PidginConversation changes its active PurpleConversation + * This will not change the theme if the theme is already set. + * (This is to prevent accidental theme changes if a new + * PurpleConversation gets added. + * + * FIXME: it's not at all clear to me as to how + * Adium themes handle the case when the PurpleConversation + * changes. + */ +static void +init_theme_for_webkit (PurpleConversation *conv, char *style_dir) +{ + GtkWidget *webkit = PIDGIN_CONVERSATION(conv)->webview; + char *header, *footer; + char *template; + + char* basedir; + char* baseuri; + PidginMessageStyle *style, *oldStyle; + oldStyle = g_object_get_data (G_OBJECT(webkit), MESSAGE_STYLE_KEY); + + if (oldStyle) return; + + purple_debug_info ("webkit", "loading %s\n", style_dir); + style = pidgin_message_style_load (style_dir); + g_assert (style); + g_assert (style->template_html); /* debugging test? */ + + basedir = g_build_filename (style->style_dir, "Contents", "Resources", "Template.html", NULL); + baseuri = g_strdup_printf ("file://%s", basedir); + header = replace_header_tokens(style->header_html, strlen(style->header_html), conv); + g_assert (style); + footer = replace_header_tokens(style->footer_html, strlen(style->footer_html), conv); + template = replace_template_tokens(style, style->template_html, strlen(style->template_html) + strlen(style->header_html), header, footer); + + g_assert(template); + + purple_debug_info ("webkit", "template: %s\n", template); + + set_theme_webkit_settings (WEBKIT_WEB_VIEW(webkit), style); + webkit_web_view_load_string(WEBKIT_WEB_VIEW(webkit), template, "text/html", "UTF-8", baseuri); + + PidginMessageStyle *copy = pidgin_message_style_copy (style); + g_object_set_data (G_OBJECT(webkit), MESSAGE_STYLE_KEY, copy); + + pidgin_message_style_unref (style); + /* I need to unref this style when the webkit object destroys */ + g_signal_connect (G_OBJECT(webkit), "destroy", G_CALLBACK(webkit_on_webview_destroy), copy); + + if (purple_conversation_get_type (conv) == PURPLE_CONV_TYPE_CHAT) + webkit_set_groupchat (GTK_WEBVIEW (webkit)); + g_free (basedir); + g_free (baseuri); + g_free (header); + g_free (footer); + g_free (template); +} + + +/* restore the non theme version of the conversation window */ +static void +finalize_theme_for_webkit (PurpleConversation *conv) +{ + GtkWidget *webview = PIDGIN_CONVERSATION(conv)->webview; + PidginMessageStyle *style = g_object_get_data (G_OBJECT(webview), MESSAGE_STYLE_KEY); + + webkit_web_view_load_string(WEBKIT_WEB_VIEW(webview), "", "text/html", "UTF-8", ""); + + g_object_set_data (G_OBJECT(webview), MESSAGE_STYLE_KEY, NULL); + pidgin_message_style_unref (style); +} + +static void +webkit_on_webview_destroy (GtkObject *object, gpointer data) +{ + pidgin_message_style_unref ((PidginMessageStyle*) data); + g_object_set_data (G_OBJECT(object), MESSAGE_STYLE_KEY, NULL); +} + +static gboolean webkit_on_displaying_im_msg (PurpleAccount *account, + const char* name, + char **pmessage, + PurpleConversation *conv, + PurpleMessageFlags flags, + gpointer data) +{ + GtkWidget *webkit; + char *message = *pmessage; + const char *alias = name; /* FIXME: signal doesn't give me alias */ + char *stripped; + char *message_html; + char *msg; + char *escape; + char *script; + char *func = "appendMessage"; + char *smileyed; + time_t mtime = time (NULL); /* FIXME: this should come from the write_conv calback, but the signal doesn't pass this to me */ + + PurpleMessageFlags old_flags = GPOINTER_TO_INT(purple_conversation_get_data(conv, "webkit-lastflags")); + PidginMessageStyle *style; + + fprintf (stderr, "hmm.. here %s %s\n", name, message); + webkit = get_webkit(conv); + stripped = g_strdup(message); + + style = g_object_get_data (G_OBJECT (webkit), MESSAGE_STYLE_KEY); + g_assert (style); + + if (flags & PURPLE_MESSAGE_SEND && old_flags & PURPLE_MESSAGE_SEND) { + message_html = style->outgoing_next_content_html; + func = "appendNextMessage"; + } else if (flags & PURPLE_MESSAGE_SEND) { + message_html = style->outgoing_content_html; + } else if (flags & PURPLE_MESSAGE_RECV && old_flags & PURPLE_MESSAGE_RECV) { + message_html = style->incoming_next_content_html; + func = "appendNextMessage"; + } else if (flags & PURPLE_MESSAGE_RECV) { + message_html = style->incoming_content_html; + } else { + message_html = style->status_html; + } + purple_conversation_set_data(conv, "webkit-lastflags", GINT_TO_POINTER(flags)); + + smileyed = smiley_parse_markup(stripped, conv->account->protocol_id); + msg = replace_message_tokens(message_html, 0, conv, name, alias, smileyed, flags, mtime); + escape = gtk_webview_quote_js_string (msg); + script = g_strdup_printf("%s(%s)", func, escape); + + purple_debug_info ("webkit", "JS: %s\n", script); + gtk_webview_safe_execute_script (GTK_WEBVIEW (webkit), script); + + g_free(script); + g_free(smileyed); + g_free(msg); + g_free(stripped); + g_free(escape); + + return TRUE; /* GtkConv should not handle this IM */ +} + +static gboolean webkit_on_displaying_chat_msg (PurpleAccount *account, + const char *who, + char **message, + PurpleConversation *conv, + PurpleMessageFlags flags, + gpointer userdata) +{ + /* handle exactly like an IM message for now */ + return webkit_on_displaying_im_msg (account, who, message, conv, flags, NULL); +} + +static void +webkit_on_conversation_displayed (PidginConversation *gtkconv, gpointer data) +{ + init_theme_for_webkit (gtkconv->active_conv, cur_style_dir); +} + +static void +webkit_on_conversation_switched (PurpleConversation *conv, gpointer data) +{ + init_theme_for_webkit (conv, cur_style_dir); +} + +static void +webkit_on_conversation_hiding (PidginConversation *gtkconv, gpointer data) +{ + /* + * I'm not sure if I need to do anything here, but let's keep + * this anyway. + */ +} + +static GList* +get_dir_dir_list (const char* dirname) +{ + GList *ret = NULL; + GDir *dir = g_dir_open (dirname, 0, NULL); + const char* subdir; + + if (!dir) return NULL; + while ((subdir = g_dir_read_name (dir))) { + ret = g_list_append (ret, g_build_filename (dirname, subdir, NULL)); + } + + g_dir_close (dir); + return ret; +} + +/** + * Get me a list of all the available themes specified by their + * directories. I don't guarrantee that these are valid themes, just + * that they are in the directories for themes. + */ +static GList* +get_style_directory_list () +{ + char *user_dir, *user_style_dir, *global_style_dir; + GList *list1, *list2; + + user_dir = get_absolute_path (purple_user_dir ()); + + user_style_dir = g_build_filename (user_dir, "styles", NULL); + global_style_dir = g_build_filename (DATADIR, "pidgin", "styles", NULL); + + list1 = get_dir_dir_list (user_style_dir); + list2 = get_dir_dir_list (global_style_dir); + + g_free (global_style_dir); + g_free (user_style_dir); + g_free (user_dir); + + return g_list_concat (list1, list2); +} + +/** + * use heuristics or previous user options to figure out what + * theme to use as default in this Pidgin instance. + */ +static void +style_set_default () +{ + GList* styles = get_style_directory_list (), *iter; + const char *stylepath = purple_prefs_get_string ("/plugins/gtk/adiumthemes/stylepath"); + g_assert (cur_style_dir == NULL); + + if (stylepath) + styles = g_list_prepend (styles, g_strdup (stylepath)); + + /* pick any one that works. Note that we have first preference + * for the one in the userdir */ + for (iter = styles; iter; iter = g_list_next (iter)) { + PidginMessageStyle *style = pidgin_message_style_load (iter->data); + if (style) { + cur_style_dir = (char*) g_strdup (iter->data); + pidgin_message_style_unref (style); + break; + } + purple_debug_info ("webkit", "Style %s is invalid\n", (char*) iter->data); + } + + for (iter = styles; iter; iter = g_list_next (iter)) + g_free (iter->data); + g_list_free (styles); +} + +static gboolean +plugin_load(PurplePlugin *plugin) +{ + style_set_default (); + if (!cur_style_dir) return FALSE; /* couldn't find a style */ + + purple_signal_connect (pidgin_conversations_get_handle (), + "displaying-im-msg", + webkit_plugin_get_handle (), + PURPLE_CALLBACK(webkit_on_displaying_im_msg), + NULL); + + purple_signal_connect (pidgin_conversations_get_handle (), + "displaying-chat-msg", + webkit_plugin_get_handle (), + PURPLE_CALLBACK(webkit_on_displaying_chat_msg), + NULL); + + purple_signal_connect (pidgin_conversations_get_handle (), + "conversation-displayed", + webkit_plugin_get_handle (), + PURPLE_CALLBACK(webkit_on_conversation_displayed), + NULL); + + purple_signal_connect (pidgin_conversations_get_handle (), + "conversation-switched", + webkit_plugin_get_handle (), + PURPLE_CALLBACK(webkit_on_conversation_switched), + NULL); + + purple_signal_connect (pidgin_conversations_get_handle (), + "conversation-hiding", + webkit_plugin_get_handle (), + PURPLE_CALLBACK(webkit_on_conversation_hiding), + NULL); + + /* finally update each of the existing conversation windows */ + { + GList* list = purple_get_conversations (); + for (;list; list = g_list_next(list)) + init_theme_for_webkit (list->data, cur_style_dir); + + } + return TRUE; +} + +static gboolean +plugin_unload(PurplePlugin *plugin) +{ + GList *list; + + webkit_plugin_free_handle (); + cur_style_dir = NULL; + list = purple_get_conversations (); + while (list) { + finalize_theme_for_webkit(list->data); + list = g_list_next(list); + } + + return TRUE; +} + +/* + * UI config code + */ + +static void +style_changed (GtkWidget* combobox, gpointer null) +{ + char *name = gtk_combo_box_get_active_text (GTK_COMBO_BOX(combobox)); + GtkWidget *dialog; + GList *styles = get_style_directory_list (), *iter; + + /* find the full path for this name, I wish I could store this info in the combobox itself. :( */ + for (iter = styles; iter; iter = g_list_next(iter)) { + char* basename = g_path_get_basename (iter->data); + if (g_str_equal (basename, name)) { + g_free (basename); + break; + } + g_free (basename); + } + + g_assert (iter); + g_free (name); + g_free (cur_style_dir); + cur_style_dir = g_strdup (iter->data);; + + /* inform the user that existing conversations haven't changed */ + dialog = gtk_message_dialog_new (NULL, 0, GTK_MESSAGE_INFO, GTK_BUTTONS_CLOSE, "The style for existing conversations have not been changed. Please close and re-open the conversation for the changes to take effect."); + g_assert (dialog); + gtk_widget_show (dialog); + g_signal_connect_swapped (dialog, "response", G_CALLBACK(gtk_widget_destroy), dialog); +} + +static GtkWidget* +get_style_config_frame () +{ + GtkWidget *combobox = gtk_combo_box_new_text (); + GList *styles = get_style_directory_list (), *iter; + int index = 0, selected = 0; + + for (iter = styles; iter; iter = g_list_next (iter)) { + PidginMessageStyle *style = pidgin_message_style_load (iter->data); + + if (style) { + char *text = g_path_get_basename (iter->data); + gtk_combo_box_append_text (GTK_COMBO_BOX(combobox), text); + g_free (text); + + if (g_str_equal (iter->data, cur_style_dir)) + selected = index; + index++; + pidgin_message_style_unref (style); + } + } + gtk_combo_box_set_active (GTK_COMBO_BOX(combobox), selected); + g_signal_connect_after (G_OBJECT(combobox), "changed", G_CALLBACK(style_changed), NULL); + return combobox; +} + +static void +variant_update_conversation (PurpleConversation *conv) +{ + PidginConversation *gtkconv = PIDGIN_CONVERSATION (conv); + WebKitWebView *webview = WEBKIT_WEB_VIEW (gtkconv->webview); + PidginMessageStyle *style = (PidginMessageStyle*) g_object_get_data (G_OBJECT(webview), MESSAGE_STYLE_KEY); + char *script; + + g_assert (style); + + script = g_strdup_printf ("setStylesheet(\"mainStyle\",\"%s\")", pidgin_message_style_get_css (style)); + gtk_webview_safe_execute_script (GTK_WEBVIEW(webview), script); + + set_theme_webkit_settings (WEBKIT_WEB_VIEW (gtkconv->webview), style); + g_free (script); +} + +static void +variant_changed (GtkWidget* combobox, gpointer null) +{ + char *name; + GList *list; + PidginMessageStyle *style = pidgin_message_style_load (cur_style_dir); + + g_assert (style); + name = gtk_combo_box_get_active_text (GTK_COMBO_BOX (combobox)); + pidgin_message_style_set_variant (style, name); + pidgin_message_style_save_state (style); + + /* update conversations */ + list = purple_get_conversations (); + while (list) { + variant_update_conversation (list->data); + list = g_list_next(list); + } + + g_free (name); + pidgin_message_style_unref (style); +} + +static GtkWidget * +get_variant_config_frame() +{ + PidginMessageStyle *style = pidgin_message_style_load (cur_style_dir); + GList *variants = pidgin_message_style_get_variants (style), *iter; + char *cur_variant = pidgin_message_style_get_variant (style); + GtkWidget *combobox = gtk_combo_box_new_text(); + int def = -1, index = 0; + + pidgin_message_style_unref (style); + + for (iter = variants; iter; iter = g_list_next (iter)) { + gtk_combo_box_append_text (GTK_COMBO_BOX(combobox), iter->data); + + if (g_str_equal (cur_variant, iter->data)) + def = index; + index ++; + + } + + gtk_combo_box_set_active (GTK_COMBO_BOX(combobox), def); + g_signal_connect (G_OBJECT(combobox), "changed", G_CALLBACK(variant_changed), NULL); + + return combobox; +} + +static void +style_changed_reset_variants (GtkWidget* combobox, gpointer table) +{ + /* I hate to do this, I swear. But I don't know how to cleanly clean an existing combobox */ + GtkWidget* variants = g_object_get_data (G_OBJECT(table), "variants-cbox"); + gtk_widget_destroy (variants); + variants = get_variant_config_frame (); + gtk_table_attach_defaults (GTK_TABLE (table), variants, 1, 2, 1, 2); + gtk_widget_show_all (GTK_WIDGET(table)); + + g_object_set_data (G_OBJECT(table), "variants-cbox", variants); +} + +static GtkWidget* +get_config_frame(PurplePlugin* plugin) +{ + GtkWidget *table = gtk_table_new (2, 2, FALSE); + GtkWidget *style_config = get_style_config_frame (); + GtkWidget *variant_config = get_variant_config_frame (); + + gtk_table_attach_defaults (GTK_TABLE(table), gtk_label_new ("Message Style"), 0, 1, 0, 1); + gtk_table_attach_defaults (GTK_TABLE(table), style_config, 1, 2, 0, 1); + gtk_table_attach_defaults (GTK_TABLE(table), gtk_label_new ("Style Variant"), 0, 1, 1, 2); + gtk_table_attach_defaults (GTK_TABLE(table), variant_config, 1, 2, 1, 2); + + + g_object_set_data (G_OBJECT(table), "variants-cbox", variant_config); + /* to clarify, this is a second signal connected on style config */ + g_signal_connect_after (G_OBJECT(style_config), "changed", G_CALLBACK(style_changed_reset_variants), table); + + return table; +} + +PidginPluginUiInfo ui_info = +{ + get_config_frame, + 0, /* page_num (Reserved) */ + + /* padding */ + NULL, + NULL, + NULL, + NULL +}; + + +static PurplePluginInfo info = +{ + PURPLE_PLUGIN_MAGIC, /* Magic */ + PURPLE_MAJOR_VERSION, /* Purple Major Version */ + PURPLE_MINOR_VERSION, /* Purple Minor Version */ + PURPLE_PLUGIN_STANDARD, /* plugin type */ +PIDGIN_PLUGIN_TYPE, /* ui requirement */ + 0, /* flags */ + NULL, /* dependencies */ + PURPLE_PRIORITY_DEFAULT, /* priority */ + + PLUGIN_ID, /* plugin id */ + NULL, /* name */ + "0.1", /* version */ + NULL, /* summary */ + NULL, /* description */ + PLUGIN_AUTHOR, /* author */ + "http://pidgin.im", /* website */ + + plugin_load, /* load */ + plugin_unload, /* unload */ + NULL, /* destroy */ + + &ui_info, /* ui_info */ + NULL, /* extra_info */ + NULL, /* prefs_info */ + NULL, /* actions */ + NULL, /* reserved 1 */ + NULL, /* reserved 2 */ + NULL, /* reserved 3 */ + NULL /* reserved 4 */ +}; + +static void +init_plugin(PurplePlugin *plugin) { + info.name = "Adium IMs"; + info.summary = "Adium-like IMs with Pidgin"; + info.description = "You can chat in Pidgin using Adium's WebKit view."; + + purple_prefs_add_none ("/plugins"); + purple_prefs_add_none ("/plugins/gtk"); + purple_prefs_add_none ("/plugins/gtk/adiumthemes"); + purple_prefs_add_string ("/plugins/gtk/adiumthemes/csspath", ""); +} + +PURPLE_INIT_PLUGIN(webkit, init_plugin, info)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/pidgin/smileyparser.c Wed Oct 14 02:41:11 2009 +0000 @@ -0,0 +1,144 @@ + +#include <gtk/gtk.h> +#include "smileyparser.h" +#include <smiley.h> +#include <string.h> +#include "gtkthemes.h" + +static char* get_fullpath (const char* filename) +{ + if (g_path_is_absolute (filename)) return g_strdup (filename); + else return g_build_path (g_get_current_dir (), filename, NULL); +} + +static void +parse_for_shortcut_plaintext (const char* text, const char* shortcut, const char* file, GString* ret) +{ + const char *tmp = text; + + for(;*tmp;) { + const char *end = strstr (tmp, shortcut); + char *path; + char *escaped_path; + + if (end == NULL) { + g_string_append (ret, tmp); + break; + } + path = get_fullpath (file); + escaped_path = g_markup_escape_text (path, -1); + + g_string_append_len (ret, tmp, end-tmp); + g_string_append_printf (ret,"<img alt='%s' src='%s' />", + shortcut, escaped_path); + g_free (path); + g_free (escaped_path); + g_assert (strlen (tmp) >= strlen (shortcut)); + tmp = end + strlen (shortcut); + } +} + +static char* +parse_for_shortcut (const char* markup, const char* shortcut, const char* file) +{ + GString* ret = g_string_new (""); + char *local_markup = g_strdup (markup); + char *escaped_shortcut = g_markup_escape_text (shortcut, -1); + + char *temp = local_markup; + + for (;*temp;) { + char *end = strchr (temp, '<'); + char *end_of_tag; + + if (!end) { + parse_for_shortcut_plaintext (temp, escaped_shortcut, file, ret); + break; + } + + *end = 0; + parse_for_shortcut_plaintext (temp, escaped_shortcut, file, ret); + *end = '<'; + + /* if this is well-formed, then there should be no '>' within + * the tag. TODO: handle a comment tag better :( */ + end_of_tag = strchr (end, '>'); + if (!end_of_tag) { + g_string_append (ret, end); + break; + } + + g_string_append_len (ret, end, end_of_tag-end+1); + + temp = end_of_tag + 1; + } + g_free (local_markup); + g_free (escaped_shortcut); + return g_string_free (ret, FALSE); +} + +static char* +parse_for_purple_smiley (const char* markup, PurpleSmiley *smiley) +{ + char *file = purple_smiley_get_full_path (smiley); + char *ret = parse_for_shortcut (markup, purple_smiley_get_shortcut (smiley), file); + g_free (file); + return ret; +} + +static char* +parse_for_smiley_list (const char* markup, GHashTable* smileys) +{ + GHashTableIter iter; + char *key, *value; + char *ret = g_strdup (markup); + + g_hash_table_iter_init (&iter, smileys); + while (g_hash_table_iter_next (&iter, (gpointer*)&key, (gpointer*)&value)) + { + char* temp = parse_for_shortcut (ret, key, value); + g_free (ret); + ret = temp; + } + return ret; +} + +char* +smiley_parse_markup (const char* markup, const char *proto_id) +{ + GList *smileys = purple_smileys_get_all (); + char *temp = g_strdup (markup), *temp2; + struct smiley_list *list; + const char *proto_name = "default"; + + if (proto_id != NULL) { + PurplePlugin *proto; + proto = purple_find_prpl (proto_id); + proto_name = proto->info->name; + } + + /* unnecessarily slow, but lets manage for now. */ + for (; smileys; smileys = g_list_next (smileys)) { + temp2 = parse_for_purple_smiley (temp, PURPLE_SMILEY (smileys->data)); + g_free (temp); + temp = temp2; + } + + /* now for each theme smiley, observe that this does look nasty */ + + if (!current_smiley_theme || !(current_smiley_theme->list)) { + printf ("theme does not exist\n"); + return temp; + } + + for (list = current_smiley_theme->list; list; list = list->next) { + if (g_str_equal (list->sml, proto_name)) { + temp2 = parse_for_smiley_list (temp, list->files); + g_free (temp); + temp = temp2; + } + } + + return temp; +} +