| 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) { |
| 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; |