# HG changeset patch # User masca@cpw.pidgin.im # Date 1315188858 0 # Node ID 472e70ea58eddaf8fe0c5b346ae354ca986552bf # Parent d82c76a842f8b02f4b41245aaee1045558022842# Parent a17de1f525a90f748f32f21f47f6b7cc9d7a3647 propagate from branch 'im.pidgin.pidgin' (head 6f21535004485d16ec64ef6a7c331cf5df7346e6) to branch 'im.pidgin.cpw.masca.webkit' (head b89ab3856a07e9d3544d89c0e4c18653ebed9d1b) diff -r d82c76a842f8 -r 472e70ea58ed configure.ac --- a/configure.ac Sun Sep 04 21:11:24 2011 +0000 +++ b/configure.ac Mon Sep 05 02:14:18 2011 +0000 @@ -710,6 +710,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 ####################################################################### @@ -2486,6 +2488,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 diff -r d82c76a842f8 -r 472e70ea58ed pidgin/Makefile.am --- a/pidgin/Makefile.am Sun Sep 04 21:11:24 2011 +0000 +++ b/pidgin/Makefile.am Mon Sep 05 02:14:18 2011 +0000 @@ -83,9 +83,11 @@ gtkstatusbox.c \ gtkthemes.c \ gtkutils.c \ + gtkwebview.c \ gtkwhiteboard.c \ minidialog.c \ - pidgintooltip.c + pidgintooltip.c \ + smileyparser.c pidgin_headers = \ gtkaccount.h \ @@ -133,10 +135,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 = \ @@ -155,6 +159,7 @@ $(INTLLIBS) \ $(GTKSPELL_LIBS) \ $(LIBXML_LIBS) \ + $(WEBKIT_LIBS) \ $(GTK_LIBS) \ $(top_builddir)/libpurple/libpurple.la @@ -178,6 +183,7 @@ $(DBUS_CFLAGS) \ $(GTKSPELL_CFLAGS) \ $(LIBXML_CFLAGS) \ + $(WEBKIT_CFLAGS) \ $(INTGG_CFLAGS) endif # ENABLE_GTK diff -r d82c76a842f8 -r 472e70ea58ed pidgin/gtkconv.c --- a/pidgin/gtkconv.c Sun Sep 04 21:11:24 2011 +0000 +++ b/pidgin/gtkconv.c Mon Sep 05 02:14:18 2011 +0000 @@ -69,6 +69,7 @@ #include "gtkprivacy.h" #include "gtkthemes.h" #include "gtkutils.h" +#include "gtkwebview.h" #include "pidginstock.h" #include "pidgintooltip.h" @@ -174,6 +175,8 @@ static GList *away_list = NULL; static GList *busy_list = NULL; static GList *xa_list = NULL; +static GList *login_list = NULL; +static GList *logout_list = NULL; static GList *offline_list = NULL; static GHashTable *prpl_lists = NULL; @@ -209,7 +212,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]; @@ -313,15 +316,6 @@ } static void -conversation_entry_clear(PidginConversation *gtkconv) -{ - GtkIMHtml *imhtml = GTK_IMHTML(gtkconv->entry); - gtk_source_undo_manager_begin_not_undoable_action(imhtml->undo_manager); - gtk_imhtml_clear(imhtml); - gtk_source_undo_manager_end_not_undoable_action(imhtml->undo_manager); -} - -static void clear_formatting_cb(GtkIMHtml *imhtml, PidginConversation *gtkconv) { default_formatize(gtkconv); @@ -430,21 +424,29 @@ return PURPLE_CMD_RET_OK; } -static void clear_conversation_scrollback_cb(PurpleConversation *conv, - void *data) +static void clear_conversation_scrollback(PurpleConversation *conv) { PidginConversation *gtkconv = NULL; + GList *iter; gtkconv = PIDGIN_CONVERSATION(conv); - if (gtkconv) - 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); +} + + +static void clear_conversation_scrollback_cb(PurpleConversation *conv, + void *data) +{ + clear_conversation_scrollback(conv); +} static PurpleCmdRet clear_command_cb(PurpleConversation *conv, const char *cmd, char **args, char **error, void *data) { - purple_conversation_clear_message_history(conv); + clear_conversation_scrollback(conv); return PURPLE_CMD_RET_OK; } @@ -452,7 +454,7 @@ clearall_command_cb(PurpleConversation *conv, const char *cmd, char **args, char **error, void *data) { - purple_conversation_foreach(purple_conversation_clear_message_history); + purple_conversation_foreach(clear_conversation_scrollback); return PURPLE_CMD_RET_OK; } @@ -619,7 +621,7 @@ account = purple_conversation_get_account(conv); if (check_for_and_do_command(conv)) { - conversation_entry_clear(gtkconv); + gtk_imhtml_clear(GTK_IMHTML(gtkconv->entry)); return; } @@ -674,7 +676,7 @@ g_free(clean); g_free(buf); - conversation_entry_clear(gtkconv); + gtk_imhtml_clear(GTK_IMHTML(gtkconv->entry)); gtkconv_set_unseen(gtkconv, PIDGIN_UNSEEN_NONE); } @@ -803,9 +805,9 @@ do_invite(GtkWidget *w, int resp, InviteBuddyInfo *info) { const char *buddy, *message; - PurpleConversation *conv; - - conv = info->conv; + PidginConversation *gtkconv; + + gtkconv = PIDGIN_CONVERSATION(info->conv); if (resp == GTK_RESPONSE_OK) { buddy = gtk_entry_get_text(GTK_ENTRY(info->entry)); @@ -814,8 +816,8 @@ if (!g_ascii_strcasecmp(buddy, "")) return; - serv_chat_invite(purple_conversation_get_gc(conv), - purple_conv_chat_get_id(PURPLE_CONV_CHAT(conv)), + serv_chat_invite(purple_conversation_get_gc(info->conv), + purple_conv_chat_get_id(PURPLE_CONV_CHAT(info->conv)), message, buddy); } @@ -909,6 +911,7 @@ InviteBuddyInfo *info = NULL; if (invite_dialog == NULL) { + PurpleConnection *gc; PidginWindow *gtkwin; GtkWidget *label; GtkWidget *vbox, *hbox; @@ -921,6 +924,7 @@ info = g_new0(InviteBuddyInfo, 1); info->conv = conv; + gc = purple_conversation_get_gc(conv); gtkwin = pidgin_conv_get_window(gtkconv); /* Create the new dialog. */ @@ -1045,32 +1049,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, "\n\n"); - fprintf(fp, "\n"); - fprintf(fp, "%s\n\n\n", name); - fprintf(fp, _("

