pidgin/gtkconv.c

changeset 39703
0d0646e7c401
parent 39702
8102041c0288
child 39704
8f0ffe559ea0
equal deleted inserted replaced
39702:8102041c0288 39703:0d0646e7c401
64 #include "gtkpounce.h" 64 #include "gtkpounce.h"
65 #include "gtkprefs.h" 65 #include "gtkprefs.h"
66 #include "gtkprivacy.h" 66 #include "gtkprivacy.h"
67 #include "gtkstyle.h" 67 #include "gtkstyle.h"
68 #include "gtkutils.h" 68 #include "gtkutils.h"
69 #include "gtkwebview.h"
70 #include "pidgingdkpixbuf.h" 69 #include "pidgingdkpixbuf.h"
71 #include "pidgininvitedialog.h" 70 #include "pidgininvitedialog.h"
72 #include "pidginlog.h" 71 #include "pidginlog.h"
73 #include "pidginmessage.h" 72 #include "pidginmessage.h"
74 #include "pidginstock.h" 73 #include "pidginstock.h"
180 /* Prototypes. <-- because Paco-Paco hates this comment. */ 179 /* Prototypes. <-- because Paco-Paco hates this comment. */
181 static gboolean infopane_entry_activate(PidginConversation *gtkconv); 180 static gboolean infopane_entry_activate(PidginConversation *gtkconv);
182 static void got_typing_keypress(PidginConversation *gtkconv, gboolean first); 181 static void got_typing_keypress(PidginConversation *gtkconv, gboolean first);
183 static void gray_stuff_out(PidginConversation *gtkconv); 182 static void gray_stuff_out(PidginConversation *gtkconv);
184 static void add_chat_user_common(PurpleChatConversation *chat, PurpleChatUser *cb, const char *old_name); 183 static void add_chat_user_common(PurpleChatConversation *chat, PurpleChatUser *cb, const char *old_name);
185 static gboolean tab_complete(PurpleConversation *conv);
186 static void pidgin_conv_updated(PurpleConversation *conv, PurpleConversationUpdateType type); 184 static void pidgin_conv_updated(PurpleConversation *conv, PurpleConversationUpdateType type);
187 static void conv_set_unseen(PurpleConversation *gtkconv, PidginUnseenState state); 185 static void conv_set_unseen(PurpleConversation *gtkconv, PidginUnseenState state);
188 static void gtkconv_set_unseen(PidginConversation *gtkconv, PidginUnseenState state); 186 static void gtkconv_set_unseen(PidginConversation *gtkconv, PidginUnseenState state);
189 static void update_typing_icon(PidginConversation *gtkconv); 187 static void update_typing_icon(PidginConversation *gtkconv);
190 static void update_typing_message(PidginConversation *gtkconv, const char *message); 188 static void update_typing_message(PidginConversation *gtkconv, const char *message);
966 pidgin_pounce_editor_show(purple_conversation_get_account(conv), 964 pidgin_pounce_editor_show(purple_conversation_get_account(conv),
967 purple_conversation_get_name(conv), NULL); 965 purple_conversation_get_name(conv), NULL);
968 } 966 }
969 967
970 static void 968 static void
971 menu_insert_link_cb(GtkAction *action, gpointer data)
972 {
973 PidginConvWindow *win = data;
974 PidginConversation *gtkconv;
975 PidginWebView *entry;
976
977 gtkconv = pidgin_conv_window_get_active_gtkconv(win);
978 entry = PIDGIN_WEBVIEW(gtkconv->entry);
979
980 pidgin_webview_activate_toolbar(entry, PIDGIN_WEBVIEW_ACTION_LINK);
981 }
982
983 static void
984 menu_insert_image_cb(GtkAction *action, gpointer data)
985 {
986 PidginConvWindow *win = data;
987 PidginConversation *gtkconv;
988 PidginWebView *entry;
989
990 gtkconv = pidgin_conv_window_get_active_gtkconv(win);
991 entry = PIDGIN_WEBVIEW(gtkconv->entry);
992
993 pidgin_webview_activate_toolbar(entry, PIDGIN_WEBVIEW_ACTION_IMAGE);
994 }
995
996 static void
997 menu_alias_cb(GtkAction *action, gpointer data) 969 menu_alias_cb(GtkAction *action, gpointer data)
998 { 970 {
999 PidginConvWindow *win = data; 971 PidginConvWindow *win = data;
1000 PurpleConversation *conv; 972 PurpleConversation *conv;
1001 PurpleAccount *account; 973 PurpleAccount *account;
1304 purple_blist_request_add_buddy(account, name, NULL, NULL); 1276 purple_blist_request_add_buddy(account, name, NULL, NULL);
1305 1277
1306 gtk_widget_grab_focus(PIDGIN_CONVERSATION(conv)->entry); 1278 gtk_widget_grab_focus(PIDGIN_CONVERSATION(conv)->entry);
1307 } 1279 }
1308 1280
1309 static char *
1310 get_class_for_user(const char *who)
1311 {
1312 return g_strconcat("-pidgin-user:", who, NULL);
1313 }
1314
1315 static GtkWidget * 1281 static GtkWidget *
1316 create_chat_menu(PurpleChatConversation *chat, const char *who, PurpleConnection *gc) 1282 create_chat_menu(PurpleChatConversation *chat, const char *who, PurpleConnection *gc)
1317 { 1283 {
1318 static GtkWidget *menu = NULL; 1284 static GtkWidget *menu = NULL;
1319 PurpleProtocol *protocol = NULL; 1285 PurpleProtocol *protocol = NULL;
1619 } 1585 }
1620 1586
1621 static void 1587 static void
1622 update_typing_inserting(PidginConversation *gtkconv) 1588 update_typing_inserting(PidginConversation *gtkconv)
1623 { 1589 {
1624 gboolean is_empty; 1590 GtkTextBuffer *buffer = NULL;
1591 gboolean is_empty = FALSE;
1625 1592
1626 g_return_if_fail(gtkconv != NULL); 1593 g_return_if_fail(gtkconv != NULL);
1627 1594
1628 is_empty = pidgin_webview_is_empty(PIDGIN_WEBVIEW(gtkconv->entry)); 1595 buffer = talkatu_editor_get_buffer(TALKATU_EDITOR(gtkconv->editor));
1596 is_empty = talkatu_buffer_get_is_empty(TALKATU_BUFFER(buffer));
1629 1597
1630 got_typing_keypress(gtkconv, is_empty); 1598 got_typing_keypress(gtkconv, is_empty);
1631 } 1599 }
1632 1600
1633 static gboolean 1601 static gboolean
1634 update_typing_deleting_cb(PidginConversation *gtkconv) 1602 update_typing_deleting_cb(PidginConversation *gtkconv)
1635 { 1603 {
1636 PurpleIMConversation *im = PURPLE_IM_CONVERSATION(gtkconv->active_conv); 1604 PurpleIMConversation *im = PURPLE_IM_CONVERSATION(gtkconv->active_conv);
1637 gboolean is_empty = pidgin_webview_is_empty(PIDGIN_WEBVIEW(gtkconv->entry)); 1605 GtkTextBuffer *buffer= NULL;
1638 1606
1639 if (!is_empty) { 1607 buffer = talkatu_editor_get_buffer(TALKATU_EDITOR(gtkconv->editor));
1608
1609 if (!talkatu_buffer_get_is_empty(buffer)) {
1640 /* We deleted all the text, so turn off typing. */ 1610 /* We deleted all the text, so turn off typing. */
1641 purple_im_conversation_stop_send_typed_timeout(im); 1611 purple_im_conversation_stop_send_typed_timeout(im);
1642 1612
1643 purple_serv_send_typing(purple_conversation_get_connection(gtkconv->active_conv), 1613 purple_serv_send_typing(purple_conversation_get_connection(gtkconv->active_conv),
1644 purple_conversation_get_name(gtkconv->active_conv), 1614 purple_conversation_get_name(gtkconv->active_conv),
1653 } 1623 }
1654 1624
1655 static void 1625 static void
1656 update_typing_deleting(PidginConversation *gtkconv) 1626 update_typing_deleting(PidginConversation *gtkconv)
1657 { 1627 {
1658 gboolean is_empty; 1628 GtkTextBuffer *buffer = NULL;
1659 1629
1660 g_return_if_fail(gtkconv != NULL); 1630 g_return_if_fail(gtkconv != NULL);
1661 1631
1662 is_empty = pidgin_webview_is_empty(PIDGIN_WEBVIEW(gtkconv->entry)); 1632 buffer = talkatu_editor_get_buffer(TALKATU_EDITOR(gtkconv->editor));
1663 1633
1664 if (!is_empty) 1634 if (!talkatu_buffer_get_is_empty(TALKATU_BUFFER(buffer))) {
1665 g_timeout_add(0, (GSourceFunc)update_typing_deleting_cb, gtkconv); 1635 g_timeout_add(0, (GSourceFunc)update_typing_deleting_cb, gtkconv);
1636 }
1666 } 1637 }
1667 1638
1668 static gboolean 1639 static gboolean
1669 conv_keypress_common(PidginConversation *gtkconv, GdkEventKey *event) 1640 conv_keypress_common(PidginConversation *gtkconv, GdkEventKey *event)
1670 { 1641 {
1776 if (conv_keypress_common(gtkconv, event)) 1747 if (conv_keypress_common(gtkconv, event))
1777 return TRUE; 1748 return TRUE;
1778 1749
1779 /* If CTRL was held down... */ 1750 /* If CTRL was held down... */
1780 if (event->state & GDK_CONTROL_MASK) { 1751 if (event->state & GDK_CONTROL_MASK) {
1781 switch (event->keyval) { 1752 }
1782 case GDK_KEY_Up:
1783 if (!gtkconv->send_history)
1784 break;
1785
1786 if (gtkconv->entry != entry)
1787 break;
1788
1789 if (!gtkconv->send_history->prev) {
1790 g_free(gtkconv->send_history->data);
1791
1792 gtkconv->send_history->data =
1793 pidgin_webview_get_body_html(PIDGIN_WEBVIEW(gtkconv->entry));
1794 }
1795
1796 if (gtkconv->send_history->next && gtkconv->send_history->next->data) {
1797 GObject *object;
1798 #if 0
1799 /* TODO WebKit: maybe not necessary? */
1800 GtkTextIter iter;
1801 GtkTextBuffer *buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(gtkconv->entry));
1802 #endif
1803
1804 gtkconv->send_history = gtkconv->send_history->next;
1805
1806 /* Block the signal to prevent application of default formatting. */
1807 object = g_object_ref(G_OBJECT(gtkconv->entry));
1808 g_signal_handlers_block_matched(object, G_SIGNAL_MATCH_DATA,
1809 0, 0, NULL, NULL, gtkconv);
1810 /* Clear the formatting. */
1811 pidgin_webview_clear_formatting(PIDGIN_WEBVIEW(gtkconv->entry));
1812 /* Unblock the signal. */
1813 g_signal_handlers_unblock_matched(object, G_SIGNAL_MATCH_DATA,
1814 0, 0, NULL, NULL, gtkconv);
1815 g_object_unref(object);
1816
1817 pidgin_webview_load_html_string(PIDGIN_WEBVIEW(gtkconv->entry),
1818 gtkconv->send_history->data);
1819 /* this is mainly just a hack so the formatting at the
1820 * cursor gets picked up. */
1821 #if 0
1822 /* TODO WebKit: maybe not necessary? */
1823 gtk_text_buffer_get_end_iter(buffer, &iter);
1824 gtk_text_buffer_move_mark_by_name(buffer, "insert", &iter);
1825 #endif
1826 }
1827
1828 return TRUE;
1829 break;
1830
1831 case GDK_KEY_Down:
1832 if (!gtkconv->send_history)
1833 break;
1834
1835 if (gtkconv->entry != entry)
1836 break;
1837
1838 if (gtkconv->send_history->prev && gtkconv->send_history->prev->data) {
1839 GObject *object;
1840 #if 0
1841 /* TODO WebKit: maybe not necessary? */
1842 GtkTextIter iter;
1843 GtkTextBuffer *buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(gtkconv->entry));
1844 #endif
1845
1846 gtkconv->send_history = gtkconv->send_history->prev;
1847
1848 /* Block the signal to prevent application of default formatting. */
1849 object = g_object_ref(G_OBJECT(gtkconv->entry));
1850 g_signal_handlers_block_matched(object, G_SIGNAL_MATCH_DATA,
1851 0, 0, NULL, NULL, gtkconv);
1852 /* Clear the formatting. */
1853 pidgin_webview_clear_formatting(PIDGIN_WEBVIEW(gtkconv->entry));
1854 /* Unblock the signal. */
1855 g_signal_handlers_unblock_matched(object, G_SIGNAL_MATCH_DATA,
1856 0, 0, NULL, NULL, gtkconv);
1857 g_object_unref(object);
1858
1859 pidgin_webview_load_html_string(PIDGIN_WEBVIEW(gtkconv->entry),
1860 gtkconv->send_history->data);
1861 /* this is mainly just a hack so the formatting at the
1862 * cursor gets picked up. */
1863 if (*(char *)gtkconv->send_history->data) {
1864 #if 0
1865 /* TODO WebKit: maybe not necessary? */
1866 gtk_text_buffer_get_end_iter(buffer, &iter);
1867 gtk_text_buffer_move_mark_by_name(buffer, "insert", &iter);
1868 #endif
1869 }
1870 }
1871
1872 return TRUE;
1873 break;
1874 } /* End of switch */
1875 }
1876
1877 /* If ALT (or whatever) was held down... */ 1753 /* If ALT (or whatever) was held down... */
1878 else if (event->state & GDK_MOD1_MASK) { 1754 else if (event->state & GDK_MOD1_MASK) {
1879
1880 } 1755 }
1881 1756
1882 /* If neither CTRL nor ALT were held down... */ 1757 /* If neither CTRL nor ALT were held down... */
1883 else { 1758 else {
1884 switch (event->keyval) { 1759 switch (event->keyval) {
1890 { 1765 {
1891 gint plugin_return; 1766 gint plugin_return;
1892 plugin_return = GPOINTER_TO_INT(purple_signal_emit_return_1( 1767 plugin_return = GPOINTER_TO_INT(purple_signal_emit_return_1(
1893 pidgin_conversations_get_handle(), "chat-nick-autocomplete", 1768 pidgin_conversations_get_handle(), "chat-nick-autocomplete",
1894 conv, event->state & GDK_SHIFT_MASK)); 1769 conv, event->state & GDK_SHIFT_MASK));
1895 return plugin_return ? TRUE : tab_complete(conv); 1770 return plugin_return;
1896 } 1771 }
1897 break; 1772 break;
1898 1773
1899 # warning fixme!! 1774 # warning fixme!!
1900 // case GDK_KEY_Page_Up: 1775 // case GDK_KEY_Page_Up:
1987 void 1862 void
1988 pidgin_conv_switch_active_conversation(PurpleConversation *conv) 1863 pidgin_conv_switch_active_conversation(PurpleConversation *conv)
1989 { 1864 {
1990 PidginConversation *gtkconv; 1865 PidginConversation *gtkconv;
1991 PurpleConversation *old_conv; 1866 PurpleConversation *old_conv;
1992 PidginWebView *entry;
1993 PurpleConnectionFlags features; 1867 PurpleConnectionFlags features;
1994 1868
1995 g_return_if_fail(conv != NULL); 1869 g_return_if_fail(conv != NULL);
1996 1870
1997 gtkconv = PIDGIN_CONVERSATION(conv); 1871 gtkconv = PIDGIN_CONVERSATION(conv);
2004 return; 1878 return;
2005 1879
2006 purple_conversation_close_logs(old_conv); 1880 purple_conversation_close_logs(old_conv);
2007 gtkconv->active_conv = conv; 1881 gtkconv->active_conv = conv;
2008 1882
2009 pidgin_webview_switch_active_conversation(
2010 PIDGIN_WEBVIEW(gtkconv->entry), conv);
2011 pidgin_webview_switch_active_conversation(
2012 PIDGIN_WEBVIEW(gtkconv->history), conv);
2013 purple_conversation_set_logging(conv, 1883 purple_conversation_set_logging(conv,
2014 gtk_toggle_action_get_active(GTK_TOGGLE_ACTION(gtkconv->win->menu->logging))); 1884 gtk_toggle_action_get_active(GTK_TOGGLE_ACTION(gtkconv->win->menu->logging)));
2015 1885
2016 entry = PIDGIN_WEBVIEW(gtkconv->entry);
2017
2018 features = purple_conversation_get_features(conv);
2019 if (!(features & PURPLE_CONNECTION_FLAG_HTML))
2020 pidgin_webview_clear_formatting(PIDGIN_WEBVIEW(gtkconv->entry));
2021 else if (features & PURPLE_CONNECTION_FLAG_FORMATTING_WBFO &&
2022 !(purple_conversation_get_features(old_conv) & PURPLE_CONNECTION_FLAG_FORMATTING_WBFO))
2023 {
2024 /* The old conversation allowed formatting on parts of the
2025 * buffer, but the new one only allows it on the whole
2026 * buffer. This code saves the formatting from the current
2027 * position of the cursor, clears the formatting, then
2028 * applies the saved formatting to the entire buffer. */
2029
2030 gboolean bold;
2031 gboolean italic;
2032 gboolean underline;
2033 gboolean strike;
2034 char *fontface = pidgin_webview_get_current_fontface(entry);
2035 char *forecolor = pidgin_webview_get_current_forecolor(entry);
2036 char *backcolor = pidgin_webview_get_current_backcolor(entry);
2037 #if 0
2038 /* TODO WebKit: Do we need this again? */
2039 char *background = pidgin_webview_get_current_background(entry);
2040 #endif
2041 gint fontsize = pidgin_webview_get_current_fontsize(entry);
2042 gboolean bold2;
2043 gboolean italic2;
2044 gboolean underline2;
2045 gboolean strike2;
2046
2047 pidgin_webview_get_current_format(entry, &bold, &italic, &underline, &strike);
2048
2049 /* Clear existing formatting */
2050 pidgin_webview_clear_formatting(entry);
2051
2052 /* Apply saved formatting to the whole buffer. */
2053
2054 pidgin_webview_get_current_format(entry, &bold2, &italic2, &underline2, &strike2);
2055
2056 if (bold != bold2)
2057 pidgin_webview_toggle_bold(entry);
2058
2059 if (italic != italic2)
2060 pidgin_webview_toggle_italic(entry);
2061
2062 if (underline != underline2)
2063 pidgin_webview_toggle_underline(entry);
2064
2065 if (strike != strike2)
2066 pidgin_webview_toggle_strike(entry);
2067
2068 pidgin_webview_toggle_fontface(entry, fontface);
2069
2070 if (!(features & PURPLE_CONNECTION_FLAG_NO_FONTSIZE))
2071 pidgin_webview_font_set_size(entry, fontsize);
2072
2073 pidgin_webview_toggle_forecolor(entry, forecolor);
2074
2075 if (!(features & PURPLE_CONNECTION_FLAG_NO_BGCOLOR))
2076 {
2077 pidgin_webview_toggle_backcolor(entry, backcolor);
2078 #if 0
2079 pidgin_webview_toggle_background(entry, background);
2080 #endif
2081 }
2082
2083 g_free(fontface);
2084 g_free(forecolor);
2085 g_free(backcolor);
2086 #if 0
2087 g_free(background);
2088 #endif
2089 }
2090 else
2091 {
2092 /* This is done in default_formatize, which is called from clear_formatting_cb,
2093 * which is (obviously) a clear_formatting signal handler. However, if we're
2094 * here, we didn't call pidgin_webview_clear_formatting() (because we want to
2095 * preserve the formatting exactly as it is), so we have to do this now. */
2096 pidgin_webview_set_whole_buffer_formatting_only(entry,
2097 (features & PURPLE_CONNECTION_FLAG_FORMATTING_WBFO));
2098 }
2099
2100 purple_signal_emit(pidgin_conversations_get_handle(), "conversation-switched", conv); 1886 purple_signal_emit(pidgin_conversations_get_handle(), "conversation-switched", conv);
2101 1887
2102 gray_stuff_out(gtkconv); 1888 gray_stuff_out(gtkconv);
2103 update_typing_icon(gtkconv); 1889 update_typing_icon(gtkconv);
2104 g_object_set_data(G_OBJECT(entry), "transient_buddy", NULL); 1890 g_object_set_data(G_OBJECT(gtkconv->entry), "transient_buddy", NULL);
2105 regenerate_options_items(gtkconv->win); 1891 regenerate_options_items(gtkconv->win);
2106 1892
2107 gtk_window_set_title(GTK_WINDOW(gtkconv->win->window), 1893 gtk_window_set_title(GTK_WINDOW(gtkconv->win->window),
2108 gtk_label_get_text(GTK_LABEL(gtkconv->tab_label))); 1894 gtk_label_get_text(GTK_LABEL(gtkconv->tab_label)));
2109 } 1895 }
2812 { "Alias", NULL, N_("Al_ias..."), NULL, NULL, G_CALLBACK(menu_alias_cb) }, 2598 { "Alias", NULL, N_("Al_ias..."), NULL, NULL, G_CALLBACK(menu_alias_cb) },
2813 { "Block", PIDGIN_STOCK_TOOLBAR_BLOCK, N_("_Block..."), NULL, NULL, G_CALLBACK(menu_block_cb) }, 2599 { "Block", PIDGIN_STOCK_TOOLBAR_BLOCK, N_("_Block..."), NULL, NULL, G_CALLBACK(menu_block_cb) },
2814 { "Unblock", PIDGIN_STOCK_TOOLBAR_UNBLOCK, N_("_Unblock..."), NULL, NULL, G_CALLBACK(menu_unblock_cb) }, 2600 { "Unblock", PIDGIN_STOCK_TOOLBAR_UNBLOCK, N_("_Unblock..."), NULL, NULL, G_CALLBACK(menu_unblock_cb) },
2815 { "Add", GTK_STOCK_ADD, N_("_Add..."), NULL, NULL, G_CALLBACK(menu_add_remove_cb) }, 2601 { "Add", GTK_STOCK_ADD, N_("_Add..."), NULL, NULL, G_CALLBACK(menu_add_remove_cb) },
2816 { "Remove", GTK_STOCK_REMOVE, N_("_Remove..."), NULL, NULL, G_CALLBACK(menu_add_remove_cb) }, 2602 { "Remove", GTK_STOCK_REMOVE, N_("_Remove..."), NULL, NULL, G_CALLBACK(menu_add_remove_cb) },
2817 { "InsertLink", PIDGIN_STOCK_TOOLBAR_INSERT_LINK, N_("Insert Lin_k..."), NULL, NULL, G_CALLBACK(menu_insert_link_cb) }, 2603 { "InsertLink", PIDGIN_STOCK_TOOLBAR_INSERT_LINK, N_("Insert Lin_k..."), NULL, NULL, NULL },
2818 { "InsertImage", PIDGIN_STOCK_TOOLBAR_INSERT_IMAGE, N_("Insert Imag_e..."), NULL, NULL, G_CALLBACK(menu_insert_image_cb) }, 2604 { "InsertImage", PIDGIN_STOCK_TOOLBAR_INSERT_IMAGE, N_("Insert Imag_e..."), NULL, NULL, NULL },
2819 { "Close", GTK_STOCK_CLOSE, N_("_Close"), "<control>W", NULL, G_CALLBACK(menu_close_conv_cb) }, 2605 { "Close", GTK_STOCK_CLOSE, N_("_Close"), "<control>W", NULL, G_CALLBACK(menu_close_conv_cb) },
2820 2606
2821 /* Options */ 2607 /* Options */
2822 { "OptionsMenu", NULL, N_("_Options"), NULL, NULL, NULL }, 2608 { "OptionsMenu", NULL, N_("_Options"), NULL, NULL, NULL },
2823 }; 2609 };
3991 gdk_rgba_free(color); 3777 gdk_rgba_free(color);
3992 #endif 3778 #endif
3993 g_free(alias_key); 3779 g_free(alias_key);
3994 } 3780 }
3995 3781
3996 /*
3997 * tab_complete_process_item:
3998 * @most_matched: Used internally by this function.
3999 * @entered: The partial string that the user types before hitting the
4000 * tab key.
4001 * @entered_chars: The length of entered.
4002 * @partial: This is a return variable. This will be set to a string
4003 * containing the largest common string between all matches. This will
4004 * be inserted into the input box at the start of the word that the
4005 * user is tab completing. For example, if a chat room contains
4006 * "AlfFan" and "AlfHater" and the user types "a<TAB>" then this will
4007 * contain "Alf"
4008 * @matches: This is a return variable. If the given name is a potential
4009 * match for the entered string, then add a copy of the name to this
4010 * list. The caller is responsible for g_free'ing the data in this
4011 * list.
4012 * @name: The buddy name or alias or slash command name that we're
4013 * checking for a match.
4014 */
4015 static void
4016 tab_complete_process_item(int *most_matched, const char *entered, gsize entered_chars, char **partial,
4017 GList **matches, const char *name)
4018 {
4019 char *nick_partial;
4020 gsize name_len = g_utf8_strlen(name, -1);
4021
4022 if (entered_chars > name_len)
4023 return;
4024
4025 nick_partial = g_utf8_substring(name, 0, entered_chars);
4026 if (purple_utf8_strcasecmp(nick_partial, entered)) {
4027 g_free(nick_partial);
4028 return;
4029 }
4030 g_free(nick_partial);
4031
4032 /* if we're here, it's a possible completion */
4033
4034 if (*most_matched == -1) {
4035 /*
4036 * this will only get called once, since from now
4037 * on *most_matched is >= 0
4038 */
4039 *most_matched = name_len;
4040 *partial = g_strdup(name);
4041 }
4042 else if (*most_matched) {
4043 char *tmp = g_strdup(name);
4044
4045 while (purple_utf8_strcasecmp(tmp, *partial)) {
4046 *(g_utf8_offset_to_pointer(*partial, *most_matched)) = '\0';
4047 if (*most_matched < (goffset)g_utf8_strlen(tmp, -1))
4048 *(g_utf8_offset_to_pointer(tmp, *most_matched)) = '\0';
4049 (*most_matched)--;
4050 }
4051 (*most_matched)++;
4052
4053 g_free(tmp);
4054 }
4055
4056 *matches = g_list_insert_sorted(*matches, g_strdup(name),
4057 (GCompareFunc)purple_utf8_strcasecmp);
4058 }
4059
4060 static gboolean
4061 is_first_container(WebKitDOMNode *container)
4062 {
4063 gchar *name;
4064 WebKitDOMNode *parent;
4065
4066 while (container) {
4067 parent = webkit_dom_node_get_parent_node(container);
4068 if (parent) {
4069 name = webkit_dom_node_get_node_name(parent);
4070
4071 if (purple_strequal(name, "BODY")) {
4072 g_free(name);
4073
4074 if (webkit_dom_node_get_previous_sibling(container) == NULL)
4075 return TRUE;
4076 else
4077 return FALSE;
4078 }
4079 g_free(name);
4080 }
4081 else
4082 break;
4083
4084 container = parent;
4085 }
4086
4087 return FALSE;
4088 }
4089
4090 static gboolean
4091 tab_complete(PurpleConversation *conv)
4092 {
4093 PidginConversation *gtkconv;
4094 WebKitDOMNode *container;
4095 glong caret, word_start, content_len;
4096 int most_matched = -1, colon = 0;
4097 char *ch, *ch2 = NULL;
4098 char *entered, *partial = NULL;
4099 char *content, *sub1, *sub2, *modified;
4100 const char *prefix;
4101 GList *matches = NULL;
4102 gboolean command = FALSE;
4103 gsize entered_chars = 0;
4104
4105 gtkconv = PIDGIN_CONVERSATION(conv);
4106 pidgin_webview_get_caret(PIDGIN_WEBVIEW(gtkconv->entry), &container, &caret);
4107
4108 /* if there's nothing there just return */
4109 if (caret <= 0)
4110 return PURPLE_IS_CHAT_CONVERSATION(conv);
4111
4112 content = webkit_dom_node_get_node_value(container);
4113 content_len = g_utf8_strlen(content, -1);
4114
4115 /* if we're at the end of ":" or ": " we need to move back 1 or 2 spaces */
4116 if (caret >= 2) {
4117 ch = g_utf8_offset_to_pointer(content, caret - 2);
4118 ch2 = g_utf8_find_next_char(ch, NULL);
4119 }
4120
4121 if (caret >= 2 && *ch == ':' && g_unichar_isspace(g_utf8_get_char(ch2)))
4122 colon = 2;
4123 else if (caret >= 1 && content[caret - 1] == ':')
4124 colon = 1;
4125
4126 caret -= colon;
4127 word_start = caret;
4128
4129 /* find the start of the word that we're tabbing. */
4130 ch = g_utf8_offset_to_pointer(content, caret);
4131 while ((ch = g_utf8_find_prev_char(content, ch))) {
4132 if (!g_unichar_isspace(g_utf8_get_char(ch)))
4133 --word_start;
4134 else
4135 break;
4136 }
4137
4138 prefix = pidgin_get_cmd_prefix();
4139 if (word_start == 0 &&
4140 ((gsize)caret >= strlen(prefix)) && !strncmp(content, prefix, strlen(prefix))) {
4141 command = TRUE;
4142 word_start += strlen(prefix);
4143 }
4144
4145 entered = g_utf8_substring(content, word_start, caret);
4146 entered_chars = g_utf8_strlen(entered, -1);
4147
4148 if (!entered_chars) {
4149 g_free(content);
4150 g_free(entered);
4151 return PURPLE_IS_CHAT_CONVERSATION(conv);
4152 }
4153
4154 if (command) {
4155 GList *list = purple_cmd_list(conv);
4156 GList *l;
4157
4158 /* Commands */
4159 for (l = list; l != NULL; l = l->next) {
4160 tab_complete_process_item(&most_matched, entered, entered_chars, &partial,
4161 &matches, l->data);
4162 }
4163 g_list_free(list);
4164 } else if (PURPLE_IS_CHAT_CONVERSATION(conv)) {
4165 GList *l, *users;
4166 GtkTreeModel *model = gtk_tree_view_get_model(GTK_TREE_VIEW(PIDGIN_CONVERSATION(conv)->u.chat->list));
4167 GtkTreeIter iter;
4168 int f;
4169
4170 /* Users */
4171 users = purple_chat_conversation_get_users(PURPLE_CHAT_CONVERSATION(conv));
4172 for (l = users; l != NULL; l = l->next) {
4173 tab_complete_process_item(&most_matched, entered, entered_chars, &partial,
4174 &matches, purple_chat_user_get_name((PurpleChatUser *)l->data));
4175 }
4176 g_list_free(users);
4177
4178 /* Aliases */
4179 if (gtk_tree_model_get_iter_first(GTK_TREE_MODEL(model), &iter))
4180 {
4181 do {
4182 char *name;
4183 char *alias;
4184
4185 gtk_tree_model_get(model, &iter,
4186 CHAT_USERS_NAME_COLUMN, &name,
4187 CHAT_USERS_ALIAS_COLUMN, &alias,
4188 -1);
4189
4190 if (name && alias && !purple_strequal(name, alias))
4191 tab_complete_process_item(&most_matched, entered, entered_chars, &partial,
4192 &matches, alias);
4193 g_free(name);
4194 g_free(alias);
4195
4196 f = gtk_tree_model_iter_next(model, &iter);
4197 } while (f != 0);
4198 }
4199 } else {
4200 g_free(content);
4201 g_free(entered);
4202 return FALSE;
4203 }
4204
4205 /* if there weren't any matches, return */
4206 if (!matches) {
4207 /* if matches isn't set partials won't be either */
4208 g_free(content);
4209 g_free(entered);
4210 return PURPLE_IS_CHAT_CONVERSATION(conv);
4211 }
4212
4213 sub1 = g_utf8_substring(content, 0, word_start);
4214 sub2 = g_utf8_substring(content, caret, content_len);
4215
4216 if (!matches->next) {
4217 /* there was only one match. fill it in. */
4218
4219 if (!colon && !word_start && is_first_container(container)) {
4220 char *tmp = NULL;
4221 if (caret < content_len) {
4222 tmp = g_strdup_printf("%s: ", (char *)matches->data);
4223 } else {
4224 char nbsp[6] = {0};
4225 g_unichar_to_utf8(0xA0, nbsp);
4226 tmp = g_strdup_printf("%s:%s", (char *)matches->data, nbsp);
4227 }
4228
4229 modified = g_strdup_printf("%s%s", tmp, sub2);
4230 webkit_dom_node_set_node_value(container, modified, NULL);
4231 pidgin_webview_set_caret(PIDGIN_WEBVIEW(gtkconv->entry), container,
4232 g_utf8_strlen(tmp, -1));
4233 g_free(tmp);
4234 g_free(modified);
4235 }
4236 else {
4237 modified = g_strdup_printf("%s%s%s", sub1, (char *)matches->data, sub2);
4238 webkit_dom_node_set_node_value(container, modified, NULL);
4239 pidgin_webview_set_caret(PIDGIN_WEBVIEW(gtkconv->entry), container,
4240 word_start + g_utf8_strlen(matches->data, -1) + colon);
4241 g_free(modified);
4242 }
4243
4244 g_free(matches->data);
4245 g_list_free(matches);
4246 }
4247 else {
4248 /*
4249 * there were lots of matches, fill in as much as possible
4250 * and display all of them
4251 */
4252 char *addthis = g_malloc0(1);
4253
4254 while (matches) {
4255 char *tmp = addthis;
4256 addthis = g_strconcat(tmp, matches->data, " ", NULL);
4257 g_free(tmp);
4258 g_free(matches->data);
4259 matches = g_list_remove(matches, matches->data);
4260 }
4261
4262 purple_conversation_write_system_message(conv, addthis, PURPLE_MESSAGE_NO_LOG);
4263
4264 modified = g_strdup_printf("%s%s%s", sub1, partial, sub2);
4265 webkit_dom_node_set_node_value(container, modified, NULL);
4266 pidgin_webview_set_caret(PIDGIN_WEBVIEW(gtkconv->entry), container,
4267 word_start + g_utf8_strlen(partial, -1) + colon);
4268 g_free(addthis);
4269 g_free(modified);
4270 }
4271
4272 g_free(content);
4273 g_free(entered);
4274 g_free(partial);
4275 g_free(sub1);
4276 g_free(sub2);
4277
4278 return TRUE;
4279 }
4280
4281 static void topic_callback(GtkWidget *w, PidginConversation *gtkconv) 3782 static void topic_callback(GtkWidget *w, PidginConversation *gtkconv)
4282 { 3783 {
4283 PurpleProtocol *protocol = NULL; 3784 PurpleProtocol *protocol = NULL;
4284 PurpleConnection *gc; 3785 PurpleConnection *gc;
4285 PurpleConversation *conv = gtkconv->active_conv; 3786 PurpleConversation *conv = gtkconv->active_conv;
5422 4923
5423 g_free(*str); 4924 g_free(*str);
5424 *str = ret; 4925 *str = ret;
5425 } 4926 }
5426 #endif 4927 #endif
5427
5428 static char *
5429 replace_message_tokens(
5430 const char *text,
5431 PurpleConversation *conv,
5432 const char *name, /* author */
5433 const char *alias, /* author's alias */
5434 const char *message,
5435 PurpleMessageFlags flags,
5436 time_t mtime)
5437 {
5438 GString *str;
5439 const char *cur = text;
5440 const char *prev = cur;
5441 struct tm *tm = NULL;
5442
5443 if (text == NULL || *text == '\0')
5444 return NULL;
5445
5446 str = g_string_new(NULL);
5447 while ((cur = strchr(cur, '%'))) {
5448 const char *replace = NULL;
5449 const char *fin = NULL;
5450 gpointer freeval = NULL;
5451
5452 if (g_str_has_prefix(cur, "%message%")) {
5453 replace = message;
5454
5455 } else if (g_str_has_prefix(cur, "%messageClasses%")) {
5456 char *user;
5457 GString *classes = g_string_new(NULL);
5458 #define ADD_CLASS(f, class) \
5459 if (flags & f) \
5460 g_string_append(classes, class);
5461 ADD_CLASS(PURPLE_MESSAGE_SEND, "outgoing ");
5462 ADD_CLASS(PURPLE_MESSAGE_RECV, "incoming ");
5463 ADD_CLASS(PURPLE_MESSAGE_SYSTEM, "event ");
5464 ADD_CLASS(PURPLE_MESSAGE_AUTO_RESP, "autoreply ");
5465 ADD_CLASS(PURPLE_MESSAGE_DELAYED, "history ");
5466 ADD_CLASS(PURPLE_MESSAGE_NICK, "mention ");
5467 #undef ADD_CLASS
5468 user = get_class_for_user(name);
5469 g_string_append(classes, user);
5470 g_free(user);
5471
5472 replace = freeval = g_string_free(classes, FALSE);
5473
5474 } else if (g_str_has_prefix(cur, "%time")) {
5475 const char *tmp = cur + strlen("%time");
5476
5477 if (*tmp == '{') {
5478 char *end;
5479 tmp++;
5480 end = strstr(tmp, "}%");
5481 if (!end) /* Invalid string */
5482 continue;
5483 if (!tm)
5484 tm = localtime(&mtime);
5485 replace = freeval = purple_uts35_to_str(tmp, end - tmp, tm);
5486 fin = end + 1;
5487 } else {
5488 if (!tm)
5489 tm = localtime(&mtime);
5490
5491 replace = purple_utf8_strftime("%X", tm);
5492 }
5493
5494 } else if (g_str_has_prefix(cur, "%shortTime%")) {
5495 if (!tm)
5496 tm = localtime(&mtime);
5497
5498 replace = purple_utf8_strftime("%H:%M", tm);
5499
5500 } else if (g_str_has_prefix(cur, "%userIconPath%")) {
5501 if (flags & PURPLE_MESSAGE_SEND) {
5502 if (purple_account_get_bool(purple_conversation_get_account(conv), "use-global-buddyicon", TRUE)) {
5503 replace = purple_prefs_get_path(PIDGIN_PREFS_ROOT "/accounts/buddyicon");
5504 } else {
5505 PurpleImage *img = purple_buddy_icons_find_account_icon(purple_conversation_get_account(conv));
5506 /* XXX: this may be NULL */
5507 replace = purple_image_get_path(img);
5508 }
5509 if (replace == NULL || !g_file_test(replace, G_FILE_TEST_EXISTS)) {
5510 replace = freeval = g_build_filename("Outgoing", "buddy_icon.png", NULL);
5511 }
5512 } else if (flags & PURPLE_MESSAGE_RECV) {
5513 PurpleBuddyIcon *icon = purple_im_conversation_get_icon(PURPLE_IM_CONVERSATION(conv));
5514 if (icon)
5515 replace = purple_buddy_icon_get_full_path(icon);
5516 if (replace == NULL || !g_file_test(replace, G_FILE_TEST_EXISTS)) {
5517 replace = freeval = g_build_filename("Incoming", "buddy_icon.png", NULL);
5518 }
5519 }
5520
5521 } else if (g_str_has_prefix(cur, "%senderScreenName%")) {
5522 replace = name;
5523
5524 } else if (g_str_has_prefix(cur, "%sender%")) {
5525 replace = alias;
5526
5527 } else if (g_str_has_prefix(cur, "%senderColor%")) {
5528 const GdkRGBA *color = get_nick_color(PIDGIN_CONVERSATION(conv), name);
5529 replace = freeval = g_strdup_printf("#%02x%02x%02x",
5530 (unsigned int)(color->red * 255),
5531 (unsigned int)(color->green * 255),
5532 (unsigned int)(color->blue * 255));
5533
5534 } else if (g_str_has_prefix(cur, "%service%")) {
5535 replace = purple_account_get_protocol_name(purple_conversation_get_account(conv));
5536
5537 } else if (g_str_has_prefix(cur, "%messageDirection%")) {
5538 replace = purple_markup_is_rtl(message) ? "rtl" : "ltr";
5539
5540 } else if (g_str_has_prefix(cur, "%status%")) {
5541 GString *classes = g_string_new(NULL);
5542
5543 if (flags & PURPLE_MESSAGE_ERROR)
5544 g_string_append(classes, "error ");
5545
5546 replace = freeval = g_string_free(classes, FALSE);
5547
5548 } else if (g_str_has_prefix(cur, "%variant%")) {
5549 replace = pidgin_conversation_theme_get_variant(PIDGIN_CONVERSATION(conv)->theme);
5550 replace = freeval = g_strdup(replace);
5551 purple_util_chrreplace(freeval, ' ', '_');
5552
5553 } else {
5554 cur++;
5555 continue;
5556 }
5557
5558 /* Here we have a replacement to make */
5559 g_string_append_len(str, prev, cur - prev);
5560 if (replace)
5561 g_string_append(str, replace);
5562 g_free(freeval);
5563 replace = freeval = NULL;
5564
5565 /* And update the pointers */
5566 if (fin) {
5567 prev = cur = fin + 1;
5568 } else {
5569 prev = cur = strchr(cur + 1, '%') + 1;
5570 }
5571
5572 }
5573
5574 /* And wrap it up */
5575 g_string_append(str, prev);
5576
5577 return g_string_free(str, FALSE);
5578 }
5579
5580 static gboolean
5581 pidgin_conv_write_smiley(GString *out, PurpleSmiley *smiley,
5582 PurpleConversation *conv, gpointer _proto_name)
5583 {
5584 gchar *escaped_shortcut;
5585 gchar *uri;
5586
5587 escaped_shortcut = g_markup_escape_text(
5588 purple_smiley_get_shortcut(smiley), -1);
5589 uri = purple_image_store_get_uri(PURPLE_IMAGE(smiley));
5590
5591 g_string_append_printf(out,
5592 "<img class=\"emoticon\" alt=\"%s\" title=\"%s\" "
5593 "src=\"%s\" />", escaped_shortcut,
5594 escaped_shortcut, uri);
5595
5596 g_free(uri);
5597 g_free(escaped_shortcut);
5598
5599 return TRUE;
5600 }
5601 4928
5602 static gboolean 4929 static gboolean
5603 writing_msg(PurpleConversation *conv, PurpleMessage *msg, gpointer _unused) 4930 writing_msg(PurpleConversation *conv, PurpleMessage *msg, gpointer _unused)
5604 { 4931 {
5605 PidginConversation *gtkconv; 4932 PidginConversation *gtkconv;
6227 PidginConvWindow *win; 5554 PidginConvWindow *win;
6228 PurpleConversation *conv = gtkconv->active_conv; 5555 PurpleConversation *conv = gtkconv->active_conv;
6229 PurpleConnection *gc; 5556 PurpleConnection *gc;
6230 PurpleProtocol *protocol = NULL; 5557 PurpleProtocol *protocol = NULL;
6231 GdkPixbuf *window_icon = NULL; 5558 GdkPixbuf *window_icon = NULL;
6232 PidginWebViewButtons buttons; 5559 // PidginWebViewButtons buttons;
6233 PurpleAccount *account; 5560 PurpleAccount *account;
6234 5561
6235 win = pidgin_conv_get_window(gtkconv); 5562 win = pidgin_conv_get_window(gtkconv);
6236 gc = purple_conversation_get_connection(conv); 5563 gc = purple_conversation_get_connection(conv);
6237 account = purple_conversation_get_account(conv); 5564 account = purple_conversation_get_account(conv);
6316 !purple_chat_conversation_has_left(PURPLE_CHAT_CONVERSATION(conv)) )) 5643 !purple_chat_conversation_has_left(PURPLE_CHAT_CONVERSATION(conv)) ))
6317 { 5644 {
6318 PurpleConnectionFlags features = purple_conversation_get_features(conv); 5645 PurpleConnectionFlags features = purple_conversation_get_features(conv);
6319 /* Account is online */ 5646 /* Account is online */
6320 /* Deal with the toolbar */ 5647 /* Deal with the toolbar */
5648 #if 0
6321 if (features & PURPLE_CONNECTION_FLAG_HTML) 5649 if (features & PURPLE_CONNECTION_FLAG_HTML)
6322 { 5650 {
6323 buttons = PIDGIN_WEBVIEW_ALL; /* Everything on */ 5651 buttons = PIDGIN_WEBVIEW_ALL; /* Everything on */
6324 if (features & PURPLE_CONNECTION_FLAG_NO_BGCOLOR) 5652 if (features & PURPLE_CONNECTION_FLAG_NO_BGCOLOR)
6325 buttons &= ~PIDGIN_WEBVIEW_BACKCOLOR; 5653 buttons &= ~PIDGIN_WEBVIEW_BACKCOLOR;
6327 { 5655 {
6328 buttons &= ~PIDGIN_WEBVIEW_GROW; 5656 buttons &= ~PIDGIN_WEBVIEW_GROW;
6329 buttons &= ~PIDGIN_WEBVIEW_SHRINK; 5657 buttons &= ~PIDGIN_WEBVIEW_SHRINK;
6330 } 5658 }
6331 if (features & PURPLE_CONNECTION_FLAG_NO_URLDESC) 5659 if (features & PURPLE_CONNECTION_FLAG_NO_URLDESC)
6332 buttons &= ~PIDGIN_WEBVIEW_LINKDESC; 5660 buttons &= ~PIDGIN_WEBVIEW_LINKDESC
6333 } else { 5661 } else {
6334 buttons = PIDGIN_WEBVIEW_SMILEY | PIDGIN_WEBVIEW_IMAGE; 5662 buttons = PIDGIN_WEBVIEW_SMILEY | PIDGIN_WEBVIEW_IMAGE;
6335 } 5663 }
6336 5664
6337 if (features & PURPLE_CONNECTION_FLAG_NO_IMAGES) 5665 if (features & PURPLE_CONNECTION_FLAG_NO_IMAGES)
6341 buttons |= PIDGIN_WEBVIEW_CUSTOM_SMILEY; 5669 buttons |= PIDGIN_WEBVIEW_CUSTOM_SMILEY;
6342 else 5670 else
6343 buttons &= ~PIDGIN_WEBVIEW_CUSTOM_SMILEY; 5671 buttons &= ~PIDGIN_WEBVIEW_CUSTOM_SMILEY;
6344 5672
6345 pidgin_webview_set_format_functions(PIDGIN_WEBVIEW(gtkconv->entry), buttons); 5673 pidgin_webview_set_format_functions(PIDGIN_WEBVIEW(gtkconv->entry), buttons);
5674 #endif
6346 5675
6347 /* Deal with menu items */ 5676 /* Deal with menu items */
6348 gtk_action_set_sensitive(win->menu->view_log, TRUE); 5677 gtk_action_set_sensitive(win->menu->view_log, TRUE);
6349 gtk_action_set_sensitive(win->menu->add_pounce, TRUE); 5678 gtk_action_set_sensitive(win->menu->add_pounce, TRUE);
6350 gtk_action_set_sensitive(win->menu->get_info, (PURPLE_PROTOCOL_IMPLEMENTS(protocol, SERVER_IFACE, get_info))); 5679 gtk_action_set_sensitive(win->menu->get_info, (PURPLE_PROTOCOL_IMPLEMENTS(protocol, SERVER_IFACE, get_info)));

mercurial