Conversation with %s

\n"), name); - - lines = gtk_imhtml_get_markup_lines( - GTK_IMHTML(PIDGIN_CONVERSATION(conv)->imhtml)); - text = g_strjoinv("
\n", lines); - fprintf(fp, "%s", text); - g_free(text); - g_strfreev(lines); - - fprintf(fp, "\n\n\n"); - fclose(fp); + /* TODO: I don't know how to support this using webkit yet. */ } /* @@ -1168,7 +1147,7 @@ PurpleConversation *conv; conv = pidgin_conv_window_get_active_conversation(win); - purple_conversation_clear_message_history(conv); + clear_conversation_scrollback(conv); } static void @@ -1627,7 +1606,7 @@ static GtkTextMark * get_mark_for_user(PidginConversation *gtkconv, const char *who) { - GtkTextBuffer *buf = gtk_text_view_get_buffer(GTK_TEXT_VIEW(gtkconv->imhtml)); + GtkTextBuffer *buf = gtk_text_view_get_buffer(GTK_TEXT_VIEW(gtkconv->webview)); char *tmp = g_strconcat("user:", who, NULL); GtkTextMark *mark = gtk_text_buffer_get_mark(buf, tmp); @@ -1638,16 +1617,8 @@ 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 * @@ -1862,10 +1833,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, @@ -1942,8 +1913,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; @@ -2200,13 +2171,13 @@ case GDK_Page_Up: case GDK_KP_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: case GDK_KP_Page_Down: - gtk_imhtml_page_down(GTK_IMHTML(gtkconv->imhtml)); + //gtk_imhtml_page_down(GTK_IMHTML(gtkconv->imhtml)); return TRUE; break; @@ -2316,7 +2287,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)); @@ -2681,6 +2652,7 @@ PidginConversation *gtkconv = (PidginConversation *)data; PurpleConversation *conv = gtkconv->active_conv; PurpleAccount *account; + PurplePluginProtocolInfo *prpl_info = NULL; GdkPixbuf *buf; GdkPixbuf *scale; @@ -3252,11 +3224,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)); @@ -3274,7 +3246,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 { @@ -3286,15 +3258,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); } } @@ -3705,38 +3677,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 @@ -3978,7 +3919,8 @@ continue; account = purple_buddy_get_account(buddy); - if (purple_account_is_connected(account) || account == gtkconv->active_conv->account) + /* FIXME: */ + if (purple_account_is_connected(account) /*|| account == gtkconv->active_conv->account*/) { /* Use the PurplePresence to get unique buddies. */ PurplePresence *presence = purple_buddy_get_presence(buddy); @@ -4091,7 +4033,7 @@ if (is_me) { GtkTextTag *tag = gtk_text_tag_table_lookup( - gtk_text_buffer_get_tag_table(GTK_IMHTML(gtkconv->imhtml)->text_buffer), + gtk_text_buffer_get_tag_table(GTK_IMHTML(gtkconv->webview)->text_buffer), "send-name"); g_object_get(tag, "foreground-gdk", &color, NULL); } else { @@ -4657,7 +4599,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; @@ -4897,13 +4839,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 } @@ -4918,7 +4860,7 @@ { gtk_widget_modify_base(gtkconv->quickfind.entry, GTK_STATE_NORMAL, NULL); - gtk_imhtml_search_clear(GTK_IMHTML(gtkconv->imhtml)); + gtk_imhtml_search_clear(GTK_IMHTML(gtkconv->webview)); gtk_widget_hide_all(gtkconv->quickfind.container); gtk_widget_grab_focus(gtkconv->entry); @@ -4931,7 +4873,7 @@ switch (event->keyval) { case GDK_Return: case GDK_KP_Enter: - if (gtk_imhtml_search_find(GTK_IMHTML(gtkconv->imhtml), gtk_entry_get_text(GTK_ENTRY(entry)))) { + if (gtk_imhtml_search_find(GTK_IMHTML(gtkconv->webview), gtk_entry_get_text(GTK_ENTRY(entry)))) { gtk_widget_modify_base(gtkconv->quickfind.entry, GTK_STATE_NORMAL, NULL); } else { GdkColor col; @@ -4984,12 +4926,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 webview_sw_hscroll; int buddyicon_size = 0; /* Setup the top part of the pane */ @@ -5081,8 +5024,18 @@ 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); + /* TODO: create a pidgin_create_webview() function in utils*/ + 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_webview_set_vadjustment(GTK_WEBVIEW(gtkconv->webview), + gtk_scrolled_window_get_vadjustment(GTK_SCROLLED_WINDOW(webview_sw))); + gtk_container_add (GTK_CONTAINER (webview_sw), gtkconv->webview); + + gtk_widget_set_size_request(gtkconv->webview, -1, 0); + if (chat) { GtkWidget *hpaned; @@ -5093,26 +5046,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); - - g_object_set(G_OBJECT(imhtml_sw), "vscrollbar-policy", GTK_POLICY_ALWAYS, NULL); - - 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); pidgin_conv_setup_quickfind(gtkconv, vbox); @@ -5353,6 +5308,8 @@ static void set_typing_font(GtkWidget *widget, GtkStyle *style, PidginConversation *gtkconv) { +/* FIXME */ +#if 0 static PangoFontDescription *font_desc = NULL; static GdkColor *color = NULL; static gboolean enable = TRUE; @@ -5383,6 +5340,7 @@ } g_signal_handlers_disconnect_by_func(G_OBJECT(widget), set_typing_font, gtkconv); +#endif } /************************************************************************** @@ -5424,9 +5382,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); @@ -5449,7 +5404,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); @@ -5461,12 +5416,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); @@ -5496,10 +5451,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), @@ -5512,7 +5463,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) @@ -5737,6 +5688,8 @@ static GtkTextTag *get_buddy_tag(PurpleConversation *conv, const char *who, PurpleMessageFlags flag, gboolean create) { +/* FIXME */ +#if 0 PidginConversation *gtkconv = PIDGIN_CONVERSATION(conv); GtkTextTag *buddytag; gchar *str; @@ -5770,6 +5723,8 @@ g_free(str); return buddytag; +#endif + return NULL; } static void pidgin_conv_calculate_newday(PidginConversation *gtkconv, time_t mtime) @@ -5826,6 +5781,7 @@ PidginConversation *gtkconv; PurpleConnection *gc; PurpleAccount *account; + PurplePluginProtocolInfo *prpl_info; int gtk_font_options = 0; int gtk_font_options_all = 0; int max_scrollback_lines; @@ -5840,8 +5796,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); @@ -5899,56 +5853,12 @@ } 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); - } - - 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), "
", gtk_font_options_all | GTK_IMHTML_NO_SCROLL); + + prpl_info = gc ? PURPLE_PLUGIN_PROTOCOL_INFO(gc->prpl) : NULL; + + /* if the buffer is not empty add a
*/ + if (!gtk_webview_is_empty (GTK_WEBVIEW(gtkconv->webview))) + gtk_webview_append_html (GTK_WEBVIEW(gtkconv->webview), "
"); /* First message in a conversation. */ if (gtkconv->newday == 0) @@ -5999,32 +5909,32 @@ 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); + pidgin_themes_smiley_themeize_custom(gtkconv->webview); } /* 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), - "%s", + "%s%s", 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), - "%s", + "%s %s", 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, - "%s", + "%s", 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("")); @@ -6034,11 +5944,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); @@ -6099,55 +6004,41 @@ g_free(alias_escaped); + /* FIXME: */ +#if 0 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); + /* const char *color = get_text_tag_color(tag); */ g_snprintf(buf2, BUF_LONG, "", 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, "%s ", 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); + gtk_webview_append_html (GTK_WEBVIEW(gtkconv->webview), buf2); + } +#endif + g_snprintf(buf2, BUF_LONG, "%s ", sml_attrib ? sml_attrib : "", str); + gtk_webview_append_html(GTK_WEBVIEW(gtkconv->webview), buf2); g_free(str); if(gc){ char *pre = g_strdup_printf("", sml_attrib ? sml_attrib : ""); char *post = ""; - 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); @@ -6177,7 +6068,7 @@ if (!(flags & PURPLE_MESSAGE_RECV) && (conv->features & PURPLE_CONNECTION_ALLOW_CUSTOM_SMILEY)) { /* Restore the smiley-data */ - pidgin_themes_smiley_themeize(gtkconv->imhtml); + pidgin_themes_smiley_themeize(gtkconv->webview); } purple_signal_emit(pidgin_conversations_get_handle(), @@ -6263,6 +6154,7 @@ GtkTreeIter iter; GtkTreeModel *model; GtkTextTag *tag; + int f = 1; chat = PURPLE_CONV_CHAT(conv); gtkconv = PIDGIN_CONVERSATION(conv); @@ -6449,7 +6341,7 @@ } } - if (!add_custom_smiley_for_imhtml(GTK_IMHTML(gtkconv->imhtml), sml, smile)) + if (!add_custom_smiley_for_imhtml(GTK_IMHTML(gtkconv->webview), sml, smile)) return FALSE; if (!remote) /* If it's a local custom smiley, then add it for the entry */ @@ -6463,6 +6355,8 @@ pidgin_conv_custom_smiley_write(PurpleConversation *conv, const char *smile, const guchar *data, gsize size) { +/* FIXME */ +#if 0 PidginConversation *gtkconv; GtkIMHtmlSmiley *smiley; const char *sml; @@ -6497,11 +6391,14 @@ g_object_unref(G_OBJECT(smiley->loader)); smiley->loader = gdk_pixbuf_loader_new(); } +#endif } static void pidgin_conv_custom_smiley_close(PurpleConversation *conv, const char *smile) { +/* FIXME*/ +#if 0 PidginConversation *gtkconv; GtkIMHtmlSmiley *smiley; const char *sml; @@ -6538,6 +6435,7 @@ g_object_unref(G_OBJECT(smiley->loader)); smiley->loader = gdk_pixbuf_loader_new(); } +#endif } static void @@ -6811,7 +6709,7 @@ } if (fields & PIDGIN_CONV_SMILEY_THEME) - pidgin_themes_smiley_themeize(PIDGIN_CONVERSATION(conv)->imhtml); + pidgin_themes_smiley_themeize(PIDGIN_CONVERSATION(conv)->webview); if ((fields & PIDGIN_CONV_COLORIZE_TITLE) || (fields & PIDGIN_CONV_SET_TITLE) || @@ -7050,6 +6948,7 @@ int size = 0; PurpleAccount *account; + PurplePluginProtocolInfo *prpl_info = NULL; PurpleBuddyIcon *icon; @@ -7066,6 +6965,8 @@ return; account = purple_conversation_get_account(conv); + if(account && account->gc) + prpl_info = PURPLE_PLUGIN_PROTOCOL_INFO(account->gc->prpl); /* Remove the current icon stuff */ children = gtk_container_get_children(GTK_CONTAINER(gtkconv->u.im->icon_container)); @@ -7114,6 +7015,7 @@ if (data == NULL) { icon = purple_conv_im_get_icon(PURPLE_CONV_IM(conv)); + if (icon == NULL) { gtk_widget_set_size_request(gtkconv->u.im->icon_container, @@ -7122,6 +7024,7 @@ } data = purple_buddy_icon_get_data(icon, &len); + if (data == NULL) { gtk_widget_set_size_request(gtkconv->u.im->icon_container, @@ -7389,7 +7292,7 @@ GTK_CHECK_MENU_ITEM(win->menu.show_timestamps), (gboolean)GPOINTER_TO_INT(value)); - gtk_imhtml_show_comments(GTK_IMHTML(gtkconv->imhtml), + gtk_imhtml_show_comments(GTK_IMHTML(gtkconv->webview), (gboolean)GPOINTER_TO_INT(value)); } } @@ -7791,7 +7694,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), "

", 0); + gtk_webview_append_html(GTK_WEBVIEW(gtkconv->webview), "

"); 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); @@ -7826,7 +7729,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), "

", 0); + gtk_webview_append_html(GTK_WEBVIEW(gtkconv->webview), "

"); g_object_set_data(G_OBJECT(gtkconv->entry), "attach-start-time", NULL); } @@ -10020,9 +9923,12 @@ static void conv_placement_by_group(PidginConversation *conv) { + PurpleConversationType type; PurpleGroup *group = NULL; GList *wl, *cl; + type = purple_conversation_get_type(conv->active_conv); + group = conv_get_group(conv); /* Go through the list of IMs and find one with this group. */ diff -r d82c76a842f8 -r 472e70ea58ed pidgin/gtkconv.h --- a/pidgin/gtkconv.h Sun Sep 04 21:11:24 2011 +0000 +++ b/pidgin/gtkconv.h Mon Sep 05 02:14:18 2011 +0000 @@ -95,7 +95,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 diff -r d82c76a842f8 -r 472e70ea58ed pidgin/gtkdialogs.c --- a/pidgin/gtkdialogs.c Sun Sep 04 21:11:24 2011 +0000 +++ b/pidgin/gtkdialogs.c Mon Sep 05 02:14:18 2011 +0000 @@ -39,10 +39,9 @@ #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; @@ -452,10 +451,12 @@ gtk_box_pack_start(GTK_BOX(vbox), logo, FALSE, FALSE, 0); frame = pidgin_create_imhtml(FALSE, &imhtml, NULL, NULL); + /* FIXME: Compile now and fix it later when we have a proper replacement for this function gtk_imhtml_set_format_functions(GTK_IMHTML(imhtml), GTK_IMHTML_ALL ^ GTK_IMHTML_SMILEY); + */ gtk_box_pack_start(GTK_BOX(vbox), frame, TRUE, TRUE, 0); - gtk_imhtml_append_text(GTK_IMHTML(imhtml), string->str, GTK_IMHTML_NO_SCROLL); + gtk_webview_append_html(GTK_WEBVIEW(imhtml), string->str); gtk_text_buffer_get_start_iter(gtk_text_view_get_buffer(GTK_TEXT_VIEW(imhtml)), &iter); gtk_text_buffer_place_cursor(gtk_text_view_get_buffer(GTK_TEXT_VIEW(imhtml)), &iter); diff -r d82c76a842f8 -r 472e70ea58ed pidgin/gtklog.c --- a/pidgin/gtklog.c Sun Sep 04 21:11:24 2011 +0000 +++ b/pidgin/gtklog.c Mon Sep 05 02:14:18 2011 +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); @@ -419,8 +419,9 @@ static gboolean search_find_cb(gpointer data) { PidginLogViewer *viewer = data; - gtk_imhtml_search_find(GTK_IMHTML(viewer->imhtml), viewer->search); - g_object_steal_data(G_OBJECT(viewer->entry), "search-find-cb"); + 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; } @@ -461,23 +462,16 @@ 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) { - guint source; - gtk_imhtml_search_clear(GTK_IMHTML(viewer->imhtml)); - source = g_idle_add(search_find_cb, viewer); - g_object_set_data_full(G_OBJECT(viewer->entry), "search-find-cb", - GINT_TO_POINTER(source), (GDestroyNotify)g_source_remove); + webkit_web_view_unmark_text_matches(WEBKIT_WEB_VIEW(viewer->web_view)); + g_idle_add(search_find_cb, viewer); } pidgin_clear_cursor(viewer->window); @@ -655,11 +649,18 @@ 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); + /* + 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); + */ + frame = pidgin_create_imhtml(FALSE, &lv->web_view, NULL, NULL); + 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), frame, TRUE, TRUE, 0); - gtk_widget_show(frame); /* Search box **********/ hbox = gtk_hbox_new(FALSE, PIDGIN_HIG_BOX_SPACE); diff -r d82c76a842f8 -r 472e70ea58ed pidgin/gtklog.h --- a/pidgin/gtklog.h Sun Sep 04 21:11:24 2011 +0000 +++ b/pidgin/gtklog.h Mon Sep 05 02:14:18 2011 +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 */ diff -r d82c76a842f8 -r 472e70ea58ed pidgin/gtknotify.c --- a/pidgin/gtknotify.c Sun Sep 04 21:11:24 2011 +0000 +++ b/pidgin/gtknotify.c Mon Sep 05 02:14:18 2011 +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 { @@ -810,21 +810,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) @@ -833,8 +818,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; @@ -869,14 +854,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); @@ -889,10 +878,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); @@ -1147,10 +1136,11 @@ info = purple_notify_user_info_get_text_with_newline(user_info, "
"); 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; diff -r d82c76a842f8 -r 472e70ea58ed pidgin/gtkthemes.c --- a/pidgin/gtkthemes.c Sun Sep 04 21:11:24 2011 +0000 +++ b/pidgin/gtkthemes.c Mon Sep 05 02:14:18 2011 +0000 @@ -278,6 +278,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 @@ -320,6 +322,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++; @@ -358,7 +361,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); } } diff -r d82c76a842f8 -r 472e70ea58ed pidgin/gtkthemes.h --- a/pidgin/gtkthemes.h Sun Sep 04 21:11:24 2011 +0000 +++ b/pidgin/gtkthemes.h Mon Sep 05 02:14:18 2011 +0000 @@ -29,6 +29,7 @@ struct smiley_list { char *sml; GSList *smileys; + GHashTable *files; /**< map from smiley shortcut to filename */ struct smiley_list *next; }; diff -r d82c76a842f8 -r 472e70ea58ed pidgin/gtkwebview.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/pidgin/gtkwebview.c Mon Sep 05 02:14:18 2011 +0000 @@ -0,0 +1,410 @@ +/* + * @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 +#endif + +#include +#include +#include +#include +#include + +#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; + GtkAdjustment *vadj; + guint scroll_src; + GTimer *scroll_time; +}; + +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 tags with . 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, ""); + 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; + WebKitWebNavigationReason reason; + + uri = webkit_network_request_get_uri (request); + reason = webkit_web_navigation_action_get_reason(navigation_action); + + if (reason == WEBKIT_WEB_NAVIGATION_REASON_LINK_CLICKED) { + /* 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); + } + + webkit_web_policy_decision_use(policy_decision); + + 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, "
"); + break; + case '\n': + break; + default: + g_string_append_c(str, *cur); + } + cur ++; + } + g_string_append_c (str, '"'); + return g_string_free (str, FALSE); +} + +void gtk_webview_set_vadjustment(GtkWebView *webview, GtkAdjustment *vadj) +{ + webview->priv->vadj = vadj; +} + +/* 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; + gtk_webview_scroll_to_end(view, TRUE); + g_free (script); + g_free (escaped); +} + +gboolean gtk_webview_is_empty (GtkWebView *view) +{ + return view->priv->empty; +} + +#define MAX_SCROLL_TIME 0.4 /* seconds */ +#define SCROLL_DELAY 33 /* milliseconds */ + +/* + * Smoothly scroll a WebView. + * + * @return TRUE if the window needs to be scrolled further, FALSE if we're at the bottom. + */ +static gboolean smooth_scroll_cb(gpointer data) +{ + struct GtkWebViewPriv *priv = data; + GtkAdjustment *adj = priv->vadj; + gdouble max_val = adj->upper - adj->page_size; + gdouble scroll_val = gtk_adjustment_get_value(adj) + ((max_val - gtk_adjustment_get_value(adj)) / 3); + + g_return_val_if_fail(priv->scroll_time != NULL, FALSE); + + if (g_timer_elapsed(priv->scroll_time, NULL) > MAX_SCROLL_TIME || scroll_val >= max_val) { + /* time's up. jump to the end and kill the timer */ + gtk_adjustment_set_value(adj, max_val); + g_timer_destroy(priv->scroll_time); + priv->scroll_time = NULL; + g_source_remove(priv->scroll_src); + priv->scroll_src = 0; + return FALSE; + } + + /* scroll by 1/3rd the remaining distance */ + gtk_adjustment_set_value(adj, scroll_val); + return TRUE; +} + +static gboolean scroll_idle_cb(gpointer data) +{ + struct GtkWebViewPriv *priv = data; + GtkAdjustment *adj = priv->vadj; + if(adj) { + gtk_adjustment_set_value(adj, adj->upper - adj->page_size); + } + priv->scroll_src = 0; + return FALSE; +} + +void gtk_webview_scroll_to_end(GtkWebView *webview, gboolean smooth) +{ + struct GtkWebViewPriv *priv = webview->priv; + if (priv->scroll_time) + g_timer_destroy(priv->scroll_time); + if (priv->scroll_src) + g_source_remove(priv->scroll_src); + if(smooth) { + priv->scroll_time = g_timer_new(); + priv->scroll_src = g_timeout_add_full(G_PRIORITY_LOW, SCROLL_DELAY, smooth_scroll_cb, priv, NULL); + } else { + priv->scroll_time = NULL; + priv->scroll_src = g_idle_add_full(G_PRIORITY_LOW, scroll_idle_cb, priv, NULL); + } +} + +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; +} diff -r d82c76a842f8 -r 472e70ea58ed pidgin/gtkwebview.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/pidgin/gtkwebview.h Mon Sep 05 02:14:18 2011 +0000 @@ -0,0 +1,143 @@ +/** + * @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 +#include +#include + +#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); + +/** + * Set the vertical adjustment for the GtkWebView. + * + * @param webview The GtkWebView. + * @param vadj The GtkAdjustment that control the webview. + */ +void gtk_webview_set_vadjustment(GtkWebView *webview, GtkAdjustment *vadj); + +/** + * 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 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); + +/** + * Scrolls the Webview to the end of its contents. + * + * @param webview The GtkWebView. + * @param smoth A boolean indicating if smooth scrolling should be used. + */ +void gtk_webview_scroll_to_end(GtkWebView *webview, gboolean smooth); + +#endif /* _PIDGIN_WEBVIEW_H_ */ diff -r d82c76a842f8 -r 472e70ea58ed pidgin/plugins/Makefile.am --- a/pidgin/plugins/Makefile.am Sun Sep 04 21:11:24 2011 +0000 +++ b/pidgin/plugins/Makefile.am Mon Sep 05 02:14:18 2011 +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 diff -r d82c76a842f8 -r 472e70ea58ed pidgin/plugins/adiumthemes/Makefile.am --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/pidgin/plugins/adiumthemes/Makefile.am Mon Sep 05 02:14:18 2011 +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) diff -r d82c76a842f8 -r 472e70ea58ed pidgin/plugins/adiumthemes/Template.html --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/pidgin/plugins/adiumthemes/Template.html Mon Sep 05 02:14:18 2011 +0000 @@ -0,0 +1,164 @@ + + + + + + + + + + + + + + + + + +%@ +
+
+%@ + + diff -r d82c76a842f8 -r 472e70ea58ed pidgin/plugins/adiumthemes/message-style.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/pidgin/plugins/adiumthemes/message-style.c Mon Sep 05 02:14:18 2011 +0000 @@ -0,0 +1,448 @@ +/* + * Adium Message Styles + * Copyright (C) 2009 Arnold Noronha + * + * 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 + +#include + +#include +#include + +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; + } +} + + diff -r d82c76a842f8 -r 472e70ea58ed pidgin/plugins/adiumthemes/message-style.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/pidgin/plugins/adiumthemes/message-style.h Mon Sep 05 02:14:18 2011 +0000 @@ -0,0 +1,59 @@ + +#include + +/* + * 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); diff -r d82c76a842f8 -r 472e70ea58ed pidgin/plugins/adiumthemes/webkit.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/pidgin/plugins/adiumthemes/webkit.c Mon Sep 05 02:14:18 2011 +0000 @@ -0,0 +1,860 @@ +/* + * Adium Message Styles + * Copyright (C) 2009 Arnold Noronha + * 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 " +#define PURPLE_PLUGINS "Hell yeah" + +/* System headers */ +#include +#include +#include + +#include + +/* Purple headers */ +#include +#include +#include +#include +#include +#include + +/* Pidgin headers */ +#include +#include +#include +#include + +#include + +#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
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 && *stylepath) + styles = g_list_prepend (styles, g_strdup (stylepath)); + else { + purple_notify_error(handle, _("Webkit themes"), + _("Can't find installed styles"), + _("Please install some theme and verify the installation path")); + + } + + /* 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);; + purple_prefs_set_string ("/plugins/gtk/adiumthemes/stylepath", cur_style_dir); + + /* 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/stylepath", ""); +} + +PURPLE_INIT_PLUGIN(webkit, init_plugin, info) diff -r d82c76a842f8 -r 472e70ea58ed pidgin/smileyparser.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/pidgin/smileyparser.c Mon Sep 05 02:14:18 2011 +0000 @@ -0,0 +1,144 @@ + +#include +#include "smileyparser.h" +#include +#include +#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,"%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; +} + diff -r d82c76a842f8 -r 472e70ea58ed pidgin/smileyparser.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/pidgin/smileyparser.h Mon Sep 05 02:14:18 2011 +0000 @@ -0,0 +1,3 @@ + +char* +smiley_parse_markup (const char* markup, const char* sml);