| |
1 /** |
| |
2 * @file gtkconv.c GTK+ Conversation API |
| |
3 * @ingroup gtkui |
| |
4 * |
| |
5 * pidgin |
| |
6 * |
| |
7 * Pidgin is the legal property of its developers, whose names are too numerous |
| |
8 * to list here. Please refer to the COPYRIGHT file distributed with this |
| |
9 * source distribution. |
| |
10 * |
| |
11 * This program is free software; you can redistribute it and/or modify |
| |
12 * it under the terms of the GNU General Public License as published by |
| |
13 * the Free Software Foundation; either version 2 of the License, or |
| |
14 * (at your option) any later version. |
| |
15 * |
| |
16 * This program is distributed in the hope that it will be useful, |
| |
17 * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| |
18 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| |
19 * GNU General Public License for more details. |
| |
20 * |
| |
21 * You should have received a copy of the GNU General Public License |
| |
22 * along with this program; if not, write to the Free Software |
| |
23 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA |
| |
24 * |
| |
25 */ |
| |
26 #include "internal.h" |
| |
27 #include "pidgin.h" |
| |
28 |
| |
29 #ifndef _WIN32 |
| |
30 # include <X11/Xlib.h> |
| |
31 #endif |
| |
32 |
| |
33 #ifdef USE_GTKSPELL |
| |
34 # include <gtkspell/gtkspell.h> |
| |
35 # ifdef _WIN32 |
| |
36 # include "wspell.h" |
| |
37 # endif |
| |
38 #endif |
| |
39 |
| |
40 #include <gdk/gdkkeysyms.h> |
| |
41 |
| |
42 #include "account.h" |
| |
43 #include "cmds.h" |
| |
44 #include "debug.h" |
| |
45 #include "idle.h" |
| |
46 #include "imgstore.h" |
| |
47 #include "log.h" |
| |
48 #include "notify.h" |
| |
49 #include "prpl.h" |
| |
50 #include "request.h" |
| |
51 #include "util.h" |
| |
52 |
| |
53 #include "gtkdnd-hints.h" |
| |
54 #include "gtkblist.h" |
| |
55 #include "gtkconv.h" |
| |
56 #include "gtkconvwin.h" |
| |
57 #include "gtkdialogs.h" |
| |
58 #include "gtkimhtml.h" |
| |
59 #include "gtkimhtmltoolbar.h" |
| |
60 #include "gtklog.h" |
| |
61 #include "gtkmenutray.h" |
| |
62 #include "gtkpounce.h" |
| |
63 #include "gtkprefs.h" |
| |
64 #include "gtkprivacy.h" |
| |
65 #include "gtkthemes.h" |
| |
66 #include "gtkutils.h" |
| |
67 #include "pidginstock.h" |
| |
68 |
| |
69 #include "gtknickcolors.h" |
| |
70 |
| |
71 #define AUTO_RESPONSE "<AUTO-REPLY> : " |
| |
72 |
| |
73 typedef enum |
| |
74 { |
| |
75 PIDGIN_CONV_SET_TITLE = 1 << 0, |
| |
76 PIDGIN_CONV_BUDDY_ICON = 1 << 1, |
| |
77 PIDGIN_CONV_MENU = 1 << 2, |
| |
78 PIDGIN_CONV_TAB_ICON = 1 << 3, |
| |
79 PIDGIN_CONV_TOPIC = 1 << 4, |
| |
80 PIDGIN_CONV_SMILEY_THEME = 1 << 5, |
| |
81 PIDGIN_CONV_COLORIZE_TITLE = 1 << 6 |
| |
82 }PidginConvFields; |
| |
83 |
| |
84 #define PIDGIN_CONV_ALL ((1 << 7) - 1) |
| |
85 |
| |
86 #define SEND_COLOR "#204a87" |
| |
87 #define RECV_COLOR "#cc0000" |
| |
88 #define HIGHLIGHT_COLOR "#AF7F00" |
| |
89 |
| |
90 /* Undef this to turn off "custom-smiley" debug messages */ |
| |
91 #define DEBUG_CUSTOM_SMILEY |
| |
92 |
| |
93 #define LUMINANCE(c) (float)((0.3*(c.red))+(0.59*(c.green))+(0.11*(c.blue))) |
| |
94 |
| |
95 #if 0 |
| |
96 /* These colors come from the default GNOME palette */ |
| |
97 static GdkColor nick_colors[] = { |
| |
98 {0, 47616, 46336, 43776}, /* Basic 3D Medium */ |
| |
99 {0, 32768, 32000, 29696}, /* Basic 3D Dark */ |
| |
100 {0, 22016, 20992, 18432}, /* 3D Shadow */ |
| |
101 {0, 33536, 42496, 32512}, /* Green Medium */ |
| |
102 {0, 23808, 29952, 21760}, /* Green Dark */ |
| |
103 {0, 17408, 22016, 12800}, /* Green Shadow */ |
| |
104 {0, 57344, 46592, 44800}, /* Red Hilight */ |
| |
105 {0, 49408, 26112, 23040}, /* Red Medium */ |
| |
106 {0, 34816, 17920, 12544}, /* Red Dark */ |
| |
107 {0, 49408, 14336, 8704}, /* Red Shadow */ |
| |
108 {0, 34816, 32512, 41728}, /* Purple Medium */ |
| |
109 {0, 25088, 23296, 33024}, /* Purple Dark */ |
| |
110 {0, 18688, 16384, 26112}, /* Purple Shadow */ |
| |
111 {0, 40192, 47104, 53760}, /* Blue Hilight */ |
| |
112 {0, 29952, 36864, 44544}, /* Blue Medium */ |
| |
113 {0, 57344, 49920, 40448}, /* Face Skin Medium */ |
| |
114 {0, 45824, 37120, 26880}, /* Face skin Dark */ |
| |
115 {0, 33280, 26112, 18176}, /* Face Skin Shadow */ |
| |
116 {0, 57088, 16896, 7680}, /* Accent Red */ |
| |
117 {0, 39168, 0, 0}, /* Accent Red Dark */ |
| |
118 {0, 17920, 40960, 17920}, /* Accent Green */ |
| |
119 {0, 9728, 50944, 9728} /* Accent Green Dark */ |
| |
120 }; |
| |
121 |
| |
122 #define NUM_NICK_COLORS (sizeof(nick_colors) / sizeof(*nick_colors)) |
| |
123 #endif |
| |
124 |
| |
125 /* From http://www.w3.org/TR/AERT#color-contrast */ |
| |
126 #define MIN_BRIGHTNESS_CONTRAST 75 |
| |
127 #define MIN_COLOR_CONTRAST 200 |
| |
128 |
| |
129 #define NUM_NICK_COLORS 220 |
| |
130 static GdkColor *nick_colors = NULL; |
| |
131 static guint nbr_nick_colors; |
| |
132 |
| |
133 typedef struct { |
| |
134 GtkWidget *window; |
| |
135 |
| |
136 GtkWidget *entry; |
| |
137 GtkWidget *message; |
| |
138 |
| |
139 PurpleConversation *conv; |
| |
140 |
| |
141 } InviteBuddyInfo; |
| |
142 |
| |
143 static GtkWidget *invite_dialog = NULL; |
| |
144 static GtkWidget *warn_close_dialog = NULL; |
| |
145 |
| |
146 static PidginWindow *hidden_convwin = NULL; |
| |
147 static GList *window_list = NULL; |
| |
148 |
| |
149 /* Lists of status icons at all available sizes for use as window icons */ |
| |
150 static GList *available_list = NULL; |
| |
151 static GList *away_list = NULL; |
| |
152 static GList *busy_list = NULL; |
| |
153 static GList *xa_list = NULL; |
| |
154 static GList *login_list = NULL; |
| |
155 static GList *logout_list = NULL; |
| |
156 static GList *offline_list = NULL; |
| |
157 static GHashTable *prpl_lists = NULL; |
| |
158 |
| |
159 static gboolean update_send_to_selection(PidginWindow *win); |
| |
160 static void generate_send_to_items(PidginWindow *win); |
| |
161 |
| |
162 /* Prototypes. <-- because Paco-Paco hates this comment. */ |
| |
163 static void got_typing_keypress(PidginConversation *gtkconv, gboolean first); |
| |
164 static void gray_stuff_out(PidginConversation *gtkconv); |
| |
165 static GList *generate_invite_user_names(PurpleConnection *gc); |
| |
166 static void add_chat_buddy_common(PurpleConversation *conv, PurpleConvChatBuddy *cb, const char *old_name); |
| |
167 static gboolean tab_complete(PurpleConversation *conv); |
| |
168 static void pidgin_conv_updated(PurpleConversation *conv, PurpleConvUpdateType type); |
| |
169 static void gtkconv_set_unseen(PidginConversation *gtkconv, PidginUnseenState state); |
| |
170 static void update_typing_icon(PidginConversation *gtkconv); |
| |
171 static const char *item_factory_translate_func (const char *path, gpointer func_data); |
| |
172 gboolean pidgin_conv_has_focus(PurpleConversation *conv); |
| |
173 static void pidgin_conv_custom_smiley_allocated(GdkPixbufLoader *loader, gpointer user_data); |
| |
174 static void pidgin_conv_custom_smiley_closed(GdkPixbufLoader *loader, gpointer user_data); |
| |
175 static GdkColor* generate_nick_colors(guint *numcolors, GdkColor background); |
| |
176 static gboolean color_is_visible(GdkColor foreground, GdkColor background, int color_contrast, int brightness_contrast); |
| |
177 static void pidgin_conv_update_fields(PurpleConversation *conv, PidginConvFields fields); |
| |
178 static void focus_out_from_menubar(GtkWidget *wid, PidginWindow *win); |
| |
179 |
| |
180 static GdkColor *get_nick_color(PidginConversation *gtkconv, const char *name) { |
| |
181 static GdkColor col; |
| |
182 GtkStyle *style = gtk_widget_get_style(gtkconv->imhtml); |
| |
183 float scale; |
| |
184 |
| |
185 col = nick_colors[g_str_hash(name) % nbr_nick_colors]; |
| |
186 scale = ((1-(LUMINANCE(style->base[GTK_STATE_NORMAL]) / LUMINANCE(style->white))) * |
| |
187 (LUMINANCE(style->white)/MAX(MAX(col.red, col.blue), col.green))); |
| |
188 |
| |
189 /* The colors are chosen to look fine on white; we should never have to darken */ |
| |
190 if (scale > 1) { |
| |
191 col.red *= scale; |
| |
192 col.green *= scale; |
| |
193 col.blue *= scale; |
| |
194 } |
| |
195 |
| |
196 return &col; |
| |
197 } |
| |
198 |
| |
199 /************************************************************************** |
| |
200 * Callbacks |
| |
201 **************************************************************************/ |
| |
202 |
| |
203 static gint |
| |
204 close_conv_cb(GtkWidget *w, PidginConversation *gtkconv) |
| |
205 { |
| |
206 GList *list = g_list_copy(gtkconv->convs); |
| |
207 |
| |
208 g_list_foreach(list, (GFunc)purple_conversation_destroy, NULL); |
| |
209 g_list_free(list); |
| |
210 |
| |
211 return TRUE; |
| |
212 } |
| |
213 |
| |
214 static gboolean |
| |
215 lbox_size_allocate_cb(GtkWidget *w, GtkAllocation *allocation, gpointer data) |
| |
216 { |
| |
217 purple_prefs_set_int(PIDGIN_PREFS_ROOT "/conversations/chat/userlist_width", allocation->width == 1 ? 0 : allocation->width); |
| |
218 |
| |
219 return FALSE; |
| |
220 } |
| |
221 |
| |
222 static gboolean |
| |
223 size_allocate_cb(GtkWidget *w, GtkAllocation *allocation, PidginConversation *gtkconv) |
| |
224 { |
| |
225 PurpleConversation *conv = gtkconv->active_conv; |
| |
226 |
| |
227 if (!GTK_WIDGET_VISIBLE(w)) |
| |
228 return FALSE; |
| |
229 |
| |
230 if (!PIDGIN_IS_PIDGIN_CONVERSATION(conv)) |
| |
231 return FALSE; |
| |
232 |
| |
233 if (gtkconv->auto_resize) { |
| |
234 return FALSE; |
| |
235 } |
| |
236 |
| |
237 /* I find that I resize the window when it has a bunch of conversations in it, mostly so that the |
| |
238 * tab bar will fit, but then I don't want new windows taking up the entire screen. I check to see |
| |
239 * if there is only one conversation in the window. This way we'll be setting new windows to the |
| |
240 * size of the last resized new window. */ |
| |
241 /* I think that the above justification is not the majority, and that the new tab resizing should |
| |
242 * negate it anyway. --luke */ |
| |
243 if (purple_conversation_get_type(conv) == PURPLE_CONV_TYPE_IM) |
| |
244 { |
| |
245 if (w == gtkconv->imhtml) { |
| |
246 purple_prefs_set_int(PIDGIN_PREFS_ROOT "/conversations/im/default_width", allocation->width); |
| |
247 purple_prefs_set_int(PIDGIN_PREFS_ROOT "/conversations/im/default_height", allocation->height); |
| |
248 } |
| |
249 if (w == gtkconv->lower_hbox) |
| |
250 purple_prefs_set_int(PIDGIN_PREFS_ROOT "/conversations/im/entry_height", allocation->height); |
| |
251 } |
| |
252 else if (purple_conversation_get_type(conv) == PURPLE_CONV_TYPE_CHAT) |
| |
253 { |
| |
254 if (w == gtkconv->imhtml) { |
| |
255 purple_prefs_set_int(PIDGIN_PREFS_ROOT "/conversations/chat/default_width", allocation->width); |
| |
256 purple_prefs_set_int(PIDGIN_PREFS_ROOT "/conversations/chat/default_height", allocation->height); |
| |
257 } |
| |
258 if (w == gtkconv->lower_hbox) |
| |
259 purple_prefs_set_int(PIDGIN_PREFS_ROOT "/conversations/chat/entry_height", allocation->height); |
| |
260 } |
| |
261 |
| |
262 return FALSE; |
| |
263 } |
| |
264 |
| |
265 static void |
| |
266 default_formatize(PidginConversation *c) |
| |
267 { |
| |
268 PurpleConversation *conv = c->active_conv; |
| |
269 |
| |
270 if (conv->features & PURPLE_CONNECTION_HTML) |
| |
271 { |
| |
272 char color[8]; |
| |
273 GdkColor fg_color, bg_color; |
| |
274 |
| |
275 if (purple_prefs_get_bool(PIDGIN_PREFS_ROOT "/conversations/send_bold") != GTK_IMHTML(c->entry)->edit.bold) |
| |
276 gtk_imhtml_toggle_bold(GTK_IMHTML(c->entry)); |
| |
277 |
| |
278 if (purple_prefs_get_bool(PIDGIN_PREFS_ROOT "/conversations/send_italic") != GTK_IMHTML(c->entry)->edit.italic) |
| |
279 gtk_imhtml_toggle_italic(GTK_IMHTML(c->entry)); |
| |
280 |
| |
281 if (purple_prefs_get_bool(PIDGIN_PREFS_ROOT "/conversations/send_underline") != GTK_IMHTML(c->entry)->edit.underline) |
| |
282 gtk_imhtml_toggle_underline(GTK_IMHTML(c->entry)); |
| |
283 |
| |
284 gtk_imhtml_toggle_fontface(GTK_IMHTML(c->entry), |
| |
285 purple_prefs_get_string(PIDGIN_PREFS_ROOT "/conversations/font_face")); |
| |
286 |
| |
287 if (!(conv->features & PURPLE_CONNECTION_NO_FONTSIZE)) |
| |
288 { |
| |
289 int size = purple_prefs_get_int(PIDGIN_PREFS_ROOT "/conversations/font_size"); |
| |
290 |
| |
291 /* 3 is the default. */ |
| |
292 if (size != 3) |
| |
293 gtk_imhtml_font_set_size(GTK_IMHTML(c->entry), size); |
| |
294 } |
| |
295 |
| |
296 if(strcmp(purple_prefs_get_string(PIDGIN_PREFS_ROOT "/conversations/fgcolor"), "") != 0) |
| |
297 { |
| |
298 gdk_color_parse(purple_prefs_get_string(PIDGIN_PREFS_ROOT "/conversations/fgcolor"), |
| |
299 &fg_color); |
| |
300 g_snprintf(color, sizeof(color), "#%02x%02x%02x", |
| |
301 fg_color.red / 256, |
| |
302 fg_color.green / 256, |
| |
303 fg_color.blue / 256); |
| |
304 } else |
| |
305 strcpy(color, ""); |
| |
306 |
| |
307 gtk_imhtml_toggle_forecolor(GTK_IMHTML(c->entry), color); |
| |
308 |
| |
309 if(!(conv->features & PURPLE_CONNECTION_NO_BGCOLOR) && |
| |
310 strcmp(purple_prefs_get_string(PIDGIN_PREFS_ROOT "/conversations/bgcolor"), "") != 0) |
| |
311 { |
| |
312 gdk_color_parse(purple_prefs_get_string(PIDGIN_PREFS_ROOT "/conversations/bgcolor"), |
| |
313 &bg_color); |
| |
314 g_snprintf(color, sizeof(color), "#%02x%02x%02x", |
| |
315 bg_color.red / 256, |
| |
316 bg_color.green / 256, |
| |
317 bg_color.blue / 256); |
| |
318 } else |
| |
319 strcpy(color, ""); |
| |
320 |
| |
321 gtk_imhtml_toggle_background(GTK_IMHTML(c->entry), color); |
| |
322 |
| |
323 if (conv->features & PURPLE_CONNECTION_FORMATTING_WBFO) |
| |
324 gtk_imhtml_set_whole_buffer_formatting_only(GTK_IMHTML(c->entry), TRUE); |
| |
325 else |
| |
326 gtk_imhtml_set_whole_buffer_formatting_only(GTK_IMHTML(c->entry), FALSE); |
| |
327 } |
| |
328 } |
| |
329 |
| |
330 static void |
| |
331 clear_formatting_cb(GtkIMHtml *imhtml, PidginConversation *gtkconv) |
| |
332 { |
| |
333 default_formatize(gtkconv); |
| |
334 } |
| |
335 |
| |
336 static const char * |
| |
337 pidgin_get_cmd_prefix(void) |
| |
338 { |
| |
339 return "/"; |
| |
340 } |
| |
341 |
| |
342 static PurpleCmdRet |
| |
343 say_command_cb(PurpleConversation *conv, |
| |
344 const char *cmd, char **args, char **error, void *data) |
| |
345 { |
| |
346 if (purple_conversation_get_type(conv) == PURPLE_CONV_TYPE_IM) |
| |
347 purple_conv_im_send(PURPLE_CONV_IM(conv), args[0]); |
| |
348 else if (purple_conversation_get_type(conv) == PURPLE_CONV_TYPE_CHAT) |
| |
349 purple_conv_chat_send(PURPLE_CONV_CHAT(conv), args[0]); |
| |
350 |
| |
351 return PURPLE_CMD_RET_OK; |
| |
352 } |
| |
353 |
| |
354 static PurpleCmdRet |
| |
355 me_command_cb(PurpleConversation *conv, |
| |
356 const char *cmd, char **args, char **error, void *data) |
| |
357 { |
| |
358 char *tmp; |
| |
359 |
| |
360 tmp = g_strdup_printf("/me %s", args[0]); |
| |
361 |
| |
362 if (purple_conversation_get_type(conv) == PURPLE_CONV_TYPE_IM) |
| |
363 purple_conv_im_send(PURPLE_CONV_IM(conv), tmp); |
| |
364 else if (purple_conversation_get_type(conv) == PURPLE_CONV_TYPE_CHAT) |
| |
365 purple_conv_chat_send(PURPLE_CONV_CHAT(conv), tmp); |
| |
366 |
| |
367 g_free(tmp); |
| |
368 return PURPLE_CMD_RET_OK; |
| |
369 } |
| |
370 |
| |
371 static PurpleCmdRet |
| |
372 debug_command_cb(PurpleConversation *conv, |
| |
373 const char *cmd, char **args, char **error, void *data) |
| |
374 { |
| |
375 char *tmp, *markup; |
| |
376 PurpleCmdStatus status; |
| |
377 |
| |
378 if (!g_ascii_strcasecmp(args[0], "version")) { |
| |
379 tmp = g_strdup_printf("me is using " PIDGIN_NAME " v%s.", VERSION); |
| |
380 markup = g_markup_escape_text(tmp, -1); |
| |
381 |
| |
382 status = purple_cmd_do_command(conv, tmp, markup, error); |
| |
383 |
| |
384 g_free(tmp); |
| |
385 g_free(markup); |
| |
386 return status; |
| |
387 } else { |
| |
388 purple_conversation_write(conv, NULL, _("Supported debug options are: version"), |
| |
389 PURPLE_MESSAGE_NO_LOG|PURPLE_MESSAGE_ERROR, time(NULL)); |
| |
390 return PURPLE_CMD_STATUS_OK; |
| |
391 } |
| |
392 } |
| |
393 |
| |
394 static PurpleCmdRet |
| |
395 clear_command_cb(PurpleConversation *conv, |
| |
396 const char *cmd, char **args, char **error, void *data) |
| |
397 { |
| |
398 PidginConversation *gtkconv = NULL; |
| |
399 |
| |
400 gtkconv = PIDGIN_CONVERSATION(conv); |
| |
401 |
| |
402 gtk_imhtml_clear(GTK_IMHTML(gtkconv->imhtml)); |
| |
403 return PURPLE_CMD_STATUS_OK; |
| |
404 } |
| |
405 |
| |
406 static PurpleCmdRet |
| |
407 help_command_cb(PurpleConversation *conv, |
| |
408 const char *cmd, char **args, char **error, void *data) |
| |
409 { |
| |
410 GList *l, *text; |
| |
411 GString *s; |
| |
412 |
| |
413 if (args[0] != NULL) { |
| |
414 s = g_string_new(""); |
| |
415 text = purple_cmd_help(conv, args[0]); |
| |
416 |
| |
417 if (text) { |
| |
418 for (l = text; l; l = l->next) |
| |
419 if (l->next) |
| |
420 g_string_append_printf(s, "%s\n", (char *)l->data); |
| |
421 else |
| |
422 g_string_append_printf(s, "%s", (char *)l->data); |
| |
423 } else { |
| |
424 g_string_append(s, _("No such command (in this context).")); |
| |
425 } |
| |
426 } else { |
| |
427 s = g_string_new(_("Use \"/help <command>\" for help on a specific command.\n" |
| |
428 "The following commands are available in this context:\n")); |
| |
429 |
| |
430 text = purple_cmd_list(conv); |
| |
431 for (l = text; l; l = l->next) |
| |
432 if (l->next) |
| |
433 g_string_append_printf(s, "%s, ", (char *)l->data); |
| |
434 else |
| |
435 g_string_append_printf(s, "%s.", (char *)l->data); |
| |
436 g_list_free(text); |
| |
437 } |
| |
438 |
| |
439 purple_conversation_write(conv, NULL, s->str, PURPLE_MESSAGE_NO_LOG, time(NULL)); |
| |
440 g_string_free(s, TRUE); |
| |
441 |
| |
442 return PURPLE_CMD_STATUS_OK; |
| |
443 } |
| |
444 |
| |
445 static void |
| |
446 send_history_add(PidginConversation *gtkconv, const char *message) |
| |
447 { |
| |
448 GList *first; |
| |
449 |
| |
450 first = g_list_first(gtkconv->send_history); |
| |
451 g_free(first->data); |
| |
452 first->data = g_strdup(message); |
| |
453 gtkconv->send_history = g_list_prepend(first, NULL); |
| |
454 } |
| |
455 |
| |
456 static void |
| |
457 reset_default_size(PidginConversation *gtkconv) |
| |
458 { |
| |
459 PurpleConversation *conv = gtkconv->active_conv; |
| |
460 if (purple_conversation_get_type(conv) == PURPLE_CONV_TYPE_CHAT) |
| |
461 gtk_widget_set_size_request(gtkconv->lower_hbox, -1, |
| |
462 purple_prefs_get_int(PIDGIN_PREFS_ROOT "/conversations/chat/entry_height")); |
| |
463 else |
| |
464 gtk_widget_set_size_request(gtkconv->lower_hbox, -1, |
| |
465 purple_prefs_get_int(PIDGIN_PREFS_ROOT "/conversations/im/entry_height")); |
| |
466 } |
| |
467 |
| |
468 static gboolean |
| |
469 check_for_and_do_command(PurpleConversation *conv) |
| |
470 { |
| |
471 PidginConversation *gtkconv; |
| |
472 char *cmd; |
| |
473 const char *prefix; |
| |
474 GtkTextIter start; |
| |
475 |
| |
476 gtkconv = PIDGIN_CONVERSATION(conv); |
| |
477 prefix = pidgin_get_cmd_prefix(); |
| |
478 |
| |
479 cmd = gtk_imhtml_get_text(GTK_IMHTML(gtkconv->entry), NULL, NULL); |
| |
480 gtk_text_buffer_get_start_iter(GTK_IMHTML(gtkconv->entry)->text_buffer, &start); |
| |
481 |
| |
482 if (cmd && (strncmp(cmd, prefix, strlen(prefix)) == 0) |
| |
483 && !gtk_text_iter_get_child_anchor(&start)) { |
| |
484 PurpleCmdStatus status; |
| |
485 char *error, *cmdline, *markup, *send_history; |
| |
486 GtkTextIter end; |
| |
487 |
| |
488 send_history = gtk_imhtml_get_markup(GTK_IMHTML(gtkconv->entry)); |
| |
489 send_history_add(gtkconv, send_history); |
| |
490 g_free(send_history); |
| |
491 |
| |
492 cmdline = cmd + strlen(prefix); |
| |
493 |
| |
494 gtk_text_iter_forward_chars(&start, g_utf8_strlen(prefix, -1)); |
| |
495 gtk_text_buffer_get_end_iter(GTK_IMHTML(gtkconv->entry)->text_buffer, &end); |
| |
496 markup = gtk_imhtml_get_markup_range(GTK_IMHTML(gtkconv->entry), &start, &end); |
| |
497 status = purple_cmd_do_command(conv, cmdline, markup, &error); |
| |
498 g_free(cmd); |
| |
499 g_free(markup); |
| |
500 |
| |
501 switch (status) { |
| |
502 case PURPLE_CMD_STATUS_OK: |
| |
503 return TRUE; |
| |
504 case PURPLE_CMD_STATUS_NOT_FOUND: |
| |
505 return FALSE; |
| |
506 case PURPLE_CMD_STATUS_WRONG_ARGS: |
| |
507 purple_conversation_write(conv, "", _("Syntax Error: You typed the wrong number of arguments " |
| |
508 "to that command."), |
| |
509 PURPLE_MESSAGE_NO_LOG, time(NULL)); |
| |
510 return TRUE; |
| |
511 case PURPLE_CMD_STATUS_FAILED: |
| |
512 purple_conversation_write(conv, "", error ? error : _("Your command failed for an unknown reason."), |
| |
513 PURPLE_MESSAGE_NO_LOG, time(NULL)); |
| |
514 g_free(error); |
| |
515 return TRUE; |
| |
516 case PURPLE_CMD_STATUS_WRONG_TYPE: |
| |
517 if(purple_conversation_get_type(conv) == PURPLE_CONV_TYPE_IM) |
| |
518 purple_conversation_write(conv, "", _("That command only works in chats, not IMs."), |
| |
519 PURPLE_MESSAGE_NO_LOG, time(NULL)); |
| |
520 else |
| |
521 purple_conversation_write(conv, "", _("That command only works in IMs, not chats."), |
| |
522 PURPLE_MESSAGE_NO_LOG, time(NULL)); |
| |
523 return TRUE; |
| |
524 case PURPLE_CMD_STATUS_WRONG_PRPL: |
| |
525 purple_conversation_write(conv, "", _("That command doesn't work on this protocol."), |
| |
526 PURPLE_MESSAGE_NO_LOG, time(NULL)); |
| |
527 return TRUE; |
| |
528 } |
| |
529 } |
| |
530 |
| |
531 g_free(cmd); |
| |
532 return FALSE; |
| |
533 } |
| |
534 |
| |
535 static void |
| |
536 send_cb(GtkWidget *widget, PidginConversation *gtkconv) |
| |
537 { |
| |
538 PurpleConversation *conv = gtkconv->active_conv; |
| |
539 PurpleAccount *account; |
| |
540 PurpleConnection *gc; |
| |
541 PurpleMessageFlags flags = 0; |
| |
542 char *buf, *clean; |
| |
543 |
| |
544 account = purple_conversation_get_account(conv); |
| |
545 |
| |
546 if (!purple_account_is_connected(account)) |
| |
547 return; |
| |
548 |
| |
549 if ((purple_conversation_get_type(conv) == PURPLE_CONV_TYPE_CHAT) && |
| |
550 purple_conv_chat_has_left(PURPLE_CONV_CHAT(conv))) |
| |
551 return; |
| |
552 |
| |
553 if (check_for_and_do_command(conv)) { |
| |
554 if (gtkconv->entry_growing) { |
| |
555 reset_default_size(gtkconv); |
| |
556 gtkconv->entry_growing = FALSE; |
| |
557 } |
| |
558 gtk_imhtml_clear(GTK_IMHTML(gtkconv->entry)); |
| |
559 return; |
| |
560 } |
| |
561 |
| |
562 buf = gtk_imhtml_get_markup(GTK_IMHTML(gtkconv->entry)); |
| |
563 clean = gtk_imhtml_get_text(GTK_IMHTML(gtkconv->entry), NULL, NULL); |
| |
564 |
| |
565 gtk_widget_grab_focus(gtkconv->entry); |
| |
566 |
| |
567 if (strlen(clean) == 0) { |
| |
568 g_free(buf); |
| |
569 g_free(clean); |
| |
570 return; |
| |
571 } |
| |
572 |
| |
573 purple_idle_touch(); |
| |
574 |
| |
575 /* XXX: is there a better way to tell if the message has images? */ |
| |
576 if (GTK_IMHTML(gtkconv->entry)->im_images != NULL) |
| |
577 flags |= PURPLE_MESSAGE_IMAGES; |
| |
578 |
| |
579 gc = purple_account_get_connection(account); |
| |
580 if (gc && (conv->features & PURPLE_CONNECTION_NO_NEWLINES)) { |
| |
581 char **bufs; |
| |
582 int i; |
| |
583 |
| |
584 bufs = gtk_imhtml_get_markup_lines(GTK_IMHTML(gtkconv->entry)); |
| |
585 for (i = 0; bufs[i]; i++) { |
| |
586 send_history_add(gtkconv, bufs[i]); |
| |
587 if (purple_conversation_get_type(conv) == PURPLE_CONV_TYPE_IM) |
| |
588 purple_conv_im_send_with_flags(PURPLE_CONV_IM(conv), bufs[i], flags); |
| |
589 else if (purple_conversation_get_type(conv) == PURPLE_CONV_TYPE_CHAT) |
| |
590 purple_conv_chat_send_with_flags(PURPLE_CONV_CHAT(conv), bufs[i], flags); |
| |
591 } |
| |
592 |
| |
593 g_strfreev(bufs); |
| |
594 |
| |
595 } else { |
| |
596 send_history_add(gtkconv, buf); |
| |
597 if (purple_conversation_get_type(conv) == PURPLE_CONV_TYPE_IM) |
| |
598 purple_conv_im_send_with_flags(PURPLE_CONV_IM(conv), buf, flags); |
| |
599 else if (purple_conversation_get_type(conv) == PURPLE_CONV_TYPE_CHAT) |
| |
600 purple_conv_chat_send_with_flags(PURPLE_CONV_CHAT(conv), buf, flags); |
| |
601 } |
| |
602 |
| |
603 g_free(clean); |
| |
604 g_free(buf); |
| |
605 |
| |
606 gtk_imhtml_clear(GTK_IMHTML(gtkconv->entry)); |
| |
607 if (gtkconv->entry_growing) { |
| |
608 reset_default_size(gtkconv); |
| |
609 gtkconv->entry_growing = FALSE; |
| |
610 } |
| |
611 gtkconv_set_unseen(gtkconv, PIDGIN_UNSEEN_NONE); |
| |
612 } |
| |
613 |
| |
614 static void |
| |
615 add_remove_cb(GtkWidget *widget, PidginConversation *gtkconv) |
| |
616 { |
| |
617 PurpleAccount *account; |
| |
618 const char *name; |
| |
619 PurpleConversation *conv = gtkconv->active_conv; |
| |
620 |
| |
621 account = purple_conversation_get_account(conv); |
| |
622 name = purple_conversation_get_name(conv); |
| |
623 |
| |
624 if (purple_conversation_get_type(conv) == PURPLE_CONV_TYPE_IM) { |
| |
625 PurpleBuddy *b; |
| |
626 |
| |
627 b = purple_find_buddy(account, name); |
| |
628 if (b != NULL) |
| |
629 pidgindialogs_remove_buddy(b); |
| |
630 else if (account != NULL && purple_account_is_connected(account)) |
| |
631 purple_blist_request_add_buddy(account, (char *)name, NULL, NULL); |
| |
632 } else if (purple_conversation_get_type(conv) == PURPLE_CONV_TYPE_CHAT) { |
| |
633 PurpleChat *c; |
| |
634 |
| |
635 c = purple_blist_find_chat(account, name); |
| |
636 if (c != NULL) |
| |
637 pidgindialogs_remove_chat(c); |
| |
638 else if (account != NULL && purple_account_is_connected(account)) |
| |
639 purple_blist_request_add_chat(account, NULL, NULL, name); |
| |
640 } |
| |
641 |
| |
642 gtk_widget_grab_focus(PIDGIN_CONVERSATION(conv)->entry); |
| |
643 } |
| |
644 |
| |
645 static void chat_do_info(PidginConversation *gtkconv, const char *who) |
| |
646 { |
| |
647 PurpleConversation *conv = gtkconv->active_conv; |
| |
648 PurplePluginProtocolInfo *prpl_info = NULL; |
| |
649 PurpleConnection *gc; |
| |
650 |
| |
651 if ((gc = purple_conversation_get_gc(conv))) { |
| |
652 prpl_info = PURPLE_PLUGIN_PROTOCOL_INFO(gc->prpl); |
| |
653 |
| |
654 /* |
| |
655 * If there are special needs for getting info on users in |
| |
656 * buddy chat "rooms"... |
| |
657 */ |
| |
658 if (prpl_info->get_cb_info != NULL) |
| |
659 { |
| |
660 prpl_info->get_cb_info(gc, |
| |
661 purple_conv_chat_get_id(PURPLE_CONV_CHAT(conv)), who); |
| |
662 } |
| |
663 else |
| |
664 prpl_info->get_info(gc, who); |
| |
665 } |
| |
666 } |
| |
667 |
| |
668 |
| |
669 static void |
| |
670 info_cb(GtkWidget *widget, PidginConversation *gtkconv) |
| |
671 { |
| |
672 PurpleConversation *conv = gtkconv->active_conv; |
| |
673 |
| |
674 if (purple_conversation_get_type(conv) == PURPLE_CONV_TYPE_IM) { |
| |
675 serv_get_info(purple_conversation_get_gc(conv), |
| |
676 purple_conversation_get_name(conv)); |
| |
677 |
| |
678 gtk_widget_grab_focus(gtkconv->entry); |
| |
679 } else if (purple_conversation_get_type(conv) == PURPLE_CONV_TYPE_CHAT) { |
| |
680 /* Get info of the person currently selected in the GtkTreeView */ |
| |
681 PidginChatPane *gtkchat; |
| |
682 GtkTreeIter iter; |
| |
683 GtkTreeModel *model; |
| |
684 GtkTreeSelection *sel; |
| |
685 char *name; |
| |
686 |
| |
687 gtkchat = gtkconv->u.chat; |
| |
688 |
| |
689 model = gtk_tree_view_get_model(GTK_TREE_VIEW(gtkchat->list)); |
| |
690 sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(gtkchat->list)); |
| |
691 |
| |
692 if (gtk_tree_selection_get_selected(sel, NULL, &iter)) |
| |
693 gtk_tree_model_get(GTK_TREE_MODEL(model), &iter, CHAT_USERS_NAME_COLUMN, &name, -1); |
| |
694 else |
| |
695 return; |
| |
696 |
| |
697 chat_do_info(gtkconv, name); |
| |
698 g_free(name); |
| |
699 } |
| |
700 } |
| |
701 |
| |
702 static void |
| |
703 block_cb(GtkWidget *widget, PidginConversation *gtkconv) |
| |
704 { |
| |
705 PurpleConversation *conv = gtkconv->active_conv; |
| |
706 PurpleAccount *account; |
| |
707 |
| |
708 account = purple_conversation_get_account(conv); |
| |
709 |
| |
710 if (account != NULL && purple_account_is_connected(account)) |
| |
711 pidgin_request_add_block(account, purple_conversation_get_name(conv)); |
| |
712 |
| |
713 gtk_widget_grab_focus(PIDGIN_CONVERSATION(conv)->entry); |
| |
714 } |
| |
715 |
| |
716 static void |
| |
717 unblock_cb(GtkWidget *widget, PidginConversation *gtkconv) |
| |
718 { |
| |
719 PurpleConversation *conv = gtkconv->active_conv; |
| |
720 PurpleAccount *account; |
| |
721 |
| |
722 account = purple_conversation_get_account(conv); |
| |
723 |
| |
724 if (account != NULL && purple_account_is_connected(account)) |
| |
725 pidgin_request_add_permit(account, purple_conversation_get_name(conv)); |
| |
726 |
| |
727 gtk_widget_grab_focus(PIDGIN_CONVERSATION(conv)->entry); |
| |
728 } |
| |
729 |
| |
730 static void |
| |
731 do_invite(GtkWidget *w, int resp, InviteBuddyInfo *info) |
| |
732 { |
| |
733 const char *buddy, *message; |
| |
734 PidginConversation *gtkconv; |
| |
735 |
| |
736 gtkconv = PIDGIN_CONVERSATION(info->conv); |
| |
737 |
| |
738 if (resp == GTK_RESPONSE_OK) { |
| |
739 buddy = gtk_entry_get_text(GTK_ENTRY(GTK_COMBO(info->entry)->entry)); |
| |
740 message = gtk_entry_get_text(GTK_ENTRY(info->message)); |
| |
741 |
| |
742 if (!g_ascii_strcasecmp(buddy, "")) |
| |
743 return; |
| |
744 |
| |
745 serv_chat_invite(purple_conversation_get_gc(info->conv), |
| |
746 purple_conv_chat_get_id(PURPLE_CONV_CHAT(info->conv)), |
| |
747 message, buddy); |
| |
748 } |
| |
749 |
| |
750 gtk_widget_destroy(invite_dialog); |
| |
751 invite_dialog = NULL; |
| |
752 |
| |
753 g_free(info); |
| |
754 } |
| |
755 |
| |
756 static void |
| |
757 invite_dnd_recv(GtkWidget *widget, GdkDragContext *dc, gint x, gint y, |
| |
758 GtkSelectionData *sd, guint inf, guint t, gpointer data) |
| |
759 { |
| |
760 InviteBuddyInfo *info = (InviteBuddyInfo *)data; |
| |
761 const char *convprotocol; |
| |
762 |
| |
763 convprotocol = purple_account_get_protocol_id(purple_conversation_get_account(info->conv)); |
| |
764 |
| |
765 if (sd->target == gdk_atom_intern("PURPLE_BLIST_NODE", FALSE)) |
| |
766 { |
| |
767 PurpleBlistNode *node = NULL; |
| |
768 PurpleBuddy *buddy; |
| |
769 |
| |
770 memcpy(&node, sd->data, sizeof(node)); |
| |
771 |
| |
772 if (PURPLE_BLIST_NODE_IS_CONTACT(node)) |
| |
773 buddy = purple_contact_get_priority_buddy((PurpleContact *)node); |
| |
774 else if (PURPLE_BLIST_NODE_IS_BUDDY(node)) |
| |
775 buddy = (PurpleBuddy *)node; |
| |
776 else |
| |
777 return; |
| |
778 |
| |
779 if (strcmp(convprotocol, purple_account_get_protocol_id(buddy->account))) |
| |
780 { |
| |
781 purple_notify_error(PIDGIN_CONVERSATION(info->conv), NULL, |
| |
782 _("That buddy is not on the same protocol as this " |
| |
783 "chat."), NULL); |
| |
784 } |
| |
785 else |
| |
786 gtk_entry_set_text(GTK_ENTRY(GTK_COMBO(info->entry)->entry), buddy->name); |
| |
787 |
| |
788 gtk_drag_finish(dc, TRUE, (dc->action == GDK_ACTION_MOVE), t); |
| |
789 } |
| |
790 else if (sd->target == gdk_atom_intern("application/x-im-contact", FALSE)) |
| |
791 { |
| |
792 char *protocol = NULL; |
| |
793 char *username = NULL; |
| |
794 PurpleAccount *account; |
| |
795 |
| |
796 if (pidgin_parse_x_im_contact((const char *)sd->data, FALSE, &account, |
| |
797 &protocol, &username, NULL)) |
| |
798 { |
| |
799 if (account == NULL) |
| |
800 { |
| |
801 purple_notify_error(PIDGIN_CONVERSATION(info->conv), NULL, |
| |
802 _("You are not currently signed on with an account that " |
| |
803 "can invite that buddy."), NULL); |
| |
804 } |
| |
805 else if (strcmp(convprotocol, purple_account_get_protocol_id(account))) |
| |
806 { |
| |
807 purple_notify_error(PIDGIN_CONVERSATION(info->conv), NULL, |
| |
808 _("That buddy is not on the same protocol as this " |
| |
809 "chat."), NULL); |
| |
810 } |
| |
811 else |
| |
812 { |
| |
813 gtk_entry_set_text(GTK_ENTRY(GTK_COMBO(info->entry)->entry), username); |
| |
814 } |
| |
815 } |
| |
816 |
| |
817 g_free(username); |
| |
818 g_free(protocol); |
| |
819 |
| |
820 gtk_drag_finish(dc, TRUE, (dc->action == GDK_ACTION_MOVE), t); |
| |
821 } |
| |
822 } |
| |
823 |
| |
824 static const GtkTargetEntry dnd_targets[] = |
| |
825 { |
| |
826 {"PURPLE_BLIST_NODE", GTK_TARGET_SAME_APP, 0}, |
| |
827 {"application/x-im-contact", 0, 1} |
| |
828 }; |
| |
829 |
| |
830 static void |
| |
831 invite_cb(GtkWidget *widget, PidginConversation *gtkconv) |
| |
832 { |
| |
833 PurpleConversation *conv = gtkconv->active_conv; |
| |
834 InviteBuddyInfo *info = NULL; |
| |
835 |
| |
836 if (invite_dialog == NULL) { |
| |
837 PurpleConnection *gc; |
| |
838 PidginWindow *gtkwin; |
| |
839 GtkWidget *label; |
| |
840 GtkWidget *vbox, *hbox; |
| |
841 GtkWidget *table; |
| |
842 GtkWidget *img; |
| |
843 |
| |
844 img = gtk_image_new_from_stock(PIDGIN_STOCK_DIALOG_QUESTION, |
| |
845 gtk_icon_size_from_name(PIDGIN_ICON_SIZE_TANGO_HUGE)); |
| |
846 |
| |
847 info = g_new0(InviteBuddyInfo, 1); |
| |
848 info->conv = conv; |
| |
849 |
| |
850 gc = purple_conversation_get_gc(conv); |
| |
851 gtkwin = pidgin_conv_get_window(gtkconv); |
| |
852 |
| |
853 /* Create the new dialog. */ |
| |
854 invite_dialog = gtk_dialog_new_with_buttons( |
| |
855 _("Invite Buddy Into Chat Room"), |
| |
856 GTK_WINDOW(gtkwin->window), 0, |
| |
857 GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL, |
| |
858 PIDGIN_STOCK_INVITE, GTK_RESPONSE_OK, NULL); |
| |
859 |
| |
860 gtk_dialog_set_default_response(GTK_DIALOG(invite_dialog), |
| |
861 GTK_RESPONSE_OK); |
| |
862 gtk_container_set_border_width(GTK_CONTAINER(invite_dialog), PIDGIN_HIG_BOX_SPACE); |
| |
863 gtk_window_set_resizable(GTK_WINDOW(invite_dialog), FALSE); |
| |
864 gtk_dialog_set_has_separator(GTK_DIALOG(invite_dialog), FALSE); |
| |
865 |
| |
866 info->window = GTK_WIDGET(invite_dialog); |
| |
867 |
| |
868 /* Setup the outside spacing. */ |
| |
869 vbox = GTK_DIALOG(invite_dialog)->vbox; |
| |
870 |
| |
871 gtk_box_set_spacing(GTK_BOX(vbox), PIDGIN_HIG_BORDER); |
| |
872 gtk_container_set_border_width(GTK_CONTAINER(vbox), PIDGIN_HIG_BOX_SPACE); |
| |
873 |
| |
874 /* Setup the inner hbox and put the dialog's icon in it. */ |
| |
875 hbox = gtk_hbox_new(FALSE, PIDGIN_HIG_BORDER); |
| |
876 gtk_container_add(GTK_CONTAINER(vbox), hbox); |
| |
877 gtk_box_pack_start(GTK_BOX(hbox), img, FALSE, FALSE, 0); |
| |
878 gtk_misc_set_alignment(GTK_MISC(img), 0, 0); |
| |
879 |
| |
880 /* Setup the right vbox. */ |
| |
881 vbox = gtk_vbox_new(FALSE, 0); |
| |
882 gtk_container_add(GTK_CONTAINER(hbox), vbox); |
| |
883 |
| |
884 /* Put our happy label in it. */ |
| |
885 label = gtk_label_new(_("Please enter the name of the user you wish " |
| |
886 "to invite, along with an optional invite " |
| |
887 "message.")); |
| |
888 gtk_widget_set_size_request(label, 350, -1); |
| |
889 gtk_label_set_line_wrap(GTK_LABEL(label), TRUE); |
| |
890 gtk_misc_set_alignment(GTK_MISC(label), 0, 0); |
| |
891 gtk_box_pack_start(GTK_BOX(vbox), label, FALSE, FALSE, 0); |
| |
892 |
| |
893 /* hbox for the table, and to give it some spacing on the left. */ |
| |
894 hbox = gtk_hbox_new(FALSE, PIDGIN_HIG_BOX_SPACE); |
| |
895 gtk_container_add(GTK_CONTAINER(vbox), hbox); |
| |
896 |
| |
897 /* Setup the table we're going to use to lay stuff out. */ |
| |
898 table = gtk_table_new(2, 2, FALSE); |
| |
899 gtk_table_set_row_spacings(GTK_TABLE(table), PIDGIN_HIG_BOX_SPACE); |
| |
900 gtk_table_set_col_spacings(GTK_TABLE(table), PIDGIN_HIG_BOX_SPACE); |
| |
901 gtk_container_set_border_width(GTK_CONTAINER(table), PIDGIN_HIG_BORDER); |
| |
902 gtk_box_pack_start(GTK_BOX(vbox), table, FALSE, FALSE, 0); |
| |
903 |
| |
904 /* Now the Buddy label */ |
| |
905 label = gtk_label_new(NULL); |
| |
906 gtk_label_set_markup_with_mnemonic(GTK_LABEL(label), _("_Buddy:")); |
| |
907 gtk_misc_set_alignment(GTK_MISC(label), 0, 0); |
| |
908 gtk_table_attach_defaults(GTK_TABLE(table), label, 0, 1, 0, 1); |
| |
909 |
| |
910 /* Now the Buddy drop-down entry field. */ |
| |
911 info->entry = gtk_combo_new(); |
| |
912 gtk_combo_set_case_sensitive(GTK_COMBO(info->entry), FALSE); |
| |
913 gtk_entry_set_activates_default( |
| |
914 GTK_ENTRY(GTK_COMBO(info->entry)->entry), TRUE); |
| |
915 |
| |
916 gtk_table_attach_defaults(GTK_TABLE(table), info->entry, 1, 2, 0, 1); |
| |
917 gtk_label_set_mnemonic_widget(GTK_LABEL(label), info->entry); |
| |
918 |
| |
919 /* Fill in the names. */ |
| |
920 gtk_combo_set_popdown_strings(GTK_COMBO(info->entry), |
| |
921 generate_invite_user_names(gc)); |
| |
922 |
| |
923 |
| |
924 /* Now the label for "Message" */ |
| |
925 label = gtk_label_new(NULL); |
| |
926 gtk_label_set_markup_with_mnemonic(GTK_LABEL(label), _("_Message:")); |
| |
927 gtk_misc_set_alignment(GTK_MISC(label), 0, 0); |
| |
928 gtk_table_attach_defaults(GTK_TABLE(table), label, 0, 1, 1, 2); |
| |
929 |
| |
930 |
| |
931 /* And finally, the Message entry field. */ |
| |
932 info->message = gtk_entry_new(); |
| |
933 gtk_entry_set_activates_default(GTK_ENTRY(info->message), TRUE); |
| |
934 |
| |
935 gtk_table_attach_defaults(GTK_TABLE(table), info->message, 1, 2, 1, 2); |
| |
936 gtk_label_set_mnemonic_widget(GTK_LABEL(label), info->message); |
| |
937 |
| |
938 /* Connect the signals. */ |
| |
939 g_signal_connect(G_OBJECT(invite_dialog), "response", |
| |
940 G_CALLBACK(do_invite), info); |
| |
941 /* Setup drag-and-drop */ |
| |
942 gtk_drag_dest_set(info->window, |
| |
943 GTK_DEST_DEFAULT_MOTION | |
| |
944 GTK_DEST_DEFAULT_DROP, |
| |
945 dnd_targets, |
| |
946 sizeof(dnd_targets) / sizeof(GtkTargetEntry), |
| |
947 GDK_ACTION_COPY); |
| |
948 gtk_drag_dest_set(info->entry, |
| |
949 GTK_DEST_DEFAULT_MOTION | |
| |
950 GTK_DEST_DEFAULT_DROP, |
| |
951 dnd_targets, |
| |
952 sizeof(dnd_targets) / sizeof(GtkTargetEntry), |
| |
953 GDK_ACTION_COPY); |
| |
954 |
| |
955 g_signal_connect(G_OBJECT(info->window), "drag_data_received", |
| |
956 G_CALLBACK(invite_dnd_recv), info); |
| |
957 g_signal_connect(G_OBJECT(info->entry), "drag_data_received", |
| |
958 G_CALLBACK(invite_dnd_recv), info); |
| |
959 |
| |
960 } |
| |
961 |
| |
962 gtk_widget_show_all(invite_dialog); |
| |
963 |
| |
964 if (info != NULL) |
| |
965 gtk_widget_grab_focus(GTK_COMBO(info->entry)->entry); |
| |
966 } |
| |
967 |
| |
968 static void |
| |
969 menu_new_conv_cb(gpointer data, guint action, GtkWidget *widget) |
| |
970 { |
| |
971 pidgindialogs_im(); |
| |
972 } |
| |
973 |
| |
974 static void |
| |
975 savelog_writefile_cb(void *user_data, const char *filename) |
| |
976 { |
| |
977 PurpleConversation *conv = (PurpleConversation *)user_data; |
| |
978 FILE *fp; |
| |
979 const char *name; |
| |
980 gchar *text; |
| |
981 |
| |
982 if ((fp = g_fopen(filename, "w+")) == NULL) { |
| |
983 purple_notify_error(PIDGIN_CONVERSATION(conv), NULL, _("Unable to open file."), NULL); |
| |
984 return; |
| |
985 } |
| |
986 |
| |
987 name = purple_conversation_get_name(conv); |
| |
988 fprintf(fp, "<html>\n<head><title>%s</title></head>\n<body>", name); |
| |
989 fprintf(fp, _("<h1>Conversation with %s</h1>\n"), name); |
| |
990 |
| |
991 text = gtk_imhtml_get_markup( |
| |
992 GTK_IMHTML(PIDGIN_CONVERSATION(conv)->imhtml)); |
| |
993 fprintf(fp, "%s", text); |
| |
994 g_free(text); |
| |
995 |
| |
996 fprintf(fp, "\n</body>\n</html>\n"); |
| |
997 fclose(fp); |
| |
998 } |
| |
999 |
| |
1000 /* |
| |
1001 * It would be kinda cool if this gave the option of saving a |
| |
1002 * plaintext v. HTML file. |
| |
1003 */ |
| |
1004 static void |
| |
1005 menu_save_as_cb(gpointer data, guint action, GtkWidget *widget) |
| |
1006 { |
| |
1007 PidginWindow *win = data; |
| |
1008 PurpleConversation *conv = pidgin_conv_window_get_active_conversation(win); |
| |
1009 gchar *buf; |
| |
1010 |
| |
1011 buf = g_strdup_printf("%s.html", purple_normalize(conv->account, conv->name)); |
| |
1012 |
| |
1013 purple_request_file(PIDGIN_CONVERSATION(conv), _("Save Conversation"), |
| |
1014 purple_escape_filename(buf), |
| |
1015 TRUE, G_CALLBACK(savelog_writefile_cb), NULL, conv); |
| |
1016 |
| |
1017 g_free(buf); |
| |
1018 } |
| |
1019 |
| |
1020 static void |
| |
1021 menu_view_log_cb(gpointer data, guint action, GtkWidget *widget) |
| |
1022 { |
| |
1023 PidginWindow *win = data; |
| |
1024 PurpleConversation *conv; |
| |
1025 PurpleLogType type; |
| |
1026 PidginBuddyList *gtkblist; |
| |
1027 GdkCursor *cursor; |
| |
1028 const char *name; |
| |
1029 PurpleAccount *account; |
| |
1030 GSList *buddies; |
| |
1031 GSList *cur; |
| |
1032 |
| |
1033 conv = pidgin_conv_window_get_active_conversation(win); |
| |
1034 |
| |
1035 if (purple_conversation_get_type(conv) == PURPLE_CONV_TYPE_IM) |
| |
1036 type = PURPLE_LOG_IM; |
| |
1037 else if (purple_conversation_get_type(conv) == PURPLE_CONV_TYPE_CHAT) |
| |
1038 type = PURPLE_LOG_CHAT; |
| |
1039 else |
| |
1040 return; |
| |
1041 |
| |
1042 gtkblist = pidgin_blist_get_default_gtk_blist(); |
| |
1043 |
| |
1044 cursor = gdk_cursor_new(GDK_WATCH); |
| |
1045 gdk_window_set_cursor(gtkblist->window->window, cursor); |
| |
1046 gdk_window_set_cursor(win->window->window, cursor); |
| |
1047 gdk_cursor_unref(cursor); |
| |
1048 #if GTK_CHECK_VERSION(2,4,0) |
| |
1049 gdk_display_flush(gdk_drawable_get_display(GDK_DRAWABLE(widget->window))); |
| |
1050 #else |
| |
1051 gdk_flush(); |
| |
1052 #endif |
| |
1053 |
| |
1054 name = purple_conversation_get_name(conv); |
| |
1055 account = purple_conversation_get_account(conv); |
| |
1056 |
| |
1057 buddies = purple_find_buddies(account, name); |
| |
1058 for (cur = buddies; cur != NULL; cur = cur->next) |
| |
1059 { |
| |
1060 PurpleBlistNode *node = cur->data; |
| |
1061 if ((node != NULL) && ((node->prev != NULL) || (node->next != NULL))) |
| |
1062 { |
| |
1063 pidgin_log_show_contact((PurpleContact *)node->parent); |
| |
1064 g_slist_free(buddies); |
| |
1065 gdk_window_set_cursor(gtkblist->window->window, NULL); |
| |
1066 gdk_window_set_cursor(win->window->window, NULL); |
| |
1067 return; |
| |
1068 } |
| |
1069 } |
| |
1070 g_slist_free(buddies); |
| |
1071 |
| |
1072 pidgin_log_show(type, name, account); |
| |
1073 |
| |
1074 gdk_window_set_cursor(gtkblist->window->window, NULL); |
| |
1075 gdk_window_set_cursor(win->window->window, NULL); |
| |
1076 } |
| |
1077 |
| |
1078 static void |
| |
1079 menu_clear_cb(gpointer data, guint action, GtkWidget *widget) |
| |
1080 { |
| |
1081 PidginWindow *win = data; |
| |
1082 PurpleConversation *conv; |
| |
1083 PidginConversation *gtkconv; |
| |
1084 |
| |
1085 conv = pidgin_conv_window_get_active_conversation(win); |
| |
1086 gtkconv = PIDGIN_CONVERSATION(conv); |
| |
1087 |
| |
1088 gtk_imhtml_clear(GTK_IMHTML(gtkconv->imhtml)); |
| |
1089 } |
| |
1090 |
| |
1091 struct _search { |
| |
1092 PidginWindow *gtkwin; |
| |
1093 GtkWidget *entry; |
| |
1094 }; |
| |
1095 |
| |
1096 static void do_search_cb(GtkWidget *widget, gint resp, struct _search *s) |
| |
1097 { |
| |
1098 PurpleConversation *conv; |
| |
1099 PidginConversation *gtk_active_conv; |
| |
1100 GList *iter; |
| |
1101 |
| |
1102 conv = pidgin_conv_window_get_active_conversation(s->gtkwin); |
| |
1103 gtk_active_conv = PIDGIN_CONVERSATION(conv); |
| |
1104 |
| |
1105 switch (resp) |
| |
1106 { |
| |
1107 case GTK_RESPONSE_OK: |
| |
1108 /* clear highlighting except the active conversation window |
| |
1109 * highlight the keywords in the active conversation window */ |
| |
1110 for (iter = pidgin_conv_window_get_gtkconvs(s->gtkwin) ; iter ; iter = iter->next) |
| |
1111 { |
| |
1112 PidginConversation *gtkconv = iter->data; |
| |
1113 |
| |
1114 if (gtkconv != gtk_active_conv) |
| |
1115 { |
| |
1116 gtk_imhtml_search_clear(GTK_IMHTML(gtkconv->imhtml)); |
| |
1117 } |
| |
1118 else |
| |
1119 { |
| |
1120 gtk_imhtml_search_find(GTK_IMHTML(gtk_active_conv->imhtml), |
| |
1121 gtk_entry_get_text(GTK_ENTRY(s->entry))); |
| |
1122 } |
| |
1123 } |
| |
1124 break; |
| |
1125 |
| |
1126 case GTK_RESPONSE_DELETE_EVENT: |
| |
1127 case GTK_RESPONSE_CLOSE: |
| |
1128 /* clear the keyword highlighting in all the conversation windows */ |
| |
1129 for (iter = pidgin_conv_window_get_gtkconvs(s->gtkwin); iter; iter=iter->next) |
| |
1130 { |
| |
1131 PidginConversation *gconv = iter->data; |
| |
1132 gtk_imhtml_search_clear(GTK_IMHTML(gconv->imhtml)); |
| |
1133 } |
| |
1134 |
| |
1135 gtk_widget_destroy(s->gtkwin->dialogs.search); |
| |
1136 s->gtkwin->dialogs.search = NULL; |
| |
1137 g_free(s); |
| |
1138 break; |
| |
1139 } |
| |
1140 } |
| |
1141 |
| |
1142 static void |
| |
1143 menu_find_cb(gpointer data, guint action, GtkWidget *widget) |
| |
1144 { |
| |
1145 PidginWindow *gtkwin = data; |
| |
1146 GtkWidget *hbox; |
| |
1147 GtkWidget *img = gtk_image_new_from_stock(PIDGIN_STOCK_DIALOG_QUESTION, |
| |
1148 gtk_icon_size_from_name(PIDGIN_ICON_SIZE_TANGO_HUGE)); |
| |
1149 GtkWidget *label; |
| |
1150 struct _search *s; |
| |
1151 |
| |
1152 if (gtkwin->dialogs.search) { |
| |
1153 gtk_window_present(GTK_WINDOW(gtkwin->dialogs.search)); |
| |
1154 return; |
| |
1155 } |
| |
1156 |
| |
1157 s = g_malloc(sizeof(struct _search)); |
| |
1158 s->gtkwin = gtkwin; |
| |
1159 |
| |
1160 gtkwin->dialogs.search = gtk_dialog_new_with_buttons(_("Find"), |
| |
1161 GTK_WINDOW(gtkwin->window), GTK_DIALOG_DESTROY_WITH_PARENT, |
| |
1162 GTK_STOCK_CLOSE, GTK_RESPONSE_CLOSE, |
| |
1163 GTK_STOCK_FIND, GTK_RESPONSE_OK, NULL); |
| |
1164 gtk_dialog_set_default_response(GTK_DIALOG(gtkwin->dialogs.search), |
| |
1165 GTK_RESPONSE_OK); |
| |
1166 g_signal_connect(G_OBJECT(gtkwin->dialogs.search), "response", |
| |
1167 G_CALLBACK(do_search_cb), s); |
| |
1168 |
| |
1169 gtk_container_set_border_width(GTK_CONTAINER(gtkwin->dialogs.search), PIDGIN_HIG_BOX_SPACE); |
| |
1170 gtk_window_set_resizable(GTK_WINDOW(gtkwin->dialogs.search), FALSE); |
| |
1171 gtk_dialog_set_has_separator(GTK_DIALOG(gtkwin->dialogs.search), FALSE); |
| |
1172 gtk_box_set_spacing(GTK_BOX(GTK_DIALOG(gtkwin->dialogs.search)->vbox), PIDGIN_HIG_BORDER); |
| |
1173 gtk_container_set_border_width( |
| |
1174 GTK_CONTAINER(GTK_DIALOG(gtkwin->dialogs.search)->vbox), PIDGIN_HIG_BOX_SPACE); |
| |
1175 |
| |
1176 hbox = gtk_hbox_new(FALSE, PIDGIN_HIG_BORDER); |
| |
1177 gtk_container_add(GTK_CONTAINER(GTK_DIALOG(gtkwin->dialogs.search)->vbox), |
| |
1178 hbox); |
| |
1179 gtk_box_pack_start(GTK_BOX(hbox), img, FALSE, FALSE, 0); |
| |
1180 |
| |
1181 gtk_misc_set_alignment(GTK_MISC(img), 0, 0); |
| |
1182 gtk_dialog_set_response_sensitive(GTK_DIALOG(gtkwin->dialogs.search), |
| |
1183 GTK_RESPONSE_OK, FALSE); |
| |
1184 |
| |
1185 label = gtk_label_new(NULL); |
| |
1186 gtk_label_set_markup_with_mnemonic(GTK_LABEL(label), _("_Search for:")); |
| |
1187 gtk_box_pack_start(GTK_BOX(hbox), label, FALSE, FALSE, 0); |
| |
1188 |
| |
1189 s->entry = gtk_entry_new(); |
| |
1190 gtk_entry_set_activates_default(GTK_ENTRY(s->entry), TRUE); |
| |
1191 gtk_label_set_mnemonic_widget(GTK_LABEL(label), GTK_WIDGET(s->entry)); |
| |
1192 g_signal_connect(G_OBJECT(s->entry), "changed", |
| |
1193 G_CALLBACK(pidgin_set_sensitive_if_input), |
| |
1194 gtkwin->dialogs.search); |
| |
1195 gtk_box_pack_start(GTK_BOX(hbox), s->entry, FALSE, FALSE, 0); |
| |
1196 |
| |
1197 gtk_widget_show_all(gtkwin->dialogs.search); |
| |
1198 gtk_widget_grab_focus(s->entry); |
| |
1199 } |
| |
1200 |
| |
1201 static void |
| |
1202 menu_send_file_cb(gpointer data, guint action, GtkWidget *widget) |
| |
1203 { |
| |
1204 PidginWindow *win = data; |
| |
1205 PurpleConversation *conv = pidgin_conv_window_get_active_conversation(win); |
| |
1206 |
| |
1207 if (purple_conversation_get_type(conv) == PURPLE_CONV_TYPE_IM) { |
| |
1208 serv_send_file(purple_conversation_get_gc(conv), purple_conversation_get_name(conv), NULL); |
| |
1209 } |
| |
1210 |
| |
1211 } |
| |
1212 |
| |
1213 static void |
| |
1214 menu_add_pounce_cb(gpointer data, guint action, GtkWidget *widget) |
| |
1215 { |
| |
1216 PidginWindow *win = data; |
| |
1217 PurpleConversation *conv; |
| |
1218 |
| |
1219 conv = pidgin_conv_window_get_active_gtkconv(win)->active_conv; |
| |
1220 |
| |
1221 pidgin_pounce_editor_show(purple_conversation_get_account(conv), |
| |
1222 purple_conversation_get_name(conv), NULL); |
| |
1223 } |
| |
1224 |
| |
1225 static void |
| |
1226 menu_alias_cb(gpointer data, guint action, GtkWidget *widget) |
| |
1227 { |
| |
1228 PidginWindow *win = data; |
| |
1229 PurpleConversation *conv; |
| |
1230 PurpleAccount *account; |
| |
1231 const char *name; |
| |
1232 |
| |
1233 conv = pidgin_conv_window_get_active_conversation(win); |
| |
1234 account = purple_conversation_get_account(conv); |
| |
1235 name = purple_conversation_get_name(conv); |
| |
1236 |
| |
1237 if (purple_conversation_get_type(conv) == PURPLE_CONV_TYPE_IM) { |
| |
1238 PurpleBuddy *b; |
| |
1239 |
| |
1240 b = purple_find_buddy(account, name); |
| |
1241 if (b != NULL) |
| |
1242 pidgindialogs_alias_buddy(b); |
| |
1243 } else if (purple_conversation_get_type(conv) == PURPLE_CONV_TYPE_CHAT) { |
| |
1244 PurpleChat *c; |
| |
1245 |
| |
1246 c = purple_blist_find_chat(account, name); |
| |
1247 if (c != NULL) |
| |
1248 pidgindialogs_alias_chat(c); |
| |
1249 } |
| |
1250 } |
| |
1251 |
| |
1252 static void |
| |
1253 menu_get_info_cb(gpointer data, guint action, GtkWidget *widget) |
| |
1254 { |
| |
1255 PidginWindow *win = data; |
| |
1256 PurpleConversation *conv; |
| |
1257 |
| |
1258 conv = pidgin_conv_window_get_active_conversation(win); |
| |
1259 |
| |
1260 info_cb(NULL, PIDGIN_CONVERSATION(conv)); |
| |
1261 } |
| |
1262 |
| |
1263 static void |
| |
1264 menu_invite_cb(gpointer data, guint action, GtkWidget *widget) |
| |
1265 { |
| |
1266 PidginWindow *win = data; |
| |
1267 PurpleConversation *conv; |
| |
1268 |
| |
1269 conv = pidgin_conv_window_get_active_conversation(win); |
| |
1270 |
| |
1271 invite_cb(NULL, PIDGIN_CONVERSATION(conv)); |
| |
1272 } |
| |
1273 |
| |
1274 static void |
| |
1275 menu_block_cb(gpointer data, guint action, GtkWidget *widget) |
| |
1276 { |
| |
1277 PidginWindow *win = data; |
| |
1278 PurpleConversation *conv; |
| |
1279 |
| |
1280 conv = pidgin_conv_window_get_active_conversation(win); |
| |
1281 |
| |
1282 block_cb(NULL, PIDGIN_CONVERSATION(conv)); |
| |
1283 } |
| |
1284 |
| |
1285 static void |
| |
1286 menu_unblock_cb(gpointer data, guint action, GtkWidget *widget) |
| |
1287 { |
| |
1288 PidginWindow *win = data; |
| |
1289 PurpleConversation *conv; |
| |
1290 |
| |
1291 conv = pidgin_conv_window_get_active_conversation(win); |
| |
1292 |
| |
1293 unblock_cb(NULL, PIDGIN_CONVERSATION(conv)); |
| |
1294 } |
| |
1295 |
| |
1296 static void |
| |
1297 menu_add_remove_cb(gpointer data, guint action, GtkWidget *widget) |
| |
1298 { |
| |
1299 PidginWindow *win = data; |
| |
1300 PurpleConversation *conv; |
| |
1301 |
| |
1302 conv = pidgin_conv_window_get_active_conversation(win); |
| |
1303 |
| |
1304 add_remove_cb(NULL, PIDGIN_CONVERSATION(conv)); |
| |
1305 } |
| |
1306 |
| |
1307 static void |
| |
1308 menu_close_conv_cb(gpointer data, guint action, GtkWidget *widget) |
| |
1309 { |
| |
1310 PidginWindow *win = data; |
| |
1311 |
| |
1312 close_conv_cb(NULL, PIDGIN_CONVERSATION(pidgin_conv_window_get_active_conversation(win))); |
| |
1313 } |
| |
1314 |
| |
1315 static void |
| |
1316 menu_logging_cb(gpointer data, guint action, GtkWidget *widget) |
| |
1317 { |
| |
1318 PidginWindow *win = data; |
| |
1319 PurpleConversation *conv; |
| |
1320 gboolean logging; |
| |
1321 |
| |
1322 conv = pidgin_conv_window_get_active_conversation(win); |
| |
1323 |
| |
1324 if (conv == NULL) |
| |
1325 return; |
| |
1326 |
| |
1327 logging = gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(widget)); |
| |
1328 |
| |
1329 if (logging == purple_conversation_is_logging(conv)) |
| |
1330 return; |
| |
1331 |
| |
1332 if (logging) |
| |
1333 { |
| |
1334 /* Enable logging first so the message below can be logged. */ |
| |
1335 purple_conversation_set_logging(conv, TRUE); |
| |
1336 |
| |
1337 purple_conversation_write(conv, NULL, |
| |
1338 _("Logging started. Future messages in this conversation will be logged."), |
| |
1339 conv->logs ? (PURPLE_MESSAGE_SYSTEM) : |
| |
1340 (PURPLE_MESSAGE_SYSTEM | PURPLE_MESSAGE_NO_LOG), |
| |
1341 time(NULL)); |
| |
1342 } |
| |
1343 else |
| |
1344 { |
| |
1345 purple_conversation_write(conv, NULL, |
| |
1346 _("Logging stopped. Future messages in this conversation will not be logged."), |
| |
1347 conv->logs ? (PURPLE_MESSAGE_SYSTEM) : |
| |
1348 (PURPLE_MESSAGE_SYSTEM | PURPLE_MESSAGE_NO_LOG), |
| |
1349 time(NULL)); |
| |
1350 |
| |
1351 /* Disable the logging second, so that the above message can be logged. */ |
| |
1352 purple_conversation_set_logging(conv, FALSE); |
| |
1353 } |
| |
1354 } |
| |
1355 |
| |
1356 static void |
| |
1357 menu_toolbar_cb(gpointer data, guint action, GtkWidget *widget) |
| |
1358 { |
| |
1359 purple_prefs_set_bool(PIDGIN_PREFS_ROOT "/conversations/show_formatting_toolbar", |
| |
1360 gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(widget))); |
| |
1361 } |
| |
1362 |
| |
1363 static void |
| |
1364 menu_sounds_cb(gpointer data, guint action, GtkWidget *widget) |
| |
1365 { |
| |
1366 PidginWindow *win = data; |
| |
1367 PurpleConversation *conv; |
| |
1368 PidginConversation *gtkconv; |
| |
1369 |
| |
1370 conv = pidgin_conv_window_get_active_conversation(win); |
| |
1371 |
| |
1372 if (!conv) |
| |
1373 return; |
| |
1374 |
| |
1375 gtkconv = PIDGIN_CONVERSATION(conv); |
| |
1376 |
| |
1377 gtkconv->make_sound = |
| |
1378 gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(widget)); |
| |
1379 } |
| |
1380 |
| |
1381 static void |
| |
1382 menu_timestamps_cb(gpointer data, guint action, GtkWidget *widget) |
| |
1383 { |
| |
1384 purple_prefs_set_bool(PIDGIN_PREFS_ROOT "/conversations/show_timestamps", |
| |
1385 gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(widget))); |
| |
1386 } |
| |
1387 |
| |
1388 static void |
| |
1389 chat_do_im(PidginConversation *gtkconv, const char *who) |
| |
1390 { |
| |
1391 PurpleConversation *conv = gtkconv->active_conv; |
| |
1392 PurpleAccount *account; |
| |
1393 PurpleConnection *gc; |
| |
1394 PurplePluginProtocolInfo *prpl_info = NULL; |
| |
1395 char *real_who; |
| |
1396 |
| |
1397 account = purple_conversation_get_account(conv); |
| |
1398 g_return_if_fail(account != NULL); |
| |
1399 |
| |
1400 gc = purple_account_get_connection(account); |
| |
1401 g_return_if_fail(gc != NULL); |
| |
1402 |
| |
1403 prpl_info = PURPLE_PLUGIN_PROTOCOL_INFO(gc->prpl); |
| |
1404 |
| |
1405 if (prpl_info && prpl_info->get_cb_real_name) |
| |
1406 real_who = prpl_info->get_cb_real_name(gc, |
| |
1407 purple_conv_chat_get_id(PURPLE_CONV_CHAT(conv)), who); |
| |
1408 else |
| |
1409 real_who = g_strdup(who); |
| |
1410 |
| |
1411 if(!real_who) |
| |
1412 return; |
| |
1413 |
| |
1414 pidgindialogs_im_with_user(account, real_who); |
| |
1415 |
| |
1416 g_free(real_who); |
| |
1417 } |
| |
1418 |
| |
1419 static void |
| |
1420 ignore_cb(GtkWidget *w, PidginConversation *gtkconv) |
| |
1421 { |
| |
1422 PurpleConversation *conv = gtkconv->active_conv; |
| |
1423 PidginChatPane *gtkchat; |
| |
1424 PurpleConvChatBuddy *cbuddy; |
| |
1425 PurpleConvChat *chat; |
| |
1426 PurpleConvChatBuddyFlags flags; |
| |
1427 GtkTreeIter iter; |
| |
1428 GtkTreeModel *model; |
| |
1429 GtkTreeSelection *sel; |
| |
1430 char *name; |
| |
1431 char *alias; |
| |
1432 |
| |
1433 chat = PURPLE_CONV_CHAT(conv); |
| |
1434 gtkchat = gtkconv->u.chat; |
| |
1435 |
| |
1436 model = gtk_tree_view_get_model(GTK_TREE_VIEW(gtkchat->list)); |
| |
1437 sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(gtkchat->list)); |
| |
1438 |
| |
1439 if (gtk_tree_selection_get_selected(sel, NULL, &iter)) { |
| |
1440 gtk_tree_model_get(GTK_TREE_MODEL(model), &iter, |
| |
1441 CHAT_USERS_NAME_COLUMN, &name, |
| |
1442 CHAT_USERS_ALIAS_COLUMN, &alias, |
| |
1443 CHAT_USERS_FLAGS_COLUMN, &flags, |
| |
1444 -1); |
| |
1445 gtk_list_store_remove(GTK_LIST_STORE(model), &iter); |
| |
1446 } |
| |
1447 else |
| |
1448 return; |
| |
1449 |
| |
1450 if (purple_conv_chat_is_user_ignored(chat, name)) |
| |
1451 purple_conv_chat_unignore(chat, name); |
| |
1452 else |
| |
1453 purple_conv_chat_ignore(chat, name); |
| |
1454 |
| |
1455 cbuddy = purple_conv_chat_cb_new(name, alias, flags); |
| |
1456 |
| |
1457 add_chat_buddy_common(conv, cbuddy, NULL); |
| |
1458 g_free(name); |
| |
1459 g_free(alias); |
| |
1460 } |
| |
1461 |
| |
1462 static void |
| |
1463 menu_chat_im_cb(GtkWidget *w, PidginConversation *gtkconv) |
| |
1464 { |
| |
1465 const char *who = g_object_get_data(G_OBJECT(w), "user_data"); |
| |
1466 |
| |
1467 chat_do_im(gtkconv, who); |
| |
1468 } |
| |
1469 |
| |
1470 static void |
| |
1471 menu_chat_send_file_cb(GtkWidget *w, PidginConversation *gtkconv) |
| |
1472 { |
| |
1473 PurpleConversation *conv = gtkconv->active_conv; |
| |
1474 const char *who = g_object_get_data(G_OBJECT(w), "user_data"); |
| |
1475 PurpleConnection *gc = purple_conversation_get_gc(conv); |
| |
1476 |
| |
1477 serv_send_file(gc, who, NULL); |
| |
1478 } |
| |
1479 |
| |
1480 static void |
| |
1481 menu_chat_info_cb(GtkWidget *w, PidginConversation *gtkconv) |
| |
1482 { |
| |
1483 char *who; |
| |
1484 |
| |
1485 who = g_object_get_data(G_OBJECT(w), "user_data"); |
| |
1486 |
| |
1487 chat_do_info(gtkconv, who); |
| |
1488 } |
| |
1489 |
| |
1490 static void |
| |
1491 menu_chat_get_away_cb(GtkWidget *w, PidginConversation *gtkconv) |
| |
1492 { |
| |
1493 PurpleConversation *conv = gtkconv->active_conv; |
| |
1494 PurplePluginProtocolInfo *prpl_info = NULL; |
| |
1495 PurpleConnection *gc; |
| |
1496 char *who; |
| |
1497 |
| |
1498 gc = purple_conversation_get_gc(conv); |
| |
1499 who = g_object_get_data(G_OBJECT(w), "user_data"); |
| |
1500 |
| |
1501 if (gc != NULL) { |
| |
1502 prpl_info = PURPLE_PLUGIN_PROTOCOL_INFO(gc->prpl); |
| |
1503 |
| |
1504 /* |
| |
1505 * May want to expand this to work similarly to menu_info_cb? |
| |
1506 */ |
| |
1507 |
| |
1508 if (prpl_info->get_cb_away != NULL) |
| |
1509 { |
| |
1510 prpl_info->get_cb_away(gc, |
| |
1511 purple_conv_chat_get_id(PURPLE_CONV_CHAT(conv)), who); |
| |
1512 } |
| |
1513 } |
| |
1514 } |
| |
1515 |
| |
1516 static void |
| |
1517 menu_chat_add_remove_cb(GtkWidget *w, PidginConversation *gtkconv) |
| |
1518 { |
| |
1519 PurpleConversation *conv = gtkconv->active_conv; |
| |
1520 PurpleAccount *account; |
| |
1521 PurpleBuddy *b; |
| |
1522 char *name; |
| |
1523 |
| |
1524 account = purple_conversation_get_account(conv); |
| |
1525 name = g_object_get_data(G_OBJECT(w), "user_data"); |
| |
1526 b = purple_find_buddy(account, name); |
| |
1527 |
| |
1528 if (b != NULL) |
| |
1529 pidgindialogs_remove_buddy(b); |
| |
1530 else if (account != NULL && purple_account_is_connected(account)) |
| |
1531 purple_blist_request_add_buddy(account, name, NULL, NULL); |
| |
1532 |
| |
1533 gtk_widget_grab_focus(PIDGIN_CONVERSATION(conv)->entry); |
| |
1534 } |
| |
1535 |
| |
1536 static GtkTextMark * |
| |
1537 get_mark_for_user(PidginConversation *gtkconv, const char *who) |
| |
1538 { |
| |
1539 GtkTextBuffer *buf = gtk_text_view_get_buffer(GTK_TEXT_VIEW(gtkconv->imhtml)); |
| |
1540 char *tmp = g_strconcat("user:", who, NULL); |
| |
1541 GtkTextMark *mark = gtk_text_buffer_get_mark(buf, tmp); |
| |
1542 |
| |
1543 g_free(tmp); |
| |
1544 return mark; |
| |
1545 } |
| |
1546 |
| |
1547 static void |
| |
1548 menu_last_said_cb(GtkWidget *w, PidginConversation *gtkconv) |
| |
1549 { |
| |
1550 GtkTextMark *mark; |
| |
1551 const char *who; |
| |
1552 |
| |
1553 who = g_object_get_data(G_OBJECT(w), "user_data"); |
| |
1554 mark = get_mark_for_user(gtkconv, who); |
| |
1555 |
| |
1556 if (mark != NULL) |
| |
1557 gtk_text_view_scroll_to_mark(GTK_TEXT_VIEW(gtkconv->imhtml), mark, 0.1, FALSE, 0, 0); |
| |
1558 else |
| |
1559 g_return_if_reached(); |
| |
1560 } |
| |
1561 |
| |
1562 static GtkWidget * |
| |
1563 create_chat_menu(PurpleConversation *conv, const char *who, PurpleConnection *gc) |
| |
1564 { |
| |
1565 static GtkWidget *menu = NULL; |
| |
1566 PurplePluginProtocolInfo *prpl_info = NULL; |
| |
1567 PurpleConvChat *chat = PURPLE_CONV_CHAT(conv); |
| |
1568 gboolean is_me = FALSE; |
| |
1569 GtkWidget *button; |
| |
1570 PurpleBuddy *buddy = NULL; |
| |
1571 |
| |
1572 if (gc != NULL) |
| |
1573 prpl_info = PURPLE_PLUGIN_PROTOCOL_INFO(gc->prpl); |
| |
1574 |
| |
1575 /* |
| |
1576 * If a menu already exists, destroy it before creating a new one, |
| |
1577 * thus freeing-up the memory it occupied. |
| |
1578 */ |
| |
1579 if (menu) |
| |
1580 gtk_widget_destroy(menu); |
| |
1581 |
| |
1582 if (!strcmp(chat->nick, purple_normalize(conv->account, who))) |
| |
1583 is_me = TRUE; |
| |
1584 |
| |
1585 menu = gtk_menu_new(); |
| |
1586 |
| |
1587 if (!is_me) { |
| |
1588 button = pidgin_new_item_from_stock(menu, _("IM"), PIDGIN_STOCK_TOOLBAR_MESSAGE_NEW, |
| |
1589 G_CALLBACK(menu_chat_im_cb), PIDGIN_CONVERSATION(conv), 0, 0, NULL); |
| |
1590 |
| |
1591 if (gc == NULL) |
| |
1592 gtk_widget_set_sensitive(button, FALSE); |
| |
1593 |
| |
1594 g_object_set_data_full(G_OBJECT(button), "user_data", g_strdup(who), g_free); |
| |
1595 |
| |
1596 |
| |
1597 if (prpl_info && prpl_info->send_file) |
| |
1598 { |
| |
1599 button = pidgin_new_item_from_stock(menu, _("Send File"), |
| |
1600 PIDGIN_STOCK_FILE_TRANSFER, G_CALLBACK(menu_chat_send_file_cb), |
| |
1601 PIDGIN_CONVERSATION(conv), 0, 0, NULL); |
| |
1602 |
| |
1603 if (gc == NULL || prpl_info == NULL || |
| |
1604 !(!prpl_info->can_receive_file || prpl_info->can_receive_file(gc, who))) |
| |
1605 { |
| |
1606 gtk_widget_set_sensitive(button, FALSE); |
| |
1607 } |
| |
1608 |
| |
1609 g_object_set_data_full(G_OBJECT(button), "user_data", g_strdup(who), g_free); |
| |
1610 } |
| |
1611 |
| |
1612 |
| |
1613 if (purple_conv_chat_is_user_ignored(PURPLE_CONV_CHAT(conv), who)) |
| |
1614 button = pidgin_new_item_from_stock(menu, _("Un-Ignore"), PIDGIN_STOCK_IGNORE, |
| |
1615 G_CALLBACK(ignore_cb), PIDGIN_CONVERSATION(conv), 0, 0, NULL); |
| |
1616 else |
| |
1617 button = pidgin_new_item_from_stock(menu, _("Ignore"), PIDGIN_STOCK_IGNORE, |
| |
1618 G_CALLBACK(ignore_cb), PIDGIN_CONVERSATION(conv), 0, 0, NULL); |
| |
1619 |
| |
1620 if (gc == NULL) |
| |
1621 gtk_widget_set_sensitive(button, FALSE); |
| |
1622 |
| |
1623 g_object_set_data_full(G_OBJECT(button), "user_data", g_strdup(who), g_free); |
| |
1624 } |
| |
1625 |
| |
1626 if (prpl_info && (prpl_info->get_info || prpl_info->get_cb_info)) { |
| |
1627 button = pidgin_new_item_from_stock(menu, _("Info"), PIDGIN_STOCK_TOOLBAR_USER_INFO, |
| |
1628 G_CALLBACK(menu_chat_info_cb), PIDGIN_CONVERSATION(conv), 0, 0, NULL); |
| |
1629 |
| |
1630 if (gc == NULL) |
| |
1631 gtk_widget_set_sensitive(button, FALSE); |
| |
1632 |
| |
1633 g_object_set_data_full(G_OBJECT(button), "user_data", g_strdup(who), g_free); |
| |
1634 } |
| |
1635 |
| |
1636 if (prpl_info && prpl_info->get_cb_away) { |
| |
1637 button = pidgin_new_item_from_stock(menu, _("Get Away Message"), PIDGIN_STOCK_AWAY, |
| |
1638 G_CALLBACK(menu_chat_get_away_cb), PIDGIN_CONVERSATION(conv), 0, 0, NULL); |
| |
1639 |
| |
1640 if (gc == NULL) |
| |
1641 gtk_widget_set_sensitive(button, FALSE); |
| |
1642 |
| |
1643 g_object_set_data_full(G_OBJECT(button), "user_data", g_strdup(who), g_free); |
| |
1644 } |
| |
1645 |
| |
1646 if (!is_me && prpl_info && !(prpl_info->options & OPT_PROTO_UNIQUE_CHATNAME)) { |
| |
1647 if ((buddy = purple_find_buddy(conv->account, who)) != NULL) |
| |
1648 button = pidgin_new_item_from_stock(menu, _("Remove"), GTK_STOCK_REMOVE, |
| |
1649 G_CALLBACK(menu_chat_add_remove_cb), PIDGIN_CONVERSATION(conv), 0, 0, NULL); |
| |
1650 else |
| |
1651 button = pidgin_new_item_from_stock(menu, _("Add"), GTK_STOCK_ADD, |
| |
1652 G_CALLBACK(menu_chat_add_remove_cb), PIDGIN_CONVERSATION(conv), 0, 0, NULL); |
| |
1653 |
| |
1654 if (gc == NULL) |
| |
1655 gtk_widget_set_sensitive(button, FALSE); |
| |
1656 |
| |
1657 g_object_set_data_full(G_OBJECT(button), "user_data", g_strdup(who), g_free); |
| |
1658 } |
| |
1659 |
| |
1660 button = pidgin_new_item_from_stock(menu, _("Last said"), GTK_STOCK_INDEX, |
| |
1661 G_CALLBACK(menu_last_said_cb), PIDGIN_CONVERSATION(conv), 0, 0, NULL); |
| |
1662 g_object_set_data_full(G_OBJECT(button), "user_data", g_strdup(who), g_free); |
| |
1663 if (!get_mark_for_user(PIDGIN_CONVERSATION(conv), who)) |
| |
1664 gtk_widget_set_sensitive(button, FALSE); |
| |
1665 |
| |
1666 if (buddy != NULL) |
| |
1667 { |
| |
1668 if (purple_account_is_connected(conv->account)) |
| |
1669 pidgin_append_blist_node_proto_menu(menu, conv->account->gc, |
| |
1670 (PurpleBlistNode *)buddy); |
| |
1671 pidgin_append_blist_node_extended_menu(menu, (PurpleBlistNode *)buddy); |
| |
1672 gtk_widget_show_all(menu); |
| |
1673 } |
| |
1674 |
| |
1675 return menu; |
| |
1676 } |
| |
1677 |
| |
1678 |
| |
1679 static gint |
| |
1680 gtkconv_chat_popup_menu_cb(GtkWidget *widget, PidginConversation *gtkconv) |
| |
1681 { |
| |
1682 PurpleConversation *conv = gtkconv->active_conv; |
| |
1683 PidginChatPane *gtkchat; |
| |
1684 PurpleConnection *gc; |
| |
1685 PurpleAccount *account; |
| |
1686 GtkTreeSelection *sel; |
| |
1687 GtkTreeIter iter; |
| |
1688 GtkTreeModel *model; |
| |
1689 GtkWidget *menu; |
| |
1690 gchar *who; |
| |
1691 |
| |
1692 gtkconv = PIDGIN_CONVERSATION(conv); |
| |
1693 gtkchat = gtkconv->u.chat; |
| |
1694 account = purple_conversation_get_account(conv); |
| |
1695 gc = account->gc; |
| |
1696 |
| |
1697 model = gtk_tree_view_get_model(GTK_TREE_VIEW(gtkchat->list)); |
| |
1698 |
| |
1699 sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(gtkchat->list)); |
| |
1700 if(!gtk_tree_selection_get_selected(sel, NULL, &iter)) |
| |
1701 return FALSE; |
| |
1702 |
| |
1703 gtk_tree_model_get(GTK_TREE_MODEL(model), &iter, CHAT_USERS_NAME_COLUMN, &who, -1); |
| |
1704 menu = create_chat_menu (conv, who, gc); |
| |
1705 gtk_menu_popup(GTK_MENU(menu), NULL, NULL, |
| |
1706 pidgin_treeview_popup_menu_position_func, widget, |
| |
1707 0, GDK_CURRENT_TIME); |
| |
1708 g_free(who); |
| |
1709 |
| |
1710 return TRUE; |
| |
1711 } |
| |
1712 |
| |
1713 |
| |
1714 static gint |
| |
1715 right_click_chat_cb(GtkWidget *widget, GdkEventButton *event, |
| |
1716 PidginConversation *gtkconv) |
| |
1717 { |
| |
1718 PurpleConversation *conv = gtkconv->active_conv; |
| |
1719 PidginChatPane *gtkchat; |
| |
1720 PurpleConnection *gc; |
| |
1721 PurpleAccount *account; |
| |
1722 GtkTreePath *path; |
| |
1723 GtkTreeIter iter; |
| |
1724 GtkTreeModel *model; |
| |
1725 GtkTreeViewColumn *column; |
| |
1726 gchar *who; |
| |
1727 int x, y; |
| |
1728 |
| |
1729 gtkchat = gtkconv->u.chat; |
| |
1730 account = purple_conversation_get_account(conv); |
| |
1731 gc = account->gc; |
| |
1732 |
| |
1733 model = gtk_tree_view_get_model(GTK_TREE_VIEW(gtkchat->list)); |
| |
1734 |
| |
1735 gtk_tree_view_get_path_at_pos(GTK_TREE_VIEW(gtkchat->list), |
| |
1736 event->x, event->y, &path, &column, &x, &y); |
| |
1737 |
| |
1738 if (path == NULL) |
| |
1739 return FALSE; |
| |
1740 |
| |
1741 gtk_tree_selection_select_path(GTK_TREE_SELECTION( |
| |
1742 gtk_tree_view_get_selection(GTK_TREE_VIEW(gtkchat->list))), path); |
| |
1743 |
| |
1744 gtk_tree_model_get_iter(GTK_TREE_MODEL(model), &iter, path); |
| |
1745 gtk_tree_model_get(GTK_TREE_MODEL(model), &iter, CHAT_USERS_NAME_COLUMN, &who, -1); |
| |
1746 |
| |
1747 if (event->button == 1 && event->type == GDK_2BUTTON_PRESS) { |
| |
1748 chat_do_im(gtkconv, who); |
| |
1749 } else if (event->button == 2 && event->type == GDK_BUTTON_PRESS) { |
| |
1750 /* Move to user's anchor */ |
| |
1751 GtkTextMark *mark = get_mark_for_user(gtkconv, who); |
| |
1752 |
| |
1753 if(mark != NULL) |
| |
1754 gtk_text_view_scroll_to_mark(GTK_TEXT_VIEW(gtkconv->imhtml), mark, 0.1, FALSE, 0, 0); |
| |
1755 } else if (event->button == 3 && event->type == GDK_BUTTON_PRESS) { |
| |
1756 GtkWidget *menu = create_chat_menu (conv, who, gc); |
| |
1757 gtk_menu_popup(GTK_MENU(menu), NULL, NULL, NULL, NULL, |
| |
1758 event->button, event->time); |
| |
1759 } |
| |
1760 |
| |
1761 g_free(who); |
| |
1762 gtk_tree_path_free(path); |
| |
1763 |
| |
1764 return TRUE; |
| |
1765 } |
| |
1766 |
| |
1767 static void |
| |
1768 move_to_next_unread_tab(PidginConversation *gtkconv, gboolean forward) |
| |
1769 { |
| |
1770 PidginConversation *next_gtkconv = NULL; |
| |
1771 PidginWindow *win; |
| |
1772 int initial, i, total, diff; |
| |
1773 |
| |
1774 win = gtkconv->win; |
| |
1775 initial = gtk_notebook_page_num(GTK_NOTEBOOK(win->notebook), |
| |
1776 gtkconv->tab_cont); |
| |
1777 total = pidgin_conv_window_get_gtkconv_count(win); |
| |
1778 /* By adding total here, the moduli calculated later will always have two |
| |
1779 * positive arguments. x % y where x < 0 is not guaranteed to return a |
| |
1780 * positive number. |
| |
1781 */ |
| |
1782 diff = (forward ? 1 : -1) + total; |
| |
1783 |
| |
1784 for (i = (initial + diff) % total; i != initial; i = (i + diff) % total) { |
| |
1785 next_gtkconv = pidgin_conv_window_get_gtkconv_at_index(win, i); |
| |
1786 if (next_gtkconv->unseen_state > 0) |
| |
1787 break; |
| |
1788 } |
| |
1789 |
| |
1790 if (i == initial) { /* no new messages */ |
| |
1791 i = (i + diff) % total; |
| |
1792 next_gtkconv = pidgin_conv_window_get_gtkconv_at_index(win, i); |
| |
1793 } |
| |
1794 |
| |
1795 if (next_gtkconv != NULL && next_gtkconv != gtkconv) |
| |
1796 pidgin_conv_window_switch_gtkconv(win, next_gtkconv); |
| |
1797 } |
| |
1798 |
| |
1799 static gboolean |
| |
1800 entry_key_press_cb(GtkWidget *entry, GdkEventKey *event, gpointer data) |
| |
1801 { |
| |
1802 PidginWindow *win; |
| |
1803 PurpleConversation *conv; |
| |
1804 PidginConversation *gtkconv; |
| |
1805 int curconv; |
| |
1806 |
| |
1807 gtkconv = (PidginConversation *)data; |
| |
1808 conv = gtkconv->active_conv; |
| |
1809 win = gtkconv->win; |
| |
1810 curconv = gtk_notebook_get_current_page(GTK_NOTEBOOK(win->notebook)); |
| |
1811 |
| |
1812 /* If CTRL was held down... */ |
| |
1813 if (event->state & GDK_CONTROL_MASK) { |
| |
1814 switch (event->keyval) { |
| |
1815 case GDK_Up: |
| |
1816 if (!gtkconv->send_history) |
| |
1817 break; |
| |
1818 |
| |
1819 if (!gtkconv->send_history->prev) { |
| |
1820 GtkTextIter start, end; |
| |
1821 |
| |
1822 g_free(gtkconv->send_history->data); |
| |
1823 |
| |
1824 gtk_text_buffer_get_start_iter(gtkconv->entry_buffer, |
| |
1825 &start); |
| |
1826 gtk_text_buffer_get_end_iter(gtkconv->entry_buffer, &end); |
| |
1827 |
| |
1828 gtkconv->send_history->data = |
| |
1829 gtk_imhtml_get_markup(GTK_IMHTML(gtkconv->entry)); |
| |
1830 } |
| |
1831 |
| |
1832 if (gtkconv->send_history->next && gtkconv->send_history->next->data) { |
| |
1833 GObject *object; |
| |
1834 GtkTextIter iter; |
| |
1835 GtkTextBuffer *buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(gtkconv->entry)); |
| |
1836 |
| |
1837 gtkconv->send_history = gtkconv->send_history->next; |
| |
1838 |
| |
1839 /* Block the signal to prevent application of default formatting. */ |
| |
1840 object = g_object_ref(G_OBJECT(gtkconv->entry)); |
| |
1841 g_signal_handlers_block_matched(object, G_SIGNAL_MATCH_DATA, 0, 0, NULL, |
| |
1842 NULL, gtkconv); |
| |
1843 /* Clear the formatting. */ |
| |
1844 gtk_imhtml_clear_formatting(GTK_IMHTML(gtkconv->entry)); |
| |
1845 /* Unblock the signal. */ |
| |
1846 g_signal_handlers_unblock_matched(object, G_SIGNAL_MATCH_DATA, 0, 0, NULL, |
| |
1847 NULL, gtkconv); |
| |
1848 g_object_unref(object); |
| |
1849 |
| |
1850 gtk_imhtml_clear(GTK_IMHTML(gtkconv->entry)); |
| |
1851 gtk_imhtml_append_text_with_images( |
| |
1852 GTK_IMHTML(gtkconv->entry), gtkconv->send_history->data, |
| |
1853 0, NULL); |
| |
1854 /* this is mainly just a hack so the formatting at the |
| |
1855 * cursor gets picked up. */ |
| |
1856 gtk_text_buffer_get_end_iter(buffer, &iter); |
| |
1857 gtk_text_buffer_move_mark_by_name(buffer, "insert", &iter); |
| |
1858 } |
| |
1859 |
| |
1860 return TRUE; |
| |
1861 break; |
| |
1862 |
| |
1863 case GDK_Down: |
| |
1864 if (!gtkconv->send_history) |
| |
1865 break; |
| |
1866 |
| |
1867 if (gtkconv->send_history->prev && gtkconv->send_history->prev->data) { |
| |
1868 GObject *object; |
| |
1869 GtkTextIter iter; |
| |
1870 GtkTextBuffer *buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(gtkconv->entry)); |
| |
1871 |
| |
1872 gtkconv->send_history = gtkconv->send_history->prev; |
| |
1873 |
| |
1874 /* Block the signal to prevent application of default formatting. */ |
| |
1875 object = g_object_ref(G_OBJECT(gtkconv->entry)); |
| |
1876 g_signal_handlers_block_matched(object, G_SIGNAL_MATCH_DATA, 0, 0, NULL, |
| |
1877 NULL, gtkconv); |
| |
1878 /* Clear the formatting. */ |
| |
1879 gtk_imhtml_clear_formatting(GTK_IMHTML(gtkconv->entry)); |
| |
1880 /* Unblock the signal. */ |
| |
1881 g_signal_handlers_unblock_matched(object, G_SIGNAL_MATCH_DATA, 0, 0, NULL, |
| |
1882 NULL, gtkconv); |
| |
1883 g_object_unref(object); |
| |
1884 |
| |
1885 gtk_imhtml_clear(GTK_IMHTML(gtkconv->entry)); |
| |
1886 gtk_imhtml_append_text_with_images( |
| |
1887 GTK_IMHTML(gtkconv->entry), gtkconv->send_history->data, |
| |
1888 0, NULL); |
| |
1889 /* this is mainly just a hack so the formatting at the |
| |
1890 * cursor gets picked up. */ |
| |
1891 if (*(char *)gtkconv->send_history->data) { |
| |
1892 gtk_text_buffer_get_end_iter(buffer, &iter); |
| |
1893 gtk_text_buffer_move_mark_by_name(buffer, "insert", &iter); |
| |
1894 } else { |
| |
1895 /* Restore the default formatting */ |
| |
1896 default_formatize(gtkconv); |
| |
1897 } |
| |
1898 } |
| |
1899 |
| |
1900 return TRUE; |
| |
1901 break; |
| |
1902 |
| |
1903 case GDK_Page_Down: |
| |
1904 case ']': |
| |
1905 if (!pidgin_conv_window_get_gtkconv_at_index(win, curconv + 1)) |
| |
1906 gtk_notebook_set_current_page(GTK_NOTEBOOK(win->notebook), 0); |
| |
1907 else |
| |
1908 gtk_notebook_set_current_page(GTK_NOTEBOOK(win->notebook), curconv + 1); |
| |
1909 return TRUE; |
| |
1910 break; |
| |
1911 |
| |
1912 case GDK_Page_Up: |
| |
1913 case '[': |
| |
1914 if (!pidgin_conv_window_get_gtkconv_at_index(win, curconv - 1)) |
| |
1915 gtk_notebook_set_current_page(GTK_NOTEBOOK(win->notebook), -1); |
| |
1916 else |
| |
1917 gtk_notebook_set_current_page(GTK_NOTEBOOK(win->notebook), curconv - 1); |
| |
1918 return TRUE; |
| |
1919 break; |
| |
1920 |
| |
1921 case GDK_Tab: |
| |
1922 case GDK_ISO_Left_Tab: |
| |
1923 if (event->state & GDK_SHIFT_MASK) { |
| |
1924 move_to_next_unread_tab(gtkconv, FALSE); |
| |
1925 } else { |
| |
1926 move_to_next_unread_tab(gtkconv, TRUE); |
| |
1927 } |
| |
1928 |
| |
1929 return TRUE; |
| |
1930 break; |
| |
1931 |
| |
1932 case GDK_comma: |
| |
1933 gtk_notebook_reorder_child(GTK_NOTEBOOK(win->notebook), |
| |
1934 gtk_notebook_get_nth_page(GTK_NOTEBOOK(win->notebook), curconv), |
| |
1935 curconv - 1); |
| |
1936 break; |
| |
1937 |
| |
1938 case GDK_period: |
| |
1939 gtk_notebook_reorder_child(GTK_NOTEBOOK(win->notebook), |
| |
1940 gtk_notebook_get_nth_page(GTK_NOTEBOOK(win->notebook), curconv), |
| |
1941 #if GTK_CHECK_VERSION(2,2,0) |
| |
1942 (curconv + 1) % gtk_notebook_get_n_pages(GTK_NOTEBOOK(win->notebook))); |
| |
1943 #else |
| |
1944 (curconv + 1) % g_list_length(GTK_NOTEBOOK(win->notebook)->children)); |
| |
1945 #endif |
| |
1946 break; |
| |
1947 |
| |
1948 } /* End of switch */ |
| |
1949 } |
| |
1950 |
| |
1951 /* If ALT (or whatever) was held down... */ |
| |
1952 else if (event->state & GDK_MOD1_MASK) |
| |
1953 { |
| |
1954 if (event->keyval > '0' && event->keyval <= '9') |
| |
1955 { |
| |
1956 guint switchto = event->keyval - '1'; |
| |
1957 if (switchto < pidgin_conv_window_get_gtkconv_count(win)) |
| |
1958 gtk_notebook_set_current_page(GTK_NOTEBOOK(win->notebook), switchto); |
| |
1959 |
| |
1960 return TRUE; |
| |
1961 } |
| |
1962 } |
| |
1963 |
| |
1964 /* If neither CTRL nor ALT were held down... */ |
| |
1965 else |
| |
1966 { |
| |
1967 switch (event->keyval) |
| |
1968 { |
| |
1969 case GDK_Tab: |
| |
1970 return tab_complete(conv); |
| |
1971 break; |
| |
1972 |
| |
1973 case GDK_Page_Up: |
| |
1974 gtk_imhtml_page_up(GTK_IMHTML(gtkconv->imhtml)); |
| |
1975 return TRUE; |
| |
1976 break; |
| |
1977 |
| |
1978 case GDK_Page_Down: |
| |
1979 gtk_imhtml_page_down(GTK_IMHTML(gtkconv->imhtml)); |
| |
1980 return TRUE; |
| |
1981 break; |
| |
1982 |
| |
1983 } |
| |
1984 } |
| |
1985 return FALSE; |
| |
1986 } |
| |
1987 |
| |
1988 /* |
| |
1989 * NOTE: |
| |
1990 * This guy just kills a single right click from being propagated any |
| |
1991 * further. I have no idea *why* we need this, but we do ... It |
| |
1992 * prevents right clicks on the GtkTextView in a convo dialog from |
| |
1993 * going all the way down to the notebook. I suspect a bug in |
| |
1994 * GtkTextView, but I'm not ready to point any fingers yet. |
| |
1995 */ |
| |
1996 static gboolean |
| |
1997 entry_stop_rclick_cb(GtkWidget *widget, GdkEventButton *event, gpointer data) |
| |
1998 { |
| |
1999 if (event->button == 3 && event->type == GDK_BUTTON_PRESS) { |
| |
2000 /* Right single click */ |
| |
2001 g_signal_stop_emission_by_name(G_OBJECT(widget), "button_press_event"); |
| |
2002 |
| |
2003 return TRUE; |
| |
2004 } |
| |
2005 |
| |
2006 return FALSE; |
| |
2007 } |
| |
2008 |
| |
2009 /* |
| |
2010 * If someone tries to type into the conversation backlog of a |
| |
2011 * conversation window then we yank focus from the conversation backlog |
| |
2012 * and give it to the text entry box so that people can type |
| |
2013 * all the live long day and it will get entered into the entry box. |
| |
2014 */ |
| |
2015 static gboolean |
| |
2016 refocus_entry_cb(GtkWidget *widget, GdkEventKey *event, gpointer data) |
| |
2017 { |
| |
2018 PidginConversation *gtkconv = data; |
| |
2019 |
| |
2020 /* If we have a valid key for the conversation display, then exit */ |
| |
2021 if ((event->state & GDK_CONTROL_MASK) || |
| |
2022 (event->keyval == GDK_F10) || |
| |
2023 (event->keyval == GDK_Shift_L) || |
| |
2024 (event->keyval == GDK_Shift_R) || |
| |
2025 (event->keyval == GDK_Escape) || |
| |
2026 (event->keyval == GDK_Up) || |
| |
2027 (event->keyval == GDK_Down) || |
| |
2028 (event->keyval == GDK_Left) || |
| |
2029 (event->keyval == GDK_Right) || |
| |
2030 (event->keyval == GDK_Home) || |
| |
2031 (event->keyval == GDK_End) || |
| |
2032 (event->keyval == GDK_Tab) || |
| |
2033 (event->keyval == GDK_ISO_Left_Tab)) |
| |
2034 return FALSE; |
| |
2035 |
| |
2036 if (event->type == GDK_KEY_RELEASE) |
| |
2037 gtk_widget_grab_focus(gtkconv->entry); |
| |
2038 |
| |
2039 gtk_widget_event(gtkconv->entry, (GdkEvent *)event); |
| |
2040 |
| |
2041 return TRUE; |
| |
2042 } |
| |
2043 |
| |
2044 void |
| |
2045 pidgin_conv_switch_active_conversation(PurpleConversation *conv) |
| |
2046 { |
| |
2047 PidginConversation *gtkconv; |
| |
2048 PurpleConversation *old_conv; |
| |
2049 GtkIMHtml *entry; |
| |
2050 const char *protocol_name; |
| |
2051 |
| |
2052 g_return_if_fail(conv != NULL); |
| |
2053 |
| |
2054 gtkconv = PIDGIN_CONVERSATION(conv); |
| |
2055 old_conv = gtkconv->active_conv; |
| |
2056 |
| |
2057 if (old_conv == conv) |
| |
2058 return; |
| |
2059 |
| |
2060 purple_conversation_close_logs(old_conv); |
| |
2061 gtkconv->active_conv = conv; |
| |
2062 |
| |
2063 purple_conversation_set_logging(conv, |
| |
2064 gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(gtkconv->win->menu.logging))); |
| |
2065 |
| |
2066 entry = GTK_IMHTML(gtkconv->entry); |
| |
2067 protocol_name = purple_account_get_protocol_name(conv->account); |
| |
2068 gtk_imhtml_set_protocol_name(entry, protocol_name); |
| |
2069 gtk_imhtml_set_protocol_name(GTK_IMHTML(gtkconv->imhtml), protocol_name); |
| |
2070 |
| |
2071 if (!(conv->features & PURPLE_CONNECTION_HTML)) |
| |
2072 gtk_imhtml_clear_formatting(GTK_IMHTML(gtkconv->entry)); |
| |
2073 else if (conv->features & PURPLE_CONNECTION_FORMATTING_WBFO && |
| |
2074 !(old_conv->features & PURPLE_CONNECTION_FORMATTING_WBFO)) |
| |
2075 { |
| |
2076 /* The old conversation allowed formatting on parts of the |
| |
2077 * buffer, but the new one only allows it on the whole |
| |
2078 * buffer. This code saves the formatting from the current |
| |
2079 * position of the cursor, clears the formatting, then |
| |
2080 * applies the saved formatting to the entire buffer. */ |
| |
2081 |
| |
2082 gboolean bold; |
| |
2083 gboolean italic; |
| |
2084 gboolean underline; |
| |
2085 char *fontface = gtk_imhtml_get_current_fontface(entry); |
| |
2086 char *forecolor = gtk_imhtml_get_current_forecolor(entry); |
| |
2087 char *backcolor = gtk_imhtml_get_current_backcolor(entry); |
| |
2088 char *background = gtk_imhtml_get_current_background(entry); |
| |
2089 gint fontsize = gtk_imhtml_get_current_fontsize(entry); |
| |
2090 gboolean bold2; |
| |
2091 gboolean italic2; |
| |
2092 gboolean underline2; |
| |
2093 |
| |
2094 gtk_imhtml_get_current_format(entry, &bold, &italic, &underline); |
| |
2095 |
| |
2096 /* Clear existing formatting */ |
| |
2097 gtk_imhtml_clear_formatting(entry); |
| |
2098 |
| |
2099 /* Apply saved formatting to the whole buffer. */ |
| |
2100 |
| |
2101 gtk_imhtml_get_current_format(entry, &bold2, &italic2, &underline2); |
| |
2102 |
| |
2103 if (bold != bold2) |
| |
2104 gtk_imhtml_toggle_bold(entry); |
| |
2105 |
| |
2106 if (italic != italic2) |
| |
2107 gtk_imhtml_toggle_italic(entry); |
| |
2108 |
| |
2109 if (underline != underline2) |
| |
2110 gtk_imhtml_toggle_underline(entry); |
| |
2111 |
| |
2112 gtk_imhtml_toggle_fontface(entry, fontface); |
| |
2113 |
| |
2114 if (!(conv->features & PURPLE_CONNECTION_NO_FONTSIZE)) |
| |
2115 gtk_imhtml_font_set_size(entry, fontsize); |
| |
2116 |
| |
2117 gtk_imhtml_toggle_forecolor(entry, forecolor); |
| |
2118 |
| |
2119 if (!(conv->features & PURPLE_CONNECTION_NO_BGCOLOR)) |
| |
2120 { |
| |
2121 gtk_imhtml_toggle_backcolor(entry, backcolor); |
| |
2122 gtk_imhtml_toggle_background(entry, background); |
| |
2123 } |
| |
2124 |
| |
2125 g_free(fontface); |
| |
2126 g_free(forecolor); |
| |
2127 g_free(backcolor); |
| |
2128 g_free(background); |
| |
2129 } |
| |
2130 else |
| |
2131 { |
| |
2132 /* This is done in default_formatize, which is called from clear_formatting_cb, |
| |
2133 * which is (obviously) a clear_formatting signal handler. However, if we're |
| |
2134 * here, we didn't call gtk_imhtml_clear_formatting() (because we want to |
| |
2135 * preserve the formatting exactly as it is), so we have to do this now. */ |
| |
2136 gtk_imhtml_set_whole_buffer_formatting_only(entry, |
| |
2137 (conv->features & PURPLE_CONNECTION_FORMATTING_WBFO)); |
| |
2138 } |
| |
2139 |
| |
2140 purple_signal_emit(pidgin_conversations_get_handle(), "conversation-switched", conv); |
| |
2141 |
| |
2142 gray_stuff_out(gtkconv); |
| |
2143 update_typing_icon(gtkconv); |
| |
2144 |
| |
2145 gtk_window_set_title(GTK_WINDOW(gtkconv->win->window), |
| |
2146 gtk_label_get_text(GTK_LABEL(gtkconv->tab_label))); |
| |
2147 } |
| |
2148 |
| |
2149 static void |
| |
2150 menu_conv_sel_send_cb(GObject *m, gpointer data) |
| |
2151 { |
| |
2152 PurpleAccount *account = g_object_get_data(m, "purple_account"); |
| |
2153 gchar *name = g_object_get_data(m, "purple_buddy_name"); |
| |
2154 PurpleConversation *conv; |
| |
2155 |
| |
2156 if (gtk_check_menu_item_get_active((GtkCheckMenuItem*) m) == FALSE) |
| |
2157 return; |
| |
2158 |
| |
2159 conv = purple_conversation_new(PURPLE_CONV_TYPE_IM, account, name); |
| |
2160 pidgin_conv_switch_active_conversation(conv); |
| |
2161 } |
| |
2162 |
| |
2163 static void |
| |
2164 insert_text_cb(GtkTextBuffer *textbuffer, GtkTextIter *position, |
| |
2165 gchar *new_text, gint new_text_length, gpointer user_data) |
| |
2166 { |
| |
2167 PidginConversation *gtkconv = (PidginConversation *)user_data; |
| |
2168 PurpleConversation *conv; |
| |
2169 |
| |
2170 g_return_if_fail(gtkconv != NULL); |
| |
2171 |
| |
2172 conv = gtkconv->active_conv; |
| |
2173 |
| |
2174 if (!purple_prefs_get_bool("/core/conversations/im/send_typing")) |
| |
2175 return; |
| |
2176 |
| |
2177 got_typing_keypress(gtkconv, (gtk_text_iter_is_start(position) && |
| |
2178 gtk_text_iter_is_end(position))); |
| |
2179 } |
| |
2180 |
| |
2181 static void |
| |
2182 delete_text_cb(GtkTextBuffer *textbuffer, GtkTextIter *start_pos, |
| |
2183 GtkTextIter *end_pos, gpointer user_data) |
| |
2184 { |
| |
2185 PidginConversation *gtkconv = (PidginConversation *)user_data; |
| |
2186 PurpleConversation *conv; |
| |
2187 PurpleConvIm *im; |
| |
2188 |
| |
2189 g_return_if_fail(gtkconv != NULL); |
| |
2190 |
| |
2191 conv = gtkconv->active_conv; |
| |
2192 |
| |
2193 if (!purple_prefs_get_bool("/core/conversations/im/send_typing")) |
| |
2194 return; |
| |
2195 |
| |
2196 im = PURPLE_CONV_IM(conv); |
| |
2197 |
| |
2198 if (gtk_text_iter_is_start(start_pos) && gtk_text_iter_is_end(end_pos)) { |
| |
2199 |
| |
2200 /* We deleted all the text, so turn off typing. */ |
| |
2201 purple_conv_im_stop_send_typed_timeout(im); |
| |
2202 |
| |
2203 serv_send_typing(purple_conversation_get_gc(conv), |
| |
2204 purple_conversation_get_name(conv), |
| |
2205 PURPLE_NOT_TYPING); |
| |
2206 } |
| |
2207 else { |
| |
2208 /* We're deleting, but not all of it, so it counts as typing. */ |
| |
2209 got_typing_keypress(gtkconv, FALSE); |
| |
2210 } |
| |
2211 } |
| |
2212 |
| |
2213 /************************************************************************** |
| |
2214 * A bunch of buddy icon functions |
| |
2215 **************************************************************************/ |
| |
2216 |
| |
2217 static GList *get_prpl_icon_list(PurpleAccount *account) |
| |
2218 { |
| |
2219 GList *l = NULL; |
| |
2220 GdkPixbuf *pixbuf; |
| |
2221 PurplePluginProtocolInfo *prpl_info = PURPLE_PLUGIN_PROTOCOL_INFO(purple_find_prpl(purple_account_get_protocol_id(account))); |
| |
2222 const char *prpl = prpl_info->list_icon(account, NULL); |
| |
2223 char *filename, *path; |
| |
2224 l = g_hash_table_lookup(prpl_lists, prpl); |
| |
2225 if (l) |
| |
2226 return l; |
| |
2227 filename = g_strdup_printf("%s.png", prpl); |
| |
2228 |
| |
2229 path = g_build_filename(DATADIR, "pixmaps", "pidgin", "protocols", "16", filename, NULL); |
| |
2230 pixbuf = gdk_pixbuf_new_from_file(path, NULL); |
| |
2231 if (pixbuf) |
| |
2232 l = g_list_append(l, pixbuf); |
| |
2233 g_free(path); |
| |
2234 |
| |
2235 path = g_build_filename(DATADIR, "pixmaps", "pidgin", "protocols", "22", filename, NULL); |
| |
2236 pixbuf = gdk_pixbuf_new_from_file(path, NULL); |
| |
2237 if (pixbuf) |
| |
2238 l = g_list_append(l, pixbuf); |
| |
2239 g_free(path); |
| |
2240 |
| |
2241 path = g_build_filename(DATADIR, "pixmaps", "pidgin", "protocols", "48", filename, NULL); |
| |
2242 pixbuf = gdk_pixbuf_new_from_file(path, NULL); |
| |
2243 if (pixbuf) |
| |
2244 l = g_list_append(l, pixbuf); |
| |
2245 g_free(path); |
| |
2246 |
| |
2247 g_hash_table_insert(prpl_lists, g_strdup(prpl), l); |
| |
2248 return l; |
| |
2249 } |
| |
2250 |
| |
2251 static GList * |
| |
2252 pidgin_conv_get_tab_icons(PurpleConversation *conv) |
| |
2253 { |
| |
2254 PurpleAccount *account = NULL; |
| |
2255 const char *name = NULL; |
| |
2256 |
| |
2257 g_return_val_if_fail(conv != NULL, NULL); |
| |
2258 |
| |
2259 account = purple_conversation_get_account(conv); |
| |
2260 name = purple_conversation_get_name(conv); |
| |
2261 |
| |
2262 g_return_val_if_fail(account != NULL, NULL); |
| |
2263 g_return_val_if_fail(name != NULL, NULL); |
| |
2264 |
| |
2265 /* Use the buddy icon, if possible */ |
| |
2266 if (purple_conversation_get_type(conv) == PURPLE_CONV_TYPE_IM) { |
| |
2267 PurpleBuddy *b = purple_find_buddy(account, name); |
| |
2268 if (b != NULL) { |
| |
2269 PurplePresence *p; |
| |
2270 p = purple_buddy_get_presence(b); |
| |
2271 if (purple_presence_is_status_primitive_active(p, PURPLE_STATUS_AWAY)) |
| |
2272 return away_list; |
| |
2273 if (purple_presence_is_status_primitive_active(p, PURPLE_STATUS_UNAVAILABLE)) |
| |
2274 return busy_list; |
| |
2275 if (purple_presence_is_status_primitive_active(p, PURPLE_STATUS_EXTENDED_AWAY)) |
| |
2276 return xa_list; |
| |
2277 if (purple_presence_is_status_primitive_active(p, PURPLE_STATUS_OFFLINE)) |
| |
2278 return offline_list; |
| |
2279 else |
| |
2280 return available_list; |
| |
2281 } |
| |
2282 } |
| |
2283 |
| |
2284 return get_prpl_icon_list(account); |
| |
2285 } |
| |
2286 |
| |
2287 GdkPixbuf * |
| |
2288 pidgin_conv_get_tab_icon(PurpleConversation *conv, gboolean small_icon) |
| |
2289 { |
| |
2290 PurpleAccount *account = NULL; |
| |
2291 const char *name = NULL; |
| |
2292 GdkPixbuf *status = NULL; |
| |
2293 PurpleBlistUiOps *ops = purple_blist_get_ui_ops(); |
| |
2294 |
| |
2295 g_return_val_if_fail(conv != NULL, NULL); |
| |
2296 |
| |
2297 account = purple_conversation_get_account(conv); |
| |
2298 name = purple_conversation_get_name(conv); |
| |
2299 |
| |
2300 g_return_val_if_fail(account != NULL, NULL); |
| |
2301 g_return_val_if_fail(name != NULL, NULL); |
| |
2302 |
| |
2303 /* Use the buddy icon, if possible */ |
| |
2304 if (purple_conversation_get_type(conv) == PURPLE_CONV_TYPE_IM) { |
| |
2305 PurpleBuddy *b = purple_find_buddy(account, name); |
| |
2306 if (b != NULL) { |
| |
2307 /* I hate this hack. It fixes a bug where the pending message icon |
| |
2308 * displays in the conv tab even though it shouldn't. |
| |
2309 * A better solution would be great. */ |
| |
2310 if (ops && ops->update) |
| |
2311 ops->update(NULL, (PurpleBlistNode*)b); |
| |
2312 |
| |
2313 status = pidgin_blist_get_status_icon((PurpleBlistNode*)b, |
| |
2314 (small_icon ? PIDGIN_STATUS_ICON_SMALL : PIDGIN_STATUS_ICON_LARGE)); |
| |
2315 } |
| |
2316 } |
| |
2317 |
| |
2318 /* If they don't have a buddy icon, then use the PRPL icon */ |
| |
2319 if (status == NULL) |
| |
2320 status = pidgin_create_prpl_icon(account, small_icon ? PIDGIN_PRPL_ICON_SMALL : PIDGIN_PRPL_ICON_LARGE); |
| |
2321 |
| |
2322 return status; |
| |
2323 } |
| |
2324 |
| |
2325 |
| |
2326 static void |
| |
2327 update_tab_icon(PurpleConversation *conv) |
| |
2328 { |
| |
2329 PidginConversation *gtkconv; |
| |
2330 PidginWindow *win; |
| |
2331 GList *l; |
| |
2332 GdkPixbuf *status = NULL; |
| |
2333 |
| |
2334 g_return_if_fail(conv != NULL); |
| |
2335 |
| |
2336 gtkconv = PIDGIN_CONVERSATION(conv); |
| |
2337 win = gtkconv->win; |
| |
2338 if (conv != gtkconv->active_conv) |
| |
2339 return; |
| |
2340 |
| |
2341 status = pidgin_conv_get_tab_icon(conv, TRUE); |
| |
2342 |
| |
2343 g_return_if_fail(status != NULL); |
| |
2344 |
| |
2345 gtk_image_set_from_pixbuf(GTK_IMAGE(gtkconv->icon), status); |
| |
2346 gtk_image_set_from_pixbuf(GTK_IMAGE(gtkconv->menu_icon), status); |
| |
2347 |
| |
2348 if (status != NULL) |
| |
2349 g_object_unref(status); |
| |
2350 |
| |
2351 if (pidgin_conv_window_is_active_conversation(conv) && |
| |
2352 (purple_conversation_get_type(conv) != PURPLE_CONV_TYPE_IM || |
| |
2353 gtkconv->u.im->anim == NULL)) |
| |
2354 { |
| |
2355 l = pidgin_conv_get_tab_icons(conv); |
| |
2356 |
| |
2357 gtk_window_set_icon_list(GTK_WINDOW(win->window), l); |
| |
2358 } |
| |
2359 } |
| |
2360 |
| |
2361 /* This gets added as an idle handler when doing something that |
| |
2362 * redraws the icon. It sets the auto_resize gboolean to TRUE. |
| |
2363 * This way, when the size_allocate callback gets triggered, it notices |
| |
2364 * that this is an autoresize, and after the main loop iterates, it |
| |
2365 * gets set back to FALSE |
| |
2366 */ |
| |
2367 static gboolean reset_auto_resize_cb(gpointer data) |
| |
2368 { |
| |
2369 PidginConversation *gtkconv = (PidginConversation *)data; |
| |
2370 gtkconv->auto_resize = FALSE; |
| |
2371 return FALSE; |
| |
2372 } |
| |
2373 |
| |
2374 static gboolean |
| |
2375 redraw_icon(gpointer data) |
| |
2376 { |
| |
2377 PidginConversation *gtkconv = (PidginConversation *)data; |
| |
2378 PurpleConversation *conv = gtkconv->active_conv; |
| |
2379 PurpleAccount *account; |
| |
2380 PurplePluginProtocolInfo *prpl_info = NULL; |
| |
2381 |
| |
2382 GdkPixbuf *buf; |
| |
2383 GdkPixbuf *scale; |
| |
2384 gint delay; |
| |
2385 int scale_width, scale_height; |
| |
2386 |
| |
2387 gtkconv = PIDGIN_CONVERSATION(conv); |
| |
2388 account = purple_conversation_get_account(conv); |
| |
2389 if(account && account->gc) |
| |
2390 prpl_info = PURPLE_PLUGIN_PROTOCOL_INFO(account->gc->prpl); |
| |
2391 |
| |
2392 gtkconv->auto_resize = TRUE; |
| |
2393 g_idle_add(reset_auto_resize_cb, gtkconv); |
| |
2394 |
| |
2395 gdk_pixbuf_animation_iter_advance(gtkconv->u.im->iter, NULL); |
| |
2396 buf = gdk_pixbuf_animation_iter_get_pixbuf(gtkconv->u.im->iter); |
| |
2397 |
| |
2398 pidgin_buddy_icon_get_scale_size(buf, &prpl_info->icon_spec, |
| |
2399 PURPLE_ICON_SCALE_DISPLAY, &scale_width, &scale_height); |
| |
2400 |
| |
2401 /* this code is ugly, and scares me */ |
| |
2402 scale = gdk_pixbuf_scale_simple(buf, |
| |
2403 MAX(gdk_pixbuf_get_width(buf) * scale_width / |
| |
2404 gdk_pixbuf_animation_get_width(gtkconv->u.im->anim), 1), |
| |
2405 MAX(gdk_pixbuf_get_height(buf) * scale_height / |
| |
2406 gdk_pixbuf_animation_get_height(gtkconv->u.im->anim), 1), |
| |
2407 GDK_INTERP_BILINEAR); |
| |
2408 |
| |
2409 gtk_image_set_from_pixbuf(GTK_IMAGE(gtkconv->u.im->icon), scale); |
| |
2410 g_object_unref(G_OBJECT(scale)); |
| |
2411 gtk_widget_queue_draw(gtkconv->u.im->icon); |
| |
2412 |
| |
2413 delay = gdk_pixbuf_animation_iter_get_delay_time(gtkconv->u.im->iter); |
| |
2414 |
| |
2415 if (delay < 100) |
| |
2416 delay = 100; |
| |
2417 |
| |
2418 gtkconv->u.im->icon_timer = g_timeout_add(delay, redraw_icon, gtkconv); |
| |
2419 |
| |
2420 return FALSE; |
| |
2421 } |
| |
2422 |
| |
2423 static void |
| |
2424 start_anim(GtkObject *obj, PidginConversation *gtkconv) |
| |
2425 { |
| |
2426 int delay; |
| |
2427 |
| |
2428 if (gtkconv->u.im->anim == NULL) |
| |
2429 return; |
| |
2430 |
| |
2431 if (gtkconv->u.im->icon_timer != 0) |
| |
2432 return; |
| |
2433 |
| |
2434 if (gdk_pixbuf_animation_is_static_image(gtkconv->u.im->anim)) |
| |
2435 return; |
| |
2436 |
| |
2437 delay = gdk_pixbuf_animation_iter_get_delay_time(gtkconv->u.im->iter); |
| |
2438 |
| |
2439 if (delay < 100) |
| |
2440 delay = 100; |
| |
2441 |
| |
2442 gtkconv->u.im->icon_timer = g_timeout_add(delay, redraw_icon, gtkconv); |
| |
2443 } |
| |
2444 |
| |
2445 static void |
| |
2446 remove_icon(GtkWidget *widget, PidginConversation *gtkconv) |
| |
2447 { |
| |
2448 PurpleConversation *conv = gtkconv->active_conv; |
| |
2449 PidginWindow *gtkwin; |
| |
2450 |
| |
2451 g_return_if_fail(conv != NULL); |
| |
2452 |
| |
2453 if (gtkconv->u.im->icon_container != NULL) |
| |
2454 gtk_widget_destroy(gtkconv->u.im->icon_container); |
| |
2455 |
| |
2456 if (gtkconv->u.im->anim != NULL) |
| |
2457 g_object_unref(G_OBJECT(gtkconv->u.im->anim)); |
| |
2458 |
| |
2459 if (gtkconv->u.im->icon_timer != 0) |
| |
2460 g_source_remove(gtkconv->u.im->icon_timer); |
| |
2461 |
| |
2462 if (gtkconv->u.im->iter != NULL) |
| |
2463 g_object_unref(G_OBJECT(gtkconv->u.im->iter)); |
| |
2464 |
| |
2465 gtkconv->u.im->icon_timer = 0; |
| |
2466 gtkconv->u.im->icon = NULL; |
| |
2467 gtkconv->u.im->anim = NULL; |
| |
2468 gtkconv->u.im->iter = NULL; |
| |
2469 gtkconv->u.im->icon_container = NULL; |
| |
2470 gtkconv->u.im->show_icon = FALSE; |
| |
2471 |
| |
2472 gtkwin = gtkconv->win; |
| |
2473 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(gtkwin->menu.show_icon), FALSE); |
| |
2474 } |
| |
2475 |
| |
2476 static void |
| |
2477 saveicon_writefile_cb(void *user_data, const char *filename) |
| |
2478 { |
| |
2479 PidginConversation *gtkconv = (PidginConversation *)user_data; |
| |
2480 PurpleConversation *conv = gtkconv->active_conv; |
| |
2481 FILE *fp; |
| |
2482 PurpleBuddyIcon *icon; |
| |
2483 const void *data; |
| |
2484 size_t len; |
| |
2485 |
| |
2486 if ((fp = g_fopen(filename, "wb")) == NULL) { |
| |
2487 purple_notify_error(gtkconv, NULL, _("Unable to open file."), NULL); |
| |
2488 return; |
| |
2489 } |
| |
2490 |
| |
2491 icon = purple_conv_im_get_icon(PURPLE_CONV_IM(conv)); |
| |
2492 data = purple_buddy_icon_get_data(icon, &len); |
| |
2493 |
| |
2494 if ((len <= 0) || (data == NULL) || (fwrite(data, 1, len, fp) != len)) { |
| |
2495 purple_notify_error(gtkconv, NULL, _("Unable to save icon file to disk."), NULL); |
| |
2496 fclose(fp); |
| |
2497 g_unlink(filename); |
| |
2498 return; |
| |
2499 } |
| |
2500 fclose(fp); |
| |
2501 } |
| |
2502 |
| |
2503 static const char * |
| |
2504 custom_icon_pref_name(PidginConversation *gtkconv) |
| |
2505 { |
| |
2506 PurpleConversation *conv; |
| |
2507 PurpleAccount *account; |
| |
2508 PurpleBuddy *buddy; |
| |
2509 |
| |
2510 conv = gtkconv->active_conv; |
| |
2511 account = purple_conversation_get_account(conv); |
| |
2512 buddy = purple_find_buddy(account, purple_conversation_get_name(conv)); |
| |
2513 if (buddy) { |
| |
2514 PurpleContact *contact = purple_buddy_get_contact(buddy); |
| |
2515 return purple_blist_node_get_string((PurpleBlistNode*)contact, "custom_buddy_icon"); |
| |
2516 } |
| |
2517 return NULL; |
| |
2518 } |
| |
2519 |
| |
2520 static void |
| |
2521 custom_icon_sel_cb(const char *filename, gpointer data) |
| |
2522 { |
| |
2523 if (filename) { |
| |
2524 PidginConversation *gtkconv = data; |
| |
2525 PurpleConversation *conv = gtkconv->active_conv; |
| |
2526 PurpleAccount *account = purple_conversation_get_account(conv); |
| |
2527 pidgin_set_custom_buddy_icon(account, purple_conversation_get_name(conv), filename); |
| |
2528 } |
| |
2529 } |
| |
2530 |
| |
2531 static void |
| |
2532 set_custom_icon_cb(GtkWidget *widget, PidginConversation *gtkconv) |
| |
2533 { |
| |
2534 GtkWidget *win = pidgin_buddy_icon_chooser_new(GTK_WINDOW(gtkconv->win->window), |
| |
2535 custom_icon_sel_cb, gtkconv); |
| |
2536 gtk_widget_show_all(win); |
| |
2537 } |
| |
2538 |
| |
2539 static void |
| |
2540 remove_custom_icon_cb(GtkWidget *widget, PidginConversation *gtkconv) |
| |
2541 { |
| |
2542 PurpleConversation *conv; |
| |
2543 PurpleAccount *account; |
| |
2544 |
| |
2545 conv = gtkconv->active_conv; |
| |
2546 account = purple_conversation_get_account(conv); |
| |
2547 pidgin_set_custom_buddy_icon(account, purple_conversation_get_name(conv), NULL); |
| |
2548 } |
| |
2549 |
| |
2550 static void |
| |
2551 icon_menu_save_cb(GtkWidget *widget, PidginConversation *gtkconv) |
| |
2552 { |
| |
2553 PurpleConversation *conv = gtkconv->active_conv; |
| |
2554 const gchar *ext; |
| |
2555 gchar *buf; |
| |
2556 |
| |
2557 g_return_if_fail(conv != NULL); |
| |
2558 |
| |
2559 ext = purple_buddy_icon_get_type(purple_conv_im_get_icon(PURPLE_CONV_IM(conv))); |
| |
2560 if (ext == NULL) |
| |
2561 ext = "icon"; |
| |
2562 |
| |
2563 buf = g_strdup_printf("%s.%s", purple_normalize(conv->account, conv->name), ext); |
| |
2564 |
| |
2565 purple_request_file(gtkconv, _("Save Icon"), buf, TRUE, |
| |
2566 G_CALLBACK(saveicon_writefile_cb), NULL, gtkconv); |
| |
2567 |
| |
2568 g_free(buf); |
| |
2569 } |
| |
2570 |
| |
2571 static void |
| |
2572 stop_anim(GtkObject *obj, PidginConversation *gtkconv) |
| |
2573 { |
| |
2574 if (gtkconv->u.im->icon_timer != 0) |
| |
2575 g_source_remove(gtkconv->u.im->icon_timer); |
| |
2576 |
| |
2577 gtkconv->u.im->icon_timer = 0; |
| |
2578 } |
| |
2579 |
| |
2580 |
| |
2581 static void |
| |
2582 toggle_icon_animate_cb(GtkWidget *w, PidginConversation *gtkconv) |
| |
2583 { |
| |
2584 gtkconv->u.im->animate = |
| |
2585 gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(w)); |
| |
2586 |
| |
2587 if (gtkconv->u.im->animate) |
| |
2588 start_anim(NULL, gtkconv); |
| |
2589 else |
| |
2590 stop_anim(NULL, gtkconv); |
| |
2591 } |
| |
2592 |
| |
2593 static gboolean |
| |
2594 icon_menu(GtkObject *obj, GdkEventButton *e, PidginConversation *gtkconv) |
| |
2595 { |
| |
2596 static GtkWidget *menu = NULL; |
| |
2597 const char *pref; |
| |
2598 |
| |
2599 if (e->button != 3 || e->type != GDK_BUTTON_PRESS) |
| |
2600 return FALSE; |
| |
2601 |
| |
2602 /* |
| |
2603 * If a menu already exists, destroy it before creating a new one, |
| |
2604 * thus freeing-up the memory it occupied. |
| |
2605 */ |
| |
2606 if (menu != NULL) |
| |
2607 gtk_widget_destroy(menu); |
| |
2608 |
| |
2609 menu = gtk_menu_new(); |
| |
2610 |
| |
2611 if (gtkconv->u.im->anim && |
| |
2612 !(gdk_pixbuf_animation_is_static_image(gtkconv->u.im->anim))) |
| |
2613 { |
| |
2614 pidgin_new_check_item(menu, _("Animate"), |
| |
2615 G_CALLBACK(toggle_icon_animate_cb), gtkconv, |
| |
2616 gtkconv->u.im->icon_timer); |
| |
2617 } |
| |
2618 |
| |
2619 pidgin_new_item_from_stock(menu, _("Hide Icon"), NULL, G_CALLBACK(remove_icon), |
| |
2620 gtkconv, 0, 0, NULL); |
| |
2621 |
| |
2622 pidgin_new_item_from_stock(menu, _("Save Icon As..."), GTK_STOCK_SAVE_AS, |
| |
2623 G_CALLBACK(icon_menu_save_cb), gtkconv, |
| |
2624 0, 0, NULL); |
| |
2625 |
| |
2626 pidgin_new_item_from_stock(menu, _("Set Custom Icon..."), NULL, |
| |
2627 G_CALLBACK(set_custom_icon_cb), gtkconv, |
| |
2628 0, 0, NULL); |
| |
2629 |
| |
2630 /* Is there a custom icon for this person? */ |
| |
2631 pref = custom_icon_pref_name(gtkconv); |
| |
2632 if (pref && *pref) { |
| |
2633 pidgin_new_item_from_stock(menu, _("Remove Custom Icon"), NULL, |
| |
2634 G_CALLBACK(remove_custom_icon_cb), gtkconv, |
| |
2635 0, 0, NULL); |
| |
2636 } |
| |
2637 |
| |
2638 gtk_menu_popup(GTK_MENU(menu), NULL, NULL, NULL, NULL, e->button, e->time); |
| |
2639 |
| |
2640 return TRUE; |
| |
2641 } |
| |
2642 |
| |
2643 static void |
| |
2644 menu_buddyicon_cb(gpointer data, guint action, GtkWidget *widget) |
| |
2645 { |
| |
2646 PidginWindow *win = data; |
| |
2647 PurpleConversation *conv; |
| |
2648 PidginConversation *gtkconv; |
| |
2649 gboolean active; |
| |
2650 |
| |
2651 conv = pidgin_conv_window_get_active_conversation(win); |
| |
2652 |
| |
2653 if (!conv) |
| |
2654 return; |
| |
2655 |
| |
2656 g_return_if_fail(purple_conversation_get_type(conv) == PURPLE_CONV_TYPE_IM); |
| |
2657 |
| |
2658 gtkconv = PIDGIN_CONVERSATION(conv); |
| |
2659 |
| |
2660 active = gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(widget)); |
| |
2661 gtkconv->u.im->show_icon = active; |
| |
2662 if (active) |
| |
2663 pidgin_conv_update_buddy_icon(conv); |
| |
2664 else |
| |
2665 remove_icon(NULL, gtkconv); |
| |
2666 } |
| |
2667 |
| |
2668 /************************************************************************** |
| |
2669 * End of the bunch of buddy icon functions |
| |
2670 **************************************************************************/ |
| |
2671 void |
| |
2672 pidgin_conv_present_conversation(PurpleConversation *conv) |
| |
2673 { |
| |
2674 PidginConversation *gtkconv = PIDGIN_CONVERSATION(conv); |
| |
2675 |
| |
2676 if(gtkconv->win==hidden_convwin) { |
| |
2677 pidgin_conv_window_remove_gtkconv(hidden_convwin, gtkconv); |
| |
2678 pidgin_conv_placement_place(gtkconv); |
| |
2679 } |
| |
2680 |
| |
2681 pidgin_conv_switch_active_conversation(conv); |
| |
2682 pidgin_conv_window_switch_gtkconv(gtkconv->win, gtkconv); |
| |
2683 gtk_window_present(GTK_WINDOW(gtkconv->win->window)); |
| |
2684 } |
| |
2685 |
| |
2686 GList * |
| |
2687 pidgin_conversations_find_unseen_list(PurpleConversationType type, |
| |
2688 PidginUnseenState min_state, |
| |
2689 gboolean hidden_only, |
| |
2690 guint max_count) |
| |
2691 { |
| |
2692 GList *l; |
| |
2693 GList *r = NULL; |
| |
2694 guint c = 0; |
| |
2695 |
| |
2696 if (type == PURPLE_CONV_TYPE_IM) { |
| |
2697 l = purple_get_ims(); |
| |
2698 } else if (type == PURPLE_CONV_TYPE_CHAT) { |
| |
2699 l = purple_get_chats(); |
| |
2700 } else { |
| |
2701 l = purple_get_conversations(); |
| |
2702 } |
| |
2703 |
| |
2704 for (; l != NULL && (max_count == 0 || c < max_count); l = l->next) { |
| |
2705 PurpleConversation *conv = (PurpleConversation*)l->data; |
| |
2706 PidginConversation *gtkconv = PIDGIN_CONVERSATION(conv); |
| |
2707 |
| |
2708 if(gtkconv->active_conv != conv) |
| |
2709 continue; |
| |
2710 |
| |
2711 if (gtkconv->unseen_state >= min_state |
| |
2712 && (!hidden_only || |
| |
2713 (hidden_only && gtkconv->win == hidden_convwin))) { |
| |
2714 |
| |
2715 r = g_list_prepend(r, conv); |
| |
2716 c++; |
| |
2717 } |
| |
2718 } |
| |
2719 |
| |
2720 return r; |
| |
2721 } |
| |
2722 |
| |
2723 static void |
| |
2724 unseen_conv_menu_cb(GtkMenuItem *item, PurpleConversation *conv) |
| |
2725 { |
| |
2726 g_return_if_fail(conv != NULL); |
| |
2727 pidgin_conv_present_conversation(conv); |
| |
2728 } |
| |
2729 |
| |
2730 guint |
| |
2731 pidgin_conversations_fill_menu(GtkWidget *menu, GList *convs) |
| |
2732 { |
| |
2733 GList *l; |
| |
2734 guint ret=0; |
| |
2735 |
| |
2736 g_return_val_if_fail(menu != NULL, 0); |
| |
2737 g_return_val_if_fail(convs != NULL, 0); |
| |
2738 |
| |
2739 for (l = convs; l != NULL ; l = l->next) { |
| |
2740 PurpleConversation *conv = (PurpleConversation*)l->data; |
| |
2741 PidginConversation *gtkconv = PIDGIN_CONVERSATION(conv); |
| |
2742 |
| |
2743 GtkWidget *icon = gtk_image_new(); |
| |
2744 GdkPixbuf *pbuf = pidgin_conv_get_tab_icon(conv, TRUE); |
| |
2745 GtkWidget *item; |
| |
2746 gchar *text = g_strdup_printf("%s (%d)", |
| |
2747 gtk_label_get_text(GTK_LABEL(gtkconv->tab_label)), |
| |
2748 gtkconv->unseen_count); |
| |
2749 |
| |
2750 gtk_image_set_from_pixbuf(GTK_IMAGE(icon), pbuf); |
| |
2751 g_object_unref(pbuf); |
| |
2752 |
| |
2753 item = gtk_image_menu_item_new_with_label(text); |
| |
2754 gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(item), icon); |
| |
2755 g_signal_connect(G_OBJECT(item), "activate", G_CALLBACK(unseen_conv_menu_cb), conv); |
| |
2756 gtk_menu_shell_append(GTK_MENU_SHELL(menu), item); |
| |
2757 g_free(text); |
| |
2758 ret++; |
| |
2759 } |
| |
2760 |
| |
2761 return ret; |
| |
2762 } |
| |
2763 |
| |
2764 PidginWindow * |
| |
2765 pidgin_conv_get_window(PidginConversation *gtkconv) |
| |
2766 { |
| |
2767 g_return_val_if_fail(gtkconv != NULL, NULL); |
| |
2768 return gtkconv->win; |
| |
2769 } |
| |
2770 |
| |
2771 static GtkItemFactoryEntry menu_items[] = |
| |
2772 { |
| |
2773 /* Conversation menu */ |
| |
2774 { N_("/_Conversation"), NULL, NULL, 0, "<Branch>", NULL }, |
| |
2775 |
| |
2776 { N_("/Conversation/New Instant _Message..."), "<CTL>M", menu_new_conv_cb, |
| |
2777 0, "<StockItem>", PIDGIN_STOCK_TOOLBAR_MESSAGE_NEW }, |
| |
2778 |
| |
2779 { "/Conversation/sep0", NULL, NULL, 0, "<Separator>", NULL }, |
| |
2780 |
| |
2781 { N_("/Conversation/_Find..."), NULL, menu_find_cb, 0, |
| |
2782 "<StockItem>", GTK_STOCK_FIND }, |
| |
2783 { N_("/Conversation/View _Log"), NULL, menu_view_log_cb, 0, "<Item>", NULL }, |
| |
2784 { N_("/Conversation/_Save As..."), NULL, menu_save_as_cb, 0, |
| |
2785 "<StockItem>", GTK_STOCK_SAVE_AS }, |
| |
2786 { N_("/Conversation/Clea_r Scrollback"), "<CTL>L", menu_clear_cb, 0, "<StockItem>", GTK_STOCK_CLEAR }, |
| |
2787 |
| |
2788 { "/Conversation/sep1", NULL, NULL, 0, "<Separator>", NULL }, |
| |
2789 |
| |
2790 { N_("/Conversation/Se_nd File..."), NULL, menu_send_file_cb, 0, "<StockItem>", PIDGIN_STOCK_FILE_TRANSFER }, |
| |
2791 { N_("/Conversation/Add Buddy _Pounce..."), NULL, menu_add_pounce_cb, |
| |
2792 0, "<Item>", NULL }, |
| |
2793 { N_("/Conversation/_Get Info"), "<CTL>O", menu_get_info_cb, 0, |
| |
2794 "<StockItem>", PIDGIN_STOCK_TOOLBAR_USER_INFO }, |
| |
2795 { N_("/Conversation/In_vite..."), NULL, menu_invite_cb, 0, |
| |
2796 "<Item>", NULL }, |
| |
2797 { N_("/Conversation/M_ore"), NULL, NULL, 0, "<Branch>", NULL }, |
| |
2798 |
| |
2799 { "/Conversation/sep2", NULL, NULL, 0, "<Separator>", NULL }, |
| |
2800 |
| |
2801 { N_("/Conversation/Al_ias..."), NULL, menu_alias_cb, 0, |
| |
2802 "<Item>", NULL }, |
| |
2803 { N_("/Conversation/_Block..."), NULL, menu_block_cb, 0, |
| |
2804 "<StockItem>", PIDGIN_STOCK_TOOLBAR_BLOCK }, |
| |
2805 { N_("/Conversation/_Unblock..."), NULL, menu_unblock_cb, 0, |
| |
2806 "<StockItem>", PIDGIN_STOCK_TOOLBAR_UNBLOCK }, |
| |
2807 { N_("/Conversation/_Add..."), NULL, menu_add_remove_cb, 0, |
| |
2808 "<StockItem>", GTK_STOCK_ADD }, |
| |
2809 { N_("/Conversation/_Remove..."), NULL, menu_add_remove_cb, 0, |
| |
2810 "<StockItem>", GTK_STOCK_REMOVE }, |
| |
2811 |
| |
2812 { "/Conversation/sep3", NULL, NULL, 0, "<Separator>", NULL }, |
| |
2813 |
| |
2814 { N_("/Conversation/_Close"), NULL, menu_close_conv_cb, 0, |
| |
2815 "<StockItem>", GTK_STOCK_CLOSE }, |
| |
2816 |
| |
2817 /* Options */ |
| |
2818 { N_("/_Options"), NULL, NULL, 0, "<Branch>", NULL }, |
| |
2819 { N_("/Options/Enable _Logging"), NULL, menu_logging_cb, 0, "<CheckItem>", NULL }, |
| |
2820 { N_("/Options/Enable _Sounds"), NULL, menu_sounds_cb, 0, "<CheckItem>", NULL }, |
| |
2821 { N_("/Options/Show Buddy _Icon"), NULL, menu_buddyicon_cb, 0, "<CheckItem>", NULL }, |
| |
2822 { "/Options/sep0", NULL, NULL, 0, "<Separator>", NULL }, |
| |
2823 { N_("/Options/Show Formatting _Toolbars"), NULL, menu_toolbar_cb, 0, "<CheckItem>", NULL }, |
| |
2824 { N_("/Options/Show Ti_mestamps"), "F2", menu_timestamps_cb, 0, "<CheckItem>", NULL }, |
| |
2825 }; |
| |
2826 |
| |
2827 static const int menu_item_count = |
| |
2828 sizeof(menu_items) / sizeof(*menu_items); |
| |
2829 |
| |
2830 static const char * |
| |
2831 item_factory_translate_func (const char *path, gpointer func_data) |
| |
2832 { |
| |
2833 return _(path); |
| |
2834 } |
| |
2835 |
| |
2836 static void |
| |
2837 sound_method_pref_changed_cb(const char *name, PurplePrefType type, |
| |
2838 gconstpointer value, gpointer data) |
| |
2839 { |
| |
2840 PidginWindow *win = data; |
| |
2841 const char *method = value; |
| |
2842 |
| |
2843 if (!strcmp(method, "none")) |
| |
2844 { |
| |
2845 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(win->menu.sounds), |
| |
2846 FALSE); |
| |
2847 gtk_widget_set_sensitive(win->menu.sounds, FALSE); |
| |
2848 } |
| |
2849 else |
| |
2850 { |
| |
2851 PidginConversation *gtkconv = pidgin_conv_window_get_active_gtkconv(win); |
| |
2852 |
| |
2853 if (gtkconv != NULL) |
| |
2854 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(win->menu.sounds), |
| |
2855 TRUE); |
| |
2856 gtk_widget_set_sensitive(win->menu.sounds, TRUE); |
| |
2857 |
| |
2858 } |
| |
2859 } |
| |
2860 |
| |
2861 static void |
| |
2862 show_buddy_icons_pref_changed_cb(const char *name, PurplePrefType type, |
| |
2863 gconstpointer value, gpointer data) |
| |
2864 { |
| |
2865 PidginWindow *win = data; |
| |
2866 gboolean show_icons = GPOINTER_TO_INT(value); |
| |
2867 |
| |
2868 if (!show_icons) |
| |
2869 { |
| |
2870 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(win->menu.show_icon), |
| |
2871 FALSE); |
| |
2872 gtk_widget_set_sensitive(win->menu.show_icon, FALSE); |
| |
2873 } |
| |
2874 else |
| |
2875 { |
| |
2876 PidginConversation *gtkconv = pidgin_conv_window_get_active_gtkconv(win); |
| |
2877 |
| |
2878 if (gtkconv != NULL) |
| |
2879 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(win->menu.show_icon), |
| |
2880 TRUE); |
| |
2881 gtk_widget_set_sensitive(win->menu.show_icon, TRUE); |
| |
2882 |
| |
2883 } |
| |
2884 } |
| |
2885 |
| |
2886 static void |
| |
2887 regenerate_options_items(PidginWindow *win) |
| |
2888 { |
| |
2889 GtkWidget *menu; |
| |
2890 GList *list; |
| |
2891 PidginConversation *gtkconv; |
| |
2892 PurpleConversation *conv; |
| |
2893 PurpleBuddy *buddy; |
| |
2894 |
| |
2895 gtkconv = pidgin_conv_window_get_active_gtkconv(win); |
| |
2896 conv = gtkconv->active_conv; |
| |
2897 buddy = purple_find_buddy(conv->account, conv->name); |
| |
2898 |
| |
2899 menu = gtk_item_factory_get_widget(win->menu.item_factory, N_("/Conversation/More")); |
| |
2900 |
| |
2901 /* Remove the previous entries */ |
| |
2902 for (list = gtk_container_get_children(GTK_CONTAINER(menu)); list; ) |
| |
2903 { |
| |
2904 GtkWidget *w = list->data; |
| |
2905 list = list->next; |
| |
2906 gtk_widget_destroy(w); |
| |
2907 } |
| |
2908 |
| |
2909 /* Now add the stuff */ |
| |
2910 if (buddy) |
| |
2911 { |
| |
2912 if (purple_account_is_connected(conv->account)) |
| |
2913 pidgin_append_blist_node_proto_menu(menu, conv->account->gc, |
| |
2914 (PurpleBlistNode *)buddy); |
| |
2915 pidgin_append_blist_node_extended_menu(menu, (PurpleBlistNode *)buddy); |
| |
2916 } |
| |
2917 |
| |
2918 if ((list = gtk_container_get_children(GTK_CONTAINER(menu))) == NULL) |
| |
2919 { |
| |
2920 GtkWidget *item = gtk_menu_item_new_with_label(_("No actions available")); |
| |
2921 gtk_menu_shell_append(GTK_MENU_SHELL(menu), item); |
| |
2922 gtk_widget_set_sensitive(item, FALSE); |
| |
2923 } |
| |
2924 |
| |
2925 gtk_widget_show_all(menu); |
| |
2926 } |
| |
2927 |
| |
2928 static void menubar_activated(GtkWidget *item, gpointer data) |
| |
2929 { |
| |
2930 PidginWindow *win = data; |
| |
2931 regenerate_options_items(win); |
| |
2932 |
| |
2933 /* The following are to make sure the 'More' submenu is not regenerated every time |
| |
2934 * the focus shifts from 'Conversations' to some other menu and back. */ |
| |
2935 g_signal_handlers_block_by_func(G_OBJECT(item), G_CALLBACK(menubar_activated), data); |
| |
2936 g_signal_connect(G_OBJECT(win->menu.menubar), "deactivate", G_CALLBACK(focus_out_from_menubar), data); |
| |
2937 } |
| |
2938 |
| |
2939 static void |
| |
2940 focus_out_from_menubar(GtkWidget *wid, PidginWindow *win) |
| |
2941 { |
| |
2942 /* The menubar has been deactivated. Make sure the 'More' submenu is regenerated next time |
| |
2943 * the 'Conversation' menu pops up. */ |
| |
2944 GtkWidget *menuitem = gtk_item_factory_get_item(win->menu.item_factory, N_("/Conversation")); |
| |
2945 g_signal_handlers_unblock_by_func(G_OBJECT(menuitem), G_CALLBACK(menubar_activated), win); |
| |
2946 g_signal_handlers_disconnect_by_func(G_OBJECT(win->menu.menubar), |
| |
2947 G_CALLBACK(focus_out_from_menubar), win); |
| |
2948 } |
| |
2949 |
| |
2950 static GtkWidget * |
| |
2951 setup_menubar(PidginWindow *win) |
| |
2952 { |
| |
2953 GtkAccelGroup *accel_group; |
| |
2954 const char *method; |
| |
2955 GtkWidget *menuitem; |
| |
2956 |
| |
2957 accel_group = gtk_accel_group_new (); |
| |
2958 gtk_window_add_accel_group(GTK_WINDOW(win->window), accel_group); |
| |
2959 g_object_unref(accel_group); |
| |
2960 |
| |
2961 win->menu.item_factory = |
| |
2962 gtk_item_factory_new(GTK_TYPE_MENU_BAR, "<main>", accel_group); |
| |
2963 |
| |
2964 gtk_item_factory_set_translate_func(win->menu.item_factory, |
| |
2965 (GtkTranslateFunc)item_factory_translate_func, |
| |
2966 NULL, NULL); |
| |
2967 |
| |
2968 gtk_item_factory_create_items(win->menu.item_factory, menu_item_count, |
| |
2969 menu_items, win); |
| |
2970 g_signal_connect(G_OBJECT(accel_group), "accel-changed", |
| |
2971 G_CALLBACK(pidgin_save_accels_cb), NULL); |
| |
2972 |
| |
2973 /* Make sure the 'Conversation -> More' menuitems are regenerated whenever |
| |
2974 * the 'Conversation' menu pops up because the entries can change after the |
| |
2975 * conversation is created. */ |
| |
2976 menuitem = gtk_item_factory_get_item(win->menu.item_factory, N_("/Conversation")); |
| |
2977 g_signal_connect(G_OBJECT(menuitem), "activate", G_CALLBACK(menubar_activated), win); |
| |
2978 |
| |
2979 win->menu.menubar = |
| |
2980 gtk_item_factory_get_widget(win->menu.item_factory, "<main>"); |
| |
2981 |
| |
2982 win->menu.view_log = |
| |
2983 gtk_item_factory_get_widget(win->menu.item_factory, |
| |
2984 N_("/Conversation/View Log")); |
| |
2985 |
| |
2986 /* --- */ |
| |
2987 |
| |
2988 win->menu.send_file = |
| |
2989 gtk_item_factory_get_widget(win->menu.item_factory, |
| |
2990 N_("/Conversation/Send File...")); |
| |
2991 |
| |
2992 win->menu.add_pounce = |
| |
2993 gtk_item_factory_get_widget(win->menu.item_factory, |
| |
2994 N_("/Conversation/Add Buddy Pounce...")); |
| |
2995 |
| |
2996 /* --- */ |
| |
2997 |
| |
2998 win->menu.get_info = |
| |
2999 gtk_item_factory_get_widget(win->menu.item_factory, |
| |
3000 N_("/Conversation/Get Info")); |
| |
3001 |
| |
3002 win->menu.invite = |
| |
3003 gtk_item_factory_get_widget(win->menu.item_factory, |
| |
3004 N_("/Conversation/Invite...")); |
| |
3005 |
| |
3006 /* --- */ |
| |
3007 |
| |
3008 win->menu.alias = |
| |
3009 gtk_item_factory_get_widget(win->menu.item_factory, |
| |
3010 N_("/Conversation/Alias...")); |
| |
3011 |
| |
3012 win->menu.block = |
| |
3013 gtk_item_factory_get_widget(win->menu.item_factory, |
| |
3014 N_("/Conversation/Block...")); |
| |
3015 |
| |
3016 win->menu.unblock = |
| |
3017 gtk_item_factory_get_widget(win->menu.item_factory, |
| |
3018 N_("/Conversation/Unblock...")); |
| |
3019 |
| |
3020 win->menu.add = |
| |
3021 gtk_item_factory_get_widget(win->menu.item_factory, |
| |
3022 N_("/Conversation/Add...")); |
| |
3023 |
| |
3024 win->menu.remove = |
| |
3025 gtk_item_factory_get_widget(win->menu.item_factory, |
| |
3026 N_("/Conversation/Remove...")); |
| |
3027 |
| |
3028 win->menu.logging = |
| |
3029 gtk_item_factory_get_widget(win->menu.item_factory, |
| |
3030 N_("/Options/Enable Logging")); |
| |
3031 win->menu.sounds = |
| |
3032 gtk_item_factory_get_widget(win->menu.item_factory, |
| |
3033 N_("/Options/Enable Sounds")); |
| |
3034 method = purple_prefs_get_string(PIDGIN_PREFS_ROOT "/sound/method"); |
| |
3035 if (method != NULL && !strcmp(method, "none")) |
| |
3036 { |
| |
3037 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(win->menu.sounds), |
| |
3038 FALSE); |
| |
3039 gtk_widget_set_sensitive(win->menu.sounds, FALSE); |
| |
3040 } |
| |
3041 purple_prefs_connect_callback(win, PIDGIN_PREFS_ROOT "/sound/method", |
| |
3042 sound_method_pref_changed_cb, win); |
| |
3043 |
| |
3044 win->menu.show_formatting_toolbar = |
| |
3045 gtk_item_factory_get_widget(win->menu.item_factory, |
| |
3046 N_("/Options/Show Formatting Toolbars")); |
| |
3047 win->menu.show_timestamps = |
| |
3048 gtk_item_factory_get_widget(win->menu.item_factory, |
| |
3049 N_("/Options/Show Timestamps")); |
| |
3050 win->menu.show_icon = |
| |
3051 gtk_item_factory_get_widget(win->menu.item_factory, |
| |
3052 N_("/Options/Show Buddy Icon")); |
| |
3053 if (!purple_prefs_get_bool(PIDGIN_PREFS_ROOT "/conversations/im/show_buddy_icons")) |
| |
3054 { |
| |
3055 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(win->menu.show_icon), |
| |
3056 FALSE); |
| |
3057 gtk_widget_set_sensitive(win->menu.show_icon, FALSE); |
| |
3058 } |
| |
3059 purple_prefs_connect_callback(win, PIDGIN_PREFS_ROOT "/conversations/im/show_buddy_icons", |
| |
3060 show_buddy_icons_pref_changed_cb, win); |
| |
3061 |
| |
3062 win->menu.tray = pidgin_menu_tray_new(); |
| |
3063 gtk_menu_shell_append(GTK_MENU_SHELL(win->menu.menubar), |
| |
3064 win->menu.tray); |
| |
3065 gtk_widget_show(win->menu.tray); |
| |
3066 |
| |
3067 gtk_widget_show(win->menu.menubar); |
| |
3068 |
| |
3069 return win->menu.menubar; |
| |
3070 } |
| |
3071 |
| |
3072 |
| |
3073 /************************************************************************** |
| |
3074 * Utility functions |
| |
3075 **************************************************************************/ |
| |
3076 |
| |
3077 static void |
| |
3078 got_typing_keypress(PidginConversation *gtkconv, gboolean first) |
| |
3079 { |
| |
3080 PurpleConversation *conv = gtkconv->active_conv; |
| |
3081 PurpleConvIm *im; |
| |
3082 |
| |
3083 /* |
| |
3084 * We know we got something, so we at least have to make sure we don't |
| |
3085 * send PURPLE_TYPED any time soon. |
| |
3086 */ |
| |
3087 |
| |
3088 im = PURPLE_CONV_IM(conv); |
| |
3089 |
| |
3090 purple_conv_im_stop_send_typed_timeout(im); |
| |
3091 purple_conv_im_start_send_typed_timeout(im); |
| |
3092 |
| |
3093 /* Check if we need to send another PURPLE_TYPING message */ |
| |
3094 if (first || (purple_conv_im_get_type_again(im) != 0 && |
| |
3095 time(NULL) > purple_conv_im_get_type_again(im))) |
| |
3096 { |
| |
3097 unsigned int timeout; |
| |
3098 timeout = serv_send_typing(purple_conversation_get_gc(conv), |
| |
3099 purple_conversation_get_name(conv), |
| |
3100 PURPLE_TYPING); |
| |
3101 purple_conv_im_set_type_again(im, timeout); |
| |
3102 } |
| |
3103 } |
| |
3104 |
| |
3105 static gboolean |
| |
3106 typing_animation(gpointer data) { |
| |
3107 PidginConversation *gtkconv = data; |
| |
3108 PidginWindow *gtkwin = gtkconv->win; |
| |
3109 const char *stock_id = NULL; |
| |
3110 |
| |
3111 if(gtkconv != pidgin_conv_window_get_active_gtkconv(gtkwin)) { |
| |
3112 return FALSE; |
| |
3113 } |
| |
3114 |
| |
3115 switch (rand() % 5) { |
| |
3116 case 0: |
| |
3117 stock_id = PIDGIN_STOCK_ANIMATION_TYPING0; |
| |
3118 break; |
| |
3119 case 1: |
| |
3120 stock_id = PIDGIN_STOCK_ANIMATION_TYPING1; |
| |
3121 break; |
| |
3122 case 2: |
| |
3123 stock_id = PIDGIN_STOCK_ANIMATION_TYPING2; |
| |
3124 break; |
| |
3125 case 3: |
| |
3126 stock_id = PIDGIN_STOCK_ANIMATION_TYPING3; |
| |
3127 break; |
| |
3128 case 4: |
| |
3129 stock_id = PIDGIN_STOCK_ANIMATION_TYPING4; |
| |
3130 break; |
| |
3131 } |
| |
3132 if (gtkwin->menu.typing_icon == NULL) { |
| |
3133 gtkwin->menu.typing_icon = gtk_image_new_from_stock(stock_id, GTK_ICON_SIZE_MENU); |
| |
3134 pidgin_menu_tray_append(PIDGIN_MENU_TRAY(gtkwin->menu.tray), |
| |
3135 gtkwin->menu.typing_icon, |
| |
3136 _("User is typing...")); |
| |
3137 } else { |
| |
3138 gtk_image_set_from_stock(GTK_IMAGE(gtkwin->menu.typing_icon), stock_id, GTK_ICON_SIZE_MENU); |
| |
3139 } |
| |
3140 gtk_widget_show(gtkwin->menu.typing_icon); |
| |
3141 return TRUE; |
| |
3142 } |
| |
3143 |
| |
3144 static void |
| |
3145 update_typing_icon(PidginConversation *gtkconv) |
| |
3146 { |
| |
3147 PidginWindow *gtkwin; |
| |
3148 PurpleConvIm *im = NULL; |
| |
3149 PurpleConversation *conv = gtkconv->active_conv; |
| |
3150 char *stock_id; |
| |
3151 const char *tooltip; |
| |
3152 |
| |
3153 gtkwin = gtkconv->win; |
| |
3154 |
| |
3155 if (purple_conversation_get_type(conv) == PURPLE_CONV_TYPE_IM) |
| |
3156 im = PURPLE_CONV_IM(conv); |
| |
3157 |
| |
3158 if (gtkwin->menu.typing_icon) { |
| |
3159 gtk_widget_hide(gtkwin->menu.typing_icon); |
| |
3160 } |
| |
3161 |
| |
3162 if (!im || (purple_conv_im_get_typing_state(im) == PURPLE_NOT_TYPING)) { |
| |
3163 if (gtkconv->u.im->typing_timer != 0) |
| |
3164 g_source_remove(gtkconv->u.im->typing_timer); |
| |
3165 return; |
| |
3166 } |
| |
3167 |
| |
3168 if (purple_conv_im_get_typing_state(im) == PURPLE_TYPING) { |
| |
3169 if (gtkconv->u.im->typing_timer == 0) { |
| |
3170 gtkconv->u.im->typing_timer = g_timeout_add(250, typing_animation, gtkconv); |
| |
3171 } |
| |
3172 stock_id = PIDGIN_STOCK_ANIMATION_TYPING1; |
| |
3173 tooltip = _("User is typing..."); |
| |
3174 } else { |
| |
3175 stock_id = PIDGIN_STOCK_ANIMATION_TYPING0; |
| |
3176 tooltip = _("User has typed something and stopped"); |
| |
3177 g_source_remove(gtkconv->u.im->typing_timer); |
| |
3178 gtkconv->u.im->typing_timer = 0; |
| |
3179 } |
| |
3180 |
| |
3181 if (gtkwin->menu.typing_icon == NULL) |
| |
3182 { |
| |
3183 gtkwin->menu.typing_icon = gtk_image_new_from_stock(stock_id, GTK_ICON_SIZE_MENU); |
| |
3184 pidgin_menu_tray_append(PIDGIN_MENU_TRAY(gtkwin->menu.tray), |
| |
3185 gtkwin->menu.typing_icon, |
| |
3186 tooltip); |
| |
3187 } |
| |
3188 else |
| |
3189 { |
| |
3190 gtk_image_set_from_stock(GTK_IMAGE(gtkwin->menu.typing_icon), stock_id, GTK_ICON_SIZE_MENU); |
| |
3191 pidgin_menu_tray_set_tooltip(PIDGIN_MENU_TRAY(gtkwin->menu.tray), |
| |
3192 gtkwin->menu.typing_icon, |
| |
3193 tooltip); |
| |
3194 } |
| |
3195 |
| |
3196 gtk_widget_show(gtkwin->menu.typing_icon); |
| |
3197 } |
| |
3198 |
| |
3199 static gboolean |
| |
3200 update_send_to_selection(PidginWindow *win) |
| |
3201 { |
| |
3202 PurpleAccount *account; |
| |
3203 PurpleConversation *conv; |
| |
3204 GtkWidget *menu; |
| |
3205 GList *child; |
| |
3206 PurpleBuddy *b; |
| |
3207 |
| |
3208 conv = pidgin_conv_window_get_active_conversation(win); |
| |
3209 |
| |
3210 if (conv == NULL) |
| |
3211 return FALSE; |
| |
3212 |
| |
3213 account = purple_conversation_get_account(conv); |
| |
3214 |
| |
3215 if (account == NULL) |
| |
3216 return FALSE; |
| |
3217 |
| |
3218 if (win->menu.send_to == NULL) |
| |
3219 return FALSE; |
| |
3220 |
| |
3221 if (!(b = purple_find_buddy(account, conv->name))) |
| |
3222 return FALSE; |
| |
3223 |
| |
3224 |
| |
3225 gtk_widget_show(win->menu.send_to); |
| |
3226 |
| |
3227 menu = gtk_menu_item_get_submenu( |
| |
3228 GTK_MENU_ITEM(win->menu.send_to)); |
| |
3229 |
| |
3230 for (child = gtk_container_get_children(GTK_CONTAINER(menu)); |
| |
3231 child != NULL; |
| |
3232 child = child->next) { |
| |
3233 |
| |
3234 GtkWidget *item = child->data; |
| |
3235 PurpleBuddy *item_buddy; |
| |
3236 PurpleAccount *item_account = g_object_get_data(G_OBJECT(item), "purple_account"); |
| |
3237 gchar *buddy_name = g_object_get_data(G_OBJECT(item), |
| |
3238 "purple_buddy_name"); |
| |
3239 item_buddy = purple_find_buddy(item_account, buddy_name); |
| |
3240 |
| |
3241 if (b == item_buddy) { |
| |
3242 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(item), TRUE); |
| |
3243 break; |
| |
3244 } |
| |
3245 } |
| |
3246 |
| |
3247 return FALSE; |
| |
3248 } |
| |
3249 |
| |
3250 static gboolean |
| |
3251 send_to_item_enter_notify_cb(GtkWidget *menuitem, GdkEventCrossing *event, GtkWidget *label) |
| |
3252 { |
| |
3253 gtk_widget_set_sensitive(GTK_WIDGET(label), TRUE); |
| |
3254 return FALSE; |
| |
3255 } |
| |
3256 |
| |
3257 static gboolean |
| |
3258 send_to_item_leave_notify_cb(GtkWidget *menuitem, GdkEventCrossing *event, GtkWidget *label) |
| |
3259 { |
| |
3260 gtk_widget_set_sensitive(GTK_WIDGET(label), FALSE); |
| |
3261 return FALSE; |
| |
3262 } |
| |
3263 |
| |
3264 static void |
| |
3265 create_sendto_item(GtkWidget *menu, GtkSizeGroup *sg, GSList **group, PurpleBuddy *buddy, PurpleAccount *account, const char *name) |
| |
3266 { |
| |
3267 GtkWidget *box; |
| |
3268 GtkWidget *label; |
| |
3269 GtkWidget *image; |
| |
3270 GtkWidget *menuitem; |
| |
3271 GdkPixbuf *pixbuf; |
| |
3272 gchar *text; |
| |
3273 |
| |
3274 /* Create a pixmap for the protocol icon. */ |
| |
3275 pixbuf = pidgin_create_prpl_icon(account, PIDGIN_PRPL_ICON_SMALL); |
| |
3276 |
| |
3277 /* Now convert it to GtkImage */ |
| |
3278 if (pixbuf == NULL) |
| |
3279 image = gtk_image_new(); |
| |
3280 else |
| |
3281 { |
| |
3282 image = gtk_image_new_from_pixbuf(pixbuf); |
| |
3283 g_object_unref(G_OBJECT(pixbuf)); |
| |
3284 } |
| |
3285 |
| |
3286 gtk_size_group_add_widget(sg, image); |
| |
3287 |
| |
3288 /* Make our menu item */ |
| |
3289 text = g_strdup_printf("%s (%s)", name, purple_account_get_username(account)); |
| |
3290 menuitem = gtk_radio_menu_item_new_with_label(*group, text); |
| |
3291 g_free(text); |
| |
3292 *group = gtk_radio_menu_item_get_group(GTK_RADIO_MENU_ITEM(menuitem)); |
| |
3293 |
| |
3294 /* Do some evil, see some evil, speak some evil. */ |
| |
3295 box = gtk_hbox_new(FALSE, 0); |
| |
3296 |
| |
3297 label = gtk_bin_get_child(GTK_BIN(menuitem)); |
| |
3298 g_object_ref(label); |
| |
3299 gtk_container_remove(GTK_CONTAINER(menuitem), label); |
| |
3300 |
| |
3301 gtk_box_pack_start(GTK_BOX(box), image, FALSE, FALSE, 0); |
| |
3302 gtk_box_pack_start(GTK_BOX(box), label, TRUE, TRUE, 4); |
| |
3303 |
| |
3304 if (buddy != NULL && |
| |
3305 !purple_presence_is_online(purple_buddy_get_presence(buddy)) && |
| |
3306 !purple_account_supports_offline_message(account, buddy)) |
| |
3307 { |
| |
3308 gtk_widget_set_sensitive(label, FALSE); |
| |
3309 |
| |
3310 /* Set the label sensitive when the menuitem is highlighted and |
| |
3311 * insensitive again when the mouse leaves it. This way, it |
| |
3312 * doesn't appear weird from the highlighting of the embossed |
| |
3313 * (insensitive style) text.*/ |
| |
3314 g_signal_connect(menuitem, "enter-notify-event", |
| |
3315 G_CALLBACK(send_to_item_enter_notify_cb), label); |
| |
3316 g_signal_connect(menuitem, "leave-notify-event", |
| |
3317 G_CALLBACK(send_to_item_leave_notify_cb), label); |
| |
3318 } |
| |
3319 |
| |
3320 g_object_unref(label); |
| |
3321 |
| |
3322 gtk_container_add(GTK_CONTAINER(menuitem), box); |
| |
3323 |
| |
3324 gtk_widget_show(label); |
| |
3325 gtk_widget_show(image); |
| |
3326 gtk_widget_show(box); |
| |
3327 |
| |
3328 /* Set our data and callbacks. */ |
| |
3329 g_object_set_data(G_OBJECT(menuitem), "purple_account", account); |
| |
3330 g_object_set_data_full(G_OBJECT(menuitem), "purple_buddy_name", g_strdup(name), g_free); |
| |
3331 |
| |
3332 g_signal_connect(G_OBJECT(menuitem), "activate", |
| |
3333 G_CALLBACK(menu_conv_sel_send_cb), NULL); |
| |
3334 |
| |
3335 gtk_widget_show(menuitem); |
| |
3336 gtk_menu_shell_append(GTK_MENU_SHELL(menu), menuitem); |
| |
3337 } |
| |
3338 |
| |
3339 static void |
| |
3340 generate_send_to_items(PidginWindow *win) |
| |
3341 { |
| |
3342 GtkWidget *menu; |
| |
3343 GSList *group = NULL; |
| |
3344 GtkSizeGroup *sg = gtk_size_group_new(GTK_SIZE_GROUP_HORIZONTAL); |
| |
3345 PidginConversation *gtkconv; |
| |
3346 GSList *l, *buds; |
| |
3347 |
| |
3348 g_return_if_fail(win != NULL); |
| |
3349 |
| |
3350 gtkconv = pidgin_conv_window_get_active_gtkconv(win); |
| |
3351 |
| |
3352 g_return_if_fail(gtkconv != NULL); |
| |
3353 |
| |
3354 if (win->menu.send_to != NULL) |
| |
3355 gtk_widget_destroy(win->menu.send_to); |
| |
3356 |
| |
3357 /* Build the Send To menu */ |
| |
3358 win->menu.send_to = gtk_menu_item_new_with_mnemonic(_("_Send To")); |
| |
3359 gtk_widget_show(win->menu.send_to); |
| |
3360 |
| |
3361 menu = gtk_menu_new(); |
| |
3362 gtk_menu_shell_insert(GTK_MENU_SHELL(win->menu.menubar), |
| |
3363 win->menu.send_to, 2); |
| |
3364 gtk_menu_item_set_submenu(GTK_MENU_ITEM(win->menu.send_to), menu); |
| |
3365 |
| |
3366 gtk_widget_show(menu); |
| |
3367 |
| |
3368 if (gtkconv->active_conv->type == PURPLE_CONV_TYPE_IM) { |
| |
3369 buds = purple_find_buddies(gtkconv->active_conv->account, gtkconv->active_conv->name); |
| |
3370 |
| |
3371 if (buds == NULL) |
| |
3372 { |
| |
3373 /* The user isn't on the buddy list. */ |
| |
3374 create_sendto_item(menu, sg, &group, NULL, gtkconv->active_conv->account, gtkconv->active_conv->name); |
| |
3375 } |
| |
3376 else |
| |
3377 { |
| |
3378 GList *list = NULL, *iter; |
| |
3379 for (l = buds; l != NULL; l = l->next) |
| |
3380 { |
| |
3381 PurpleBlistNode *node; |
| |
3382 |
| |
3383 node = (PurpleBlistNode *) purple_buddy_get_contact((PurpleBuddy *)l->data); |
| |
3384 |
| |
3385 for (node = node->child; node != NULL; node = node->next) |
| |
3386 { |
| |
3387 PurpleBuddy *buddy = (PurpleBuddy *)node; |
| |
3388 PurpleAccount *account; |
| |
3389 |
| |
3390 if (!PURPLE_BLIST_NODE_IS_BUDDY(node)) |
| |
3391 continue; |
| |
3392 |
| |
3393 account = purple_buddy_get_account(buddy); |
| |
3394 if (purple_account_is_connected(account)) |
| |
3395 { |
| |
3396 /* Use the PurplePresence to get unique buddies. */ |
| |
3397 PurplePresence *presence = purple_buddy_get_presence(buddy); |
| |
3398 if (!g_list_find(list, presence)) |
| |
3399 list = g_list_prepend(list, presence); |
| |
3400 } |
| |
3401 } |
| |
3402 } |
| |
3403 |
| |
3404 /* Loop over the list backwards so we get the items in the right order, |
| |
3405 * since we did a g_list_prepend() earlier. */ |
| |
3406 for (iter = g_list_last(list); iter != NULL; iter = iter->prev) |
| |
3407 { |
| |
3408 PurplePresence *pre = iter->data; |
| |
3409 PurpleBuddy *buddy = purple_presence_get_buddies(pre)->data; |
| |
3410 create_sendto_item(menu, sg, &group, buddy, |
| |
3411 purple_buddy_get_account(buddy), purple_buddy_get_name(buddy)); |
| |
3412 } |
| |
3413 g_list_free(list); |
| |
3414 g_slist_free(buds); |
| |
3415 } |
| |
3416 } |
| |
3417 |
| |
3418 g_object_unref(sg); |
| |
3419 |
| |
3420 gtk_widget_show(win->menu.send_to); |
| |
3421 /* TODO: This should never be insensitive. Possibly hidden or not. */ |
| |
3422 if (!group) |
| |
3423 gtk_widget_set_sensitive(win->menu.send_to, FALSE); |
| |
3424 update_send_to_selection(win); |
| |
3425 } |
| |
3426 |
| |
3427 static GList * |
| |
3428 generate_invite_user_names(PurpleConnection *gc) |
| |
3429 { |
| |
3430 PurpleBlistNode *gnode,*cnode,*bnode; |
| |
3431 static GList *tmp = NULL; |
| |
3432 |
| |
3433 g_list_free(tmp); |
| |
3434 |
| |
3435 tmp = g_list_append(NULL, ""); |
| |
3436 |
| |
3437 if (gc != NULL) { |
| |
3438 for(gnode = purple_get_blist()->root; gnode; gnode = gnode->next) { |
| |
3439 if(!PURPLE_BLIST_NODE_IS_GROUP(gnode)) |
| |
3440 continue; |
| |
3441 for(cnode = gnode->child; cnode; cnode = cnode->next) { |
| |
3442 if(!PURPLE_BLIST_NODE_IS_CONTACT(cnode)) |
| |
3443 continue; |
| |
3444 for(bnode = cnode->child; bnode; bnode = bnode->next) { |
| |
3445 PurpleBuddy *buddy; |
| |
3446 |
| |
3447 if(!PURPLE_BLIST_NODE_IS_BUDDY(bnode)) |
| |
3448 continue; |
| |
3449 |
| |
3450 buddy = (PurpleBuddy *)bnode; |
| |
3451 |
| |
3452 if (buddy->account == gc->account && |
| |
3453 PURPLE_BUDDY_IS_ONLINE(buddy)) |
| |
3454 tmp = g_list_insert_sorted(tmp, buddy->name, |
| |
3455 (GCompareFunc)g_utf8_collate); |
| |
3456 } |
| |
3457 } |
| |
3458 } |
| |
3459 } |
| |
3460 |
| |
3461 return tmp; |
| |
3462 } |
| |
3463 |
| |
3464 static GdkPixbuf * |
| |
3465 get_chat_buddy_status_icon(PurpleConvChat *chat, const char *name, PurpleConvChatBuddyFlags flags) |
| |
3466 { |
| |
3467 PidginConversation *gtkconv = PIDGIN_CONVERSATION(chat->conv); |
| |
3468 GdkPixbuf *pixbuf, *scale, *scale2; |
| |
3469 char *filename; |
| |
3470 const char *image = NULL; |
| |
3471 |
| |
3472 if (flags & PURPLE_CBFLAGS_FOUNDER) { |
| |
3473 image = PIDGIN_STOCK_STATUS_FOUNDER; |
| |
3474 } else if (flags & PURPLE_CBFLAGS_OP) { |
| |
3475 image = PIDGIN_STOCK_STATUS_OPERATOR; |
| |
3476 } else if (flags & PURPLE_CBFLAGS_HALFOP) { |
| |
3477 image = PIDGIN_STOCK_STATUS_HALFOP; |
| |
3478 } else if (flags & PURPLE_CBFLAGS_VOICE) { |
| |
3479 image = PIDGIN_STOCK_STATUS_VOICE; |
| |
3480 } else if ((!flags) && purple_conv_chat_is_user_ignored(chat, name)) { |
| |
3481 image = PIDGIN_STOCK_STATUS_IGNORED; |
| |
3482 } else { |
| |
3483 return NULL; |
| |
3484 } |
| |
3485 |
| |
3486 pixbuf = gtk_widget_render_icon (gtkconv->tab_cont, image, gtk_icon_size_from_name(PIDGIN_ICON_SIZE_TANGO_EXTRA_SMALL), |
| |
3487 "GtkTreeView"); |
| |
3488 |
| |
3489 if (!pixbuf) |
| |
3490 return NULL; |
| |
3491 |
| |
3492 scale = gdk_pixbuf_scale_simple(pixbuf, 16, 16, GDK_INTERP_BILINEAR); |
| |
3493 g_object_unref(pixbuf); |
| |
3494 |
| |
3495 if (flags && purple_conv_chat_is_user_ignored(chat, name)) { |
| |
3496 filename = g_build_filename(DATADIR, "pixmaps", "pidgin", "status", "default", "ignored.png", NULL); |
| |
3497 pixbuf = gdk_pixbuf_new_from_file(filename, NULL); |
| |
3498 g_free(filename); |
| |
3499 scale2 = gdk_pixbuf_scale_simple(pixbuf, 16, 16, GDK_INTERP_BILINEAR); |
| |
3500 g_object_unref(pixbuf); |
| |
3501 gdk_pixbuf_composite(scale2, scale, 0, 0, 16, 16, 0, 0, 1, 1, GDK_INTERP_BILINEAR, 192); |
| |
3502 g_object_unref(scale2); |
| |
3503 } |
| |
3504 |
| |
3505 return scale; |
| |
3506 } |
| |
3507 |
| |
3508 static void |
| |
3509 add_chat_buddy_common(PurpleConversation *conv, PurpleConvChatBuddy *cb, const char *old_name) |
| |
3510 { |
| |
3511 PidginConversation *gtkconv; |
| |
3512 PidginChatPane *gtkchat; |
| |
3513 PurpleConvChat *chat; |
| |
3514 PurpleConnection *gc; |
| |
3515 PurplePluginProtocolInfo *prpl_info; |
| |
3516 GtkListStore *ls; |
| |
3517 GdkPixbuf *pixbuf; |
| |
3518 GtkTreeIter iter; |
| |
3519 gboolean is_me = FALSE; |
| |
3520 gboolean is_buddy; |
| |
3521 gchar *tmp, *alias_key, *name, *alias; |
| |
3522 int flags; |
| |
3523 |
| |
3524 alias = cb->alias; |
| |
3525 name = cb->name; |
| |
3526 flags = GPOINTER_TO_INT(cb->flags); |
| |
3527 |
| |
3528 chat = PURPLE_CONV_CHAT(conv); |
| |
3529 gtkconv = PIDGIN_CONVERSATION(conv); |
| |
3530 gtkchat = gtkconv->u.chat; |
| |
3531 gc = purple_conversation_get_gc(conv); |
| |
3532 |
| |
3533 if (!gc || !(prpl_info = PURPLE_PLUGIN_PROTOCOL_INFO(gc->prpl))) |
| |
3534 return; |
| |
3535 |
| |
3536 ls = GTK_LIST_STORE(gtk_tree_view_get_model(GTK_TREE_VIEW(gtkchat->list))); |
| |
3537 |
| |
3538 pixbuf = get_chat_buddy_status_icon(chat, name, flags); |
| |
3539 |
| |
3540 if (!strcmp(chat->nick, purple_normalize(conv->account, old_name != NULL ? old_name : name))) |
| |
3541 is_me = TRUE; |
| |
3542 |
| |
3543 is_buddy = (purple_find_buddy(conv->account, name) != NULL); |
| |
3544 |
| |
3545 tmp = g_utf8_casefold(alias, -1); |
| |
3546 alias_key = g_utf8_collate_key(tmp, -1); |
| |
3547 g_free(tmp); |
| |
3548 |
| |
3549 if (is_me) |
| |
3550 { |
| |
3551 GdkColor send_color; |
| |
3552 gdk_color_parse(SEND_COLOR, &send_color); |
| |
3553 |
| |
3554 #if GTK_CHECK_VERSION(2,6,0) |
| |
3555 gtk_list_store_insert_with_values(ls, &iter, |
| |
3556 /* |
| |
3557 * The GTK docs are mute about the effects of the "row" value for performance. |
| |
3558 * X-Chat hardcodes their value to 0 (prepend) and -1 (append), so we will too. |
| |
3559 * It *might* be faster to search the gtk_list_store and set row accurately, |
| |
3560 * but no one in #gtk+ seems to know anything about it either. |
| |
3561 * Inserting in the "wrong" location has no visible ill effects. - F.P. |
| |
3562 */ |
| |
3563 -1, /* "row" */ |
| |
3564 CHAT_USERS_ICON_COLUMN, pixbuf, |
| |
3565 CHAT_USERS_ALIAS_COLUMN, alias, |
| |
3566 CHAT_USERS_ALIAS_KEY_COLUMN, alias_key, |
| |
3567 CHAT_USERS_NAME_COLUMN, name, |
| |
3568 CHAT_USERS_FLAGS_COLUMN, flags, |
| |
3569 CHAT_USERS_COLOR_COLUMN, &send_color, |
| |
3570 CHAT_USERS_WEIGHT_COLUMN, is_buddy ? PANGO_WEIGHT_BOLD : PANGO_WEIGHT_NORMAL, |
| |
3571 -1); |
| |
3572 } |
| |
3573 else |
| |
3574 { |
| |
3575 gtk_list_store_insert_with_values(ls, &iter, |
| |
3576 -1, /* "row" */ |
| |
3577 CHAT_USERS_ICON_COLUMN, pixbuf, |
| |
3578 CHAT_USERS_ALIAS_COLUMN, alias, |
| |
3579 CHAT_USERS_ALIAS_KEY_COLUMN, alias_key, |
| |
3580 CHAT_USERS_NAME_COLUMN, name, |
| |
3581 CHAT_USERS_FLAGS_COLUMN, flags, |
| |
3582 CHAT_USERS_COLOR_COLUMN, get_nick_color(gtkconv, name), |
| |
3583 CHAT_USERS_WEIGHT_COLUMN, is_buddy ? PANGO_WEIGHT_BOLD : PANGO_WEIGHT_NORMAL, |
| |
3584 -1); |
| |
3585 #else |
| |
3586 gtk_list_store_append(ls, &iter); |
| |
3587 gtk_list_store_set(ls, &iter, |
| |
3588 CHAT_USERS_ICON_COLUMN, pixbuf, |
| |
3589 CHAT_USERS_ALIAS_COLUMN, alias, |
| |
3590 CHAT_USERS_ALIAS_KEY_COLUMN, alias_key, |
| |
3591 CHAT_USERS_NAME_COLUMN, name, |
| |
3592 CHAT_USERS_FLAGS_COLUMN, flags, |
| |
3593 CHAT_USERS_COLOR_COLUMN, &send_color, |
| |
3594 CHAT_USERS_WEIGHT_COLUMN, is_buddy ? PANGO_WEIGHT_BOLD : PANGO_WEIGHT_NORMAL, |
| |
3595 -1); |
| |
3596 } |
| |
3597 else |
| |
3598 { |
| |
3599 gtk_list_store_append(ls, &iter); |
| |
3600 gtk_list_store_set(ls, &iter, |
| |
3601 CHAT_USERS_ICON_COLUMN, pixbuf, |
| |
3602 CHAT_USERS_ALIAS_COLUMN, alias, |
| |
3603 CHAT_USERS_ALIAS_KEY_COLUMN, alias_key, |
| |
3604 CHAT_USERS_NAME_COLUMN, name, |
| |
3605 CHAT_USERS_FLAGS_COLUMN, flags, |
| |
3606 CHAT_USERS_COLOR_COLUMN, get_nick_color(gtkconv, name), |
| |
3607 CHAT_USERS_WEIGHT_COLUMN, is_buddy ? PANGO_WEIGHT_BOLD : PANGO_WEIGHT_NORMAL, |
| |
3608 -1); |
| |
3609 #endif |
| |
3610 } |
| |
3611 |
| |
3612 if (pixbuf) |
| |
3613 g_object_unref(pixbuf); |
| |
3614 g_free(alias_key); |
| |
3615 } |
| |
3616 |
| |
3617 static void |
| |
3618 tab_complete_process_item(int *most_matched, char *entered, char **partial, char *nick_partial, |
| |
3619 GList **matches, gboolean command, char *name) |
| |
3620 { |
| |
3621 strncpy(nick_partial, name, strlen(entered)); |
| |
3622 nick_partial[strlen(entered)] = '\0'; |
| |
3623 if (purple_utf8_strcasecmp(nick_partial, entered)) |
| |
3624 return; |
| |
3625 |
| |
3626 /* if we're here, it's a possible completion */ |
| |
3627 |
| |
3628 if (*most_matched == -1) { |
| |
3629 /* |
| |
3630 * this will only get called once, since from now |
| |
3631 * on *most_matched is >= 0 |
| |
3632 */ |
| |
3633 *most_matched = strlen(name); |
| |
3634 *partial = g_strdup(name); |
| |
3635 } |
| |
3636 else if (*most_matched) { |
| |
3637 char *tmp = g_strdup(name); |
| |
3638 |
| |
3639 while (purple_utf8_strcasecmp(tmp, *partial)) { |
| |
3640 (*partial)[*most_matched] = '\0'; |
| |
3641 if (*most_matched < strlen(tmp)) |
| |
3642 tmp[*most_matched] = '\0'; |
| |
3643 (*most_matched)--; |
| |
3644 } |
| |
3645 (*most_matched)++; |
| |
3646 |
| |
3647 g_free(tmp); |
| |
3648 } |
| |
3649 |
| |
3650 *matches = g_list_insert_sorted(*matches, g_strdup(name), |
| |
3651 (GCompareFunc)purple_utf8_strcasecmp); |
| |
3652 } |
| |
3653 |
| |
3654 static gboolean |
| |
3655 tab_complete(PurpleConversation *conv) |
| |
3656 { |
| |
3657 PidginConversation *gtkconv; |
| |
3658 GtkTextIter cursor, word_start, start_buffer; |
| |
3659 int start; |
| |
3660 int most_matched = -1; |
| |
3661 char *entered, *partial = NULL; |
| |
3662 char *text; |
| |
3663 char *nick_partial; |
| |
3664 const char *prefix; |
| |
3665 GList *matches = NULL; |
| |
3666 gboolean command = FALSE; |
| |
3667 |
| |
3668 gtkconv = PIDGIN_CONVERSATION(conv); |
| |
3669 |
| |
3670 gtk_text_buffer_get_start_iter(gtkconv->entry_buffer, &start_buffer); |
| |
3671 gtk_text_buffer_get_iter_at_mark(gtkconv->entry_buffer, &cursor, |
| |
3672 gtk_text_buffer_get_insert(gtkconv->entry_buffer)); |
| |
3673 |
| |
3674 word_start = cursor; |
| |
3675 |
| |
3676 /* if there's nothing there just return */ |
| |
3677 if (!gtk_text_iter_compare(&cursor, &start_buffer)) |
| |
3678 return (purple_conversation_get_type(conv) == PURPLE_CONV_TYPE_CHAT) ? TRUE : FALSE; |
| |
3679 |
| |
3680 text = gtk_text_buffer_get_text(gtkconv->entry_buffer, &start_buffer, |
| |
3681 &cursor, FALSE); |
| |
3682 |
| |
3683 /* if we're at the end of ": " we need to move back 2 spaces */ |
| |
3684 start = strlen(text) - 1; |
| |
3685 |
| |
3686 if (strlen(text) >= 2 && !strncmp(&text[start-1], ": ", 2)) { |
| |
3687 gtk_text_iter_backward_chars(&word_start, 2); |
| |
3688 start-=2; |
| |
3689 } |
| |
3690 |
| |
3691 /* find the start of the word that we're tabbing */ |
| |
3692 while (start >= 0 && text[start] != ' ') { |
| |
3693 gtk_text_iter_backward_char(&word_start); |
| |
3694 start--; |
| |
3695 } |
| |
3696 |
| |
3697 prefix = pidgin_get_cmd_prefix(); |
| |
3698 if (start == -1 && (strlen(text) >= strlen(prefix)) && !strncmp(text, prefix, strlen(prefix))) { |
| |
3699 command = TRUE; |
| |
3700 gtk_text_iter_forward_chars(&word_start, strlen(prefix)); |
| |
3701 } |
| |
3702 |
| |
3703 g_free(text); |
| |
3704 |
| |
3705 entered = gtk_text_buffer_get_text(gtkconv->entry_buffer, &word_start, |
| |
3706 &cursor, FALSE); |
| |
3707 |
| |
3708 if (!g_utf8_strlen(entered, -1)) { |
| |
3709 g_free(entered); |
| |
3710 return (purple_conversation_get_type(conv) == PURPLE_CONV_TYPE_CHAT) ? TRUE : FALSE; |
| |
3711 } |
| |
3712 |
| |
3713 nick_partial = g_malloc(strlen(entered)+1); |
| |
3714 |
| |
3715 if (command) { |
| |
3716 GList *list = purple_cmd_list(conv); |
| |
3717 GList *l; |
| |
3718 |
| |
3719 /* Commands */ |
| |
3720 for (l = list; l != NULL; l = l->next) { |
| |
3721 tab_complete_process_item(&most_matched, entered, &partial, nick_partial, |
| |
3722 &matches, TRUE, l->data); |
| |
3723 } |
| |
3724 g_list_free(list); |
| |
3725 } else if (purple_conversation_get_type(conv) == PURPLE_CONV_TYPE_CHAT) { |
| |
3726 PurpleConvChat *chat = PURPLE_CONV_CHAT(conv); |
| |
3727 GList *l = purple_conv_chat_get_users(chat); |
| |
3728 GtkTreeModel *model = gtk_tree_view_get_model(GTK_TREE_VIEW(PIDGIN_CONVERSATION(conv)->u.chat->list)); |
| |
3729 GtkTreeIter iter; |
| |
3730 int f; |
| |
3731 |
| |
3732 /* Users */ |
| |
3733 for (; l != NULL; l = l->next) { |
| |
3734 tab_complete_process_item(&most_matched, entered, &partial, nick_partial, |
| |
3735 &matches, TRUE, ((PurpleConvChatBuddy *)l->data)->name); |
| |
3736 } |
| |
3737 |
| |
3738 |
| |
3739 /* Aliases */ |
| |
3740 if (gtk_tree_model_get_iter_first(GTK_TREE_MODEL(model), &iter)) |
| |
3741 { |
| |
3742 do { |
| |
3743 char *name; |
| |
3744 char *alias; |
| |
3745 |
| |
3746 gtk_tree_model_get(model, &iter, |
| |
3747 CHAT_USERS_NAME_COLUMN, &name, |
| |
3748 CHAT_USERS_ALIAS_COLUMN, &alias, |
| |
3749 -1); |
| |
3750 |
| |
3751 if (strcmp(name, alias)) |
| |
3752 tab_complete_process_item(&most_matched, entered, &partial, nick_partial, |
| |
3753 &matches, FALSE, alias); |
| |
3754 g_free(name); |
| |
3755 g_free(alias); |
| |
3756 |
| |
3757 f = gtk_tree_model_iter_next(model, &iter); |
| |
3758 } while (f != 0); |
| |
3759 } |
| |
3760 } else { |
| |
3761 g_free(nick_partial); |
| |
3762 g_free(entered); |
| |
3763 return FALSE; |
| |
3764 } |
| |
3765 |
| |
3766 g_free(nick_partial); |
| |
3767 |
| |
3768 /* we're only here if we're doing new style */ |
| |
3769 |
| |
3770 /* if there weren't any matches, return */ |
| |
3771 if (!matches) { |
| |
3772 /* if matches isn't set partials won't be either */ |
| |
3773 g_free(entered); |
| |
3774 return (purple_conversation_get_type(conv) == PURPLE_CONV_TYPE_CHAT) ? TRUE : FALSE; |
| |
3775 } |
| |
3776 |
| |
3777 gtk_text_buffer_delete(gtkconv->entry_buffer, &word_start, &cursor); |
| |
3778 |
| |
3779 if (!matches->next) { |
| |
3780 /* there was only one match. fill it in. */ |
| |
3781 gtk_text_buffer_get_start_iter(gtkconv->entry_buffer, &start_buffer); |
| |
3782 gtk_text_buffer_get_iter_at_mark(gtkconv->entry_buffer, &cursor, |
| |
3783 gtk_text_buffer_get_insert(gtkconv->entry_buffer)); |
| |
3784 |
| |
3785 if (!gtk_text_iter_compare(&cursor, &start_buffer)) { |
| |
3786 char *tmp = g_strdup_printf("%s: ", (char *)matches->data); |
| |
3787 gtk_text_buffer_insert_at_cursor(gtkconv->entry_buffer, tmp, -1); |
| |
3788 g_free(tmp); |
| |
3789 } |
| |
3790 else |
| |
3791 gtk_text_buffer_insert_at_cursor(gtkconv->entry_buffer, |
| |
3792 matches->data, -1); |
| |
3793 |
| |
3794 g_free(matches->data); |
| |
3795 matches = g_list_remove(matches, matches->data); |
| |
3796 } |
| |
3797 else { |
| |
3798 /* |
| |
3799 * there were lots of matches, fill in as much as possible |
| |
3800 * and display all of them |
| |
3801 */ |
| |
3802 char *addthis = g_malloc0(1); |
| |
3803 |
| |
3804 while (matches) { |
| |
3805 char *tmp = addthis; |
| |
3806 addthis = g_strconcat(tmp, matches->data, " ", NULL); |
| |
3807 g_free(tmp); |
| |
3808 g_free(matches->data); |
| |
3809 matches = g_list_remove(matches, matches->data); |
| |
3810 } |
| |
3811 |
| |
3812 purple_conversation_write(conv, NULL, addthis, PURPLE_MESSAGE_NO_LOG, |
| |
3813 time(NULL)); |
| |
3814 gtk_text_buffer_insert_at_cursor(gtkconv->entry_buffer, partial, -1); |
| |
3815 g_free(addthis); |
| |
3816 } |
| |
3817 |
| |
3818 g_free(entered); |
| |
3819 g_free(partial); |
| |
3820 |
| |
3821 return TRUE; |
| |
3822 } |
| |
3823 |
| |
3824 static void topic_callback(GtkWidget *w, PidginConversation *gtkconv) |
| |
3825 { |
| |
3826 PurplePluginProtocolInfo *prpl_info = NULL; |
| |
3827 PurpleConnection *gc; |
| |
3828 PurpleConversation *conv = gtkconv->active_conv; |
| |
3829 PidginChatPane *gtkchat; |
| |
3830 char *new_topic; |
| |
3831 const char *current_topic; |
| |
3832 |
| |
3833 gc = purple_conversation_get_gc(conv); |
| |
3834 |
| |
3835 if(!gc || !(prpl_info = PURPLE_PLUGIN_PROTOCOL_INFO(gc->prpl))) |
| |
3836 return; |
| |
3837 |
| |
3838 if(prpl_info->set_chat_topic == NULL) |
| |
3839 return; |
| |
3840 |
| |
3841 gtkconv = PIDGIN_CONVERSATION(conv); |
| |
3842 gtkchat = gtkconv->u.chat; |
| |
3843 new_topic = g_strdup(gtk_entry_get_text(GTK_ENTRY(gtkchat->topic_text))); |
| |
3844 current_topic = purple_conv_chat_get_topic(PURPLE_CONV_CHAT(conv)); |
| |
3845 |
| |
3846 if(current_topic && !g_utf8_collate(new_topic, current_topic)){ |
| |
3847 g_free(new_topic); |
| |
3848 return; |
| |
3849 } |
| |
3850 |
| |
3851 gtk_entry_set_text(GTK_ENTRY(gtkchat->topic_text), current_topic); |
| |
3852 |
| |
3853 prpl_info->set_chat_topic(gc, purple_conv_chat_get_id(PURPLE_CONV_CHAT(conv)), |
| |
3854 new_topic); |
| |
3855 |
| |
3856 g_free(new_topic); |
| |
3857 } |
| |
3858 |
| |
3859 static gint |
| |
3860 sort_chat_users(GtkTreeModel *model, GtkTreeIter *a, GtkTreeIter *b, gpointer userdata) |
| |
3861 { |
| |
3862 PurpleConvChatBuddyFlags f1 = 0, f2 = 0; |
| |
3863 char *user1 = NULL, *user2 = NULL; |
| |
3864 gboolean buddy1 = FALSE, buddy2 = FALSE; |
| |
3865 gint ret = 0; |
| |
3866 |
| |
3867 gtk_tree_model_get(model, a, |
| |
3868 CHAT_USERS_ALIAS_KEY_COLUMN, &user1, |
| |
3869 CHAT_USERS_FLAGS_COLUMN, &f1, |
| |
3870 CHAT_USERS_WEIGHT_COLUMN, &buddy1, |
| |
3871 -1); |
| |
3872 gtk_tree_model_get(model, b, |
| |
3873 CHAT_USERS_ALIAS_KEY_COLUMN, &user2, |
| |
3874 CHAT_USERS_FLAGS_COLUMN, &f2, |
| |
3875 CHAT_USERS_WEIGHT_COLUMN, &buddy2, |
| |
3876 -1); |
| |
3877 |
| |
3878 if (user1 == NULL || user2 == NULL) { |
| |
3879 if (!(user1 == NULL && user2 == NULL)) |
| |
3880 ret = (user1 == NULL) ? -1: 1; |
| |
3881 } else if (f1 != f2) { |
| |
3882 /* sort more important users first */ |
| |
3883 ret = (f1 > f2) ? -1 : 1; |
| |
3884 } else if (buddy1 != buddy2) { |
| |
3885 ret = (buddy1 > buddy2) ? -1 : 1; |
| |
3886 } else { |
| |
3887 ret = strcmp(user1, user2); |
| |
3888 } |
| |
3889 |
| |
3890 g_free(user1); |
| |
3891 g_free(user2); |
| |
3892 |
| |
3893 return ret; |
| |
3894 } |
| |
3895 |
| |
3896 static void |
| |
3897 update_chat_alias(PurpleBuddy *buddy, PurpleConversation *conv, PurpleConnection *gc, PurplePluginProtocolInfo *prpl_info) |
| |
3898 { |
| |
3899 PidginConversation *gtkconv = PIDGIN_CONVERSATION(conv); |
| |
3900 PurpleConvChat *chat = PURPLE_CONV_CHAT(conv); |
| |
3901 GtkTreeModel *model; |
| |
3902 char *normalized_name; |
| |
3903 GtkTreeIter iter; |
| |
3904 int f; |
| |
3905 |
| |
3906 g_return_if_fail(buddy != NULL); |
| |
3907 g_return_if_fail(conv != NULL); |
| |
3908 |
| |
3909 /* This is safe because this callback is only used in chats, not IMs. */ |
| |
3910 model = gtk_tree_view_get_model(GTK_TREE_VIEW(gtkconv->u.chat->list)); |
| |
3911 |
| |
3912 if (!gtk_tree_model_get_iter_first(GTK_TREE_MODEL(model), &iter)) |
| |
3913 return; |
| |
3914 |
| |
3915 normalized_name = g_strdup(purple_normalize(conv->account, buddy->name)); |
| |
3916 |
| |
3917 do { |
| |
3918 char *name; |
| |
3919 |
| |
3920 gtk_tree_model_get(model, &iter, CHAT_USERS_NAME_COLUMN, &name, -1); |
| |
3921 |
| |
3922 if (!strcmp(normalized_name, purple_normalize(conv->account, name))) { |
| |
3923 const char *alias = name; |
| |
3924 char *tmp; |
| |
3925 char *alias_key = NULL; |
| |
3926 PurpleBuddy *buddy2; |
| |
3927 |
| |
3928 if (strcmp(chat->nick, purple_normalize(conv->account, name))) { |
| |
3929 /* This user is not me, so look into updating the alias. */ |
| |
3930 |
| |
3931 if ((buddy2 = purple_find_buddy(conv->account, name)) != NULL) { |
| |
3932 alias = purple_buddy_get_contact_alias(buddy2); |
| |
3933 } |
| |
3934 |
| |
3935 tmp = g_utf8_casefold(alias, -1); |
| |
3936 alias_key = g_utf8_collate_key(tmp, -1); |
| |
3937 g_free(tmp); |
| |
3938 |
| |
3939 gtk_list_store_set(GTK_LIST_STORE(model), &iter, |
| |
3940 CHAT_USERS_ALIAS_COLUMN, alias, |
| |
3941 CHAT_USERS_ALIAS_KEY_COLUMN, alias_key, |
| |
3942 -1); |
| |
3943 g_free(alias_key); |
| |
3944 } |
| |
3945 g_free(name); |
| |
3946 break; |
| |
3947 } |
| |
3948 |
| |
3949 f = gtk_tree_model_iter_next(model, &iter); |
| |
3950 |
| |
3951 g_free(name); |
| |
3952 } while (f != 0); |
| |
3953 |
| |
3954 g_free(normalized_name); |
| |
3955 } |
| |
3956 |
| |
3957 static void |
| |
3958 blist_node_aliased_cb(PurpleBlistNode *node, const char *old_alias, PurpleConversation *conv) |
| |
3959 { |
| |
3960 PurpleConnection *gc; |
| |
3961 PurplePluginProtocolInfo *prpl_info; |
| |
3962 |
| |
3963 g_return_if_fail(node != NULL); |
| |
3964 g_return_if_fail(conv != NULL); |
| |
3965 |
| |
3966 gc = purple_conversation_get_gc(conv); |
| |
3967 g_return_if_fail(gc != NULL); |
| |
3968 g_return_if_fail(gc->prpl != NULL); |
| |
3969 prpl_info = PURPLE_PLUGIN_PROTOCOL_INFO(gc->prpl); |
| |
3970 |
| |
3971 if (prpl_info->options & OPT_PROTO_UNIQUE_CHATNAME) |
| |
3972 return; |
| |
3973 |
| |
3974 if (PURPLE_BLIST_NODE_IS_CONTACT(node)) |
| |
3975 { |
| |
3976 PurpleBlistNode *bnode; |
| |
3977 |
| |
3978 for(bnode = node->child; bnode; bnode = bnode->next) { |
| |
3979 |
| |
3980 if(!PURPLE_BLIST_NODE_IS_BUDDY(bnode)) |
| |
3981 continue; |
| |
3982 |
| |
3983 update_chat_alias((PurpleBuddy *)bnode, conv, gc, prpl_info); |
| |
3984 } |
| |
3985 } |
| |
3986 else if (PURPLE_BLIST_NODE_IS_BUDDY(node)) |
| |
3987 update_chat_alias((PurpleBuddy *)node, conv, gc, prpl_info); |
| |
3988 } |
| |
3989 |
| |
3990 static void |
| |
3991 buddy_cb_common(PurpleBuddy *buddy, PurpleConversation *conv, gboolean is_buddy) |
| |
3992 { |
| |
3993 GtkTreeModel *model; |
| |
3994 char *normalized_name; |
| |
3995 GtkTreeIter iter; |
| |
3996 int f; |
| |
3997 |
| |
3998 g_return_if_fail(buddy != NULL); |
| |
3999 g_return_if_fail(conv != NULL); |
| |
4000 |
| |
4001 /* Do nothing if the buddy does not belong to the conv's account */ |
| |
4002 if (purple_buddy_get_account(buddy) != purple_conversation_get_account(conv)) |
| |
4003 return; |
| |
4004 |
| |
4005 /* This is safe because this callback is only used in chats, not IMs. */ |
| |
4006 model = gtk_tree_view_get_model(GTK_TREE_VIEW(PIDGIN_CONVERSATION(conv)->u.chat->list)); |
| |
4007 |
| |
4008 if (!gtk_tree_model_get_iter_first(GTK_TREE_MODEL(model), &iter)) |
| |
4009 return; |
| |
4010 |
| |
4011 normalized_name = g_strdup(purple_normalize(conv->account, buddy->name)); |
| |
4012 |
| |
4013 do { |
| |
4014 char *name; |
| |
4015 |
| |
4016 gtk_tree_model_get(model, &iter, CHAT_USERS_NAME_COLUMN, &name, -1); |
| |
4017 |
| |
4018 if (!strcmp(normalized_name, purple_normalize(conv->account, name))) { |
| |
4019 gtk_list_store_set(GTK_LIST_STORE(model), &iter, |
| |
4020 CHAT_USERS_WEIGHT_COLUMN, is_buddy ? PANGO_WEIGHT_BOLD : PANGO_WEIGHT_NORMAL, -1); |
| |
4021 g_free(name); |
| |
4022 break; |
| |
4023 } |
| |
4024 |
| |
4025 f = gtk_tree_model_iter_next(model, &iter); |
| |
4026 |
| |
4027 g_free(name); |
| |
4028 } while (f != 0); |
| |
4029 |
| |
4030 g_free(normalized_name); |
| |
4031 |
| |
4032 blist_node_aliased_cb((PurpleBlistNode *)buddy, NULL, conv); |
| |
4033 } |
| |
4034 |
| |
4035 static void |
| |
4036 buddy_added_cb(PurpleBuddy *buddy, PurpleConversation *conv) |
| |
4037 { |
| |
4038 buddy_cb_common(buddy, conv, TRUE); |
| |
4039 } |
| |
4040 |
| |
4041 static void |
| |
4042 buddy_removed_cb(PurpleBuddy *buddy, PurpleConversation *conv) |
| |
4043 { |
| |
4044 /* If there's another buddy for the same "dude" on the list, do nothing. */ |
| |
4045 if (purple_find_buddy(buddy->account, buddy->name) != NULL) |
| |
4046 return; |
| |
4047 |
| |
4048 buddy_cb_common(buddy, conv, FALSE); |
| |
4049 } |
| |
4050 |
| |
4051 static void send_menu_cb(GtkWidget *widget, PidginConversation *gtkconv) |
| |
4052 { |
| |
4053 g_signal_emit_by_name(gtkconv->entry, "message_send"); |
| |
4054 } |
| |
4055 |
| |
4056 static void |
| |
4057 entry_popup_menu_cb(GtkIMHtml *imhtml, GtkMenu *menu, gpointer data) |
| |
4058 { |
| |
4059 GtkWidget *menuitem; |
| |
4060 PidginConversation *gtkconv = data; |
| |
4061 |
| |
4062 g_return_if_fail(menu != NULL); |
| |
4063 g_return_if_fail(gtkconv != NULL); |
| |
4064 |
| |
4065 menuitem = pidgin_new_item_from_stock(NULL, _("_Send"), NULL, |
| |
4066 G_CALLBACK(send_menu_cb), gtkconv, |
| |
4067 0, 0, NULL); |
| |
4068 if (gtk_text_buffer_get_char_count(imhtml->text_buffer) == 0) |
| |
4069 gtk_widget_set_sensitive(menuitem, FALSE); |
| |
4070 gtk_menu_shell_insert(GTK_MENU_SHELL(menu), menuitem, 0); |
| |
4071 |
| |
4072 menuitem = gtk_separator_menu_item_new(); |
| |
4073 gtk_widget_show(menuitem); |
| |
4074 gtk_menu_shell_insert(GTK_MENU_SHELL(menu), menuitem, 1); |
| |
4075 } |
| |
4076 |
| |
4077 |
| |
4078 static void resize_imhtml_cb(PidginConversation *gtkconv) |
| |
4079 { |
| |
4080 GtkTextBuffer *buffer; |
| |
4081 GtkTextIter iter; |
| |
4082 int wrapped_lines; |
| |
4083 int lines; |
| |
4084 GdkRectangle oneline; |
| |
4085 GtkRequisition sr; |
| |
4086 int height; |
| |
4087 int pad_top, pad_inside, pad_bottom; |
| |
4088 |
| |
4089 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(gtkconv->entry)); |
| |
4090 |
| |
4091 wrapped_lines = 1; |
| |
4092 gtk_text_buffer_get_start_iter(buffer, &iter); |
| |
4093 gtk_text_view_get_iter_location(GTK_TEXT_VIEW(gtkconv->entry), &iter, &oneline); |
| |
4094 while (gtk_text_view_forward_display_line(GTK_TEXT_VIEW(gtkconv->entry), &iter)) |
| |
4095 wrapped_lines++; |
| |
4096 |
| |
4097 lines = gtk_text_buffer_get_line_count(buffer); |
| |
4098 |
| |
4099 /* Show a maximum of 4 lines */ |
| |
4100 lines = MIN(lines, 4); |
| |
4101 wrapped_lines = MIN(wrapped_lines, 4); |
| |
4102 |
| |
4103 pad_top = gtk_text_view_get_pixels_above_lines(GTK_TEXT_VIEW(gtkconv->entry)); |
| |
4104 pad_bottom = gtk_text_view_get_pixels_below_lines(GTK_TEXT_VIEW(gtkconv->entry)); |
| |
4105 pad_inside = gtk_text_view_get_pixels_inside_wrap(GTK_TEXT_VIEW(gtkconv->entry)); |
| |
4106 |
| |
4107 height = (oneline.height + pad_top + pad_bottom) * lines; |
| |
4108 height += (oneline.height + pad_inside) * (wrapped_lines - lines); |
| |
4109 |
| |
4110 gtk_widget_size_request(gtkconv->lower_hbox, &sr); |
| |
4111 if (sr.height < height + PIDGIN_HIG_BOX_SPACE) { |
| |
4112 gtkconv->auto_resize = TRUE; |
| |
4113 gtkconv->entry_growing = TRUE; |
| |
4114 gtk_widget_set_size_request(gtkconv->lower_hbox, -1, height + PIDGIN_HIG_BOX_SPACE); |
| |
4115 g_idle_add(reset_auto_resize_cb, gtkconv); |
| |
4116 } |
| |
4117 } |
| |
4118 |
| |
4119 static GtkWidget * |
| |
4120 setup_chat_pane(PidginConversation *gtkconv) |
| |
4121 { |
| |
4122 PurplePluginProtocolInfo *prpl_info; |
| |
4123 PurpleConversation *conv = gtkconv->active_conv; |
| |
4124 PidginChatPane *gtkchat; |
| |
4125 PurpleConnection *gc; |
| |
4126 GtkWidget *vpaned, *hpaned; |
| |
4127 GtkWidget *vbox, *hbox, *frame; |
| |
4128 GtkWidget *imhtml_sw; |
| |
4129 GtkPolicyType imhtml_sw_hscroll; |
| |
4130 GtkWidget *lbox; |
| |
4131 GtkWidget *label; |
| |
4132 GtkWidget *list; |
| |
4133 GtkWidget *sw; |
| |
4134 GtkListStore *ls; |
| |
4135 GtkCellRenderer *rend; |
| |
4136 GtkTreeViewColumn *col; |
| |
4137 void *blist_handle = purple_blist_get_handle(); |
| |
4138 GList *focus_chain = NULL; |
| |
4139 |
| |
4140 gtkchat = gtkconv->u.chat; |
| |
4141 gc = purple_conversation_get_gc(conv); |
| |
4142 g_return_val_if_fail(gc != NULL, NULL); |
| |
4143 g_return_val_if_fail(gc->prpl != NULL, NULL); |
| |
4144 prpl_info = PURPLE_PLUGIN_PROTOCOL_INFO(gc->prpl); |
| |
4145 |
| |
4146 /* Setup the outer pane. */ |
| |
4147 vpaned = gtk_vpaned_new(); |
| |
4148 gtk_widget_show(vpaned); |
| |
4149 |
| |
4150 /* Setup the top part of the pane. */ |
| |
4151 vbox = gtk_vbox_new(FALSE, PIDGIN_HIG_BOX_SPACE); |
| |
4152 gtk_paned_pack1(GTK_PANED(vpaned), vbox, TRUE, TRUE); |
| |
4153 gtk_widget_show(vbox); |
| |
4154 |
| |
4155 if (prpl_info->options & OPT_PROTO_CHAT_TOPIC) |
| |
4156 { |
| |
4157 hbox = gtk_hbox_new(FALSE, PIDGIN_HIG_BOX_SPACE); |
| |
4158 gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, FALSE, 0); |
| |
4159 gtk_widget_show(hbox); |
| |
4160 |
| |
4161 label = gtk_label_new(_("Topic:")); |
| |
4162 gtk_box_pack_start(GTK_BOX(hbox), label, FALSE, FALSE, 0); |
| |
4163 gtk_widget_show(label); |
| |
4164 |
| |
4165 gtkchat->topic_text = gtk_entry_new(); |
| |
4166 |
| |
4167 if(prpl_info->set_chat_topic == NULL) { |
| |
4168 gtk_editable_set_editable(GTK_EDITABLE(gtkchat->topic_text), FALSE); |
| |
4169 } else { |
| |
4170 g_signal_connect(GTK_OBJECT(gtkchat->topic_text), "activate", |
| |
4171 G_CALLBACK(topic_callback), gtkconv); |
| |
4172 } |
| |
4173 |
| |
4174 gtk_box_pack_start(GTK_BOX(hbox), gtkchat->topic_text, TRUE, TRUE, 0); |
| |
4175 gtk_widget_show(gtkchat->topic_text); |
| |
4176 } |
| |
4177 |
| |
4178 /* Setup the horizontal pane. */ |
| |
4179 hpaned = gtk_hpaned_new(); |
| |
4180 gtk_box_pack_start(GTK_BOX(vbox), hpaned, TRUE, TRUE, 0); |
| |
4181 gtk_widget_show(hpaned); |
| |
4182 |
| |
4183 /* Setup gtkihmtml. */ |
| |
4184 frame = pidgin_create_imhtml(FALSE, >kconv->imhtml, NULL, &imhtml_sw); |
| |
4185 gtk_widget_set_name(gtkconv->imhtml, "pidgin_conv_imhtml"); |
| |
4186 gtk_imhtml_show_comments(GTK_IMHTML(gtkconv->imhtml), TRUE); |
| |
4187 gtk_paned_pack1(GTK_PANED(hpaned), frame, TRUE, TRUE); |
| |
4188 gtk_widget_show(frame); |
| |
4189 gtk_scrolled_window_get_policy(GTK_SCROLLED_WINDOW(imhtml_sw), |
| |
4190 &imhtml_sw_hscroll, NULL); |
| |
4191 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(imhtml_sw), |
| |
4192 imhtml_sw_hscroll, GTK_POLICY_ALWAYS); |
| |
4193 |
| |
4194 gtk_widget_set_size_request(gtkconv->imhtml, |
| |
4195 purple_prefs_get_int(PIDGIN_PREFS_ROOT "/conversations/chat/default_width"), |
| |
4196 purple_prefs_get_int(PIDGIN_PREFS_ROOT "/conversations/chat/default_height")); |
| |
4197 g_signal_connect(G_OBJECT(gtkconv->imhtml), "size-allocate", |
| |
4198 G_CALLBACK(size_allocate_cb), gtkconv); |
| |
4199 |
| |
4200 g_signal_connect_after(G_OBJECT(gtkconv->imhtml), "button_press_event", |
| |
4201 G_CALLBACK(entry_stop_rclick_cb), NULL); |
| |
4202 g_signal_connect(G_OBJECT(gtkconv->imhtml), "key_press_event", |
| |
4203 G_CALLBACK(refocus_entry_cb), gtkconv); |
| |
4204 g_signal_connect(G_OBJECT(gtkconv->imhtml), "key_release_event", |
| |
4205 G_CALLBACK(refocus_entry_cb), gtkconv); |
| |
4206 |
| |
4207 /* Build the right pane. */ |
| |
4208 lbox = gtk_vbox_new(FALSE, PIDGIN_HIG_BOX_SPACE); |
| |
4209 gtk_paned_pack2(GTK_PANED(hpaned), lbox, FALSE, TRUE); |
| |
4210 gtk_widget_show(lbox); |
| |
4211 |
| |
4212 /* Setup the label telling how many people are in the room. */ |
| |
4213 gtkchat->count = gtk_label_new(_("0 people in room")); |
| |
4214 gtk_box_pack_start(GTK_BOX(lbox), gtkchat->count, FALSE, FALSE, 0); |
| |
4215 gtk_widget_show(gtkchat->count); |
| |
4216 |
| |
4217 /* Setup the list of users. */ |
| |
4218 sw = gtk_scrolled_window_new(NULL, NULL); |
| |
4219 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(sw), |
| |
4220 GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC); |
| |
4221 gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(sw), GTK_SHADOW_IN); |
| |
4222 gtk_box_pack_start(GTK_BOX(lbox), sw, TRUE, TRUE, 0); |
| |
4223 gtk_widget_show(sw); |
| |
4224 |
| |
4225 ls = gtk_list_store_new(CHAT_USERS_COLUMNS, GDK_TYPE_PIXBUF, G_TYPE_STRING, |
| |
4226 G_TYPE_STRING, G_TYPE_STRING, G_TYPE_INT, |
| |
4227 GDK_TYPE_COLOR, G_TYPE_INT); |
| |
4228 gtk_tree_sortable_set_sort_func(GTK_TREE_SORTABLE(ls), CHAT_USERS_ALIAS_KEY_COLUMN, |
| |
4229 sort_chat_users, NULL, NULL); |
| |
4230 |
| |
4231 list = gtk_tree_view_new_with_model(GTK_TREE_MODEL(ls)); |
| |
4232 |
| |
4233 rend = gtk_cell_renderer_pixbuf_new(); |
| |
4234 |
| |
4235 col = gtk_tree_view_column_new_with_attributes(NULL, rend, |
| |
4236 "pixbuf", CHAT_USERS_ICON_COLUMN, NULL); |
| |
4237 gtk_tree_view_column_set_sizing(col, GTK_TREE_VIEW_COLUMN_AUTOSIZE); |
| |
4238 gtk_tree_view_append_column(GTK_TREE_VIEW(list), col); |
| |
4239 gtk_widget_set_size_request(lbox, |
| |
4240 purple_prefs_get_int(PIDGIN_PREFS_ROOT "/conversations/chat/userlist_width"), -1); |
| |
4241 |
| |
4242 g_signal_connect(G_OBJECT(list), "button_press_event", |
| |
4243 G_CALLBACK(right_click_chat_cb), gtkconv); |
| |
4244 g_signal_connect(G_OBJECT(list), "popup-menu", |
| |
4245 G_CALLBACK(gtkconv_chat_popup_menu_cb), gtkconv); |
| |
4246 g_signal_connect(G_OBJECT(lbox), "size-allocate", G_CALLBACK(lbox_size_allocate_cb), gtkconv); |
| |
4247 |
| |
4248 |
| |
4249 rend = gtk_cell_renderer_text_new(); |
| |
4250 |
| |
4251 g_object_set(rend, |
| |
4252 "foreground-set", TRUE, |
| |
4253 "weight-set", TRUE, |
| |
4254 NULL); |
| |
4255 col = gtk_tree_view_column_new_with_attributes(NULL, rend, |
| |
4256 "text", CHAT_USERS_ALIAS_COLUMN, |
| |
4257 "foreground-gdk", CHAT_USERS_COLOR_COLUMN, |
| |
4258 "weight", CHAT_USERS_WEIGHT_COLUMN, |
| |
4259 NULL); |
| |
4260 |
| |
4261 purple_signal_connect(blist_handle, "buddy-added", |
| |
4262 gtkchat, PURPLE_CALLBACK(buddy_added_cb), conv); |
| |
4263 purple_signal_connect(blist_handle, "buddy-removed", |
| |
4264 gtkchat, PURPLE_CALLBACK(buddy_removed_cb), conv); |
| |
4265 purple_signal_connect(blist_handle, "blist-node-aliased", |
| |
4266 gtkchat, PURPLE_CALLBACK(blist_node_aliased_cb), conv); |
| |
4267 |
| |
4268 #if GTK_CHECK_VERSION(2,6,0) |
| |
4269 gtk_tree_view_column_set_expand(col, TRUE); |
| |
4270 g_object_set(rend, "ellipsize", PANGO_ELLIPSIZE_END, NULL); |
| |
4271 #endif |
| |
4272 |
| |
4273 gtk_tree_view_append_column(GTK_TREE_VIEW(list), col); |
| |
4274 |
| |
4275 gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(list), FALSE); |
| |
4276 gtk_widget_show(list); |
| |
4277 |
| |
4278 gtkchat->list = list; |
| |
4279 |
| |
4280 gtk_container_add(GTK_CONTAINER(sw), list); |
| |
4281 |
| |
4282 /* Setup the bottom half of the conversation window */ |
| |
4283 vbox = gtk_vbox_new(FALSE, PIDGIN_HIG_BOX_SPACE); |
| |
4284 gtk_paned_pack2(GTK_PANED(vpaned), vbox, FALSE, TRUE); |
| |
4285 gtk_widget_show(vbox); |
| |
4286 |
| |
4287 gtkconv->lower_hbox = gtk_hbox_new(FALSE, PIDGIN_HIG_BOX_SPACE); |
| |
4288 gtk_box_pack_start(GTK_BOX(vbox), gtkconv->lower_hbox, TRUE, TRUE, 0); |
| |
4289 gtk_widget_show(gtkconv->lower_hbox); |
| |
4290 |
| |
4291 vbox = gtk_vbox_new(FALSE, PIDGIN_HIG_BOX_SPACE); |
| |
4292 gtk_box_pack_end(GTK_BOX(gtkconv->lower_hbox), vbox, TRUE, TRUE, 0); |
| |
4293 gtk_widget_show(vbox); |
| |
4294 |
| |
4295 /* Setup the toolbar, entry widget and all signals */ |
| |
4296 frame = pidgin_create_imhtml(TRUE, >kconv->entry, >kconv->toolbar, NULL); |
| |
4297 gtk_box_pack_start(GTK_BOX(vbox), frame, TRUE, TRUE, 0); |
| |
4298 gtk_widget_show(frame); |
| |
4299 |
| |
4300 g_signal_connect(G_OBJECT(gtkconv->entry), "populate-popup", |
| |
4301 G_CALLBACK(entry_popup_menu_cb), gtkconv); |
| |
4302 |
| |
4303 gtk_widget_set_name(gtkconv->entry, "pidgin_conv_entry"); |
| |
4304 gtk_imhtml_set_protocol_name(GTK_IMHTML(gtkconv->entry), |
| |
4305 purple_account_get_protocol_name(conv->account)); |
| |
4306 gtk_widget_set_size_request(gtkconv->lower_hbox, -1, |
| |
4307 purple_prefs_get_int(PIDGIN_PREFS_ROOT "/conversations/chat/entry_height")); |
| |
4308 gtkconv->entry_buffer = |
| |
4309 gtk_text_view_get_buffer(GTK_TEXT_VIEW(gtkconv->entry)); |
| |
4310 g_object_set_data(G_OBJECT(gtkconv->entry_buffer), "user_data", gtkconv); |
| |
4311 g_signal_connect_swapped(G_OBJECT(gtkconv->entry_buffer), "changed", |
| |
4312 G_CALLBACK(resize_imhtml_cb), gtkconv); |
| |
4313 |
| |
4314 g_signal_connect(G_OBJECT(gtkconv->entry), "key_press_event", |
| |
4315 G_CALLBACK(entry_key_press_cb), gtkconv); |
| |
4316 g_signal_connect_after(G_OBJECT(gtkconv->entry), "message_send", |
| |
4317 G_CALLBACK(send_cb), gtkconv); |
| |
4318 g_signal_connect_after(G_OBJECT(gtkconv->entry), "button_press_event", |
| |
4319 G_CALLBACK(entry_stop_rclick_cb), NULL); |
| |
4320 g_signal_connect(G_OBJECT(gtkconv->lower_hbox), "size-allocate", |
| |
4321 G_CALLBACK(size_allocate_cb), gtkconv); |
| |
4322 |
| |
4323 default_formatize(gtkconv); |
| |
4324 |
| |
4325 /* |
| |
4326 * Focus for chat windows should be as follows: |
| |
4327 * Tab title -> chat topic -> conversation scrollback -> user list -> |
| |
4328 * user list buttons -> entry -> buttons at bottom |
| |
4329 */ |
| |
4330 focus_chain = g_list_prepend(focus_chain, gtkconv->entry); |
| |
4331 gtk_container_set_focus_chain(GTK_CONTAINER(vbox), focus_chain); |
| |
4332 |
| |
4333 return vpaned; |
| |
4334 } |
| |
4335 |
| |
4336 static GtkWidget * |
| |
4337 setup_im_pane(PidginConversation *gtkconv) |
| |
4338 { |
| |
4339 PurpleConversation *conv = gtkconv->active_conv; |
| |
4340 GtkWidget *frame; |
| |
4341 GtkWidget *imhtml_sw; |
| |
4342 GtkPolicyType imhtml_sw_hscroll; |
| |
4343 GtkWidget *paned; |
| |
4344 GtkWidget *vbox; |
| |
4345 GtkWidget *vbox2; |
| |
4346 GList *focus_chain = NULL; |
| |
4347 |
| |
4348 /* Setup the outer pane */ |
| |
4349 paned = gtk_vpaned_new(); |
| |
4350 gtk_widget_show(paned); |
| |
4351 |
| |
4352 /* Setup the top part of the pane */ |
| |
4353 vbox = gtk_vbox_new(FALSE, PIDGIN_HIG_BOX_SPACE); |
| |
4354 gtk_paned_pack1(GTK_PANED(paned), vbox, TRUE, TRUE); |
| |
4355 gtk_widget_show(vbox); |
| |
4356 |
| |
4357 /* Setup the gtkimhtml widget */ |
| |
4358 frame = pidgin_create_imhtml(FALSE, >kconv->imhtml, NULL, &imhtml_sw); |
| |
4359 gtk_widget_set_name(gtkconv->imhtml, "pidgin_conv_imhtml"); |
| |
4360 gtk_imhtml_show_comments(GTK_IMHTML(gtkconv->imhtml),TRUE); |
| |
4361 gtk_box_pack_start(GTK_BOX(vbox), frame, TRUE, TRUE, 0); |
| |
4362 gtk_widget_show(frame); |
| |
4363 gtk_scrolled_window_get_policy(GTK_SCROLLED_WINDOW(imhtml_sw), |
| |
4364 &imhtml_sw_hscroll, NULL); |
| |
4365 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(imhtml_sw), |
| |
4366 imhtml_sw_hscroll, GTK_POLICY_ALWAYS); |
| |
4367 |
| |
4368 gtk_widget_set_size_request(gtkconv->imhtml, |
| |
4369 purple_prefs_get_int(PIDGIN_PREFS_ROOT "/conversations/im/default_width"), |
| |
4370 purple_prefs_get_int(PIDGIN_PREFS_ROOT "/conversations/im/default_height")); |
| |
4371 g_signal_connect(G_OBJECT(gtkconv->imhtml), "size-allocate", |
| |
4372 G_CALLBACK(size_allocate_cb), gtkconv); |
| |
4373 |
| |
4374 g_signal_connect_after(G_OBJECT(gtkconv->imhtml), "button_press_event", |
| |
4375 G_CALLBACK(entry_stop_rclick_cb), NULL); |
| |
4376 g_signal_connect(G_OBJECT(gtkconv->imhtml), "key_press_event", |
| |
4377 G_CALLBACK(refocus_entry_cb), gtkconv); |
| |
4378 g_signal_connect(G_OBJECT(gtkconv->imhtml), "key_release_event", |
| |
4379 G_CALLBACK(refocus_entry_cb), gtkconv); |
| |
4380 |
| |
4381 /* Setup the bottom half of the conversation window */ |
| |
4382 vbox2 = gtk_vbox_new(FALSE, PIDGIN_HIG_BOX_SPACE); |
| |
4383 gtk_paned_pack2(GTK_PANED(paned), vbox2, FALSE, TRUE); |
| |
4384 gtk_widget_show(vbox2); |
| |
4385 |
| |
4386 gtkconv->lower_hbox = gtk_hbox_new(FALSE, PIDGIN_HIG_BOX_SPACE); |
| |
4387 gtk_box_pack_start(GTK_BOX(vbox2), gtkconv->lower_hbox, TRUE, TRUE, 0); |
| |
4388 gtk_widget_show(gtkconv->lower_hbox); |
| |
4389 |
| |
4390 vbox2 = gtk_vbox_new(FALSE, PIDGIN_HIG_BOX_SPACE); |
| |
4391 gtk_box_pack_end(GTK_BOX(gtkconv->lower_hbox), vbox2, TRUE, TRUE, 0); |
| |
4392 gtk_widget_show(vbox2); |
| |
4393 |
| |
4394 /* Setup the toolbar, entry widget and all signals */ |
| |
4395 frame = pidgin_create_imhtml(TRUE, >kconv->entry, >kconv->toolbar, NULL); |
| |
4396 gtk_box_pack_start(GTK_BOX(vbox2), frame, TRUE, TRUE, 0); |
| |
4397 gtk_widget_show(frame); |
| |
4398 |
| |
4399 g_signal_connect(G_OBJECT(gtkconv->entry), "populate-popup", |
| |
4400 G_CALLBACK(entry_popup_menu_cb), gtkconv); |
| |
4401 |
| |
4402 gtk_widget_set_name(gtkconv->entry, "pidgin_conv_entry"); |
| |
4403 gtk_imhtml_set_protocol_name(GTK_IMHTML(gtkconv->entry), |
| |
4404 purple_account_get_protocol_name(conv->account)); |
| |
4405 gtk_widget_set_size_request(gtkconv->lower_hbox, -1, |
| |
4406 purple_prefs_get_int(PIDGIN_PREFS_ROOT "/conversations/im/entry_height")); |
| |
4407 gtkconv->entry_buffer = |
| |
4408 gtk_text_view_get_buffer(GTK_TEXT_VIEW(gtkconv->entry)); |
| |
4409 g_object_set_data(G_OBJECT(gtkconv->entry_buffer), "user_data", gtkconv); |
| |
4410 |
| |
4411 g_signal_connect(G_OBJECT(gtkconv->entry), "key_press_event", |
| |
4412 G_CALLBACK(entry_key_press_cb), gtkconv); |
| |
4413 g_signal_connect_after(G_OBJECT(gtkconv->entry), "message_send", |
| |
4414 G_CALLBACK(send_cb), gtkconv); |
| |
4415 g_signal_connect_after(G_OBJECT(gtkconv->entry), "button_press_event", |
| |
4416 G_CALLBACK(entry_stop_rclick_cb), NULL); |
| |
4417 g_signal_connect(G_OBJECT(gtkconv->lower_hbox), "size-allocate", |
| |
4418 G_CALLBACK(size_allocate_cb), gtkconv); |
| |
4419 |
| |
4420 g_signal_connect(G_OBJECT(gtkconv->entry_buffer), "insert_text", |
| |
4421 G_CALLBACK(insert_text_cb), gtkconv); |
| |
4422 g_signal_connect(G_OBJECT(gtkconv->entry_buffer), "delete_range", |
| |
4423 G_CALLBACK(delete_text_cb), gtkconv); |
| |
4424 g_signal_connect_swapped(G_OBJECT(gtkconv->entry_buffer), "changed", |
| |
4425 G_CALLBACK(resize_imhtml_cb), gtkconv); |
| |
4426 |
| |
4427 /* had to move this after the imtoolbar is attached so that the |
| |
4428 * signals get fired to toggle the buttons on the toolbar as well. |
| |
4429 */ |
| |
4430 default_formatize(gtkconv); |
| |
4431 |
| |
4432 g_signal_connect_after(G_OBJECT(gtkconv->entry), "format_function_clear", |
| |
4433 G_CALLBACK(clear_formatting_cb), gtkconv); |
| |
4434 |
| |
4435 gtkconv->u.im->animate = purple_prefs_get_bool(PIDGIN_PREFS_ROOT "/conversations/im/animate_buddy_icons"); |
| |
4436 gtkconv->u.im->show_icon = TRUE; |
| |
4437 |
| |
4438 /* |
| |
4439 * Focus for IM windows should be as follows: |
| |
4440 * Tab title -> conversation scrollback -> entry |
| |
4441 */ |
| |
4442 focus_chain = g_list_prepend(focus_chain, gtkconv->entry); |
| |
4443 gtk_container_set_focus_chain(GTK_CONTAINER(vbox2), focus_chain); |
| |
4444 |
| |
4445 gtkconv->u.im->typing_timer = 0; |
| |
4446 return paned; |
| |
4447 } |
| |
4448 |
| |
4449 static void |
| |
4450 conv_dnd_recv(GtkWidget *widget, GdkDragContext *dc, guint x, guint y, |
| |
4451 GtkSelectionData *sd, guint info, guint t, |
| |
4452 PidginConversation *gtkconv) |
| |
4453 { |
| |
4454 PurpleConversation *conv = gtkconv->active_conv; |
| |
4455 PidginWindow *win = gtkconv->win; |
| |
4456 PurpleConversation *c; |
| |
4457 if (sd->target == gdk_atom_intern("PURPLE_BLIST_NODE", FALSE)) |
| |
4458 { |
| |
4459 PurpleBlistNode *n = NULL; |
| |
4460 PurpleBuddy *b; |
| |
4461 PidginConversation *gtkconv = NULL; |
| |
4462 |
| |
4463 n = *(PurpleBlistNode **)sd->data; |
| |
4464 |
| |
4465 if (PURPLE_BLIST_NODE_IS_CONTACT(n)) |
| |
4466 b = purple_contact_get_priority_buddy((PurpleContact*)n); |
| |
4467 else if (PURPLE_BLIST_NODE_IS_BUDDY(n)) |
| |
4468 b = (PurpleBuddy*)n; |
| |
4469 else |
| |
4470 return; |
| |
4471 |
| |
4472 /* |
| |
4473 * If we already have an open conversation with this buddy, then |
| |
4474 * just move the conv to this window. Otherwise, create a new |
| |
4475 * conv and add it to this window. |
| |
4476 */ |
| |
4477 c = purple_find_conversation_with_account(PURPLE_CONV_TYPE_IM, b->name, b->account); |
| |
4478 if (c != NULL) { |
| |
4479 PidginWindow *oldwin; |
| |
4480 gtkconv = PIDGIN_CONVERSATION(c); |
| |
4481 oldwin = gtkconv->win; |
| |
4482 if (oldwin != win) { |
| |
4483 pidgin_conv_window_remove_gtkconv(oldwin, gtkconv); |
| |
4484 pidgin_conv_window_add_gtkconv(win, gtkconv); |
| |
4485 } |
| |
4486 } else { |
| |
4487 c = purple_conversation_new(PURPLE_CONV_TYPE_IM, b->account, b->name); |
| |
4488 gtkconv = PIDGIN_CONVERSATION(c); |
| |
4489 if (gtkconv->win != win) |
| |
4490 { |
| |
4491 pidgin_conv_window_remove_gtkconv(gtkconv->win, gtkconv); |
| |
4492 pidgin_conv_window_add_gtkconv(win, gtkconv); |
| |
4493 } |
| |
4494 } |
| |
4495 |
| |
4496 /* Make this conversation the active conversation */ |
| |
4497 pidgin_conv_window_switch_gtkconv(win, gtkconv); |
| |
4498 |
| |
4499 gtk_drag_finish(dc, TRUE, (dc->action == GDK_ACTION_MOVE), t); |
| |
4500 } |
| |
4501 else if (sd->target == gdk_atom_intern("application/x-im-contact", FALSE)) |
| |
4502 { |
| |
4503 char *protocol = NULL; |
| |
4504 char *username = NULL; |
| |
4505 PurpleAccount *account; |
| |
4506 PidginConversation *gtkconv; |
| |
4507 |
| |
4508 if (pidgin_parse_x_im_contact((const char *)sd->data, FALSE, &account, |
| |
4509 &protocol, &username, NULL)) |
| |
4510 { |
| |
4511 if (account == NULL) |
| |
4512 { |
| |
4513 purple_notify_error(win, NULL, |
| |
4514 _("You are not currently signed on with an account that " |
| |
4515 "can add that buddy."), NULL); |
| |
4516 } |
| |
4517 else |
| |
4518 { |
| |
4519 c = purple_conversation_new(PURPLE_CONV_TYPE_IM, account, username); |
| |
4520 gtkconv = PIDGIN_CONVERSATION(c); |
| |
4521 if (gtkconv->win != win) |
| |
4522 { |
| |
4523 pidgin_conv_window_remove_gtkconv(gtkconv->win, gtkconv); |
| |
4524 pidgin_conv_window_add_gtkconv(win, gtkconv); |
| |
4525 } |
| |
4526 } |
| |
4527 } |
| |
4528 |
| |
4529 g_free(username); |
| |
4530 g_free(protocol); |
| |
4531 |
| |
4532 gtk_drag_finish(dc, TRUE, (dc->action == GDK_ACTION_MOVE), t); |
| |
4533 } |
| |
4534 else if (sd->target == gdk_atom_intern("text/uri-list", FALSE)) { |
| |
4535 if (purple_conversation_get_type(conv) == PURPLE_CONV_TYPE_IM) |
| |
4536 pidgin_dnd_file_manage(sd, purple_conversation_get_account(conv), purple_conversation_get_name(conv)); |
| |
4537 gtk_drag_finish(dc, TRUE, (dc->action == GDK_ACTION_MOVE), t); |
| |
4538 } |
| |
4539 else |
| |
4540 gtk_drag_finish(dc, FALSE, FALSE, t); |
| |
4541 } |
| |
4542 |
| |
4543 |
| |
4544 static const GtkTargetEntry te[] = |
| |
4545 { |
| |
4546 GTK_IMHTML_DND_TARGETS, |
| |
4547 {"PURPLE_BLIST_NODE", GTK_TARGET_SAME_APP, GTK_IMHTML_DRAG_NUM}, |
| |
4548 {"application/x-im-contact", 0, GTK_IMHTML_DRAG_NUM + 1} |
| |
4549 }; |
| |
4550 |
| |
4551 static PidginConversation * |
| |
4552 pidgin_conv_find_gtkconv(PurpleConversation * conv) |
| |
4553 { |
| |
4554 PurpleBuddy *bud = purple_find_buddy(conv->account, conv->name), *b; |
| |
4555 PurpleContact *c; |
| |
4556 PurpleBlistNode *cn; |
| |
4557 |
| |
4558 if (!bud) |
| |
4559 return NULL; |
| |
4560 |
| |
4561 if (!(c = purple_buddy_get_contact(bud))) |
| |
4562 return NULL; |
| |
4563 |
| |
4564 cn = (PurpleBlistNode *)c; |
| |
4565 for (b = (PurpleBuddy *)cn->child; b; b = (PurpleBuddy *) ((PurpleBlistNode *)b)->next) { |
| |
4566 PurpleConversation *conv; |
| |
4567 if ((conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_IM, b->name, b->account))) { |
| |
4568 if (conv->ui_data) |
| |
4569 return conv->ui_data; |
| |
4570 } |
| |
4571 } |
| |
4572 |
| |
4573 return NULL; |
| |
4574 } |
| |
4575 |
| |
4576 static void |
| |
4577 buddy_update_cb(PurpleBlistNode *bnode, gpointer null) |
| |
4578 { |
| |
4579 GList *list; |
| |
4580 |
| |
4581 g_return_if_fail(bnode); |
| |
4582 g_return_if_fail(PURPLE_BLIST_NODE_IS_BUDDY(bnode)); |
| |
4583 |
| |
4584 for (list = pidgin_conv_windows_get_list(); list; list = list->next) |
| |
4585 { |
| |
4586 PidginWindow *win = list->data; |
| |
4587 PurpleConversation *conv = pidgin_conv_window_get_active_conversation(win); |
| |
4588 |
| |
4589 if (purple_conversation_get_type(conv) != PURPLE_CONV_TYPE_IM) |
| |
4590 continue; |
| |
4591 |
| |
4592 pidgin_conv_update_fields(conv, PIDGIN_CONV_MENU); |
| |
4593 } |
| |
4594 } |
| |
4595 |
| |
4596 /************************************************************************** |
| |
4597 * Conversation UI operations |
| |
4598 **************************************************************************/ |
| |
4599 static void |
| |
4600 private_gtkconv_new(PurpleConversation *conv, gboolean hidden) |
| |
4601 { |
| |
4602 PidginConversation *gtkconv; |
| |
4603 PurpleConversationType conv_type = purple_conversation_get_type(conv); |
| |
4604 GtkWidget *pane = NULL; |
| |
4605 GtkWidget *tab_cont; |
| |
4606 |
| |
4607 if (conv_type == PURPLE_CONV_TYPE_IM && (gtkconv = pidgin_conv_find_gtkconv(conv))) { |
| |
4608 conv->ui_data = gtkconv; |
| |
4609 if (!g_list_find(gtkconv->convs, conv)) |
| |
4610 gtkconv->convs = g_list_prepend(gtkconv->convs, conv); |
| |
4611 pidgin_conv_switch_active_conversation(conv); |
| |
4612 return; |
| |
4613 } |
| |
4614 |
| |
4615 gtkconv = g_new0(PidginConversation, 1); |
| |
4616 conv->ui_data = gtkconv; |
| |
4617 gtkconv->active_conv = conv; |
| |
4618 gtkconv->convs = g_list_prepend(gtkconv->convs, conv); |
| |
4619 gtkconv->send_history = g_list_append(NULL, NULL); |
| |
4620 |
| |
4621 /* Setup some initial variables. */ |
| |
4622 gtkconv->sg = gtk_size_group_new(GTK_SIZE_GROUP_BOTH); |
| |
4623 gtkconv->tooltips = gtk_tooltips_new(); |
| |
4624 gtkconv->unseen_state = PIDGIN_UNSEEN_NONE; |
| |
4625 gtkconv->unseen_count = 0; |
| |
4626 |
| |
4627 if (conv_type == PURPLE_CONV_TYPE_IM) { |
| |
4628 gtkconv->u.im = g_malloc0(sizeof(PidginImPane)); |
| |
4629 |
| |
4630 pane = setup_im_pane(gtkconv); |
| |
4631 } else if (conv_type == PURPLE_CONV_TYPE_CHAT) { |
| |
4632 gtkconv->u.chat = g_malloc0(sizeof(PidginChatPane)); |
| |
4633 pane = setup_chat_pane(gtkconv); |
| |
4634 } |
| |
4635 |
| |
4636 gtk_imhtml_set_format_functions(GTK_IMHTML(gtkconv->imhtml), |
| |
4637 gtk_imhtml_get_format_functions(GTK_IMHTML(gtkconv->imhtml)) | GTK_IMHTML_IMAGE); |
| |
4638 |
| |
4639 if (pane == NULL) { |
| |
4640 if (conv_type == PURPLE_CONV_TYPE_CHAT) |
| |
4641 g_free(gtkconv->u.chat); |
| |
4642 else if (conv_type == PURPLE_CONV_TYPE_IM) |
| |
4643 g_free(gtkconv->u.im); |
| |
4644 |
| |
4645 g_free(gtkconv); |
| |
4646 conv->ui_data = NULL; |
| |
4647 return; |
| |
4648 } |
| |
4649 |
| |
4650 /* Setup drag-and-drop */ |
| |
4651 gtk_drag_dest_set(pane, |
| |
4652 GTK_DEST_DEFAULT_MOTION | |
| |
4653 GTK_DEST_DEFAULT_DROP, |
| |
4654 te, sizeof(te) / sizeof(GtkTargetEntry), |
| |
4655 GDK_ACTION_COPY); |
| |
4656 gtk_drag_dest_set(pane, |
| |
4657 GTK_DEST_DEFAULT_MOTION | |
| |
4658 GTK_DEST_DEFAULT_DROP, |
| |
4659 te, sizeof(te) / sizeof(GtkTargetEntry), |
| |
4660 GDK_ACTION_COPY); |
| |
4661 gtk_drag_dest_set(gtkconv->imhtml, 0, |
| |
4662 te, sizeof(te) / sizeof(GtkTargetEntry), |
| |
4663 GDK_ACTION_COPY); |
| |
4664 |
| |
4665 gtk_drag_dest_set(gtkconv->entry, 0, |
| |
4666 te, sizeof(te) / sizeof(GtkTargetEntry), |
| |
4667 GDK_ACTION_COPY); |
| |
4668 |
| |
4669 g_signal_connect(G_OBJECT(pane), "drag_data_received", |
| |
4670 G_CALLBACK(conv_dnd_recv), gtkconv); |
| |
4671 g_signal_connect(G_OBJECT(gtkconv->imhtml), "drag_data_received", |
| |
4672 G_CALLBACK(conv_dnd_recv), gtkconv); |
| |
4673 g_signal_connect(G_OBJECT(gtkconv->entry), "drag_data_received", |
| |
4674 G_CALLBACK(conv_dnd_recv), gtkconv); |
| |
4675 |
| |
4676 /* Setup the container for the tab. */ |
| |
4677 gtkconv->tab_cont = tab_cont = gtk_vbox_new(FALSE, PIDGIN_HIG_BOX_SPACE); |
| |
4678 g_object_set_data(G_OBJECT(tab_cont), "PidginConversation", gtkconv); |
| |
4679 gtk_container_set_border_width(GTK_CONTAINER(tab_cont), PIDGIN_HIG_BOX_SPACE); |
| |
4680 gtk_container_add(GTK_CONTAINER(tab_cont), pane); |
| |
4681 gtk_widget_show(pane); |
| |
4682 |
| |
4683 gtkconv->make_sound = TRUE; |
| |
4684 |
| |
4685 if (purple_prefs_get_bool(PIDGIN_PREFS_ROOT "/conversations/show_formatting_toolbar")) |
| |
4686 gtk_widget_show(gtkconv->toolbar); |
| |
4687 else |
| |
4688 gtk_widget_hide(gtkconv->toolbar); |
| |
4689 |
| |
4690 gtk_imhtml_show_comments(GTK_IMHTML(gtkconv->imhtml), |
| |
4691 purple_prefs_get_bool(PIDGIN_PREFS_ROOT "/conversations/show_timestamps")); |
| |
4692 gtk_imhtml_set_protocol_name(GTK_IMHTML(gtkconv->imhtml), |
| |
4693 purple_account_get_protocol_name(conv->account)); |
| |
4694 |
| |
4695 g_signal_connect_swapped(G_OBJECT(pane), "focus", |
| |
4696 G_CALLBACK(gtk_widget_grab_focus), |
| |
4697 gtkconv->entry); |
| |
4698 |
| |
4699 if (hidden) |
| |
4700 pidgin_conv_window_add_gtkconv(hidden_convwin, gtkconv); |
| |
4701 else |
| |
4702 pidgin_conv_placement_place(gtkconv); |
| |
4703 |
| |
4704 if (nick_colors == NULL) { |
| |
4705 nbr_nick_colors = NUM_NICK_COLORS; |
| |
4706 nick_colors = generate_nick_colors(&nbr_nick_colors, gtk_widget_get_style(gtkconv->imhtml)->base[GTK_STATE_NORMAL]); |
| |
4707 } |
| |
4708 } |
| |
4709 |
| |
4710 static void |
| |
4711 pidgin_conv_new_hidden(PurpleConversation *conv) |
| |
4712 { |
| |
4713 private_gtkconv_new(conv, TRUE); |
| |
4714 } |
| |
4715 |
| |
4716 void |
| |
4717 pidgin_conv_new(PurpleConversation *conv) |
| |
4718 { |
| |
4719 private_gtkconv_new(conv, FALSE); |
| |
4720 } |
| |
4721 |
| |
4722 static void |
| |
4723 received_im_msg_cb(PurpleAccount *account, char *sender, char *message, |
| |
4724 PurpleConversation *conv, PurpleMessageFlags flags) |
| |
4725 { |
| |
4726 PurpleConversationUiOps *ui_ops = pidgin_conversations_get_conv_ui_ops(); |
| |
4727 if (conv != NULL) |
| |
4728 return; |
| |
4729 |
| |
4730 /* create hidden conv if hide_new pref is always */ |
| |
4731 if (strcmp(purple_prefs_get_string(PIDGIN_PREFS_ROOT "/conversations/im/hide_new"), "always") == 0) |
| |
4732 { |
| |
4733 ui_ops->create_conversation = pidgin_conv_new_hidden; |
| |
4734 purple_conversation_new(PURPLE_CONV_TYPE_IM, account, sender); |
| |
4735 ui_ops->create_conversation = pidgin_conv_new; |
| |
4736 return; |
| |
4737 } |
| |
4738 |
| |
4739 /* create hidden conv if hide_new pref is away and account is away */ |
| |
4740 if (strcmp(purple_prefs_get_string(PIDGIN_PREFS_ROOT "/conversations/im/hide_new"), "away") == 0 && |
| |
4741 !purple_status_is_available(purple_account_get_active_status(account))) |
| |
4742 { |
| |
4743 ui_ops->create_conversation = pidgin_conv_new_hidden; |
| |
4744 purple_conversation_new(PURPLE_CONV_TYPE_IM, account, sender); |
| |
4745 ui_ops->create_conversation = pidgin_conv_new; |
| |
4746 return; |
| |
4747 } |
| |
4748 } |
| |
4749 |
| |
4750 static void |
| |
4751 pidgin_conv_destroy(PurpleConversation *conv) |
| |
4752 { |
| |
4753 PidginConversation *gtkconv = PIDGIN_CONVERSATION(conv); |
| |
4754 |
| |
4755 gtkconv->convs = g_list_remove(gtkconv->convs, conv); |
| |
4756 /* Don't destroy ourselves until all our convos are gone */ |
| |
4757 if (gtkconv->convs) |
| |
4758 return; |
| |
4759 |
| |
4760 pidgin_conv_window_remove_gtkconv(gtkconv->win, gtkconv); |
| |
4761 |
| |
4762 /* If the "Save Conversation" or "Save Icon" dialogs are open then close them */ |
| |
4763 purple_request_close_with_handle(gtkconv); |
| |
4764 purple_notify_close_with_handle(gtkconv); |
| |
4765 |
| |
4766 gtk_widget_destroy(gtkconv->tab_cont); |
| |
4767 g_object_unref(gtkconv->tab_cont); |
| |
4768 |
| |
4769 if (purple_conversation_get_type(conv) == PURPLE_CONV_TYPE_IM) { |
| |
4770 if (gtkconv->u.im->icon_timer != 0) |
| |
4771 g_source_remove(gtkconv->u.im->icon_timer); |
| |
4772 |
| |
4773 if (gtkconv->u.im->anim != NULL) |
| |
4774 g_object_unref(G_OBJECT(gtkconv->u.im->anim)); |
| |
4775 |
| |
4776 g_free(gtkconv->u.im); |
| |
4777 } else if (purple_conversation_get_type(conv) == PURPLE_CONV_TYPE_CHAT) { |
| |
4778 purple_signals_disconnect_by_handle(gtkconv->u.chat); |
| |
4779 g_free(gtkconv->u.chat); |
| |
4780 } |
| |
4781 |
| |
4782 gtk_object_sink(GTK_OBJECT(gtkconv->tooltips)); |
| |
4783 |
| |
4784 gtkconv->send_history = g_list_first(gtkconv->send_history); |
| |
4785 g_list_foreach(gtkconv->send_history, (GFunc)g_free, NULL); |
| |
4786 g_list_free(gtkconv->send_history); |
| |
4787 |
| |
4788 g_free(gtkconv); |
| |
4789 } |
| |
4790 |
| |
4791 |
| |
4792 static void |
| |
4793 pidgin_conv_write_im(PurpleConversation *conv, const char *who, |
| |
4794 const char *message, PurpleMessageFlags flags, |
| |
4795 time_t mtime) |
| |
4796 { |
| |
4797 PidginConversation *gtkconv; |
| |
4798 |
| |
4799 gtkconv = PIDGIN_CONVERSATION(conv); |
| |
4800 |
| |
4801 if (conv != gtkconv->active_conv && |
| |
4802 flags & PURPLE_MESSAGE_ACTIVE_ONLY) |
| |
4803 { |
| |
4804 /* Plugins that want these messages suppressed should be |
| |
4805 * calling purple_conv_im_write(), so they get suppressed here, |
| |
4806 * before being written to the log. */ |
| |
4807 purple_debug_info("gtkconv", |
| |
4808 "Suppressing message for an inactive conversation in pidgin_conv_write_im()\n"); |
| |
4809 return; |
| |
4810 } |
| |
4811 |
| |
4812 purple_conversation_write(conv, who, message, flags, mtime); |
| |
4813 } |
| |
4814 |
| |
4815 /* The callback for an event on a link tag. */ |
| |
4816 static gboolean buddytag_event(GtkTextTag *tag, GObject *imhtml, |
| |
4817 GdkEvent *event, GtkTextIter *arg2, gpointer data) { |
| |
4818 if (event->type == GDK_BUTTON_PRESS |
| |
4819 || event->type == GDK_2BUTTON_PRESS) { |
| |
4820 GdkEventButton *btn_event = (GdkEventButton*) event; |
| |
4821 PurpleConversation *conv = data; |
| |
4822 char *buddyname; |
| |
4823 |
| |
4824 /* strlen("BUDDY ") == 6 */ |
| |
4825 g_return_val_if_fail((tag->name != NULL) |
| |
4826 && (strlen(tag->name) > 6), FALSE); |
| |
4827 |
| |
4828 buddyname = (tag->name) + 6; |
| |
4829 |
| |
4830 if (btn_event->button == 2 |
| |
4831 && event->type == GDK_2BUTTON_PRESS) { |
| |
4832 chat_do_info(PIDGIN_CONVERSATION(conv), buddyname); |
| |
4833 |
| |
4834 return TRUE; |
| |
4835 } else if (btn_event->button == 3 |
| |
4836 && event->type == GDK_BUTTON_PRESS) { |
| |
4837 GtkTextIter start, end; |
| |
4838 |
| |
4839 /* we shouldn't display the popup |
| |
4840 * if the user has selected something: */ |
| |
4841 if (!gtk_text_buffer_get_selection_bounds( |
| |
4842 gtk_text_iter_get_buffer(arg2), |
| |
4843 &start, &end)) { |
| |
4844 GtkWidget *menu = NULL; |
| |
4845 PurpleConnection *gc = |
| |
4846 purple_conversation_get_gc(conv); |
| |
4847 |
| |
4848 |
| |
4849 menu = create_chat_menu(conv, buddyname, gc); |
| |
4850 gtk_menu_popup(GTK_MENU(menu), NULL, NULL, |
| |
4851 NULL, GTK_WIDGET(imhtml), |
| |
4852 btn_event->button, |
| |
4853 btn_event->time); |
| |
4854 |
| |
4855 /* Don't propagate the event any further */ |
| |
4856 return TRUE; |
| |
4857 } |
| |
4858 } |
| |
4859 } |
| |
4860 |
| |
4861 return FALSE; |
| |
4862 } |
| |
4863 |
| |
4864 static GtkTextTag *get_buddy_tag(PurpleConversation *conv, const char *who) { |
| |
4865 PidginConversation *gtkconv = PIDGIN_CONVERSATION(conv); |
| |
4866 GtkTextTag *buddytag; |
| |
4867 gchar *str; |
| |
4868 |
| |
4869 str = g_strdup_printf("BUDDY %s", who); |
| |
4870 |
| |
4871 buddytag = gtk_text_tag_table_lookup( |
| |
4872 gtk_text_buffer_get_tag_table( |
| |
4873 GTK_IMHTML(gtkconv->imhtml)->text_buffer), str); |
| |
4874 |
| |
4875 if (buddytag == NULL) { |
| |
4876 buddytag = gtk_text_buffer_create_tag( |
| |
4877 GTK_IMHTML(gtkconv->imhtml)->text_buffer, str, NULL); |
| |
4878 |
| |
4879 g_signal_connect(G_OBJECT(buddytag), "event", |
| |
4880 G_CALLBACK(buddytag_event), conv); |
| |
4881 } |
| |
4882 |
| |
4883 g_free(str); |
| |
4884 |
| |
4885 return buddytag; |
| |
4886 } |
| |
4887 |
| |
4888 static void pidgin_conv_calculate_newday(PidginConversation *gtkconv, time_t mtime) |
| |
4889 { |
| |
4890 struct tm *tm = localtime(&mtime); |
| |
4891 |
| |
4892 tm->tm_hour = tm->tm_min = tm->tm_sec = 0; |
| |
4893 tm->tm_mday++; |
| |
4894 |
| |
4895 gtkconv->newday = mktime(tm); |
| |
4896 } |
| |
4897 |
| |
4898 /* Detect string direction and encapsulate the string in RLE/LRE/PDF unicode characters |
| |
4899 str - pointer to string (string is re-allocated and the pointer updated) */ |
| |
4900 static void |
| |
4901 str_embed_direction_chars(char **str) |
| |
4902 { |
| |
4903 char pre_str[4]; |
| |
4904 char post_str[10]; |
| |
4905 char *ret = g_malloc(strlen(*str)+13); |
| |
4906 |
| |
4907 if (PANGO_DIRECTION_RTL == pango_find_base_dir(*str, -1)) |
| |
4908 { |
| |
4909 g_sprintf(pre_str, "%c%c%c", |
| |
4910 0xE2, 0x80, 0xAB); /* RLE */ |
| |
4911 g_sprintf(post_str, "%c%c%c%c%c%c%c%c%c", |
| |
4912 0xE2, 0x80, 0xAC, /* PDF */ |
| |
4913 0xE2, 0x80, 0x8E, /* LRM */ |
| |
4914 0xE2, 0x80, 0xAC); /* PDF */ |
| |
4915 } |
| |
4916 else |
| |
4917 { |
| |
4918 g_sprintf(pre_str, "%c%c%c", |
| |
4919 0xE2, 0x80, 0xAA); /* LRE */ |
| |
4920 g_sprintf(post_str, "%c%c%c%c%c%c%c%c%c", |
| |
4921 0xE2, 0x80, 0xAC, /* PDF */ |
| |
4922 0xE2, 0x80, 0x8F, /* RLM */ |
| |
4923 0xE2, 0x80, 0xAC); /* PDF */ |
| |
4924 } |
| |
4925 |
| |
4926 g_sprintf(ret, "%s%s%s", pre_str, *str, post_str); |
| |
4927 |
| |
4928 g_free(*str); |
| |
4929 *str = ret; |
| |
4930 } |
| |
4931 |
| |
4932 /* Returns true if the given HTML contains RTL text */ |
| |
4933 static gboolean |
| |
4934 html_is_rtl(const char *html) |
| |
4935 { |
| |
4936 GData *attributes; |
| |
4937 const gchar *start, *end; |
| |
4938 gboolean res = FALSE; |
| |
4939 |
| |
4940 if (purple_markup_find_tag("span", html, &start, &end, &attributes)) |
| |
4941 { |
| |
4942 /* tmp is a member of attributes and is free with g_datalist_clear call */ |
| |
4943 const char *tmp = g_datalist_get_data(&attributes, "dir"); |
| |
4944 if (tmp && !g_ascii_strcasecmp(tmp, "RTL")) |
| |
4945 res = TRUE; |
| |
4946 if (!res) |
| |
4947 { |
| |
4948 tmp = g_datalist_get_data(&attributes, "style"); |
| |
4949 if (tmp) |
| |
4950 { |
| |
4951 char *tmp2 = purple_markup_get_css_property(tmp, "direction"); |
| |
4952 if (tmp2 && !g_ascii_strcasecmp(tmp2, "RTL")) |
| |
4953 res = TRUE; |
| |
4954 g_free(tmp2); |
| |
4955 } |
| |
4956 |
| |
4957 } |
| |
4958 g_datalist_clear(&attributes); |
| |
4959 } |
| |
4960 return res; |
| |
4961 } |
| |
4962 |
| |
4963 |
| |
4964 static void |
| |
4965 pidgin_conv_write_conv(PurpleConversation *conv, const char *name, const char *alias, |
| |
4966 const char *message, PurpleMessageFlags flags, |
| |
4967 time_t mtime) |
| |
4968 { |
| |
4969 PidginConversation *gtkconv; |
| |
4970 PidginWindow *win; |
| |
4971 PurpleConnection *gc; |
| |
4972 PurpleAccount *account; |
| |
4973 PurplePluginProtocolInfo *prpl_info; |
| |
4974 int gtk_font_options = 0; |
| |
4975 int gtk_font_options_all = 0; |
| |
4976 int max_scrollback_lines; |
| |
4977 int line_count; |
| |
4978 char buf2[BUF_LONG]; |
| |
4979 gboolean show_date; |
| |
4980 char *mdate; |
| |
4981 char color[10]; |
| |
4982 char *str; |
| |
4983 char *with_font_tag; |
| |
4984 char *sml_attrib = NULL; |
| |
4985 size_t length; |
| |
4986 PurpleConversationType type; |
| |
4987 char *displaying; |
| |
4988 gboolean plugin_return; |
| |
4989 char *bracket; |
| |
4990 int tag_count = 0; |
| |
4991 gboolean is_rtl_message = FALSE; |
| |
4992 |
| |
4993 g_return_if_fail(conv != NULL); |
| |
4994 gtkconv = PIDGIN_CONVERSATION(conv); |
| |
4995 g_return_if_fail(gtkconv != NULL); |
| |
4996 |
| |
4997 if (conv != gtkconv->active_conv) |
| |
4998 { |
| |
4999 if (flags & PURPLE_MESSAGE_ACTIVE_ONLY) |
| |
5000 { |
| |
5001 /* Unless this had PURPLE_MESSAGE_NO_LOG, this message |
| |
5002 * was logged. Plugin writers: if this isn't what |
| |
5003 * you wanted, call purple_conv_im_write() instead of |
| |
5004 * purple_conversation_write(). */ |
| |
5005 purple_debug_info("gtkconv", |
| |
5006 "Suppressing message for an inactive conversation in pidgin_conv_write_conv()\n"); |
| |
5007 return; |
| |
5008 } |
| |
5009 |
| |
5010 /* Set the active conversation to the one that just messaged us. */ |
| |
5011 /* TODO: consider not doing this if the account is offline or something */ |
| |
5012 if (flags & (PURPLE_MESSAGE_SEND | PURPLE_MESSAGE_RECV)) |
| |
5013 pidgin_conv_switch_active_conversation(conv); |
| |
5014 } |
| |
5015 |
| |
5016 type = purple_conversation_get_type(conv); |
| |
5017 account = purple_conversation_get_account(conv); |
| |
5018 g_return_if_fail(account != NULL); |
| |
5019 gc = purple_account_get_connection(account); |
| |
5020 g_return_if_fail(gc != NULL); |
| |
5021 |
| |
5022 /* Make sure URLs are clickable */ |
| |
5023 displaying = purple_markup_linkify(message); |
| |
5024 plugin_return = GPOINTER_TO_INT(purple_signal_emit_return_1( |
| |
5025 pidgin_conversations_get_handle(), (type == PURPLE_CONV_TYPE_IM ? |
| |
5026 "displaying-im-msg" : "displaying-chat-msg"), |
| |
5027 account, name, &displaying, conv, flags)); |
| |
5028 if (plugin_return) |
| |
5029 { |
| |
5030 g_free(displaying); |
| |
5031 return; |
| |
5032 } |
| |
5033 length = strlen(displaying) + 1; |
| |
5034 |
| |
5035 /* Awful hack to work around GtkIMHtml's inefficient rendering of messages with lots of formatting changes. |
| |
5036 * If a message has over 100 '<' characters, strip formatting before appending it. Hopefully nobody actually |
| |
5037 * needs that much formatting, anyway. |
| |
5038 */ |
| |
5039 for (bracket = strchr(displaying, '<'); bracket && *(bracket + 1); bracket = strchr(bracket + 1, '<')) |
| |
5040 tag_count++; |
| |
5041 |
| |
5042 if (tag_count > 100) { |
| |
5043 char *tmp = displaying; |
| |
5044 displaying = purple_markup_strip_html(tmp); |
| |
5045 g_free(tmp); |
| |
5046 } |
| |
5047 |
| |
5048 win = gtkconv->win; |
| |
5049 prpl_info = PURPLE_PLUGIN_PROTOCOL_INFO(gc->prpl); |
| |
5050 |
| |
5051 line_count = gtk_text_buffer_get_line_count( |
| |
5052 gtk_text_view_get_buffer(GTK_TEXT_VIEW( |
| |
5053 gtkconv->imhtml))); |
| |
5054 |
| |
5055 max_scrollback_lines = purple_prefs_get_int( |
| |
5056 PIDGIN_PREFS_ROOT "/conversations/scrollback_lines"); |
| |
5057 /* If we're sitting at more than 100 lines more than the |
| |
5058 max scrollback, trim down to max scrollback */ |
| |
5059 if (max_scrollback_lines > 0 |
| |
5060 && line_count > (max_scrollback_lines + 100)) { |
| |
5061 GtkTextBuffer *text_buffer = gtk_text_view_get_buffer( |
| |
5062 GTK_TEXT_VIEW(gtkconv->imhtml)); |
| |
5063 GtkTextIter start, end; |
| |
5064 |
| |
5065 gtk_text_buffer_get_start_iter(text_buffer, &start); |
| |
5066 gtk_text_buffer_get_iter_at_line(text_buffer, &end, |
| |
5067 (line_count - max_scrollback_lines)); |
| |
5068 gtk_imhtml_delete(GTK_IMHTML(gtkconv->imhtml), &start, &end); |
| |
5069 } |
| |
5070 |
| |
5071 if (type == PURPLE_CONV_TYPE_CHAT) |
| |
5072 { |
| |
5073 /* Create anchor for user */ |
| |
5074 GtkTextIter iter; |
| |
5075 char *tmp = g_strconcat("user:", name, NULL); |
| |
5076 |
| |
5077 gtk_text_buffer_get_end_iter(gtk_text_view_get_buffer(GTK_TEXT_VIEW(gtkconv->imhtml)), &iter); |
| |
5078 gtk_text_buffer_create_mark(gtk_text_view_get_buffer(GTK_TEXT_VIEW(gtkconv->imhtml)), |
| |
5079 tmp, &iter, TRUE); |
| |
5080 g_free(tmp); |
| |
5081 } |
| |
5082 |
| |
5083 if (purple_prefs_get_bool(PIDGIN_PREFS_ROOT "/conversations/use_smooth_scrolling")) |
| |
5084 gtk_font_options_all |= GTK_IMHTML_USE_SMOOTHSCROLLING; |
| |
5085 |
| |
5086 if (gtk_text_buffer_get_char_count(gtk_text_view_get_buffer(GTK_TEXT_VIEW(gtkconv->imhtml)))) |
| |
5087 gtk_imhtml_append_text(GTK_IMHTML(gtkconv->imhtml), "<BR>", gtk_font_options_all); |
| |
5088 |
| |
5089 /* First message in a conversation. */ |
| |
5090 if (gtkconv->newday == 0) |
| |
5091 pidgin_conv_calculate_newday(gtkconv, mtime); |
| |
5092 |
| |
5093 /* Show the date on the first message in a new day, or if the message is |
| |
5094 * older than 20 minutes. */ |
| |
5095 show_date = (mtime >= gtkconv->newday) || (time(NULL) > mtime + 20*60); |
| |
5096 |
| |
5097 mdate = purple_signal_emit_return_1(pidgin_conversations_get_handle(), |
| |
5098 "conversation-timestamp", |
| |
5099 conv, mtime, show_date); |
| |
5100 |
| |
5101 if (mdate == NULL) |
| |
5102 { |
| |
5103 struct tm *tm = localtime(&mtime); |
| |
5104 const char *tmp; |
| |
5105 if (show_date) |
| |
5106 tmp = purple_date_format_long(tm); |
| |
5107 else |
| |
5108 tmp = purple_time_format(tm); |
| |
5109 mdate = g_strdup_printf("(%s)", tmp); |
| |
5110 } |
| |
5111 |
| |
5112 /* Bi-Directional support - set timestamp direction using unicode characters */ |
| |
5113 is_rtl_message = html_is_rtl(message); |
| |
5114 /* Enforce direction only if message is RTL - doesn't effect LTR users */ |
| |
5115 if (is_rtl_message) |
| |
5116 str_embed_direction_chars(&mdate); |
| |
5117 |
| |
5118 if (mtime >= gtkconv->newday) |
| |
5119 pidgin_conv_calculate_newday(gtkconv, mtime); |
| |
5120 |
| |
5121 sml_attrib = g_strdup_printf("sml=\"%s\"", purple_account_get_protocol_name(account)); |
| |
5122 |
| |
5123 gtk_font_options |= GTK_IMHTML_NO_COMMENTS; |
| |
5124 |
| |
5125 if ((flags & PURPLE_MESSAGE_RECV) && |
| |
5126 !purple_prefs_get_bool(PIDGIN_PREFS_ROOT "/conversations/show_incoming_formatting")) |
| |
5127 gtk_font_options |= GTK_IMHTML_NO_COLOURS | GTK_IMHTML_NO_FONTS | GTK_IMHTML_NO_SIZES | GTK_IMHTML_NO_FORMATTING; |
| |
5128 |
| |
5129 /* this is gonna crash one day, I can feel it. */ |
| |
5130 if (PURPLE_PLUGIN_PROTOCOL_INFO(purple_find_prpl(purple_account_get_protocol_id(conv->account)))->options & |
| |
5131 OPT_PROTO_USE_POINTSIZE) { |
| |
5132 gtk_font_options |= GTK_IMHTML_USE_POINTSIZE; |
| |
5133 } |
| |
5134 |
| |
5135 |
| |
5136 /* TODO: These colors should not be hardcoded so log.c can use them */ |
| |
5137 if (flags & PURPLE_MESSAGE_RAW) { |
| |
5138 gtk_imhtml_append_text(GTK_IMHTML(gtkconv->imhtml), message, gtk_font_options_all); |
| |
5139 } else if (flags & PURPLE_MESSAGE_SYSTEM) { |
| |
5140 g_snprintf(buf2, sizeof(buf2), |
| |
5141 "<FONT %s><FONT SIZE=\"2\"><!--%s --></FONT><B>%s</B></FONT>", |
| |
5142 sml_attrib ? sml_attrib : "", mdate, displaying); |
| |
5143 |
| |
5144 gtk_imhtml_append_text(GTK_IMHTML(gtkconv->imhtml), buf2, gtk_font_options_all); |
| |
5145 |
| |
5146 } else if (flags & PURPLE_MESSAGE_ERROR) { |
| |
5147 g_snprintf(buf2, sizeof(buf2), |
| |
5148 "<FONT COLOR=\"#ff0000\"><FONT %s><FONT SIZE=\"2\"><!--%s --></FONT><B>%s</B></FONT></FONT>", |
| |
5149 sml_attrib ? sml_attrib : "", mdate, displaying); |
| |
5150 |
| |
5151 gtk_imhtml_append_text(GTK_IMHTML(gtkconv->imhtml), buf2, gtk_font_options_all); |
| |
5152 |
| |
5153 } else if (flags & PURPLE_MESSAGE_NO_LOG) { |
| |
5154 g_snprintf(buf2, BUF_LONG, |
| |
5155 "<B><FONT %s COLOR=\"#777777\">%s</FONT></B>", |
| |
5156 sml_attrib ? sml_attrib : "", displaying); |
| |
5157 |
| |
5158 gtk_imhtml_append_text(GTK_IMHTML(gtkconv->imhtml), buf2, gtk_font_options_all); |
| |
5159 } else { |
| |
5160 char *new_message = g_memdup(displaying, length); |
| |
5161 char *alias_escaped = (alias ? g_markup_escape_text(alias, strlen(alias)) : g_strdup("")); |
| |
5162 /* The initial offset is to deal with |
| |
5163 * escaped entities making the string longer */ |
| |
5164 int tag_start_offset = alias ? (strlen(alias_escaped) - strlen(alias)) : 0; |
| |
5165 int tag_end_offset = 0; |
| |
5166 GtkSmileyTree *tree = NULL; |
| |
5167 GHashTable *smiley_data = NULL; |
| |
5168 |
| |
5169 /* Enforce direction on alias */ |
| |
5170 if (is_rtl_message) |
| |
5171 str_embed_direction_chars(&alias_escaped); |
| |
5172 |
| |
5173 if (flags & PURPLE_MESSAGE_SEND) |
| |
5174 { |
| |
5175 /* Temporarily revert to the original smiley-data to avoid showing up |
| |
5176 * custom smileys of the buddy when sending message |
| |
5177 */ |
| |
5178 tree = GTK_IMHTML(gtkconv->imhtml)->default_smilies; |
| |
5179 GTK_IMHTML(gtkconv->imhtml)->default_smilies = |
| |
5180 GTK_IMHTML(gtkconv->entry)->default_smilies; |
| |
5181 smiley_data = GTK_IMHTML(gtkconv->imhtml)->smiley_data; |
| |
5182 GTK_IMHTML(gtkconv->imhtml)->smiley_data = GTK_IMHTML(gtkconv->entry)->smiley_data; |
| |
5183 } |
| |
5184 |
| |
5185 if (flags & PURPLE_MESSAGE_WHISPER) { |
| |
5186 str = g_malloc(1024); |
| |
5187 |
| |
5188 /* If we're whispering, it's not an autoresponse. */ |
| |
5189 if (purple_message_meify(new_message, -1 )) { |
| |
5190 g_snprintf(str, 1024, "***%s", alias_escaped); |
| |
5191 strcpy(color, "#6C2585"); |
| |
5192 tag_start_offset += 3; |
| |
5193 } |
| |
5194 else { |
| |
5195 g_snprintf(str, 1024, "*%s*:", alias_escaped); |
| |
5196 tag_start_offset += 1; |
| |
5197 tag_end_offset = 2; |
| |
5198 strcpy(color, "#00FF00"); |
| |
5199 } |
| |
5200 } |
| |
5201 else { |
| |
5202 if (purple_message_meify(new_message, -1)) { |
| |
5203 str = g_malloc(1024); |
| |
5204 |
| |
5205 if (flags & PURPLE_MESSAGE_AUTO_RESP) { |
| |
5206 g_snprintf(str, 1024, "%s ***%s", AUTO_RESPONSE, alias_escaped); |
| |
5207 tag_start_offset += 4 |
| |
5208 + strlen(AUTO_RESPONSE); |
| |
5209 } else { |
| |
5210 g_snprintf(str, 1024, "***%s", alias_escaped); |
| |
5211 tag_start_offset += 3; |
| |
5212 } |
| |
5213 |
| |
5214 if (flags & PURPLE_MESSAGE_NICK) |
| |
5215 strcpy(color, HIGHLIGHT_COLOR); |
| |
5216 else |
| |
5217 strcpy(color, "#062585"); |
| |
5218 } |
| |
5219 else { |
| |
5220 str = g_malloc(1024); |
| |
5221 if (flags & PURPLE_MESSAGE_AUTO_RESP) { |
| |
5222 g_snprintf(str, 1024, "%s %s", alias_escaped, AUTO_RESPONSE); |
| |
5223 tag_start_offset += 1 |
| |
5224 + strlen(AUTO_RESPONSE); |
| |
5225 } else { |
| |
5226 g_snprintf(str, 1024, "%s:", alias_escaped); |
| |
5227 tag_end_offset = 1; |
| |
5228 } |
| |
5229 if (flags & PURPLE_MESSAGE_NICK) |
| |
5230 strcpy(color, HIGHLIGHT_COLOR); |
| |
5231 else if (flags & PURPLE_MESSAGE_RECV) { |
| |
5232 if (type == PURPLE_CONV_TYPE_CHAT) { |
| |
5233 GdkColor *col = get_nick_color(gtkconv, name); |
| |
5234 |
| |
5235 g_snprintf(color, sizeof(color), "#%02X%02X%02X", |
| |
5236 col->red >> 8, col->green >> 8, col->blue >> 8); |
| |
5237 } else |
| |
5238 strcpy(color, RECV_COLOR); |
| |
5239 } |
| |
5240 else if (flags & PURPLE_MESSAGE_SEND) |
| |
5241 strcpy(color, SEND_COLOR); |
| |
5242 else { |
| |
5243 purple_debug_error("gtkconv", "message missing flags\n"); |
| |
5244 strcpy(color, "#000000"); |
| |
5245 } |
| |
5246 } |
| |
5247 } |
| |
5248 |
| |
5249 g_free(alias_escaped); |
| |
5250 |
| |
5251 /* Are we in a chat where we can tell which users are buddies? */ |
| |
5252 if (!(prpl_info->options & OPT_PROTO_UNIQUE_CHATNAME) && |
| |
5253 purple_conversation_get_type(conv) == PURPLE_CONV_TYPE_CHAT) { |
| |
5254 |
| |
5255 /* Bold buddies to make them stand out from non-buddies. */ |
| |
5256 if (flags & PURPLE_MESSAGE_SEND || |
| |
5257 flags & PURPLE_MESSAGE_NICK || |
| |
5258 purple_find_buddy(account, name) != NULL) { |
| |
5259 g_snprintf(buf2, BUF_LONG, |
| |
5260 "<FONT COLOR=\"%s\" %s><FONT SIZE=\"2\"><!--%s --></FONT>" |
| |
5261 "<B>%s</B></FONT> ", |
| |
5262 color, sml_attrib ? sml_attrib : "", mdate, str); |
| |
5263 } else { |
| |
5264 g_snprintf(buf2, BUF_LONG, |
| |
5265 "<FONT COLOR=\"%s\" %s><FONT SIZE=\"2\"><!--%s --></FONT>" |
| |
5266 "%s</FONT> ", |
| |
5267 color, sml_attrib ? sml_attrib : "", mdate, str); |
| |
5268 |
| |
5269 } |
| |
5270 } else { |
| |
5271 /* Bold everyone's name to make the name stand out from the message. */ |
| |
5272 g_snprintf(buf2, BUF_LONG, |
| |
5273 "<FONT COLOR=\"%s\" %s><FONT SIZE=\"2\"><!--%s --></FONT>" |
| |
5274 "<B>%s</B></FONT> ", |
| |
5275 color, sml_attrib ? sml_attrib : "", mdate, str); |
| |
5276 } |
| |
5277 |
| |
5278 gtk_imhtml_append_text(GTK_IMHTML(gtkconv->imhtml), buf2, gtk_font_options_all); |
| |
5279 |
| |
5280 if (purple_conversation_get_type(conv) == PURPLE_CONV_TYPE_CHAT && |
| |
5281 !(flags & PURPLE_MESSAGE_SEND)) { |
| |
5282 |
| |
5283 GtkTextIter start, end; |
| |
5284 GtkTextTag *buddytag = get_buddy_tag(conv, name); |
| |
5285 |
| |
5286 gtk_text_buffer_get_end_iter( |
| |
5287 GTK_IMHTML(gtkconv->imhtml)->text_buffer, |
| |
5288 &end); |
| |
5289 gtk_text_iter_backward_chars(&end, |
| |
5290 tag_end_offset + 1); |
| |
5291 |
| |
5292 gtk_text_buffer_get_end_iter( |
| |
5293 GTK_IMHTML(gtkconv->imhtml)->text_buffer, |
| |
5294 &start); |
| |
5295 gtk_text_iter_backward_chars(&start, |
| |
5296 strlen(str) + 1 - tag_start_offset); |
| |
5297 |
| |
5298 gtk_text_buffer_apply_tag( |
| |
5299 GTK_IMHTML(gtkconv->imhtml)->text_buffer, |
| |
5300 buddytag, &start, &end); |
| |
5301 } |
| |
5302 |
| |
5303 g_free(str); |
| |
5304 |
| |
5305 if(gc){ |
| |
5306 char *pre = g_strdup_printf("<font %s>", sml_attrib ? sml_attrib : ""); |
| |
5307 char *post = "</font>"; |
| |
5308 int pre_len = strlen(pre); |
| |
5309 int post_len = strlen(post); |
| |
5310 |
| |
5311 with_font_tag = g_malloc(length + pre_len + post_len + 1); |
| |
5312 |
| |
5313 strcpy(with_font_tag, pre); |
| |
5314 memcpy(with_font_tag + pre_len, new_message, length); |
| |
5315 strcpy(with_font_tag + pre_len + length, post); |
| |
5316 |
| |
5317 length += pre_len + post_len; |
| |
5318 g_free(pre); |
| |
5319 } |
| |
5320 else |
| |
5321 with_font_tag = g_memdup(new_message, length); |
| |
5322 |
| |
5323 gtk_imhtml_append_text(GTK_IMHTML(gtkconv->imhtml), |
| |
5324 with_font_tag, gtk_font_options | gtk_font_options_all); |
| |
5325 |
| |
5326 if (flags & PURPLE_MESSAGE_SEND) |
| |
5327 { |
| |
5328 /* Restore the smiley-data */ |
| |
5329 GTK_IMHTML(gtkconv->imhtml)->default_smilies = tree; |
| |
5330 GTK_IMHTML(gtkconv->imhtml)->smiley_data = smiley_data; |
| |
5331 } |
| |
5332 |
| |
5333 g_free(with_font_tag); |
| |
5334 g_free(new_message); |
| |
5335 } |
| |
5336 |
| |
5337 g_free(mdate); |
| |
5338 g_free(sml_attrib); |
| |
5339 |
| |
5340 /* Tab highlighting stuff */ |
| |
5341 if (!(flags & PURPLE_MESSAGE_SEND) && !pidgin_conv_has_focus(conv)) |
| |
5342 { |
| |
5343 PidginUnseenState unseen = PIDGIN_UNSEEN_NONE; |
| |
5344 |
| |
5345 if ((flags & PURPLE_MESSAGE_NICK) == PURPLE_MESSAGE_NICK) |
| |
5346 unseen = PIDGIN_UNSEEN_NICK; |
| |
5347 else if (((flags & PURPLE_MESSAGE_SYSTEM) == PURPLE_MESSAGE_SYSTEM) || |
| |
5348 ((flags & PURPLE_MESSAGE_ERROR) == PURPLE_MESSAGE_ERROR)) |
| |
5349 unseen = PIDGIN_UNSEEN_EVENT; |
| |
5350 else if ((flags & PURPLE_MESSAGE_NO_LOG) == PURPLE_MESSAGE_NO_LOG) |
| |
5351 unseen = PIDGIN_UNSEEN_NO_LOG; |
| |
5352 else |
| |
5353 unseen = PIDGIN_UNSEEN_TEXT; |
| |
5354 |
| |
5355 gtkconv_set_unseen(gtkconv, unseen); |
| |
5356 } |
| |
5357 |
| |
5358 purple_signal_emit(pidgin_conversations_get_handle(), |
| |
5359 (type == PURPLE_CONV_TYPE_IM ? "displayed-im-msg" : "displayed-chat-msg"), |
| |
5360 account, name, displaying, conv, flags); |
| |
5361 g_free(displaying); |
| |
5362 } |
| |
5363 static void |
| |
5364 pidgin_conv_chat_add_users(PurpleConversation *conv, GList *cbuddies, gboolean new_arrivals) |
| |
5365 { |
| |
5366 PurpleConvChat *chat; |
| |
5367 PidginConversation *gtkconv; |
| |
5368 PidginChatPane *gtkchat; |
| |
5369 GtkListStore *ls; |
| |
5370 GList *l; |
| |
5371 |
| |
5372 char tmp[BUF_LONG]; |
| |
5373 int num_users; |
| |
5374 |
| |
5375 chat = PURPLE_CONV_CHAT(conv); |
| |
5376 gtkconv = PIDGIN_CONVERSATION(conv); |
| |
5377 gtkchat = gtkconv->u.chat; |
| |
5378 |
| |
5379 num_users = g_list_length(purple_conv_chat_get_users(chat)); |
| |
5380 |
| |
5381 g_snprintf(tmp, sizeof(tmp), |
| |
5382 ngettext("%d person in room", "%d people in room", |
| |
5383 num_users), |
| |
5384 num_users); |
| |
5385 |
| |
5386 gtk_label_set_text(GTK_LABEL(gtkchat->count), tmp); |
| |
5387 |
| |
5388 ls = GTK_LIST_STORE(gtk_tree_view_get_model(GTK_TREE_VIEW(gtkchat->list))); |
| |
5389 |
| |
5390 #if GTK_CHECK_VERSION(2,6,0) |
| |
5391 gtk_tree_sortable_set_sort_column_id(GTK_TREE_SORTABLE(ls), GTK_TREE_SORTABLE_UNSORTED_SORT_COLUMN_ID, |
| |
5392 GTK_TREE_SORTABLE_UNSORTED_SORT_COLUMN_ID); |
| |
5393 #endif |
| |
5394 |
| |
5395 l = cbuddies; |
| |
5396 while (l != NULL) { |
| |
5397 add_chat_buddy_common(conv, (PurpleConvChatBuddy *)l->data, NULL); |
| |
5398 l = l->next; |
| |
5399 } |
| |
5400 |
| |
5401 /* Currently GTK+ maintains our sorted list after it's in the tree. |
| |
5402 * This may change if it turns out we can manage it faster ourselves. |
| |
5403 */ |
| |
5404 gtk_tree_sortable_set_sort_column_id(GTK_TREE_SORTABLE(ls), CHAT_USERS_ALIAS_KEY_COLUMN, |
| |
5405 GTK_SORT_ASCENDING); |
| |
5406 } |
| |
5407 |
| |
5408 static void |
| |
5409 pidgin_conv_chat_rename_user(PurpleConversation *conv, const char *old_name, |
| |
5410 const char *new_name, const char *new_alias) |
| |
5411 { |
| |
5412 PurpleConvChat *chat; |
| |
5413 PidginConversation *gtkconv; |
| |
5414 PidginChatPane *gtkchat; |
| |
5415 PurpleConvChatBuddyFlags flags; |
| |
5416 PurpleConvChatBuddy *cbuddy; |
| |
5417 GtkTreeIter iter; |
| |
5418 GtkTreeModel *model; |
| |
5419 int f = 1; |
| |
5420 |
| |
5421 chat = PURPLE_CONV_CHAT(conv); |
| |
5422 gtkconv = PIDGIN_CONVERSATION(conv); |
| |
5423 gtkchat = gtkconv->u.chat; |
| |
5424 |
| |
5425 model = gtk_tree_view_get_model(GTK_TREE_VIEW(gtkchat->list)); |
| |
5426 |
| |
5427 if (!gtk_tree_model_get_iter_first(GTK_TREE_MODEL(model), &iter)) |
| |
5428 return; |
| |
5429 |
| |
5430 while (f != 0) { |
| |
5431 char *val; |
| |
5432 |
| |
5433 gtk_tree_model_get(GTK_TREE_MODEL(model), &iter, CHAT_USERS_NAME_COLUMN, &val, CHAT_USERS_FLAGS_COLUMN, &flags, -1); |
| |
5434 |
| |
5435 if (!purple_utf8_strcasecmp(old_name, val)) { |
| |
5436 gtk_list_store_remove(GTK_LIST_STORE(model), &iter); |
| |
5437 g_free(val); |
| |
5438 break; |
| |
5439 } |
| |
5440 |
| |
5441 f = gtk_tree_model_iter_next(GTK_TREE_MODEL(model), &iter); |
| |
5442 |
| |
5443 g_free(val); |
| |
5444 } |
| |
5445 |
| |
5446 if (!purple_conv_chat_find_user(chat, old_name)) |
| |
5447 return; |
| |
5448 |
| |
5449 g_return_if_fail(new_alias != NULL); |
| |
5450 |
| |
5451 cbuddy = purple_conv_chat_cb_new(new_name, new_alias, flags); |
| |
5452 |
| |
5453 add_chat_buddy_common(conv, cbuddy, old_name); |
| |
5454 } |
| |
5455 |
| |
5456 static void |
| |
5457 pidgin_conv_chat_remove_users(PurpleConversation *conv, GList *users) |
| |
5458 { |
| |
5459 PurpleConvChat *chat; |
| |
5460 PidginConversation *gtkconv; |
| |
5461 PidginChatPane *gtkchat; |
| |
5462 GtkTreeIter iter; |
| |
5463 GtkTreeModel *model; |
| |
5464 GList *l; |
| |
5465 char tmp[BUF_LONG]; |
| |
5466 int num_users; |
| |
5467 gboolean f; |
| |
5468 |
| |
5469 chat = PURPLE_CONV_CHAT(conv); |
| |
5470 gtkconv = PIDGIN_CONVERSATION(conv); |
| |
5471 gtkchat = gtkconv->u.chat; |
| |
5472 |
| |
5473 num_users = g_list_length(purple_conv_chat_get_users(chat)); |
| |
5474 |
| |
5475 for (l = users; l != NULL; l = l->next) { |
| |
5476 model = gtk_tree_view_get_model(GTK_TREE_VIEW(gtkchat->list)); |
| |
5477 |
| |
5478 if (!gtk_tree_model_get_iter_first(GTK_TREE_MODEL(model), &iter)) |
| |
5479 continue; |
| |
5480 |
| |
5481 do { |
| |
5482 char *val; |
| |
5483 |
| |
5484 gtk_tree_model_get(GTK_TREE_MODEL(model), &iter, |
| |
5485 CHAT_USERS_NAME_COLUMN, &val, -1); |
| |
5486 |
| |
5487 if (!purple_utf8_strcasecmp((char *)l->data, val)) { |
| |
5488 #if GTK_CHECK_VERSION(2,2,0) |
| |
5489 f = gtk_list_store_remove(GTK_LIST_STORE(model), &iter); |
| |
5490 #else |
| |
5491 gtk_list_store_remove(GTK_LIST_STORE(model), &iter); |
| |
5492 f = gtk_tree_model_iter_next(GTK_TREE_MODEL(model), &iter); |
| |
5493 #endif |
| |
5494 } |
| |
5495 else |
| |
5496 f = gtk_tree_model_iter_next(GTK_TREE_MODEL(model), &iter); |
| |
5497 |
| |
5498 g_free(val); |
| |
5499 } while (f); |
| |
5500 } |
| |
5501 |
| |
5502 g_snprintf(tmp, sizeof(tmp), |
| |
5503 ngettext("%d person in room", "%d people in room", |
| |
5504 num_users), num_users); |
| |
5505 |
| |
5506 gtk_label_set_text(GTK_LABEL(gtkchat->count), tmp); |
| |
5507 } |
| |
5508 |
| |
5509 static void |
| |
5510 pidgin_conv_chat_update_user(PurpleConversation *conv, const char *user) |
| |
5511 { |
| |
5512 PurpleConvChat *chat; |
| |
5513 PurpleConvChatBuddyFlags flags; |
| |
5514 PurpleConvChatBuddy *cbuddy; |
| |
5515 PidginConversation *gtkconv; |
| |
5516 PidginChatPane *gtkchat; |
| |
5517 GtkTreeIter iter; |
| |
5518 GtkTreeModel *model; |
| |
5519 int f = 1; |
| |
5520 char *alias = NULL; |
| |
5521 |
| |
5522 chat = PURPLE_CONV_CHAT(conv); |
| |
5523 gtkconv = PIDGIN_CONVERSATION(conv); |
| |
5524 gtkchat = gtkconv->u.chat; |
| |
5525 |
| |
5526 model = gtk_tree_view_get_model(GTK_TREE_VIEW(gtkchat->list)); |
| |
5527 |
| |
5528 if (!gtk_tree_model_get_iter_first(GTK_TREE_MODEL(model), &iter)) |
| |
5529 return; |
| |
5530 |
| |
5531 while (f != 0) { |
| |
5532 char *val; |
| |
5533 |
| |
5534 gtk_tree_model_get(GTK_TREE_MODEL(model), &iter, CHAT_USERS_NAME_COLUMN, &val, -1); |
| |
5535 |
| |
5536 if (!purple_utf8_strcasecmp(user, val)) { |
| |
5537 gtk_tree_model_get(GTK_TREE_MODEL(model), &iter, CHAT_USERS_ALIAS_COLUMN, &alias, -1); |
| |
5538 gtk_list_store_remove(GTK_LIST_STORE(model), &iter); |
| |
5539 g_free(val); |
| |
5540 break; |
| |
5541 } |
| |
5542 |
| |
5543 f = gtk_tree_model_iter_next(GTK_TREE_MODEL(model), &iter); |
| |
5544 |
| |
5545 g_free(val); |
| |
5546 } |
| |
5547 |
| |
5548 if (!purple_conv_chat_find_user(chat, user)) |
| |
5549 { |
| |
5550 g_free(alias); |
| |
5551 return; |
| |
5552 } |
| |
5553 |
| |
5554 g_return_if_fail(alias != NULL); |
| |
5555 |
| |
5556 flags = purple_conv_chat_user_get_flags(chat, user); |
| |
5557 |
| |
5558 cbuddy = purple_conv_chat_cb_new(user, alias, flags); |
| |
5559 |
| |
5560 add_chat_buddy_common(conv, cbuddy, NULL); |
| |
5561 g_free(alias); |
| |
5562 } |
| |
5563 |
| |
5564 gboolean |
| |
5565 pidgin_conv_has_focus(PurpleConversation *conv) |
| |
5566 { |
| |
5567 PidginConversation *gtkconv = PIDGIN_CONVERSATION(conv); |
| |
5568 PidginWindow *win; |
| |
5569 gboolean has_focus; |
| |
5570 |
| |
5571 win = gtkconv->win; |
| |
5572 |
| |
5573 g_object_get(G_OBJECT(win->window), "has-toplevel-focus", &has_focus, NULL); |
| |
5574 |
| |
5575 if (has_focus && pidgin_conv_window_is_active_conversation(conv)) |
| |
5576 return TRUE; |
| |
5577 |
| |
5578 return FALSE; |
| |
5579 } |
| |
5580 |
| |
5581 static void pidgin_conv_custom_smiley_allocated(GdkPixbufLoader *loader, gpointer user_data) |
| |
5582 { |
| |
5583 GtkIMHtmlSmiley *smiley; |
| |
5584 |
| |
5585 smiley = (GtkIMHtmlSmiley *)user_data; |
| |
5586 smiley->icon = gdk_pixbuf_loader_get_animation(loader); |
| |
5587 |
| |
5588 if (smiley->icon) |
| |
5589 g_object_ref(G_OBJECT(smiley->icon)); |
| |
5590 #ifdef DEBUG_CUSTOM_SMILEY |
| |
5591 purple_debug_info("custom-smiley", "pidgin_conv_custom_smiley_allocated(): got GdkPixbufAnimation %p for smiley '%s'\n", smiley->icon, smiley->smile); |
| |
5592 #endif |
| |
5593 } |
| |
5594 |
| |
5595 static void pidgin_conv_custom_smiley_closed(GdkPixbufLoader *loader, gpointer user_data) |
| |
5596 { |
| |
5597 GtkIMHtmlSmiley *smiley; |
| |
5598 GtkWidget *icon = NULL; |
| |
5599 GtkTextChildAnchor *anchor = NULL; |
| |
5600 GSList *current = NULL; |
| |
5601 |
| |
5602 smiley = (GtkIMHtmlSmiley *)user_data; |
| |
5603 if (!smiley->imhtml) { |
| |
5604 #ifdef DEBUG_CUSTOM_SMILEY |
| |
5605 purple_debug_error("custom-smiley", "pidgin_conv_custom_smiley_closed(): orphan smiley found: %p\n", smiley); |
| |
5606 #endif |
| |
5607 g_object_unref(G_OBJECT(loader)); |
| |
5608 smiley->loader = NULL; |
| |
5609 return; |
| |
5610 } |
| |
5611 |
| |
5612 for (current = smiley->anchors; current; current = g_slist_next(current)) { |
| |
5613 |
| |
5614 icon = gtk_image_new_from_animation(smiley->icon); |
| |
5615 |
| |
5616 #ifdef DEBUG_CUSTOM_SMILEY |
| |
5617 purple_debug_info("custom-smiley", "pidgin_conv_custom_smiley_closed(): got GtkImage %p from GtkPixbufAnimation %p for smiley '%s'\n", |
| |
5618 icon, smiley->icon, smiley->smile); |
| |
5619 #endif |
| |
5620 if (icon) { |
| |
5621 gtk_widget_show(icon); |
| |
5622 |
| |
5623 anchor = GTK_TEXT_CHILD_ANCHOR(current->data); |
| |
5624 |
| |
5625 g_object_set_data_full(G_OBJECT(anchor), "gtkimhtml_plaintext", purple_unescape_html(smiley->smile), g_free); |
| |
5626 g_object_set_data_full(G_OBJECT(anchor), "gtkimhtml_htmltext", g_strdup(smiley->smile), g_free); |
| |
5627 |
| |
5628 if (smiley->imhtml) |
| |
5629 gtk_text_view_add_child_at_anchor(GTK_TEXT_VIEW(smiley->imhtml), icon, anchor); |
| |
5630 } |
| |
5631 |
| |
5632 } |
| |
5633 |
| |
5634 g_slist_free(smiley->anchors); |
| |
5635 smiley->anchors = NULL; |
| |
5636 |
| |
5637 g_object_unref(G_OBJECT(loader)); |
| |
5638 smiley->loader = NULL; |
| |
5639 } |
| |
5640 |
| |
5641 static gboolean |
| |
5642 add_custom_smiley_for_imhtml(GtkIMHtml *imhtml, const char *sml, const char *smile) |
| |
5643 { |
| |
5644 GtkIMHtmlSmiley *smiley; |
| |
5645 GdkPixbufLoader *loader; |
| |
5646 |
| |
5647 smiley = gtk_imhtml_smiley_get(imhtml, sml, smile); |
| |
5648 |
| |
5649 if (smiley) { |
| |
5650 |
| |
5651 if (!(smiley->flags & GTK_IMHTML_SMILEY_CUSTOM)) { |
| |
5652 return FALSE; |
| |
5653 } |
| |
5654 |
| |
5655 /* Close the old GdkPixbufAnimation, then create a new one for |
| |
5656 * the smiley we are about to receive */ |
| |
5657 g_object_unref(G_OBJECT(smiley->icon)); |
| |
5658 |
| |
5659 /* XXX: Is it necessary to _unref the loader first? */ |
| |
5660 smiley->loader = gdk_pixbuf_loader_new(); |
| |
5661 smiley->icon = NULL; |
| |
5662 |
| |
5663 g_signal_connect(smiley->loader, "area_prepared", G_CALLBACK(pidgin_conv_custom_smiley_allocated), smiley); |
| |
5664 g_signal_connect(smiley->loader, "closed", G_CALLBACK(pidgin_conv_custom_smiley_closed), smiley); |
| |
5665 |
| |
5666 return TRUE; |
| |
5667 } |
| |
5668 |
| |
5669 loader = gdk_pixbuf_loader_new(); |
| |
5670 |
| |
5671 /* this is wrong, this file ought not call g_new on GtkIMHtmlSmiley */ |
| |
5672 /* Let gtk_imhtml have a gtk_imhtml_smiley_new function, and let |
| |
5673 GtkIMHtmlSmiley by opaque */ |
| |
5674 smiley = g_new0(GtkIMHtmlSmiley, 1); |
| |
5675 smiley->file = NULL; |
| |
5676 smiley->smile = g_strdup(smile); |
| |
5677 smiley->loader = loader; |
| |
5678 smiley->flags = smiley->flags | GTK_IMHTML_SMILEY_CUSTOM; |
| |
5679 |
| |
5680 g_signal_connect(smiley->loader, "area_prepared", G_CALLBACK(pidgin_conv_custom_smiley_allocated), smiley); |
| |
5681 g_signal_connect(smiley->loader, "closed", G_CALLBACK(pidgin_conv_custom_smiley_closed), smiley); |
| |
5682 |
| |
5683 gtk_imhtml_associate_smiley(imhtml, sml, smiley); |
| |
5684 |
| |
5685 return TRUE; |
| |
5686 } |
| |
5687 |
| |
5688 static gboolean |
| |
5689 pidgin_conv_custom_smiley_add(PurpleConversation *conv, const char *smile, gboolean remote) |
| |
5690 { |
| |
5691 PidginConversation *gtkconv; |
| |
5692 struct smiley_list *list; |
| |
5693 const char *sml = NULL, *conv_sml; |
| |
5694 |
| |
5695 if (!conv || !smile || !*smile) { |
| |
5696 return FALSE; |
| |
5697 } |
| |
5698 |
| |
5699 /* If smileys are off, return false */ |
| |
5700 if (pidginthemes_smileys_disabled()) |
| |
5701 return FALSE; |
| |
5702 |
| |
5703 /* If possible add this smiley to the current theme. |
| |
5704 * The addition is only temporary: custom smilies aren't saved to disk. */ |
| |
5705 conv_sml = purple_account_get_protocol_name(conv->account); |
| |
5706 gtkconv = PIDGIN_CONVERSATION(conv); |
| |
5707 |
| |
5708 for (list = (struct smiley_list *)current_smiley_theme->list; list; list = list->next) { |
| |
5709 if (!strcmp(list->sml, conv_sml)) { |
| |
5710 sml = list->sml; |
| |
5711 break; |
| |
5712 } |
| |
5713 } |
| |
5714 |
| |
5715 if (!add_custom_smiley_for_imhtml(GTK_IMHTML(gtkconv->imhtml), sml, smile)) |
| |
5716 return FALSE; |
| |
5717 |
| |
5718 if (!remote) /* If it's a local custom smiley, then add it for the entry */ |
| |
5719 if (!add_custom_smiley_for_imhtml(GTK_IMHTML(gtkconv->entry), sml, smile)) |
| |
5720 return FALSE; |
| |
5721 |
| |
5722 return TRUE; |
| |
5723 } |
| |
5724 |
| |
5725 static void |
| |
5726 pidgin_conv_custom_smiley_write(PurpleConversation *conv, const char *smile, |
| |
5727 const guchar *data, gsize size) |
| |
5728 { |
| |
5729 PidginConversation *gtkconv; |
| |
5730 GtkIMHtmlSmiley *smiley; |
| |
5731 GdkPixbufLoader *loader; |
| |
5732 const char *sml; |
| |
5733 |
| |
5734 sml = purple_account_get_protocol_name(conv->account); |
| |
5735 gtkconv = PIDGIN_CONVERSATION(conv); |
| |
5736 smiley = gtk_imhtml_smiley_get(GTK_IMHTML(gtkconv->imhtml), sml, smile); |
| |
5737 |
| |
5738 if (!smiley) |
| |
5739 return; |
| |
5740 |
| |
5741 loader = smiley->loader; |
| |
5742 if (!loader) |
| |
5743 return; |
| |
5744 |
| |
5745 gdk_pixbuf_loader_write(loader, data, size, NULL); |
| |
5746 } |
| |
5747 |
| |
5748 static void |
| |
5749 pidgin_conv_custom_smiley_close(PurpleConversation *conv, const char *smile) |
| |
5750 { |
| |
5751 PidginConversation *gtkconv; |
| |
5752 GtkIMHtmlSmiley *smiley; |
| |
5753 GdkPixbufLoader *loader; |
| |
5754 const char *sml; |
| |
5755 |
| |
5756 g_return_if_fail(conv != NULL); |
| |
5757 g_return_if_fail(smile != NULL); |
| |
5758 |
| |
5759 sml = purple_account_get_protocol_name(conv->account); |
| |
5760 gtkconv = PIDGIN_CONVERSATION(conv); |
| |
5761 smiley = gtk_imhtml_smiley_get(GTK_IMHTML(gtkconv->imhtml), sml, smile); |
| |
5762 |
| |
5763 if (!smiley) |
| |
5764 return; |
| |
5765 |
| |
5766 loader = smiley->loader; |
| |
5767 |
| |
5768 if (!loader) |
| |
5769 return; |
| |
5770 |
| |
5771 |
| |
5772 |
| |
5773 purple_debug_info("gtkconv", "About to close the smiley pixbuf\n"); |
| |
5774 |
| |
5775 gdk_pixbuf_loader_close(loader, NULL); |
| |
5776 |
| |
5777 } |
| |
5778 |
| |
5779 static void |
| |
5780 pidgin_conv_send_confirm(PurpleConversation *conv, const char *message) |
| |
5781 { |
| |
5782 PidginConversation *gtkconv = PIDGIN_CONVERSATION(conv); |
| |
5783 |
| |
5784 gtk_imhtml_append_text(GTK_IMHTML(gtkconv->entry), message, 0); |
| |
5785 } |
| |
5786 |
| |
5787 /* |
| |
5788 * Makes sure all the menu items and all the buttons are hidden/shown and |
| |
5789 * sensitive/insensitive. This is called after changing tabs and when an |
| |
5790 * account signs on or off. |
| |
5791 */ |
| |
5792 static void |
| |
5793 gray_stuff_out(PidginConversation *gtkconv) |
| |
5794 { |
| |
5795 PidginWindow *win; |
| |
5796 PurpleConversation *conv = gtkconv->active_conv; |
| |
5797 PurpleConnection *gc; |
| |
5798 PurplePluginProtocolInfo *prpl_info = NULL; |
| |
5799 GdkPixbuf *window_icon = NULL; |
| |
5800 GtkIMHtmlButtons buttons; |
| |
5801 PurpleAccount *account; |
| |
5802 |
| |
5803 win = pidgin_conv_get_window(gtkconv); |
| |
5804 gc = purple_conversation_get_gc(conv); |
| |
5805 account = purple_conversation_get_account(conv); |
| |
5806 |
| |
5807 if (gc != NULL) |
| |
5808 prpl_info = PURPLE_PLUGIN_PROTOCOL_INFO(gc->prpl); |
| |
5809 |
| |
5810 if (win->menu.send_to != NULL) |
| |
5811 update_send_to_selection(win); |
| |
5812 |
| |
5813 /* |
| |
5814 * Handle hiding and showing stuff based on what type of conv this is. |
| |
5815 * Stuff that Purple IMs support in general should be shown for IM |
| |
5816 * conversations. Stuff that Purple chats support in general should be |
| |
5817 * shown for chat conversations. It doesn't matter whether the PRPL |
| |
5818 * supports it or not--that only affects if the button or menu item |
| |
5819 * is sensitive or not. |
| |
5820 */ |
| |
5821 if (purple_conversation_get_type(conv) == PURPLE_CONV_TYPE_IM) { |
| |
5822 /* Show stuff that applies to IMs, hide stuff that applies to chats */ |
| |
5823 |
| |
5824 /* Deal with menu items */ |
| |
5825 gtk_widget_show(win->menu.view_log); |
| |
5826 gtk_widget_show(win->menu.send_file); |
| |
5827 gtk_widget_show(win->menu.add_pounce); |
| |
5828 gtk_widget_show(win->menu.get_info); |
| |
5829 gtk_widget_hide(win->menu.invite); |
| |
5830 gtk_widget_show(win->menu.alias); |
| |
5831 if (purple_privacy_check(account, purple_conversation_get_name(conv))) { |
| |
5832 gtk_widget_hide(win->menu.unblock); |
| |
5833 gtk_widget_show(win->menu.block); |
| |
5834 } else { |
| |
5835 gtk_widget_hide(win->menu.block); |
| |
5836 gtk_widget_show(win->menu.unblock); |
| |
5837 } |
| |
5838 |
| |
5839 if ((account == NULL) || purple_find_buddy(account, purple_conversation_get_name(conv)) == NULL) { |
| |
5840 gtk_widget_show(win->menu.add); |
| |
5841 gtk_widget_hide(win->menu.remove); |
| |
5842 } else { |
| |
5843 gtk_widget_show(win->menu.remove); |
| |
5844 gtk_widget_hide(win->menu.add); |
| |
5845 } |
| |
5846 |
| |
5847 gtk_widget_show(win->menu.show_icon); |
| |
5848 } else if (purple_conversation_get_type(conv) == PURPLE_CONV_TYPE_CHAT) { |
| |
5849 /* Show stuff that applies to Chats, hide stuff that applies to IMs */ |
| |
5850 |
| |
5851 /* Deal with menu items */ |
| |
5852 gtk_widget_show(win->menu.view_log); |
| |
5853 gtk_widget_hide(win->menu.send_file); |
| |
5854 gtk_widget_hide(win->menu.add_pounce); |
| |
5855 gtk_widget_hide(win->menu.get_info); |
| |
5856 gtk_widget_show(win->menu.invite); |
| |
5857 gtk_widget_show(win->menu.alias); |
| |
5858 gtk_widget_hide(win->menu.block); |
| |
5859 gtk_widget_hide(win->menu.unblock); |
| |
5860 gtk_widget_hide(win->menu.show_icon); |
| |
5861 |
| |
5862 if ((account == NULL) || purple_blist_find_chat(account, purple_conversation_get_name(conv)) == NULL) { |
| |
5863 /* If the chat is NOT in the buddy list */ |
| |
5864 gtk_widget_show(win->menu.add); |
| |
5865 gtk_widget_hide(win->menu.remove); |
| |
5866 } else { |
| |
5867 /* If the chat IS in the buddy list */ |
| |
5868 gtk_widget_hide(win->menu.add); |
| |
5869 gtk_widget_show(win->menu.remove); |
| |
5870 } |
| |
5871 |
| |
5872 } |
| |
5873 |
| |
5874 /* |
| |
5875 * Handle graying stuff out based on whether an account is connected |
| |
5876 * and what features that account supports. |
| |
5877 */ |
| |
5878 if ((gc != NULL) && |
| |
5879 ((purple_conversation_get_type(conv) != PURPLE_CONV_TYPE_CHAT) || |
| |
5880 !purple_conv_chat_has_left(PURPLE_CONV_CHAT(conv)) )) |
| |
5881 { |
| |
5882 /* Account is online */ |
| |
5883 /* Deal with the toolbar */ |
| |
5884 if (conv->features & PURPLE_CONNECTION_HTML) |
| |
5885 { |
| |
5886 buttons = GTK_IMHTML_ALL; /* Everything on */ |
| |
5887 if (conv->features & PURPLE_CONNECTION_NO_BGCOLOR) |
| |
5888 buttons &= ~GTK_IMHTML_BACKCOLOR; |
| |
5889 if (conv->features & PURPLE_CONNECTION_NO_FONTSIZE) |
| |
5890 { |
| |
5891 buttons &= ~GTK_IMHTML_GROW; |
| |
5892 buttons &= ~GTK_IMHTML_SHRINK; |
| |
5893 } |
| |
5894 if (conv->features & PURPLE_CONNECTION_NO_URLDESC) |
| |
5895 buttons &= ~GTK_IMHTML_LINKDESC; |
| |
5896 } else { |
| |
5897 buttons = GTK_IMHTML_SMILEY | GTK_IMHTML_IMAGE; |
| |
5898 } |
| |
5899 |
| |
5900 if (!(prpl_info->options & OPT_PROTO_IM_IMAGE) || |
| |
5901 conv->features & PURPLE_CONNECTION_NO_IMAGES) |
| |
5902 buttons &= ~GTK_IMHTML_IMAGE; |
| |
5903 |
| |
5904 gtk_imhtml_set_format_functions(GTK_IMHTML(gtkconv->entry), buttons); |
| |
5905 if (account != NULL) |
| |
5906 gtk_imhtmltoolbar_associate_smileys(GTK_IMHTMLTOOLBAR(gtkconv->toolbar), purple_account_get_protocol_id(account)); |
| |
5907 |
| |
5908 /* Deal with menu items */ |
| |
5909 gtk_widget_set_sensitive(win->menu.view_log, TRUE); |
| |
5910 gtk_widget_set_sensitive(win->menu.add_pounce, TRUE); |
| |
5911 gtk_widget_set_sensitive(win->menu.get_info, (prpl_info->get_info != NULL)); |
| |
5912 gtk_widget_set_sensitive(win->menu.invite, (prpl_info->chat_invite != NULL)); |
| |
5913 |
| |
5914 if (purple_conversation_get_type(conv) == PURPLE_CONV_TYPE_IM) |
| |
5915 { |
| |
5916 gtk_widget_set_sensitive(win->menu.add, (prpl_info->add_buddy != NULL)); |
| |
5917 gtk_widget_set_sensitive(win->menu.remove, (prpl_info->remove_buddy != NULL)); |
| |
5918 gtk_widget_set_sensitive(win->menu.send_file, |
| |
5919 (prpl_info->send_file != NULL && (!prpl_info->can_receive_file || |
| |
5920 prpl_info->can_receive_file(gc, purple_conversation_get_name(conv))))); |
| |
5921 gtk_widget_set_sensitive(win->menu.alias, |
| |
5922 (account != NULL) && |
| |
5923 (purple_find_buddy(account, purple_conversation_get_name(conv)) != NULL)); |
| |
5924 } |
| |
5925 else if (purple_conversation_get_type(conv) == PURPLE_CONV_TYPE_CHAT) |
| |
5926 { |
| |
5927 gtk_widget_set_sensitive(win->menu.add, (prpl_info->join_chat != NULL)); |
| |
5928 gtk_widget_set_sensitive(win->menu.remove, (prpl_info->join_chat != NULL)); |
| |
5929 gtk_widget_set_sensitive(win->menu.alias, |
| |
5930 (account != NULL) && |
| |
5931 (purple_blist_find_chat(account, purple_conversation_get_name(conv)) != NULL)); |
| |
5932 } |
| |
5933 |
| |
5934 } else { |
| |
5935 /* Account is offline */ |
| |
5936 /* Or it's a chat that we've left. */ |
| |
5937 |
| |
5938 /* Then deal with menu items */ |
| |
5939 gtk_widget_set_sensitive(win->menu.view_log, TRUE); |
| |
5940 gtk_widget_set_sensitive(win->menu.send_file, FALSE); |
| |
5941 gtk_widget_set_sensitive(win->menu.add_pounce, TRUE); |
| |
5942 gtk_widget_set_sensitive(win->menu.get_info, FALSE); |
| |
5943 gtk_widget_set_sensitive(win->menu.invite, FALSE); |
| |
5944 gtk_widget_set_sensitive(win->menu.alias, FALSE); |
| |
5945 gtk_widget_set_sensitive(win->menu.add, FALSE); |
| |
5946 gtk_widget_set_sensitive(win->menu.remove, FALSE); |
| |
5947 } |
| |
5948 |
| |
5949 /* |
| |
5950 * Update the window's icon |
| |
5951 */ |
| |
5952 if (pidgin_conv_window_is_active_conversation(conv)) |
| |
5953 { |
| |
5954 GList *l = NULL; |
| |
5955 if ((purple_conversation_get_type(conv) == PURPLE_CONV_TYPE_IM) && |
| |
5956 (gtkconv->u.im->anim)) |
| |
5957 { |
| |
5958 window_icon = |
| |
5959 gdk_pixbuf_animation_get_static_image(gtkconv->u.im->anim); |
| |
5960 g_object_ref(window_icon); |
| |
5961 l = g_list_append(l, window_icon); |
| |
5962 } else { |
| |
5963 l = pidgin_conv_get_tab_icons(conv); |
| |
5964 } |
| |
5965 gtk_window_set_icon_list(GTK_WINDOW(win->window), l); |
| |
5966 if (window_icon != NULL) { |
| |
5967 g_object_unref(G_OBJECT(window_icon)); |
| |
5968 g_list_free(l); |
| |
5969 } |
| |
5970 } |
| |
5971 } |
| |
5972 |
| |
5973 static void |
| |
5974 pidgin_conv_update_fields(PurpleConversation *conv, PidginConvFields fields) |
| |
5975 { |
| |
5976 PidginConversation *gtkconv; |
| |
5977 PidginWindow *win; |
| |
5978 |
| |
5979 gtkconv = PIDGIN_CONVERSATION(conv); |
| |
5980 if (!gtkconv) |
| |
5981 return; |
| |
5982 win = pidgin_conv_get_window(gtkconv); |
| |
5983 if (!win) |
| |
5984 return; |
| |
5985 |
| |
5986 if (fields & PIDGIN_CONV_SET_TITLE) |
| |
5987 { |
| |
5988 purple_conversation_autoset_title(conv); |
| |
5989 } |
| |
5990 |
| |
5991 if (fields & PIDGIN_CONV_BUDDY_ICON) |
| |
5992 { |
| |
5993 if (purple_conversation_get_type(conv) == PURPLE_CONV_TYPE_IM) |
| |
5994 pidgin_conv_update_buddy_icon(conv); |
| |
5995 } |
| |
5996 |
| |
5997 if (fields & PIDGIN_CONV_MENU) |
| |
5998 { |
| |
5999 gray_stuff_out(PIDGIN_CONVERSATION(conv)); |
| |
6000 generate_send_to_items(win); |
| |
6001 } |
| |
6002 |
| |
6003 if (fields & PIDGIN_CONV_TAB_ICON) |
| |
6004 { |
| |
6005 update_tab_icon(conv); |
| |
6006 generate_send_to_items(win); /* To update the icons in SendTo menu */ |
| |
6007 } |
| |
6008 |
| |
6009 if ((fields & PIDGIN_CONV_TOPIC) && |
| |
6010 purple_conversation_get_type(conv) == PURPLE_CONV_TYPE_CHAT) |
| |
6011 { |
| |
6012 const char *topic; |
| |
6013 PurpleConvChat *chat = PURPLE_CONV_CHAT(conv); |
| |
6014 PidginChatPane *gtkchat = gtkconv->u.chat; |
| |
6015 |
| |
6016 if (gtkchat->topic_text != NULL) |
| |
6017 { |
| |
6018 topic = purple_conv_chat_get_topic(chat); |
| |
6019 |
| |
6020 gtk_entry_set_text(GTK_ENTRY(gtkchat->topic_text), topic ? topic : ""); |
| |
6021 gtk_tooltips_set_tip(gtkconv->tooltips, gtkchat->topic_text, |
| |
6022 topic ? topic : "", NULL); |
| |
6023 } |
| |
6024 } |
| |
6025 |
| |
6026 if (fields & PIDGIN_CONV_SMILEY_THEME) |
| |
6027 pidginthemes_smiley_themeize(PIDGIN_CONVERSATION(conv)->imhtml); |
| |
6028 |
| |
6029 if ((fields & PIDGIN_CONV_COLORIZE_TITLE) || |
| |
6030 (fields & PIDGIN_CONV_SET_TITLE)) |
| |
6031 { |
| |
6032 char *title; |
| |
6033 PurpleConvIm *im = NULL; |
| |
6034 PurpleAccount *account = purple_conversation_get_account(conv); |
| |
6035 AtkObject *accessibility_obj; |
| |
6036 /* I think this is a little longer than it needs to be but I'm lazy. */ |
| |
6037 char style[51]; |
| |
6038 |
| |
6039 if (purple_conversation_get_type(conv) == PURPLE_CONV_TYPE_IM) |
| |
6040 im = PURPLE_CONV_IM(conv); |
| |
6041 |
| |
6042 if ((account == NULL) || |
| |
6043 !purple_account_is_connected(account) || |
| |
6044 ((purple_conversation_get_type(conv) == PURPLE_CONV_TYPE_CHAT) |
| |
6045 && purple_conv_chat_has_left(PURPLE_CONV_CHAT(conv)))) |
| |
6046 title = g_strdup_printf("(%s)", purple_conversation_get_title(conv)); |
| |
6047 else |
| |
6048 title = g_strdup(purple_conversation_get_title(conv)); |
| |
6049 |
| |
6050 *style = '\0'; |
| |
6051 |
| |
6052 if (!GTK_WIDGET_REALIZED(gtkconv->tab_label)) |
| |
6053 gtk_widget_realize(gtkconv->tab_label); |
| |
6054 |
| |
6055 accessibility_obj = gtk_widget_get_accessible(gtkconv->tab_cont); |
| |
6056 if (im != NULL && |
| |
6057 purple_conv_im_get_typing_state(im) == PURPLE_TYPING) |
| |
6058 { |
| |
6059 atk_object_set_description(accessibility_obj, _("Typing")); |
| |
6060 strncpy(style, "color=\"#47A046\"", sizeof(style)); |
| |
6061 } |
| |
6062 else if (im != NULL && |
| |
6063 purple_conv_im_get_typing_state(im) == PURPLE_TYPED) |
| |
6064 { |
| |
6065 atk_object_set_description(accessibility_obj, _("Stopped Typing")); |
| |
6066 strncpy(style, "color=\"#D1940C\"", sizeof(style)); |
| |
6067 } |
| |
6068 else if (gtkconv->unseen_state == PIDGIN_UNSEEN_NICK) |
| |
6069 { |
| |
6070 atk_object_set_description(accessibility_obj, _("Nick Said")); |
| |
6071 strncpy(style, "color=\"#0D4E91\" style=\"italic\" weight=\"bold\"", sizeof(style)); |
| |
6072 } |
| |
6073 else if (gtkconv->unseen_state == PIDGIN_UNSEEN_TEXT) |
| |
6074 { |
| |
6075 atk_object_set_description(accessibility_obj, _("Unread Messages")); |
| |
6076 strncpy(style, "color=\"#DF421E\" weight=\"bold\"", sizeof(style)); |
| |
6077 } |
| |
6078 else if (gtkconv->unseen_state == PIDGIN_UNSEEN_EVENT) |
| |
6079 { |
| |
6080 atk_object_set_description(accessibility_obj, _("New Event")); |
| |
6081 strncpy(style, "color=\"#868272\" style=\"italic\"", sizeof(style)); |
| |
6082 } |
| |
6083 |
| |
6084 if (*style != '\0') |
| |
6085 { |
| |
6086 char *html_title,*label; |
| |
6087 |
| |
6088 html_title = g_markup_escape_text(title, -1); |
| |
6089 |
| |
6090 label = g_strdup_printf("<span %s>%s</span>", |
| |
6091 style, html_title); |
| |
6092 g_free(html_title); |
| |
6093 gtk_label_set_markup(GTK_LABEL(gtkconv->tab_label), label); |
| |
6094 g_free(label); |
| |
6095 } |
| |
6096 else |
| |
6097 gtk_label_set_text(GTK_LABEL(gtkconv->tab_label), title); |
| |
6098 |
| |
6099 if (pidgin_conv_window_is_active_conversation(conv)) |
| |
6100 update_typing_icon(gtkconv); |
| |
6101 |
| |
6102 gtk_label_set_text(GTK_LABEL(gtkconv->menu_label), title); |
| |
6103 if (pidgin_conv_window_is_active_conversation(conv)) |
| |
6104 gtk_window_set_title(GTK_WINDOW(win->window), title); |
| |
6105 |
| |
6106 g_free(title); |
| |
6107 } |
| |
6108 } |
| |
6109 |
| |
6110 static void |
| |
6111 pidgin_conv_updated(PurpleConversation *conv, PurpleConvUpdateType type) |
| |
6112 { |
| |
6113 PidginConvFields flags = 0; |
| |
6114 |
| |
6115 g_return_if_fail(conv != NULL); |
| |
6116 |
| |
6117 if (type == PURPLE_CONV_UPDATE_ACCOUNT) |
| |
6118 { |
| |
6119 flags = PIDGIN_CONV_ALL; |
| |
6120 } |
| |
6121 else if (type == PURPLE_CONV_UPDATE_TYPING || |
| |
6122 type == PURPLE_CONV_UPDATE_UNSEEN || |
| |
6123 type == PURPLE_CONV_UPDATE_TITLE) |
| |
6124 { |
| |
6125 flags = PIDGIN_CONV_COLORIZE_TITLE; |
| |
6126 } |
| |
6127 else if (type == PURPLE_CONV_UPDATE_TOPIC) |
| |
6128 { |
| |
6129 flags = PIDGIN_CONV_TOPIC; |
| |
6130 } |
| |
6131 else if (type == PURPLE_CONV_ACCOUNT_ONLINE || |
| |
6132 type == PURPLE_CONV_ACCOUNT_OFFLINE) |
| |
6133 { |
| |
6134 flags = PIDGIN_CONV_MENU | PIDGIN_CONV_TAB_ICON | PIDGIN_CONV_SET_TITLE; |
| |
6135 } |
| |
6136 else if (type == PURPLE_CONV_UPDATE_AWAY) |
| |
6137 { |
| |
6138 flags = PIDGIN_CONV_TAB_ICON; |
| |
6139 } |
| |
6140 else if (type == PURPLE_CONV_UPDATE_ADD || |
| |
6141 type == PURPLE_CONV_UPDATE_REMOVE || |
| |
6142 type == PURPLE_CONV_UPDATE_CHATLEFT) |
| |
6143 { |
| |
6144 flags = PIDGIN_CONV_SET_TITLE | PIDGIN_CONV_MENU; |
| |
6145 } |
| |
6146 else if (type == PURPLE_CONV_UPDATE_ICON) |
| |
6147 { |
| |
6148 flags = PIDGIN_CONV_BUDDY_ICON; |
| |
6149 } |
| |
6150 else if (type == PURPLE_CONV_UPDATE_FEATURES) |
| |
6151 { |
| |
6152 flags = PIDGIN_CONV_MENU; |
| |
6153 } |
| |
6154 |
| |
6155 pidgin_conv_update_fields(conv, flags); |
| |
6156 } |
| |
6157 |
| |
6158 static PurpleConversationUiOps conversation_ui_ops = |
| |
6159 { |
| |
6160 pidgin_conv_new, |
| |
6161 pidgin_conv_destroy, /* destroy_conversation */ |
| |
6162 NULL, /* write_chat */ |
| |
6163 pidgin_conv_write_im, /* write_im */ |
| |
6164 pidgin_conv_write_conv, /* write_conv */ |
| |
6165 pidgin_conv_chat_add_users, /* chat_add_users */ |
| |
6166 pidgin_conv_chat_rename_user, /* chat_rename_user */ |
| |
6167 pidgin_conv_chat_remove_users, /* chat_remove_users */ |
| |
6168 pidgin_conv_chat_update_user, /* chat_update_user */ |
| |
6169 pidgin_conv_present_conversation, /* present */ |
| |
6170 pidgin_conv_has_focus, /* has_focus */ |
| |
6171 pidgin_conv_custom_smiley_add, /* custom_smiley_add */ |
| |
6172 pidgin_conv_custom_smiley_write, /* custom_smiley_write */ |
| |
6173 pidgin_conv_custom_smiley_close, /* custom_smiley_close */ |
| |
6174 pidgin_conv_send_confirm, /* send_confirm */ |
| |
6175 }; |
| |
6176 |
| |
6177 PurpleConversationUiOps * |
| |
6178 pidgin_conversations_get_conv_ui_ops(void) |
| |
6179 { |
| |
6180 return &conversation_ui_ops; |
| |
6181 } |
| |
6182 |
| |
6183 /************************************************************************** |
| |
6184 * Public conversation utility functions |
| |
6185 **************************************************************************/ |
| |
6186 void |
| |
6187 pidgin_conv_update_buddy_icon(PurpleConversation *conv) |
| |
6188 { |
| |
6189 PidginConversation *gtkconv; |
| |
6190 PidginWindow *win; |
| |
6191 |
| |
6192 GdkPixbufLoader *loader; |
| |
6193 GdkPixbufAnimation *anim; |
| |
6194 GError *err = NULL; |
| |
6195 |
| |
6196 const char *custom = NULL; |
| |
6197 const void *data = NULL; |
| |
6198 size_t len; |
| |
6199 |
| |
6200 GdkPixbuf *buf; |
| |
6201 |
| |
6202 GtkWidget *event; |
| |
6203 GtkWidget *frame; |
| |
6204 GdkPixbuf *scale; |
| |
6205 int scale_width, scale_height; |
| |
6206 |
| |
6207 PurpleAccount *account; |
| |
6208 PurplePluginProtocolInfo *prpl_info = NULL; |
| |
6209 |
| |
6210 PurpleBuddyIcon *icon; |
| |
6211 |
| |
6212 g_return_if_fail(conv != NULL); |
| |
6213 g_return_if_fail(PIDGIN_IS_PIDGIN_CONVERSATION(conv)); |
| |
6214 g_return_if_fail(purple_conversation_get_type(conv) == PURPLE_CONV_TYPE_IM); |
| |
6215 |
| |
6216 gtkconv = PIDGIN_CONVERSATION(conv); |
| |
6217 win = gtkconv->win; |
| |
6218 if (conv != gtkconv->active_conv) |
| |
6219 return; |
| |
6220 |
| |
6221 if (!gtkconv->u.im->show_icon) |
| |
6222 return; |
| |
6223 |
| |
6224 account = purple_conversation_get_account(conv); |
| |
6225 if(account && account->gc) |
| |
6226 prpl_info = PURPLE_PLUGIN_PROTOCOL_INFO(account->gc->prpl); |
| |
6227 |
| |
6228 /* Remove the current icon stuff */ |
| |
6229 if (gtkconv->u.im->icon_container != NULL) |
| |
6230 gtk_widget_destroy(gtkconv->u.im->icon_container); |
| |
6231 gtkconv->u.im->icon_container = NULL; |
| |
6232 |
| |
6233 if (gtkconv->u.im->anim != NULL) |
| |
6234 g_object_unref(G_OBJECT(gtkconv->u.im->anim)); |
| |
6235 |
| |
6236 gtkconv->u.im->anim = NULL; |
| |
6237 |
| |
6238 if (gtkconv->u.im->icon_timer != 0) |
| |
6239 g_source_remove(gtkconv->u.im->icon_timer); |
| |
6240 |
| |
6241 gtkconv->u.im->icon_timer = 0; |
| |
6242 |
| |
6243 if (gtkconv->u.im->iter != NULL) |
| |
6244 g_object_unref(G_OBJECT(gtkconv->u.im->iter)); |
| |
6245 |
| |
6246 gtkconv->u.im->iter = NULL; |
| |
6247 |
| |
6248 if (!purple_prefs_get_bool(PIDGIN_PREFS_ROOT "/conversations/im/show_buddy_icons")) |
| |
6249 return; |
| |
6250 |
| |
6251 if (purple_conversation_get_gc(conv) == NULL) |
| |
6252 return; |
| |
6253 |
| |
6254 custom = custom_icon_pref_name(gtkconv); |
| |
6255 if (custom) { |
| |
6256 /* There is a custom icon for this user */ |
| |
6257 char *contents = NULL; |
| |
6258 if (!g_file_get_contents(custom, &contents, &len, &err)) { |
| |
6259 purple_debug_warning("custom icon", "could not load custom icon %s for %s\n", |
| |
6260 custom, purple_conversation_get_name(conv)); |
| |
6261 g_error_free(err); |
| |
6262 err = NULL; |
| |
6263 } else |
| |
6264 data = contents; |
| |
6265 } |
| |
6266 |
| |
6267 if (data == NULL) { |
| |
6268 icon = purple_conv_im_get_icon(PURPLE_CONV_IM(conv)); |
| |
6269 |
| |
6270 if (icon == NULL) |
| |
6271 return; |
| |
6272 |
| |
6273 data = purple_buddy_icon_get_data(icon, &len); |
| |
6274 custom = NULL; |
| |
6275 } |
| |
6276 |
| |
6277 loader = gdk_pixbuf_loader_new(); |
| |
6278 gdk_pixbuf_loader_write(loader, data, len, NULL); |
| |
6279 gdk_pixbuf_loader_close(loader, &err); |
| |
6280 anim = gdk_pixbuf_loader_get_animation(loader); |
| |
6281 if (anim) |
| |
6282 g_object_ref(G_OBJECT(anim)); |
| |
6283 g_object_unref(loader); |
| |
6284 |
| |
6285 if (custom) |
| |
6286 g_free((void*)data); |
| |
6287 |
| |
6288 if (!anim) |
| |
6289 return; |
| |
6290 gtkconv->u.im->anim = anim; |
| |
6291 |
| |
6292 if (err) { |
| |
6293 purple_debug(PURPLE_DEBUG_ERROR, "gtkconv", |
| |
6294 "Buddy icon error: %s\n", err->message); |
| |
6295 g_error_free(err); |
| |
6296 } |
| |
6297 |
| |
6298 if (!gtkconv->u.im->anim) |
| |
6299 return; |
| |
6300 |
| |
6301 if (gdk_pixbuf_animation_is_static_image(gtkconv->u.im->anim)) { |
| |
6302 gtkconv->u.im->iter = NULL; |
| |
6303 buf = gdk_pixbuf_animation_get_static_image(gtkconv->u.im->anim); |
| |
6304 } else { |
| |
6305 gtkconv->u.im->iter = |
| |
6306 gdk_pixbuf_animation_get_iter(gtkconv->u.im->anim, NULL); /* LEAK */ |
| |
6307 buf = gdk_pixbuf_animation_iter_get_pixbuf(gtkconv->u.im->iter); |
| |
6308 if (gtkconv->u.im->animate) |
| |
6309 start_anim(NULL, gtkconv); |
| |
6310 } |
| |
6311 |
| |
6312 pidgin_buddy_icon_get_scale_size(buf, &prpl_info->icon_spec, |
| |
6313 PURPLE_ICON_SCALE_DISPLAY, &scale_width, &scale_height); |
| |
6314 scale = gdk_pixbuf_scale_simple(buf, |
| |
6315 MAX(gdk_pixbuf_get_width(buf) * scale_width / |
| |
6316 gdk_pixbuf_animation_get_width(gtkconv->u.im->anim), 1), |
| |
6317 MAX(gdk_pixbuf_get_height(buf) * scale_height / |
| |
6318 gdk_pixbuf_animation_get_height(gtkconv->u.im->anim), 1), |
| |
6319 GDK_INTERP_BILINEAR); |
| |
6320 |
| |
6321 gtkconv->u.im->icon_container = gtk_vbox_new(FALSE, 0); |
| |
6322 |
| |
6323 frame = gtk_frame_new(NULL); |
| |
6324 gtk_frame_set_shadow_type(GTK_FRAME(frame), GTK_SHADOW_NONE); |
| |
6325 gtk_box_pack_start(GTK_BOX(gtkconv->u.im->icon_container), frame, |
| |
6326 FALSE, FALSE, 0); |
| |
6327 |
| |
6328 event = gtk_event_box_new(); |
| |
6329 gtk_container_add(GTK_CONTAINER(frame), event); |
| |
6330 g_signal_connect(G_OBJECT(event), "button-press-event", |
| |
6331 G_CALLBACK(icon_menu), gtkconv); |
| |
6332 gtk_widget_show(event); |
| |
6333 |
| |
6334 gtkconv->u.im->icon = gtk_image_new_from_pixbuf(scale); |
| |
6335 gtkconv->auto_resize = TRUE; |
| |
6336 /* Reset the size request to allow the buddy icon to resize */ |
| |
6337 gtk_widget_set_size_request(gtkconv->lower_hbox, -1, -1); |
| |
6338 g_idle_add(reset_auto_resize_cb, gtkconv); |
| |
6339 gtk_widget_set_size_request(gtkconv->u.im->icon, scale_width, scale_height); |
| |
6340 gtk_container_add(GTK_CONTAINER(event), gtkconv->u.im->icon); |
| |
6341 gtk_widget_show(gtkconv->u.im->icon); |
| |
6342 |
| |
6343 g_object_unref(G_OBJECT(scale)); |
| |
6344 |
| |
6345 gtk_box_pack_start(GTK_BOX(gtkconv->lower_hbox), |
| |
6346 gtkconv->u.im->icon_container, FALSE, FALSE, 0); |
| |
6347 |
| |
6348 gtk_widget_show(gtkconv->u.im->icon_container); |
| |
6349 gtk_widget_show(frame); |
| |
6350 |
| |
6351 /* The buddy icon code needs badly to be fixed. */ |
| |
6352 if(pidgin_conv_window_is_active_conversation(conv)) |
| |
6353 { |
| |
6354 buf = gdk_pixbuf_animation_get_static_image(gtkconv->u.im->anim); |
| |
6355 gtk_window_set_icon(GTK_WINDOW(win->window), buf); |
| |
6356 } |
| |
6357 } |
| |
6358 |
| |
6359 void |
| |
6360 pidgin_conv_update_buttons_by_protocol(PurpleConversation *conv) |
| |
6361 { |
| |
6362 PidginWindow *win; |
| |
6363 |
| |
6364 if (!PIDGIN_IS_PIDGIN_CONVERSATION(conv)) |
| |
6365 return; |
| |
6366 |
| |
6367 win = PIDGIN_CONVERSATION(conv)->win; |
| |
6368 |
| |
6369 if (win != NULL && pidgin_conv_window_is_active_conversation(conv)) |
| |
6370 gray_stuff_out(PIDGIN_CONVERSATION(conv)); |
| |
6371 } |
| |
6372 |
| |
6373 int |
| |
6374 pidgin_conv_get_tab_at_xy(PidginWindow *win, int x, int y, gboolean *to_right) |
| |
6375 { |
| |
6376 gint nb_x, nb_y, x_rel, y_rel; |
| |
6377 GtkNotebook *notebook; |
| |
6378 GtkWidget *page, *tab; |
| |
6379 gint i, page_num = -1; |
| |
6380 gint count; |
| |
6381 gboolean horiz; |
| |
6382 |
| |
6383 if (to_right) |
| |
6384 *to_right = FALSE; |
| |
6385 |
| |
6386 notebook = GTK_NOTEBOOK(win->notebook); |
| |
6387 |
| |
6388 gdk_window_get_origin(win->notebook->window, &nb_x, &nb_y); |
| |
6389 x_rel = x - nb_x; |
| |
6390 y_rel = y - nb_y; |
| |
6391 |
| |
6392 horiz = (gtk_notebook_get_tab_pos(notebook) == GTK_POS_TOP || |
| |
6393 gtk_notebook_get_tab_pos(notebook) == GTK_POS_BOTTOM); |
| |
6394 |
| |
6395 #if GTK_CHECK_VERSION(2,2,0) |
| |
6396 count = gtk_notebook_get_n_pages(GTK_NOTEBOOK(notebook)); |
| |
6397 #else |
| |
6398 /* this is hacky, but it's only for Gtk 2.0.0... */ |
| |
6399 count = g_list_length(GTK_NOTEBOOK(notebook)->children); |
| |
6400 #endif |
| |
6401 |
| |
6402 for (i = 0; i < count; i++) { |
| |
6403 |
| |
6404 page = gtk_notebook_get_nth_page(GTK_NOTEBOOK(notebook), i); |
| |
6405 tab = gtk_notebook_get_tab_label(GTK_NOTEBOOK(notebook), page); |
| |
6406 |
| |
6407 /* Make sure the tab is not hidden beyond an arrow */ |
| |
6408 if (!GTK_WIDGET_DRAWABLE(tab)) |
| |
6409 continue; |
| |
6410 |
| |
6411 if (horiz) { |
| |
6412 if (x_rel >= tab->allocation.x - PIDGIN_HIG_BOX_SPACE && |
| |
6413 x_rel <= tab->allocation.x + tab->allocation.width + PIDGIN_HIG_BOX_SPACE) { |
| |
6414 page_num = i; |
| |
6415 |
| |
6416 if (to_right && x_rel >= tab->allocation.x + tab->allocation.width/2) |
| |
6417 *to_right = TRUE; |
| |
6418 |
| |
6419 break; |
| |
6420 } |
| |
6421 } else { |
| |
6422 if (y_rel >= tab->allocation.y - PIDGIN_HIG_BOX_SPACE && |
| |
6423 y_rel <= tab->allocation.y + tab->allocation.height + PIDGIN_HIG_BOX_SPACE) { |
| |
6424 page_num = i; |
| |
6425 |
| |
6426 if (to_right && y_rel >= tab->allocation.y + tab->allocation.height/2) |
| |
6427 *to_right = TRUE; |
| |
6428 |
| |
6429 break; |
| |
6430 } |
| |
6431 } |
| |
6432 } |
| |
6433 |
| |
6434 if (page_num == -1) { |
| |
6435 /* Add after the last tab */ |
| |
6436 page_num = count - 1; |
| |
6437 } |
| |
6438 |
| |
6439 return page_num; |
| |
6440 } |
| |
6441 |
| |
6442 static void |
| |
6443 close_on_tabs_pref_cb(const char *name, PurplePrefType type, |
| |
6444 gconstpointer value, gpointer data) |
| |
6445 { |
| |
6446 GList *l; |
| |
6447 PurpleConversation *conv; |
| |
6448 PidginConversation *gtkconv; |
| |
6449 |
| |
6450 for (l = purple_get_conversations(); l != NULL; l = l->next) { |
| |
6451 conv = (PurpleConversation *)l->data; |
| |
6452 |
| |
6453 if (!PIDGIN_IS_PIDGIN_CONVERSATION(conv)) |
| |
6454 continue; |
| |
6455 |
| |
6456 gtkconv = PIDGIN_CONVERSATION(conv); |
| |
6457 |
| |
6458 if (value) |
| |
6459 gtk_widget_show(gtkconv->close); |
| |
6460 else |
| |
6461 gtk_widget_hide(gtkconv->close); |
| |
6462 } |
| |
6463 } |
| |
6464 |
| |
6465 static void |
| |
6466 spellcheck_pref_cb(const char *name, PurplePrefType type, |
| |
6467 gconstpointer value, gpointer data) |
| |
6468 { |
| |
6469 #ifdef USE_GTKSPELL |
| |
6470 GList *cl; |
| |
6471 PurpleConversation *conv; |
| |
6472 PidginConversation *gtkconv; |
| |
6473 GtkSpell *spell; |
| |
6474 |
| |
6475 for (cl = purple_get_conversations(); cl != NULL; cl = cl->next) { |
| |
6476 |
| |
6477 conv = (PurpleConversation *)cl->data; |
| |
6478 |
| |
6479 if (!PIDGIN_IS_PIDGIN_CONVERSATION(conv)) |
| |
6480 continue; |
| |
6481 |
| |
6482 gtkconv = PIDGIN_CONVERSATION(conv); |
| |
6483 |
| |
6484 if (value) |
| |
6485 pidgin_setup_gtkspell(GTK_TEXT_VIEW(gtkconv->entry)); |
| |
6486 else { |
| |
6487 spell = gtkspell_get_from_text_view(GTK_TEXT_VIEW(gtkconv->entry)); |
| |
6488 gtkspell_detach(spell); |
| |
6489 } |
| |
6490 } |
| |
6491 #endif |
| |
6492 } |
| |
6493 |
| |
6494 static void |
| |
6495 tab_side_pref_cb(const char *name, PurplePrefType type, |
| |
6496 gconstpointer value, gpointer data) |
| |
6497 { |
| |
6498 GList *l; |
| |
6499 GtkPositionType pos; |
| |
6500 PidginWindow *win; |
| |
6501 |
| |
6502 pos = GPOINTER_TO_INT(value); |
| |
6503 |
| |
6504 for (l = pidgin_conv_windows_get_list(); l != NULL; l = l->next) { |
| |
6505 win = l->data; |
| |
6506 |
| |
6507 gtk_notebook_set_tab_pos(GTK_NOTEBOOK(win->notebook), pos&~8); |
| |
6508 } |
| |
6509 } |
| |
6510 |
| |
6511 static void |
| |
6512 show_timestamps_pref_cb(const char *name, PurplePrefType type, |
| |
6513 gconstpointer value, gpointer data) |
| |
6514 { |
| |
6515 GList *l; |
| |
6516 PurpleConversation *conv; |
| |
6517 PidginConversation *gtkconv; |
| |
6518 PidginWindow *win; |
| |
6519 |
| |
6520 for (l = purple_get_conversations(); l != NULL; l = l->next) |
| |
6521 { |
| |
6522 conv = (PurpleConversation *)l->data; |
| |
6523 |
| |
6524 if (!PIDGIN_IS_PIDGIN_CONVERSATION(conv)) |
| |
6525 continue; |
| |
6526 |
| |
6527 gtkconv = PIDGIN_CONVERSATION(conv); |
| |
6528 win = gtkconv->win; |
| |
6529 |
| |
6530 gtk_check_menu_item_set_active( |
| |
6531 GTK_CHECK_MENU_ITEM(win->menu.show_timestamps), |
| |
6532 (gboolean)GPOINTER_TO_INT(value)); |
| |
6533 |
| |
6534 gtk_imhtml_show_comments(GTK_IMHTML(gtkconv->imhtml), |
| |
6535 (gboolean)GPOINTER_TO_INT(value)); |
| |
6536 } |
| |
6537 } |
| |
6538 |
| |
6539 static void |
| |
6540 show_formatting_toolbar_pref_cb(const char *name, PurplePrefType type, |
| |
6541 gconstpointer value, gpointer data) |
| |
6542 { |
| |
6543 GList *l; |
| |
6544 PurpleConversation *conv; |
| |
6545 PidginConversation *gtkconv; |
| |
6546 PidginWindow *win; |
| |
6547 |
| |
6548 for (l = purple_get_conversations(); l != NULL; l = l->next) |
| |
6549 { |
| |
6550 conv = (PurpleConversation *)l->data; |
| |
6551 |
| |
6552 if (!PIDGIN_IS_PIDGIN_CONVERSATION(conv)) |
| |
6553 continue; |
| |
6554 |
| |
6555 gtkconv = PIDGIN_CONVERSATION(conv); |
| |
6556 win = gtkconv->win; |
| |
6557 |
| |
6558 gtk_check_menu_item_set_active( |
| |
6559 GTK_CHECK_MENU_ITEM(win->menu.show_formatting_toolbar), |
| |
6560 (gboolean)GPOINTER_TO_INT(value)); |
| |
6561 |
| |
6562 if ((gboolean)GPOINTER_TO_INT(value)) |
| |
6563 gtk_widget_show(gtkconv->toolbar); |
| |
6564 else |
| |
6565 gtk_widget_hide(gtkconv->toolbar); |
| |
6566 } |
| |
6567 } |
| |
6568 |
| |
6569 static void |
| |
6570 animate_buddy_icons_pref_cb(const char *name, PurplePrefType type, |
| |
6571 gconstpointer value, gpointer data) |
| |
6572 { |
| |
6573 GList *l; |
| |
6574 PurpleConversation *conv; |
| |
6575 PidginConversation *gtkconv; |
| |
6576 PidginWindow *win; |
| |
6577 |
| |
6578 if (!purple_prefs_get_bool(PIDGIN_PREFS_ROOT "/conversations/im/show_buddy_icons")) |
| |
6579 return; |
| |
6580 |
| |
6581 /* Set the "animate" flag for each icon based on the new preference */ |
| |
6582 for (l = purple_get_ims(); l != NULL; l = l->next) { |
| |
6583 conv = (PurpleConversation *)l->data; |
| |
6584 gtkconv = PIDGIN_CONVERSATION(conv); |
| |
6585 gtkconv->u.im->animate = GPOINTER_TO_INT(value); |
| |
6586 } |
| |
6587 |
| |
6588 /* Now either stop or start animation for the active conversation in each window */ |
| |
6589 for (l = pidgin_conv_windows_get_list(); l != NULL; l = l->next) { |
| |
6590 win = l->data; |
| |
6591 conv = pidgin_conv_window_get_active_conversation(win); |
| |
6592 pidgin_conv_update_buddy_icon(conv); |
| |
6593 } |
| |
6594 } |
| |
6595 |
| |
6596 static void |
| |
6597 show_buddy_icons_pref_cb(const char *name, PurplePrefType type, |
| |
6598 gconstpointer value, gpointer data) |
| |
6599 { |
| |
6600 GList *l; |
| |
6601 |
| |
6602 for (l = purple_get_conversations(); l != NULL; l = l->next) { |
| |
6603 PurpleConversation *conv = l->data; |
| |
6604 |
| |
6605 if (purple_conversation_get_type(conv) == PURPLE_CONV_TYPE_IM) |
| |
6606 pidgin_conv_update_buddy_icon(conv); |
| |
6607 } |
| |
6608 } |
| |
6609 |
| |
6610 static void |
| |
6611 conv_placement_usetabs_cb(const char *name, PurplePrefType type, |
| |
6612 gconstpointer value, gpointer data) |
| |
6613 { |
| |
6614 purple_prefs_trigger_callback(PIDGIN_PREFS_ROOT "/conversations/placement"); |
| |
6615 } |
| |
6616 |
| |
6617 static void |
| |
6618 account_status_changed_cb(PurpleAccount *account, PurpleStatus *oldstatus, |
| |
6619 PurpleStatus *newstatus) |
| |
6620 { |
| |
6621 GList *l; |
| |
6622 PurpleConversation *conv = NULL; |
| |
6623 PidginConversation *gtkconv; |
| |
6624 |
| |
6625 if(strcmp(purple_prefs_get_string(PIDGIN_PREFS_ROOT "/conversations/im/hide_new"), "away")!=0) |
| |
6626 return; |
| |
6627 |
| |
6628 if(purple_status_is_available(oldstatus) || !purple_status_is_available(newstatus)) |
| |
6629 return; |
| |
6630 |
| |
6631 while ((l = hidden_convwin->gtkconvs) != NULL) |
| |
6632 { |
| |
6633 gtkconv = l->data; |
| |
6634 |
| |
6635 conv = gtkconv->active_conv; |
| |
6636 |
| |
6637 while(l && !purple_status_is_available( |
| |
6638 purple_account_get_active_status( |
| |
6639 purple_conversation_get_account(conv)))) |
| |
6640 l = l->next; |
| |
6641 if (!l) |
| |
6642 break; |
| |
6643 |
| |
6644 pidgin_conv_window_remove_gtkconv(hidden_convwin, gtkconv); |
| |
6645 pidgin_conv_placement_place(gtkconv); |
| |
6646 |
| |
6647 /* TODO: do we need to do anything for any other conversations that are in the same gtkconv here? |
| |
6648 * I'm a little concerned that not doing so will cause the "pending" indicator in the gtkblist not to be cleared. -DAA*/ |
| |
6649 purple_conversation_update(conv, PURPLE_CONV_UPDATE_UNSEEN); |
| |
6650 } |
| |
6651 } |
| |
6652 |
| |
6653 static void |
| |
6654 hide_new_pref_cb(const char *name, PurplePrefType type, |
| |
6655 gconstpointer value, gpointer data) |
| |
6656 { |
| |
6657 GList *l; |
| |
6658 PurpleConversation *conv = NULL; |
| |
6659 PidginConversation *gtkconv; |
| |
6660 gboolean when_away = FALSE; |
| |
6661 |
| |
6662 if(!hidden_convwin) |
| |
6663 return; |
| |
6664 |
| |
6665 if(strcmp(purple_prefs_get_string(PIDGIN_PREFS_ROOT "/conversations/im/hide_new"), "always")==0) |
| |
6666 return; |
| |
6667 |
| |
6668 if(strcmp(purple_prefs_get_string(PIDGIN_PREFS_ROOT "/conversations/im/hide_new"), "away")==0) |
| |
6669 when_away = TRUE; |
| |
6670 |
| |
6671 while ((l = hidden_convwin->gtkconvs) != NULL) |
| |
6672 { |
| |
6673 gtkconv = l->data; |
| |
6674 |
| |
6675 conv = gtkconv->active_conv; |
| |
6676 |
| |
6677 if(when_away && !purple_status_is_available( |
| |
6678 purple_account_get_active_status( |
| |
6679 purple_conversation_get_account(conv)))) |
| |
6680 continue; |
| |
6681 |
| |
6682 pidgin_conv_window_remove_gtkconv(hidden_convwin, gtkconv); |
| |
6683 pidgin_conv_placement_place(gtkconv); |
| |
6684 } |
| |
6685 } |
| |
6686 |
| |
6687 |
| |
6688 static void |
| |
6689 conv_placement_pref_cb(const char *name, PurplePrefType type, |
| |
6690 gconstpointer value, gpointer data) |
| |
6691 { |
| |
6692 PidginConvPlacementFunc func; |
| |
6693 |
| |
6694 if (strcmp(name, PIDGIN_PREFS_ROOT "/conversations/placement")) |
| |
6695 return; |
| |
6696 |
| |
6697 func = pidgin_conv_placement_get_fnc(value); |
| |
6698 |
| |
6699 if (func == NULL) |
| |
6700 return; |
| |
6701 |
| |
6702 pidgin_conv_placement_set_current_func(func); |
| |
6703 } |
| |
6704 |
| |
6705 static PidginConversation * |
| |
6706 get_gtkconv_with_contact(PurpleContact *contact) |
| |
6707 { |
| |
6708 PurpleBlistNode *node; |
| |
6709 |
| |
6710 node = ((PurpleBlistNode*)contact)->child; |
| |
6711 |
| |
6712 for (; node; node = node->next) |
| |
6713 { |
| |
6714 PurpleBuddy *buddy = (PurpleBuddy*)node; |
| |
6715 PurpleConversation *conv; |
| |
6716 conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_IM, buddy->name, buddy->account); |
| |
6717 if (conv) |
| |
6718 return PIDGIN_CONVERSATION(conv); |
| |
6719 } |
| |
6720 return NULL; |
| |
6721 } |
| |
6722 |
| |
6723 static void |
| |
6724 account_signed_off_cb(PurpleConnection *gc, gpointer event) |
| |
6725 { |
| |
6726 GList *iter; |
| |
6727 |
| |
6728 for (iter = purple_get_conversations(); iter; iter = iter->next) |
| |
6729 { |
| |
6730 PurpleConversation *conv = iter->data; |
| |
6731 |
| |
6732 /* This seems fine in theory, but we also need to cover the |
| |
6733 * case of this account matching one of the other buddies in |
| |
6734 * one of the contacts containing the buddy corresponding to |
| |
6735 * a conversation. It's easier to just update them all. */ |
| |
6736 /* if (purple_conversation_get_account(conv) == account) */ |
| |
6737 pidgin_conv_update_fields(conv, PIDGIN_CONV_TAB_ICON | |
| |
6738 PIDGIN_CONV_MENU | PIDGIN_CONV_COLORIZE_TITLE); |
| |
6739 } |
| |
6740 } |
| |
6741 |
| |
6742 static gboolean |
| |
6743 update_buddy_status_timeout(PurpleBuddy *buddy) |
| |
6744 { |
| |
6745 /* To remove the signing-on/off door icon */ |
| |
6746 PurpleConversation *conv; |
| |
6747 |
| |
6748 conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_IM, buddy->name, buddy->account); |
| |
6749 if (conv) |
| |
6750 pidgin_conv_update_fields(conv, PIDGIN_CONV_TAB_ICON); |
| |
6751 |
| |
6752 return FALSE; |
| |
6753 } |
| |
6754 |
| |
6755 static void |
| |
6756 update_buddy_status_changed(PurpleBuddy *buddy, PurpleStatus *old, PurpleStatus *newstatus) |
| |
6757 { |
| |
6758 PidginConversation *gtkconv; |
| |
6759 PurpleConversation *conv; |
| |
6760 |
| |
6761 gtkconv = get_gtkconv_with_contact(purple_buddy_get_contact(buddy)); |
| |
6762 if (gtkconv) |
| |
6763 { |
| |
6764 conv = gtkconv->active_conv; |
| |
6765 pidgin_conv_update_fields(conv, PIDGIN_CONV_TAB_ICON | PIDGIN_CONV_COLORIZE_TITLE); |
| |
6766 if ((purple_status_is_online(old) ^ purple_status_is_online(newstatus)) != 0) |
| |
6767 pidgin_conv_update_fields(conv, PIDGIN_CONV_MENU); |
| |
6768 } |
| |
6769 |
| |
6770 /* In case a conversation is started after the buddy has signed-on/off */ |
| |
6771 g_timeout_add(11000, (GSourceFunc)update_buddy_status_timeout, buddy); |
| |
6772 } |
| |
6773 |
| |
6774 static void |
| |
6775 update_buddy_privacy_changed(PurpleBuddy *buddy) |
| |
6776 { |
| |
6777 PidginConversation *gtkconv; |
| |
6778 PurpleConversation *conv; |
| |
6779 |
| |
6780 gtkconv = get_gtkconv_with_contact(purple_buddy_get_contact(buddy)); |
| |
6781 if (gtkconv) { |
| |
6782 conv = gtkconv->active_conv; |
| |
6783 pidgin_conv_update_fields(conv, PIDGIN_CONV_TAB_ICON | PIDGIN_CONV_MENU); |
| |
6784 } |
| |
6785 } |
| |
6786 |
| |
6787 static void |
| |
6788 update_buddy_idle_changed(PurpleBuddy *buddy, gboolean old, gboolean newidle) |
| |
6789 { |
| |
6790 PurpleConversation *conv; |
| |
6791 |
| |
6792 conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_IM, buddy->name, buddy->account); |
| |
6793 if (conv) |
| |
6794 pidgin_conv_update_fields(conv, PIDGIN_CONV_TAB_ICON); |
| |
6795 } |
| |
6796 |
| |
6797 static void |
| |
6798 update_buddy_icon(PurpleBuddy *buddy) |
| |
6799 { |
| |
6800 PurpleConversation *conv; |
| |
6801 |
| |
6802 conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_IM, buddy->name, buddy->account); |
| |
6803 if (conv) |
| |
6804 pidgin_conv_update_fields(conv, PIDGIN_CONV_BUDDY_ICON); |
| |
6805 } |
| |
6806 |
| |
6807 static void |
| |
6808 update_buddy_sign(PurpleBuddy *buddy, const char *which) |
| |
6809 { |
| |
6810 PurplePresence *presence; |
| |
6811 PurpleStatus *on, *off; |
| |
6812 |
| |
6813 presence = purple_buddy_get_presence(buddy); |
| |
6814 if (!presence) |
| |
6815 return; |
| |
6816 off = purple_presence_get_status(presence, "offline"); |
| |
6817 on = purple_presence_get_status(presence, "available"); |
| |
6818 |
| |
6819 if (*(which+1) == 'f') |
| |
6820 update_buddy_status_changed(buddy, on, off); |
| |
6821 else |
| |
6822 update_buddy_status_changed(buddy, off, on); |
| |
6823 } |
| |
6824 |
| |
6825 static void |
| |
6826 update_conversation_switched(PurpleConversation *conv) |
| |
6827 { |
| |
6828 pidgin_conv_update_fields(conv, PIDGIN_CONV_TAB_ICON | PIDGIN_CONV_SET_TITLE | |
| |
6829 PIDGIN_CONV_MENU | PIDGIN_CONV_BUDDY_ICON); |
| |
6830 } |
| |
6831 |
| |
6832 static void |
| |
6833 update_buddy_typing(PurpleAccount *account, const char *who) |
| |
6834 { |
| |
6835 PurpleConversation *conv; |
| |
6836 PidginConversation *gtkconv; |
| |
6837 |
| |
6838 conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_IM, who, account); |
| |
6839 if (!conv) |
| |
6840 return; |
| |
6841 |
| |
6842 gtkconv = PIDGIN_CONVERSATION(conv); |
| |
6843 if (gtkconv && gtkconv->active_conv == conv) |
| |
6844 pidgin_conv_update_fields(conv, PIDGIN_CONV_COLORIZE_TITLE); |
| |
6845 } |
| |
6846 |
| |
6847 static void |
| |
6848 update_chat(PurpleConversation *conv) |
| |
6849 { |
| |
6850 pidgin_conv_update_fields(conv, PIDGIN_CONV_TOPIC | |
| |
6851 PIDGIN_CONV_MENU | PIDGIN_CONV_SET_TITLE); |
| |
6852 } |
| |
6853 |
| |
6854 static void |
| |
6855 update_chat_topic(PurpleConversation *conv, const char *old, const char *new) |
| |
6856 { |
| |
6857 pidgin_conv_update_fields(conv, PIDGIN_CONV_TOPIC); |
| |
6858 } |
| |
6859 |
| |
6860 void * |
| |
6861 pidgin_conversations_get_handle(void) |
| |
6862 { |
| |
6863 static int handle; |
| |
6864 |
| |
6865 return &handle; |
| |
6866 } |
| |
6867 |
| |
6868 void |
| |
6869 pidgin_conversations_init(void) |
| |
6870 { |
| |
6871 void *handle = pidgin_conversations_get_handle(); |
| |
6872 void *blist_handle = purple_blist_get_handle(); |
| |
6873 |
| |
6874 /* Conversations */ |
| |
6875 purple_prefs_add_none(PIDGIN_PREFS_ROOT "/conversations"); |
| |
6876 purple_prefs_add_bool(PIDGIN_PREFS_ROOT "/conversations/use_smooth_scrolling", TRUE); |
| |
6877 purple_prefs_add_bool(PIDGIN_PREFS_ROOT "/conversations/close_on_tabs", TRUE); |
| |
6878 purple_prefs_add_bool(PIDGIN_PREFS_ROOT "/conversations/send_bold", FALSE); |
| |
6879 purple_prefs_add_bool(PIDGIN_PREFS_ROOT "/conversations/send_italic", FALSE); |
| |
6880 purple_prefs_add_bool(PIDGIN_PREFS_ROOT "/conversations/send_underline", FALSE); |
| |
6881 purple_prefs_add_bool(PIDGIN_PREFS_ROOT "/conversations/spellcheck", TRUE); |
| |
6882 purple_prefs_add_bool(PIDGIN_PREFS_ROOT "/conversations/show_incoming_formatting", TRUE); |
| |
6883 |
| |
6884 purple_prefs_add_bool(PIDGIN_PREFS_ROOT "/conversations/show_timestamps", TRUE); |
| |
6885 purple_prefs_add_bool(PIDGIN_PREFS_ROOT "/conversations/show_formatting_toolbar", TRUE); |
| |
6886 |
| |
6887 purple_prefs_add_string(PIDGIN_PREFS_ROOT "/conversations/placement", "last"); |
| |
6888 purple_prefs_add_int(PIDGIN_PREFS_ROOT "/conversations/placement_number", 1); |
| |
6889 purple_prefs_add_string(PIDGIN_PREFS_ROOT "/conversations/bgcolor", ""); |
| |
6890 purple_prefs_add_string(PIDGIN_PREFS_ROOT "/conversations/fgcolor", ""); |
| |
6891 purple_prefs_add_string(PIDGIN_PREFS_ROOT "/conversations/font_face", ""); |
| |
6892 purple_prefs_add_int(PIDGIN_PREFS_ROOT "/conversations/font_size", 3); |
| |
6893 purple_prefs_add_bool(PIDGIN_PREFS_ROOT "/conversations/tabs", TRUE); |
| |
6894 purple_prefs_add_int(PIDGIN_PREFS_ROOT "/conversations/tab_side", GTK_POS_TOP); |
| |
6895 purple_prefs_add_int(PIDGIN_PREFS_ROOT "/conversations/scrollback_lines", 4000); |
| |
6896 |
| |
6897 /* Conversations -> Chat */ |
| |
6898 purple_prefs_add_none(PIDGIN_PREFS_ROOT "/conversations/chat"); |
| |
6899 purple_prefs_add_int(PIDGIN_PREFS_ROOT "/conversations/chat/default_width", 410); |
| |
6900 purple_prefs_add_int(PIDGIN_PREFS_ROOT "/conversations/chat/default_height", 160); |
| |
6901 purple_prefs_add_int(PIDGIN_PREFS_ROOT "/conversations/chat/entry_height", 50); |
| |
6902 purple_prefs_add_int(PIDGIN_PREFS_ROOT "/conversations/chat/userlist_width", 80); |
| |
6903 /* Conversations -> IM */ |
| |
6904 purple_prefs_add_none(PIDGIN_PREFS_ROOT "/conversations/im"); |
| |
6905 |
| |
6906 purple_prefs_add_bool(PIDGIN_PREFS_ROOT "/conversations/im/animate_buddy_icons", TRUE); |
| |
6907 |
| |
6908 purple_prefs_add_int(PIDGIN_PREFS_ROOT "/conversations/im/default_width", 410); |
| |
6909 purple_prefs_add_int(PIDGIN_PREFS_ROOT "/conversations/im/default_height", 160); |
| |
6910 purple_prefs_add_int(PIDGIN_PREFS_ROOT "/conversations/im/entry_height", 50); |
| |
6911 purple_prefs_add_bool(PIDGIN_PREFS_ROOT "/conversations/im/show_buddy_icons", TRUE); |
| |
6912 |
| |
6913 purple_prefs_add_string(PIDGIN_PREFS_ROOT "/conversations/im/hide_new", "never"); |
| |
6914 |
| |
6915 /* Connect callbacks. */ |
| |
6916 purple_prefs_connect_callback(handle, PIDGIN_PREFS_ROOT "/conversations/close_on_tabs", |
| |
6917 close_on_tabs_pref_cb, NULL); |
| |
6918 purple_prefs_connect_callback(handle, PIDGIN_PREFS_ROOT "/conversations/show_timestamps", |
| |
6919 show_timestamps_pref_cb, NULL); |
| |
6920 purple_prefs_connect_callback(handle, PIDGIN_PREFS_ROOT "/conversations/show_formatting_toolbar", |
| |
6921 show_formatting_toolbar_pref_cb, NULL); |
| |
6922 purple_prefs_connect_callback(handle, PIDGIN_PREFS_ROOT "/conversations/spellcheck", |
| |
6923 spellcheck_pref_cb, NULL); |
| |
6924 purple_prefs_connect_callback(handle, PIDGIN_PREFS_ROOT "/conversations/tab_side", |
| |
6925 tab_side_pref_cb, NULL); |
| |
6926 |
| |
6927 purple_prefs_connect_callback(handle, PIDGIN_PREFS_ROOT "/conversations/tabs", |
| |
6928 conv_placement_usetabs_cb, NULL); |
| |
6929 |
| |
6930 purple_prefs_connect_callback(handle, PIDGIN_PREFS_ROOT "/conversations/placement", |
| |
6931 conv_placement_pref_cb, NULL); |
| |
6932 purple_prefs_trigger_callback(PIDGIN_PREFS_ROOT "/conversations/placement"); |
| |
6933 |
| |
6934 /* IM callbacks */ |
| |
6935 purple_prefs_connect_callback(handle, PIDGIN_PREFS_ROOT "/conversations/im/animate_buddy_icons", |
| |
6936 animate_buddy_icons_pref_cb, NULL); |
| |
6937 purple_prefs_connect_callback(handle, PIDGIN_PREFS_ROOT "/conversations/im/show_buddy_icons", |
| |
6938 show_buddy_icons_pref_cb, NULL); |
| |
6939 purple_prefs_connect_callback(handle, PIDGIN_PREFS_ROOT "/conversations/im/hide_new", |
| |
6940 hide_new_pref_cb, NULL); |
| |
6941 |
| |
6942 |
| |
6943 |
| |
6944 /********************************************************************** |
| |
6945 * Register signals |
| |
6946 **********************************************************************/ |
| |
6947 purple_signal_register(handle, "conversation-dragging", |
| |
6948 purple_marshal_VOID__POINTER_POINTER, NULL, 2, |
| |
6949 purple_value_new(PURPLE_TYPE_BOXED, |
| |
6950 "PidginWindow *"), |
| |
6951 purple_value_new(PURPLE_TYPE_BOXED, |
| |
6952 "PidginWindow *")); |
| |
6953 |
| |
6954 purple_signal_register(handle, "conversation-timestamp", |
| |
6955 #if SIZEOF_TIME_T == 4 |
| |
6956 purple_marshal_POINTER__POINTER_INT_BOOLEAN, |
| |
6957 #elif SIZEOF_TIME_T == 8 |
| |
6958 purple_marshal_POINTER__POINTER_INT64_BOOLEAN, |
| |
6959 #else |
| |
6960 #error Unkown size of time_t |
| |
6961 #endif |
| |
6962 purple_value_new(PURPLE_TYPE_STRING), 3, |
| |
6963 purple_value_new(PURPLE_TYPE_SUBTYPE, |
| |
6964 PURPLE_SUBTYPE_CONVERSATION), |
| |
6965 #if SIZEOF_TIME_T == 4 |
| |
6966 purple_value_new(PURPLE_TYPE_INT), |
| |
6967 #elif SIZEOF_TIME_T == 8 |
| |
6968 purple_value_new(PURPLE_TYPE_INT64), |
| |
6969 #else |
| |
6970 # error Unknown size of time_t |
| |
6971 #endif |
| |
6972 purple_value_new(PURPLE_TYPE_BOOLEAN)); |
| |
6973 |
| |
6974 purple_signal_register(handle, "displaying-im-msg", |
| |
6975 purple_marshal_BOOLEAN__POINTER_POINTER_POINTER_POINTER_POINTER, |
| |
6976 purple_value_new(PURPLE_TYPE_BOOLEAN), 5, |
| |
6977 purple_value_new(PURPLE_TYPE_SUBTYPE, |
| |
6978 PURPLE_SUBTYPE_ACCOUNT), |
| |
6979 purple_value_new(PURPLE_TYPE_STRING), |
| |
6980 purple_value_new_outgoing(PURPLE_TYPE_STRING), |
| |
6981 purple_value_new(PURPLE_TYPE_SUBTYPE, |
| |
6982 PURPLE_SUBTYPE_CONVERSATION), |
| |
6983 purple_value_new(PURPLE_TYPE_INT)); |
| |
6984 |
| |
6985 purple_signal_register(handle, "displayed-im-msg", |
| |
6986 purple_marshal_VOID__POINTER_POINTER_POINTER_POINTER_UINT, |
| |
6987 NULL, 5, |
| |
6988 purple_value_new(PURPLE_TYPE_SUBTYPE, |
| |
6989 PURPLE_SUBTYPE_ACCOUNT), |
| |
6990 purple_value_new(PURPLE_TYPE_STRING), |
| |
6991 purple_value_new(PURPLE_TYPE_STRING), |
| |
6992 purple_value_new(PURPLE_TYPE_SUBTYPE, |
| |
6993 PURPLE_SUBTYPE_CONVERSATION), |
| |
6994 purple_value_new(PURPLE_TYPE_INT)); |
| |
6995 |
| |
6996 purple_signal_register(handle, "displaying-chat-msg", |
| |
6997 purple_marshal_BOOLEAN__POINTER_POINTER_POINTER_POINTER_POINTER, |
| |
6998 purple_value_new(PURPLE_TYPE_BOOLEAN), 5, |
| |
6999 purple_value_new(PURPLE_TYPE_SUBTYPE, |
| |
7000 PURPLE_SUBTYPE_ACCOUNT), |
| |
7001 purple_value_new(PURPLE_TYPE_STRING), |
| |
7002 purple_value_new_outgoing(PURPLE_TYPE_STRING), |
| |
7003 purple_value_new(PURPLE_TYPE_SUBTYPE, |
| |
7004 PURPLE_SUBTYPE_CONVERSATION), |
| |
7005 purple_value_new(PURPLE_TYPE_INT)); |
| |
7006 |
| |
7007 purple_signal_register(handle, "displayed-chat-msg", |
| |
7008 purple_marshal_VOID__POINTER_POINTER_POINTER_POINTER_UINT, |
| |
7009 NULL, 5, |
| |
7010 purple_value_new(PURPLE_TYPE_SUBTYPE, |
| |
7011 PURPLE_SUBTYPE_ACCOUNT), |
| |
7012 purple_value_new(PURPLE_TYPE_STRING), |
| |
7013 purple_value_new(PURPLE_TYPE_STRING), |
| |
7014 purple_value_new(PURPLE_TYPE_SUBTYPE, |
| |
7015 PURPLE_SUBTYPE_CONVERSATION), |
| |
7016 purple_value_new(PURPLE_TYPE_INT)); |
| |
7017 |
| |
7018 purple_signal_register(handle, "conversation-switched", |
| |
7019 purple_marshal_VOID__POINTER_POINTER, NULL, 1, |
| |
7020 purple_value_new(PURPLE_TYPE_SUBTYPE, |
| |
7021 PURPLE_SUBTYPE_CONVERSATION)); |
| |
7022 |
| |
7023 /********************************************************************** |
| |
7024 * Register commands |
| |
7025 **********************************************************************/ |
| |
7026 purple_cmd_register("say", "S", PURPLE_CMD_P_DEFAULT, |
| |
7027 PURPLE_CMD_FLAG_CHAT | PURPLE_CMD_FLAG_IM, NULL, |
| |
7028 say_command_cb, _("say <message>: Send a message normally as if you weren't using a command."), NULL); |
| |
7029 purple_cmd_register("me", "S", PURPLE_CMD_P_DEFAULT, |
| |
7030 PURPLE_CMD_FLAG_CHAT | PURPLE_CMD_FLAG_IM, NULL, |
| |
7031 me_command_cb, _("me <action>: Send an IRC style action to a buddy or chat."), NULL); |
| |
7032 purple_cmd_register("debug", "w", PURPLE_CMD_P_DEFAULT, |
| |
7033 PURPLE_CMD_FLAG_CHAT | PURPLE_CMD_FLAG_IM, NULL, |
| |
7034 debug_command_cb, _("debug <option>: Send various debug information to the current conversation."), NULL); |
| |
7035 purple_cmd_register("clear", "", PURPLE_CMD_P_DEFAULT, |
| |
7036 PURPLE_CMD_FLAG_CHAT | PURPLE_CMD_FLAG_IM, NULL, |
| |
7037 clear_command_cb, _("clear: Clears the conversation scrollback."), NULL); |
| |
7038 purple_cmd_register("help", "w", PURPLE_CMD_P_DEFAULT, |
| |
7039 PURPLE_CMD_FLAG_CHAT | PURPLE_CMD_FLAG_IM | PURPLE_CMD_FLAG_ALLOW_WRONG_ARGS, NULL, |
| |
7040 help_command_cb, _("help <command>: Help on a specific command."), NULL); |
| |
7041 |
| |
7042 /********************************************************************** |
| |
7043 * UI operations |
| |
7044 **********************************************************************/ |
| |
7045 |
| |
7046 purple_signal_connect(purple_connections_get_handle(), "signed-on", handle, |
| |
7047 G_CALLBACK(account_signed_off_cb), |
| |
7048 GINT_TO_POINTER(PURPLE_CONV_ACCOUNT_ONLINE)); |
| |
7049 purple_signal_connect(purple_connections_get_handle(), "signed-off", handle, |
| |
7050 G_CALLBACK(account_signed_off_cb), |
| |
7051 GINT_TO_POINTER(PURPLE_CONV_ACCOUNT_OFFLINE)); |
| |
7052 |
| |
7053 purple_signal_connect(purple_conversations_get_handle(), "received-im-msg", |
| |
7054 handle, G_CALLBACK(received_im_msg_cb), NULL); |
| |
7055 |
| |
7056 purple_conversations_set_ui_ops(&conversation_ui_ops); |
| |
7057 |
| |
7058 hidden_convwin = pidgin_conv_window_new(); |
| |
7059 window_list = g_list_remove(window_list, hidden_convwin); |
| |
7060 |
| |
7061 purple_signal_connect(purple_accounts_get_handle(), "account-status-changed", |
| |
7062 handle, PURPLE_CALLBACK(account_status_changed_cb), NULL); |
| |
7063 |
| |
7064 /* Callbacks to update a conversation */ |
| |
7065 purple_signal_connect(blist_handle, "buddy-added", handle, |
| |
7066 G_CALLBACK(buddy_update_cb), NULL); |
| |
7067 purple_signal_connect(blist_handle, "buddy-removed", handle, |
| |
7068 G_CALLBACK(buddy_update_cb), NULL); |
| |
7069 purple_signal_connect(blist_handle, "buddy-signed-on", |
| |
7070 handle, PURPLE_CALLBACK(update_buddy_sign), "on"); |
| |
7071 purple_signal_connect(blist_handle, "buddy-signed-off", |
| |
7072 handle, PURPLE_CALLBACK(update_buddy_sign), "off"); |
| |
7073 purple_signal_connect(blist_handle, "buddy-status-changed", |
| |
7074 handle, PURPLE_CALLBACK(update_buddy_status_changed), NULL); |
| |
7075 purple_signal_connect(blist_handle, "buddy-privacy-changed", |
| |
7076 handle, PURPLE_CALLBACK(update_buddy_privacy_changed), NULL); |
| |
7077 purple_signal_connect(blist_handle, "buddy-idle-changed", |
| |
7078 handle, PURPLE_CALLBACK(update_buddy_idle_changed), NULL); |
| |
7079 purple_signal_connect(blist_handle, "buddy-icon-changed", |
| |
7080 handle, PURPLE_CALLBACK(update_buddy_icon), NULL); |
| |
7081 purple_signal_connect(purple_conversations_get_handle(), "buddy-typing", |
| |
7082 handle, PURPLE_CALLBACK(update_buddy_typing), NULL); |
| |
7083 purple_signal_connect(purple_conversations_get_handle(), "buddy-typing-stopped", |
| |
7084 handle, PURPLE_CALLBACK(update_buddy_typing), NULL); |
| |
7085 purple_signal_connect(pidgin_conversations_get_handle(), "conversation-switched", |
| |
7086 handle, PURPLE_CALLBACK(update_conversation_switched), NULL); |
| |
7087 purple_signal_connect(purple_conversations_get_handle(), "chat-left", handle, |
| |
7088 PURPLE_CALLBACK(update_chat), NULL); |
| |
7089 purple_signal_connect(purple_conversations_get_handle(), "chat-joined", handle, |
| |
7090 PURPLE_CALLBACK(update_chat), NULL); |
| |
7091 purple_signal_connect(purple_conversations_get_handle(), "chat-topic-changed", handle, |
| |
7092 PURPLE_CALLBACK(update_chat_topic), NULL); |
| |
7093 purple_signal_connect_priority(purple_conversations_get_handle(), "conversation-updated", handle, |
| |
7094 PURPLE_CALLBACK(pidgin_conv_updated), NULL, |
| |
7095 PURPLE_SIGNAL_PRIORITY_LOWEST); |
| |
7096 } |
| |
7097 |
| |
7098 void |
| |
7099 pidgin_conversations_uninit(void) |
| |
7100 { |
| |
7101 purple_prefs_disconnect_by_handle(pidgin_conversations_get_handle()); |
| |
7102 purple_signals_disconnect_by_handle(pidgin_conversations_get_handle()); |
| |
7103 purple_signals_unregister_by_instance(pidgin_conversations_get_handle()); |
| |
7104 pidgin_conv_window_destroy(hidden_convwin); |
| |
7105 hidden_convwin=NULL; |
| |
7106 } |
| |
7107 |
| |
7108 |
| |
7109 |
| |
7110 |
| |
7111 |
| |
7112 |
| |
7113 |
| |
7114 |
| |
7115 |
| |
7116 |
| |
7117 |
| |
7118 |
| |
7119 |
| |
7120 |
| |
7121 |
| |
7122 |
| |
7123 /* down here is where gtkconvwin.c ought to start. except they share like every freaking function, |
| |
7124 * and touch each others' private members all day long */ |
| |
7125 |
| |
7126 /** |
| |
7127 * @file gtkconvwin.c GTK+ Conversation Window API |
| |
7128 * @ingroup gtkui |
| |
7129 * |
| |
7130 * pidgin |
| |
7131 * |
| |
7132 * Pidgin is the legal property of its developers, whose names are too numerous |
| |
7133 * to list here. Please refer to the COPYRIGHT file distributed with this |
| |
7134 * source distribution. |
| |
7135 * |
| |
7136 * This program is free software; you can redistribute it and/or modify |
| |
7137 * it under the terms of the GNU General Public License as published by |
| |
7138 * the Free Software Foundation; either version 2 of the License, or |
| |
7139 * (at your option) any later version. |
| |
7140 * |
| |
7141 * This program is distributed in the hope that it will be useful, |
| |
7142 * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| |
7143 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| |
7144 * GNU General Public License for more details. |
| |
7145 * |
| |
7146 * You should have received a copy of the GNU General Public License |
| |
7147 * along with this program; if not, write to the Free Software |
| |
7148 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA |
| |
7149 * |
| |
7150 */ |
| |
7151 #include "internal.h" |
| |
7152 #include "pidgin.h" |
| |
7153 |
| |
7154 |
| |
7155 #include <gdk/gdkkeysyms.h> |
| |
7156 |
| |
7157 #include "account.h" |
| |
7158 #include "cmds.h" |
| |
7159 #include "debug.h" |
| |
7160 #include "imgstore.h" |
| |
7161 #include "log.h" |
| |
7162 #include "notify.h" |
| |
7163 #include "prpl.h" |
| |
7164 #include "request.h" |
| |
7165 #include "util.h" |
| |
7166 |
| |
7167 #include "gtkdnd-hints.h" |
| |
7168 #include "gtkblist.h" |
| |
7169 #include "gtkconv.h" |
| |
7170 #include "gtkdialogs.h" |
| |
7171 #include "gtkmenutray.h" |
| |
7172 #include "gtkpounce.h" |
| |
7173 #include "gtkprefs.h" |
| |
7174 #include "gtkprivacy.h" |
| |
7175 #include "gtkutils.h" |
| |
7176 #include "pidginstock.h" |
| |
7177 #include "gtkimhtml.h" |
| |
7178 #include "gtkimhtmltoolbar.h" |
| |
7179 |
| |
7180 static void |
| |
7181 do_close(GtkWidget *w, int resp, PidginWindow *win) |
| |
7182 { |
| |
7183 gtk_widget_destroy(warn_close_dialog); |
| |
7184 warn_close_dialog = NULL; |
| |
7185 |
| |
7186 if (resp == GTK_RESPONSE_OK) |
| |
7187 pidgin_conv_window_destroy(win); |
| |
7188 } |
| |
7189 |
| |
7190 static void |
| |
7191 build_warn_close_dialog(PidginWindow *gtkwin) |
| |
7192 { |
| |
7193 GtkWidget *label; |
| |
7194 GtkWidget *vbox, *hbox; |
| |
7195 GtkWidget *img; |
| |
7196 |
| |
7197 g_return_if_fail(warn_close_dialog == NULL); |
| |
7198 |
| |
7199 |
| |
7200 warn_close_dialog = gtk_dialog_new_with_buttons( |
| |
7201 _("Confirm close"), |
| |
7202 GTK_WINDOW(gtkwin->window), GTK_DIALOG_MODAL, |
| |
7203 GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL, |
| |
7204 PIDGIN_STOCK_CLOSE_TABS, GTK_RESPONSE_OK, NULL); |
| |
7205 |
| |
7206 gtk_dialog_set_default_response(GTK_DIALOG(warn_close_dialog), |
| |
7207 GTK_RESPONSE_OK); |
| |
7208 |
| |
7209 gtk_container_set_border_width(GTK_CONTAINER(warn_close_dialog), |
| |
7210 6); |
| |
7211 gtk_window_set_resizable(GTK_WINDOW(warn_close_dialog), FALSE); |
| |
7212 gtk_dialog_set_has_separator(GTK_DIALOG(warn_close_dialog), |
| |
7213 FALSE); |
| |
7214 |
| |
7215 /* Setup the outside spacing. */ |
| |
7216 vbox = GTK_DIALOG(warn_close_dialog)->vbox; |
| |
7217 |
| |
7218 gtk_box_set_spacing(GTK_BOX(vbox), 12); |
| |
7219 gtk_container_set_border_width(GTK_CONTAINER(vbox), 6); |
| |
7220 |
| |
7221 img = gtk_image_new_from_stock(PIDGIN_STOCK_DIALOG_WARNING, |
| |
7222 gtk_icon_size_from_name(PIDGIN_ICON_SIZE_TANGO_HUGE)); |
| |
7223 /* Setup the inner hbox and put the dialog's icon in it. */ |
| |
7224 hbox = gtk_hbox_new(FALSE, 12); |
| |
7225 gtk_container_add(GTK_CONTAINER(vbox), hbox); |
| |
7226 gtk_box_pack_start(GTK_BOX(hbox), img, FALSE, FALSE, 0); |
| |
7227 gtk_misc_set_alignment(GTK_MISC(img), 0, 0); |
| |
7228 |
| |
7229 /* Setup the right vbox. */ |
| |
7230 vbox = gtk_vbox_new(FALSE, 12); |
| |
7231 gtk_container_add(GTK_CONTAINER(hbox), vbox); |
| |
7232 |
| |
7233 label = gtk_label_new(_("You have unread messages. Are you sure you want to close the window?")); |
| |
7234 gtk_widget_set_size_request(label, 350, -1); |
| |
7235 gtk_label_set_line_wrap(GTK_LABEL(label), TRUE); |
| |
7236 gtk_misc_set_alignment(GTK_MISC(label), 0, 0); |
| |
7237 gtk_box_pack_start(GTK_BOX(vbox), label, FALSE, FALSE, 0); |
| |
7238 |
| |
7239 /* Connect the signals. */ |
| |
7240 g_signal_connect(G_OBJECT(warn_close_dialog), "response", |
| |
7241 G_CALLBACK(do_close), gtkwin); |
| |
7242 |
| |
7243 } |
| |
7244 |
| |
7245 /************************************************************************** |
| |
7246 * Callbacks |
| |
7247 **************************************************************************/ |
| |
7248 |
| |
7249 static gboolean |
| |
7250 close_win_cb(GtkWidget *w, GdkEventAny *e, gpointer d) |
| |
7251 { |
| |
7252 PidginWindow *win = d; |
| |
7253 GList *l; |
| |
7254 |
| |
7255 /* If there are unread messages then show a warning dialog */ |
| |
7256 for (l = pidgin_conv_window_get_gtkconvs(win); |
| |
7257 l != NULL; l = l->next) |
| |
7258 { |
| |
7259 PidginConversation *gtkconv = l->data; |
| |
7260 if (purple_conversation_get_type(gtkconv->active_conv) == PURPLE_CONV_TYPE_IM && |
| |
7261 gtkconv->unseen_state >= PIDGIN_UNSEEN_TEXT) |
| |
7262 { |
| |
7263 build_warn_close_dialog(win); |
| |
7264 gtk_widget_show_all(warn_close_dialog); |
| |
7265 |
| |
7266 return TRUE; |
| |
7267 } |
| |
7268 } |
| |
7269 |
| |
7270 pidgin_conv_window_destroy(win); |
| |
7271 |
| |
7272 return TRUE; |
| |
7273 } |
| |
7274 |
| |
7275 static void |
| |
7276 gtkconv_set_unseen(PidginConversation *gtkconv, PidginUnseenState state) |
| |
7277 { |
| |
7278 if (state == PIDGIN_UNSEEN_NONE) |
| |
7279 { |
| |
7280 gtkconv->unseen_count = 0; |
| |
7281 gtkconv->unseen_state = PIDGIN_UNSEEN_NONE; |
| |
7282 } |
| |
7283 else |
| |
7284 { |
| |
7285 if (state >= PIDGIN_UNSEEN_TEXT) |
| |
7286 gtkconv->unseen_count++; |
| |
7287 |
| |
7288 if (state > gtkconv->unseen_state) |
| |
7289 gtkconv->unseen_state = state; |
| |
7290 } |
| |
7291 |
| |
7292 purple_conversation_update(gtkconv->active_conv, PURPLE_CONV_UPDATE_UNSEEN); |
| |
7293 } |
| |
7294 |
| |
7295 /* |
| |
7296 * When a conversation window is focused, we know the user |
| |
7297 * has looked at it so we know there are no longer unseen |
| |
7298 * messages. |
| |
7299 */ |
| |
7300 static gint |
| |
7301 focus_win_cb(GtkWidget *w, GdkEventFocus *e, gpointer d) |
| |
7302 { |
| |
7303 PidginWindow *win = d; |
| |
7304 PidginConversation *gtkconv = pidgin_conv_window_get_active_gtkconv(win); |
| |
7305 |
| |
7306 gtkconv_set_unseen(gtkconv, PIDGIN_UNSEEN_NONE); |
| |
7307 |
| |
7308 return FALSE; |
| |
7309 } |
| |
7310 |
| |
7311 #if !GTK_CHECK_VERSION(2,6,0) |
| |
7312 /* Courtesy of Galeon! */ |
| |
7313 static void |
| |
7314 tab_close_button_state_changed_cb(GtkWidget *widget, GtkStateType prev_state) |
| |
7315 { |
| |
7316 if (GTK_WIDGET_STATE(widget) == GTK_STATE_ACTIVE) |
| |
7317 gtk_widget_set_state(widget, GTK_STATE_NORMAL); |
| |
7318 } |
| |
7319 #endif |
| |
7320 |
| |
7321 static void |
| |
7322 notebook_init_grab(PidginWindow *gtkwin, GtkWidget *widget) |
| |
7323 { |
| |
7324 static GdkCursor *cursor = NULL; |
| |
7325 |
| |
7326 gtkwin->in_drag = TRUE; |
| |
7327 |
| |
7328 if (gtkwin->drag_leave_signal) { |
| |
7329 g_signal_handler_disconnect(G_OBJECT(widget), |
| |
7330 gtkwin->drag_leave_signal); |
| |
7331 gtkwin->drag_leave_signal = 0; |
| |
7332 } |
| |
7333 |
| |
7334 if (cursor == NULL) |
| |
7335 cursor = gdk_cursor_new(GDK_FLEUR); |
| |
7336 |
| |
7337 /* Grab the pointer */ |
| |
7338 gtk_grab_add(gtkwin->notebook); |
| |
7339 #ifndef _WIN32 |
| |
7340 /* Currently for win32 GTK+ (as of 2.2.1), gdk_pointer_is_grabbed will |
| |
7341 always be true after a button press. */ |
| |
7342 if (!gdk_pointer_is_grabbed()) |
| |
7343 #endif |
| |
7344 gdk_pointer_grab(gtkwin->notebook->window, FALSE, |
| |
7345 GDK_BUTTON1_MOTION_MASK | GDK_BUTTON_RELEASE_MASK, |
| |
7346 NULL, cursor, GDK_CURRENT_TIME); |
| |
7347 } |
| |
7348 |
| |
7349 static gboolean |
| |
7350 notebook_motion_cb(GtkWidget *widget, GdkEventButton *e, PidginWindow *win) |
| |
7351 { |
| |
7352 |
| |
7353 /* |
| |
7354 * Make sure the user moved the mouse far enough for the |
| |
7355 * drag to be initiated. |
| |
7356 */ |
| |
7357 if (win->in_predrag) { |
| |
7358 if (e->x_root < win->drag_min_x || |
| |
7359 e->x_root >= win->drag_max_x || |
| |
7360 e->y_root < win->drag_min_y || |
| |
7361 e->y_root >= win->drag_max_y) { |
| |
7362 |
| |
7363 win->in_predrag = FALSE; |
| |
7364 notebook_init_grab(win, widget); |
| |
7365 } |
| |
7366 } |
| |
7367 else { /* Otherwise, draw the arrows. */ |
| |
7368 PidginWindow *dest_win; |
| |
7369 GtkNotebook *dest_notebook; |
| |
7370 GtkWidget *tab; |
| |
7371 gint nb_x, nb_y, page_num; |
| |
7372 gint arrow1_x, arrow1_y, arrow2_x, arrow2_y; |
| |
7373 gboolean horiz_tabs = FALSE; |
| |
7374 PidginConversation *gtkconv; |
| |
7375 gboolean to_right = FALSE; |
| |
7376 |
| |
7377 /* Get the window that the cursor is over. */ |
| |
7378 dest_win = pidgin_conv_window_get_at_xy(e->x_root, e->y_root); |
| |
7379 |
| |
7380 if (dest_win == NULL) { |
| |
7381 dnd_hints_hide_all(); |
| |
7382 |
| |
7383 return TRUE; |
| |
7384 } |
| |
7385 |
| |
7386 dest_notebook = GTK_NOTEBOOK(dest_win->notebook); |
| |
7387 |
| |
7388 gdk_window_get_origin(GTK_WIDGET(dest_notebook)->window, &nb_x, &nb_y); |
| |
7389 |
| |
7390 arrow1_x = arrow2_x = nb_x; |
| |
7391 arrow1_y = arrow2_y = nb_y; |
| |
7392 |
| |
7393 page_num = pidgin_conv_get_tab_at_xy(dest_win, |
| |
7394 e->x_root, e->y_root, &to_right); |
| |
7395 to_right = to_right && (win != dest_win); |
| |
7396 |
| |
7397 if (gtk_notebook_get_tab_pos(dest_notebook) == GTK_POS_TOP || |
| |
7398 gtk_notebook_get_tab_pos(dest_notebook) == GTK_POS_BOTTOM) { |
| |
7399 |
| |
7400 horiz_tabs = TRUE; |
| |
7401 } |
| |
7402 |
| |
7403 gtkconv = pidgin_conv_window_get_gtkconv_at_index(dest_win, page_num); |
| |
7404 tab = gtkconv->tabby; |
| |
7405 |
| |
7406 if (horiz_tabs) { |
| |
7407 arrow1_x = arrow2_x = nb_x + tab->allocation.x; |
| |
7408 |
| |
7409 if (((gpointer)win == (gpointer)dest_win && win->drag_tab < page_num) || to_right) { |
| |
7410 arrow1_x += tab->allocation.width; |
| |
7411 arrow2_x += tab->allocation.width; |
| |
7412 } |
| |
7413 |
| |
7414 arrow1_y = nb_y + tab->allocation.y; |
| |
7415 arrow2_y = nb_y + tab->allocation.y + tab->allocation.height; |
| |
7416 } else { |
| |
7417 arrow1_x = nb_x + tab->allocation.x; |
| |
7418 arrow2_x = nb_x + tab->allocation.x + tab->allocation.width; |
| |
7419 arrow1_y = arrow2_y = nb_y + tab->allocation.y; |
| |
7420 |
| |
7421 if (((gpointer)win == (gpointer)dest_win && win->drag_tab < page_num) || to_right) { |
| |
7422 arrow1_y += tab->allocation.height; |
| |
7423 arrow2_y += tab->allocation.height; |
| |
7424 } |
| |
7425 } |
| |
7426 |
| |
7427 if (horiz_tabs) { |
| |
7428 dnd_hints_show(HINT_ARROW_DOWN, arrow1_x, arrow1_y); |
| |
7429 dnd_hints_show(HINT_ARROW_UP, arrow2_x, arrow2_y); |
| |
7430 } else { |
| |
7431 dnd_hints_show(HINT_ARROW_RIGHT, arrow1_x, arrow1_y); |
| |
7432 dnd_hints_show(HINT_ARROW_LEFT, arrow2_x, arrow2_y); |
| |
7433 } |
| |
7434 } |
| |
7435 |
| |
7436 return TRUE; |
| |
7437 } |
| |
7438 |
| |
7439 static gboolean |
| |
7440 notebook_leave_cb(GtkWidget *widget, GdkEventCrossing *e, PidginWindow *win) |
| |
7441 { |
| |
7442 if (win->in_drag) |
| |
7443 return FALSE; |
| |
7444 |
| |
7445 if (e->x_root < win->drag_min_x || |
| |
7446 e->x_root >= win->drag_max_x || |
| |
7447 e->y_root < win->drag_min_y || |
| |
7448 e->y_root >= win->drag_max_y) { |
| |
7449 |
| |
7450 win->in_predrag = FALSE; |
| |
7451 notebook_init_grab(win, widget); |
| |
7452 } |
| |
7453 |
| |
7454 return TRUE; |
| |
7455 } |
| |
7456 |
| |
7457 /* |
| |
7458 * THANK YOU GALEON! |
| |
7459 */ |
| |
7460 static gboolean |
| |
7461 notebook_press_cb(GtkWidget *widget, GdkEventButton *e, PidginWindow *win) |
| |
7462 { |
| |
7463 gint nb_x, nb_y, x_rel, y_rel; |
| |
7464 int tab_clicked; |
| |
7465 GtkWidget *page; |
| |
7466 GtkWidget *tab; |
| |
7467 |
| |
7468 if (e->button == 2 && e->type == GDK_BUTTON_PRESS) { |
| |
7469 PidginConversation *gtkconv; |
| |
7470 tab_clicked = pidgin_conv_get_tab_at_xy(win, e->x_root, e->y_root, NULL); |
| |
7471 |
| |
7472 if (tab_clicked == -1) |
| |
7473 return FALSE; |
| |
7474 |
| |
7475 gtkconv = pidgin_conv_window_get_gtkconv_at_index(win, tab_clicked); |
| |
7476 close_conv_cb(NULL, gtkconv); |
| |
7477 return TRUE; |
| |
7478 } |
| |
7479 |
| |
7480 |
| |
7481 if (e->button != 1 || e->type != GDK_BUTTON_PRESS) |
| |
7482 return FALSE; |
| |
7483 |
| |
7484 |
| |
7485 if (win->in_drag) { |
| |
7486 purple_debug(PURPLE_DEBUG_WARNING, "gtkconv", |
| |
7487 "Already in the middle of a window drag at tab_press_cb\n"); |
| |
7488 return TRUE; |
| |
7489 } |
| |
7490 |
| |
7491 /* |
| |
7492 * Make sure a tab was actually clicked. The arrow buttons |
| |
7493 * mess things up. |
| |
7494 */ |
| |
7495 tab_clicked = pidgin_conv_get_tab_at_xy(win, e->x_root, e->y_root, NULL); |
| |
7496 |
| |
7497 if (tab_clicked == -1) |
| |
7498 return FALSE; |
| |
7499 |
| |
7500 /* |
| |
7501 * Get the relative position of the press event, with regards to |
| |
7502 * the position of the notebook. |
| |
7503 */ |
| |
7504 gdk_window_get_origin(win->notebook->window, &nb_x, &nb_y); |
| |
7505 |
| |
7506 x_rel = e->x_root - nb_x; |
| |
7507 y_rel = e->y_root - nb_y; |
| |
7508 |
| |
7509 /* Reset the min/max x/y */ |
| |
7510 win->drag_min_x = 0; |
| |
7511 win->drag_min_y = 0; |
| |
7512 win->drag_max_x = 0; |
| |
7513 win->drag_max_y = 0; |
| |
7514 |
| |
7515 /* Find out which tab was dragged. */ |
| |
7516 page = gtk_notebook_get_nth_page(GTK_NOTEBOOK(win->notebook), tab_clicked); |
| |
7517 tab = gtk_notebook_get_tab_label(GTK_NOTEBOOK(win->notebook), page); |
| |
7518 |
| |
7519 win->drag_min_x = tab->allocation.x + nb_x; |
| |
7520 win->drag_min_y = tab->allocation.y + nb_y; |
| |
7521 win->drag_max_x = tab->allocation.width + win->drag_min_x; |
| |
7522 win->drag_max_y = tab->allocation.height + win->drag_min_y; |
| |
7523 |
| |
7524 /* Make sure the click occurred in the tab. */ |
| |
7525 if (e->x_root < win->drag_min_x || |
| |
7526 e->x_root >= win->drag_max_x || |
| |
7527 e->y_root < win->drag_min_y || |
| |
7528 e->y_root >= win->drag_max_y) { |
| |
7529 |
| |
7530 return FALSE; |
| |
7531 } |
| |
7532 |
| |
7533 win->in_predrag = TRUE; |
| |
7534 win->drag_tab = tab_clicked; |
| |
7535 |
| |
7536 /* Connect the new motion signals. */ |
| |
7537 win->drag_motion_signal = |
| |
7538 g_signal_connect(G_OBJECT(widget), "motion_notify_event", |
| |
7539 G_CALLBACK(notebook_motion_cb), win); |
| |
7540 |
| |
7541 win->drag_leave_signal = |
| |
7542 g_signal_connect(G_OBJECT(widget), "leave_notify_event", |
| |
7543 G_CALLBACK(notebook_leave_cb), win); |
| |
7544 |
| |
7545 return FALSE; |
| |
7546 } |
| |
7547 |
| |
7548 static gboolean |
| |
7549 notebook_release_cb(GtkWidget *widget, GdkEventButton *e, PidginWindow *win) |
| |
7550 { |
| |
7551 PidginWindow *dest_win; |
| |
7552 PurpleConversation *conv; |
| |
7553 PidginConversation *gtkconv; |
| |
7554 gint dest_page_num = 0; |
| |
7555 gboolean new_window = FALSE; |
| |
7556 gboolean to_right = FALSE; |
| |
7557 |
| |
7558 /* |
| |
7559 * Don't check to make sure that the event's window matches the |
| |
7560 * widget's, because we may be getting an event passed on from the |
| |
7561 * close button. |
| |
7562 */ |
| |
7563 if (e->button != 1 && e->type != GDK_BUTTON_RELEASE) |
| |
7564 return FALSE; |
| |
7565 |
| |
7566 if (gdk_pointer_is_grabbed()) { |
| |
7567 gdk_pointer_ungrab(GDK_CURRENT_TIME); |
| |
7568 gtk_grab_remove(widget); |
| |
7569 } |
| |
7570 |
| |
7571 if (!win->in_predrag && !win->in_drag) |
| |
7572 return FALSE; |
| |
7573 |
| |
7574 /* Disconnect the motion signal. */ |
| |
7575 if (win->drag_motion_signal) { |
| |
7576 g_signal_handler_disconnect(G_OBJECT(widget), |
| |
7577 win->drag_motion_signal); |
| |
7578 |
| |
7579 win->drag_motion_signal = 0; |
| |
7580 } |
| |
7581 |
| |
7582 /* |
| |
7583 * If we're in a pre-drag, we'll also need to disconnect the leave |
| |
7584 * signal. |
| |
7585 */ |
| |
7586 if (win->in_predrag) { |
| |
7587 win->in_predrag = FALSE; |
| |
7588 |
| |
7589 if (win->drag_leave_signal) { |
| |
7590 g_signal_handler_disconnect(G_OBJECT(widget), |
| |
7591 win->drag_leave_signal); |
| |
7592 |
| |
7593 win->drag_leave_signal = 0; |
| |
7594 } |
| |
7595 } |
| |
7596 |
| |
7597 /* If we're not in drag... */ |
| |
7598 /* We're perfectly normal people! */ |
| |
7599 if (!win->in_drag) |
| |
7600 return FALSE; |
| |
7601 |
| |
7602 win->in_drag = FALSE; |
| |
7603 |
| |
7604 dnd_hints_hide_all(); |
| |
7605 |
| |
7606 dest_win = pidgin_conv_window_get_at_xy(e->x_root, e->y_root); |
| |
7607 |
| |
7608 conv = pidgin_conv_window_get_active_conversation(win); |
| |
7609 |
| |
7610 if (dest_win == NULL) { |
| |
7611 /* If the current window doesn't have any other conversations, |
| |
7612 * there isn't much point transferring the conv to a new window. */ |
| |
7613 if (pidgin_conv_window_get_gtkconv_count(win) > 1) { |
| |
7614 /* Make a new window to stick this to. */ |
| |
7615 dest_win = pidgin_conv_window_new(); |
| |
7616 new_window = TRUE; |
| |
7617 } |
| |
7618 } |
| |
7619 |
| |
7620 if (dest_win == NULL) |
| |
7621 return FALSE; |
| |
7622 |
| |
7623 purple_signal_emit(pidgin_conversations_get_handle(), |
| |
7624 "conversation-dragging", win, dest_win); |
| |
7625 |
| |
7626 /* Get the destination page number. */ |
| |
7627 if (!new_window) |
| |
7628 dest_page_num = pidgin_conv_get_tab_at_xy(dest_win, |
| |
7629 e->x_root, e->y_root, &to_right); |
| |
7630 |
| |
7631 gtkconv = pidgin_conv_window_get_gtkconv_at_index(win, win->drag_tab); |
| |
7632 |
| |
7633 if (win == dest_win) { |
| |
7634 gtk_notebook_reorder_child(GTK_NOTEBOOK(win->notebook), gtkconv->tab_cont, dest_page_num); |
| |
7635 } else { |
| |
7636 pidgin_conv_window_remove_gtkconv(win, gtkconv); |
| |
7637 pidgin_conv_window_add_gtkconv(dest_win, gtkconv); |
| |
7638 gtk_notebook_reorder_child(GTK_NOTEBOOK(dest_win->notebook), gtkconv->tab_cont, dest_page_num + to_right); |
| |
7639 pidgin_conv_window_switch_gtkconv(dest_win, gtkconv); |
| |
7640 if (new_window) { |
| |
7641 gint win_width, win_height; |
| |
7642 |
| |
7643 gtk_window_get_size(GTK_WINDOW(dest_win->window), |
| |
7644 &win_width, &win_height); |
| |
7645 |
| |
7646 gtk_window_move(GTK_WINDOW(dest_win->window), |
| |
7647 e->x_root - (win_width / 2), |
| |
7648 e->y_root - (win_height / 2)); |
| |
7649 |
| |
7650 pidgin_conv_window_show(dest_win); |
| |
7651 } |
| |
7652 } |
| |
7653 |
| |
7654 gtk_widget_grab_focus(PIDGIN_CONVERSATION(conv)->entry); |
| |
7655 |
| |
7656 return TRUE; |
| |
7657 } |
| |
7658 |
| |
7659 |
| |
7660 static void |
| |
7661 before_switch_conv_cb(GtkNotebook *notebook, GtkWidget *page, gint page_num, |
| |
7662 gpointer user_data) |
| |
7663 { |
| |
7664 PidginWindow *win; |
| |
7665 PurpleConversation *conv; |
| |
7666 PidginConversation *gtkconv; |
| |
7667 |
| |
7668 win = user_data; |
| |
7669 conv = pidgin_conv_window_get_active_conversation(win); |
| |
7670 |
| |
7671 g_return_if_fail(conv != NULL); |
| |
7672 |
| |
7673 if (purple_conversation_get_type(conv) != PURPLE_CONV_TYPE_IM) |
| |
7674 return; |
| |
7675 |
| |
7676 gtkconv = PIDGIN_CONVERSATION(conv); |
| |
7677 |
| |
7678 stop_anim(NULL, gtkconv); |
| |
7679 } |
| |
7680 static void |
| |
7681 close_window(GtkWidget *w, PidginWindow *win) |
| |
7682 { |
| |
7683 close_win_cb(w, NULL, win); |
| |
7684 } |
| |
7685 |
| |
7686 static void |
| |
7687 detach_tab_cb(GtkWidget *w, GObject *menu) |
| |
7688 { |
| |
7689 PidginWindow *win, *new_window; |
| |
7690 PidginConversation *gtkconv; |
| |
7691 |
| |
7692 gtkconv = g_object_get_data(menu, "clicked_tab"); |
| |
7693 |
| |
7694 if (!gtkconv) |
| |
7695 return; |
| |
7696 |
| |
7697 win = pidgin_conv_get_window(gtkconv); |
| |
7698 /* Nothing to do if there's only one tab in the window */ |
| |
7699 if (pidgin_conv_window_get_gtkconv_count(win) == 1) |
| |
7700 return; |
| |
7701 |
| |
7702 pidgin_conv_window_remove_gtkconv(win, gtkconv); |
| |
7703 |
| |
7704 new_window = pidgin_conv_window_new(); |
| |
7705 pidgin_conv_window_add_gtkconv(new_window, gtkconv); |
| |
7706 pidgin_conv_window_show(new_window); |
| |
7707 } |
| |
7708 |
| |
7709 static void |
| |
7710 close_others_cb(GtkWidget *w, GObject *menu) |
| |
7711 { |
| |
7712 GList *iter; |
| |
7713 PidginConversation *gtkconv; |
| |
7714 PidginWindow *win; |
| |
7715 |
| |
7716 gtkconv = g_object_get_data(menu, "clicked_tab"); |
| |
7717 |
| |
7718 if (!gtkconv) |
| |
7719 return; |
| |
7720 |
| |
7721 win = pidgin_conv_get_window(gtkconv); |
| |
7722 |
| |
7723 for (iter = pidgin_conv_window_get_gtkconvs(win); iter; ) |
| |
7724 { |
| |
7725 PidginConversation *gconv = iter->data; |
| |
7726 iter = iter->next; |
| |
7727 |
| |
7728 if (gconv != gtkconv) |
| |
7729 { |
| |
7730 close_conv_cb(NULL, gconv); |
| |
7731 } |
| |
7732 } |
| |
7733 } |
| |
7734 |
| |
7735 static void close_tab_cb(GtkWidget *w, GObject *menu) |
| |
7736 { |
| |
7737 PidginConversation *gtkconv; |
| |
7738 |
| |
7739 gtkconv = g_object_get_data(menu, "clicked_tab"); |
| |
7740 |
| |
7741 if (gtkconv) |
| |
7742 close_conv_cb(NULL, gtkconv); |
| |
7743 } |
| |
7744 |
| |
7745 static gboolean |
| |
7746 right_click_menu_cb(GtkNotebook *notebook, GdkEventButton *event, PidginWindow *win) |
| |
7747 { |
| |
7748 GtkWidget *item, *menu; |
| |
7749 PidginConversation *gtkconv; |
| |
7750 |
| |
7751 if (event->type != GDK_BUTTON_PRESS || event->button != 3) |
| |
7752 return FALSE; |
| |
7753 |
| |
7754 gtkconv = pidgin_conv_window_get_gtkconv_at_index(win, |
| |
7755 pidgin_conv_get_tab_at_xy(win, event->x_root, event->y_root, NULL)); |
| |
7756 |
| |
7757 if (g_object_get_data(G_OBJECT(notebook->menu), "clicked_tab")) |
| |
7758 { |
| |
7759 g_object_set_data(G_OBJECT(notebook->menu), "clicked_tab", gtkconv); |
| |
7760 return FALSE; |
| |
7761 } |
| |
7762 |
| |
7763 g_object_set_data(G_OBJECT(notebook->menu), "clicked_tab", gtkconv); |
| |
7764 |
| |
7765 menu = notebook->menu; |
| |
7766 pidgin_separator(GTK_WIDGET(menu)); |
| |
7767 |
| |
7768 item = gtk_menu_item_new_with_label(_("Close other tabs")); |
| |
7769 gtk_widget_show(item); |
| |
7770 gtk_menu_shell_append(GTK_MENU_SHELL(menu), item); |
| |
7771 g_signal_connect(G_OBJECT(item), "activate", |
| |
7772 G_CALLBACK(close_others_cb), menu); |
| |
7773 |
| |
7774 item = gtk_menu_item_new_with_label(_("Close all tabs")); |
| |
7775 gtk_widget_show(item); |
| |
7776 gtk_menu_shell_append(GTK_MENU_SHELL(menu), item); |
| |
7777 g_signal_connect(G_OBJECT(item), "activate", |
| |
7778 G_CALLBACK(close_window), win); |
| |
7779 |
| |
7780 pidgin_separator(menu); |
| |
7781 |
| |
7782 item = gtk_menu_item_new_with_label(_("Detach this tab")); |
| |
7783 gtk_widget_show(item); |
| |
7784 gtk_menu_shell_append(GTK_MENU_SHELL(menu), item); |
| |
7785 g_signal_connect(G_OBJECT(item), "activate", |
| |
7786 G_CALLBACK(detach_tab_cb), menu); |
| |
7787 |
| |
7788 item = gtk_menu_item_new_with_label(_("Close this tab")); |
| |
7789 gtk_widget_show(item); |
| |
7790 gtk_menu_shell_append(GTK_MENU_SHELL(menu), item); |
| |
7791 g_signal_connect(G_OBJECT(item), "activate", |
| |
7792 G_CALLBACK(close_tab_cb), menu); |
| |
7793 |
| |
7794 return FALSE; |
| |
7795 } |
| |
7796 |
| |
7797 static void |
| |
7798 switch_conv_cb(GtkNotebook *notebook, GtkWidget *page, gint page_num, |
| |
7799 gpointer user_data) |
| |
7800 { |
| |
7801 PidginWindow *win; |
| |
7802 PurpleConversation *conv; |
| |
7803 PidginConversation *gtkconv; |
| |
7804 const char *sound_method; |
| |
7805 |
| |
7806 win = user_data; |
| |
7807 gtkconv = pidgin_conv_window_get_gtkconv_at_index(win, page_num); |
| |
7808 conv = gtkconv->active_conv; |
| |
7809 |
| |
7810 g_return_if_fail(conv != NULL); |
| |
7811 |
| |
7812 /* clear unseen flag if conversation is not hidden */ |
| |
7813 if(!pidgin_conv_is_hidden(gtkconv)) { |
| |
7814 gtkconv_set_unseen(gtkconv, PIDGIN_UNSEEN_NONE); |
| |
7815 } |
| |
7816 |
| |
7817 /* Update the menubar */ |
| |
7818 |
| |
7819 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(gtkconv->win->menu.logging), |
| |
7820 purple_conversation_is_logging(conv)); |
| |
7821 |
| |
7822 generate_send_to_items(win); |
| |
7823 regenerate_options_items(win); |
| |
7824 |
| |
7825 pidgin_conv_switch_active_conversation(conv); |
| |
7826 |
| |
7827 sound_method = purple_prefs_get_string(PIDGIN_PREFS_ROOT "/sound/method"); |
| |
7828 if (strcmp(sound_method, "none") != 0) |
| |
7829 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(win->menu.sounds), |
| |
7830 gtkconv->make_sound); |
| |
7831 |
| |
7832 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(win->menu.show_formatting_toolbar), |
| |
7833 purple_prefs_get_bool(PIDGIN_PREFS_ROOT "/conversations/show_formatting_toolbar")); |
| |
7834 |
| |
7835 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(win->menu.show_timestamps), |
| |
7836 purple_prefs_get_bool(PIDGIN_PREFS_ROOT "/conversations/show_timestamps")); |
| |
7837 |
| |
7838 if (purple_conversation_get_type(conv) == PURPLE_CONV_TYPE_IM && |
| |
7839 purple_prefs_get_bool(PIDGIN_PREFS_ROOT "/conversations/im/show_buddy_icons")) |
| |
7840 { |
| |
7841 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(win->menu.show_icon), |
| |
7842 gtkconv->u.im->show_icon); |
| |
7843 } |
| |
7844 |
| |
7845 /* |
| |
7846 * We pause icons when they are not visible. If this icon should |
| |
7847 * be animated then start it back up again. |
| |
7848 */ |
| |
7849 if ((purple_conversation_get_type(conv) == PURPLE_CONV_TYPE_IM) && |
| |
7850 (gtkconv->u.im->animate)) |
| |
7851 start_anim(NULL, gtkconv); |
| |
7852 |
| |
7853 purple_signal_emit(pidgin_conversations_get_handle(), "conversation-switched", conv); |
| |
7854 } |
| |
7855 |
| |
7856 /************************************************************************** |
| |
7857 * GTK+ window ops |
| |
7858 **************************************************************************/ |
| |
7859 |
| |
7860 GList * |
| |
7861 pidgin_conv_windows_get_list() |
| |
7862 { |
| |
7863 return window_list; |
| |
7864 } |
| |
7865 |
| |
7866 static GList* |
| |
7867 make_status_icon_list(const char *stock, GtkWidget *w) |
| |
7868 { |
| |
7869 GList *l = NULL; |
| |
7870 l = g_list_append(l, gtk_widget_render_icon (w, stock, |
| |
7871 gtk_icon_size_from_name(PIDGIN_ICON_SIZE_TANGO_EXTRA_SMALL), "GtkWindow")); |
| |
7872 l = g_list_append(l, gtk_widget_render_icon (w, stock, |
| |
7873 gtk_icon_size_from_name(PIDGIN_ICON_SIZE_TANGO_SMALL), "GtkWindow")); |
| |
7874 l = g_list_append(l, gtk_widget_render_icon (w, stock, |
| |
7875 gtk_icon_size_from_name(PIDGIN_ICON_SIZE_TANGO_MEDIUM), "GtkWindow")); |
| |
7876 l = g_list_append(l, gtk_widget_render_icon (w, stock, |
| |
7877 gtk_icon_size_from_name(PIDGIN_ICON_SIZE_TANGO_LARGE), "GtkWindow")); |
| |
7878 return l; |
| |
7879 } |
| |
7880 |
| |
7881 static void |
| |
7882 create_icon_lists(GtkWidget *w) |
| |
7883 { |
| |
7884 available_list = make_status_icon_list(PIDGIN_STOCK_STATUS_AVAILABLE, w); |
| |
7885 busy_list = make_status_icon_list(PIDGIN_STOCK_STATUS_BUSY, w); |
| |
7886 xa_list = make_status_icon_list(PIDGIN_STOCK_STATUS_XA, w); |
| |
7887 login_list = make_status_icon_list(PIDGIN_STOCK_STATUS_LOGIN, w); |
| |
7888 logout_list = make_status_icon_list(PIDGIN_STOCK_STATUS_LOGOUT, w); |
| |
7889 offline_list = make_status_icon_list(PIDGIN_STOCK_STATUS_OFFLINE, w); |
| |
7890 away_list = make_status_icon_list(PIDGIN_STOCK_STATUS_AWAY, w); |
| |
7891 prpl_lists = g_hash_table_new(g_str_hash, g_str_equal); |
| |
7892 } |
| |
7893 |
| |
7894 PidginWindow * |
| |
7895 pidgin_conv_window_new() |
| |
7896 { |
| |
7897 PidginWindow *win; |
| |
7898 GtkPositionType pos; |
| |
7899 GtkWidget *testidea; |
| |
7900 GtkWidget *menubar; |
| |
7901 |
| |
7902 win = g_malloc0(sizeof(PidginWindow)); |
| |
7903 |
| |
7904 window_list = g_list_append(window_list, win); |
| |
7905 |
| |
7906 /* Create the window. */ |
| |
7907 win->window = gtk_window_new(GTK_WINDOW_TOPLEVEL); |
| |
7908 gtk_window_set_role(GTK_WINDOW(win->window), "conversation"); |
| |
7909 gtk_window_set_resizable(GTK_WINDOW(win->window), TRUE); |
| |
7910 gtk_container_set_border_width(GTK_CONTAINER(win->window), 0); |
| |
7911 GTK_WINDOW(win->window)->allow_shrink = TRUE; |
| |
7912 |
| |
7913 if (available_list == NULL) { |
| |
7914 create_icon_lists(win->window); |
| |
7915 } |
| |
7916 |
| |
7917 g_signal_connect(G_OBJECT(win->window), "delete_event", |
| |
7918 G_CALLBACK(close_win_cb), win); |
| |
7919 |
| |
7920 g_signal_connect(G_OBJECT(win->window), "focus_in_event", |
| |
7921 G_CALLBACK(focus_win_cb), win); |
| |
7922 |
| |
7923 /* Create the notebook. */ |
| |
7924 win->notebook = gtk_notebook_new(); |
| |
7925 |
| |
7926 pos = purple_prefs_get_int(PIDGIN_PREFS_ROOT "/conversations/tab_side"); |
| |
7927 |
| |
7928 #if 0 |
| |
7929 gtk_notebook_set_tab_hborder(GTK_NOTEBOOK(win->notebook), 0); |
| |
7930 gtk_notebook_set_tab_vborder(GTK_NOTEBOOK(win->notebook), 0); |
| |
7931 #endif |
| |
7932 gtk_notebook_set_tab_pos(GTK_NOTEBOOK(win->notebook), pos); |
| |
7933 gtk_notebook_set_scrollable(GTK_NOTEBOOK(win->notebook), TRUE); |
| |
7934 gtk_notebook_popup_enable(GTK_NOTEBOOK(win->notebook)); |
| |
7935 gtk_notebook_set_show_tabs(GTK_NOTEBOOK(win->notebook), FALSE); |
| |
7936 gtk_notebook_set_show_border(GTK_NOTEBOOK(win->notebook), FALSE); |
| |
7937 |
| |
7938 g_signal_connect(G_OBJECT(win->notebook), "button-press-event", |
| |
7939 G_CALLBACK(right_click_menu_cb), win); |
| |
7940 |
| |
7941 gtk_widget_show(win->notebook); |
| |
7942 |
| |
7943 g_signal_connect(G_OBJECT(win->notebook), "switch_page", |
| |
7944 G_CALLBACK(before_switch_conv_cb), win); |
| |
7945 g_signal_connect_after(G_OBJECT(win->notebook), "switch_page", |
| |
7946 G_CALLBACK(switch_conv_cb), win); |
| |
7947 |
| |
7948 /* Setup the tab drag and drop signals. */ |
| |
7949 gtk_widget_add_events(win->notebook, |
| |
7950 GDK_BUTTON1_MOTION_MASK | GDK_LEAVE_NOTIFY_MASK); |
| |
7951 g_signal_connect(G_OBJECT(win->notebook), "button_press_event", |
| |
7952 G_CALLBACK(notebook_press_cb), win); |
| |
7953 g_signal_connect(G_OBJECT(win->notebook), "button_release_event", |
| |
7954 G_CALLBACK(notebook_release_cb), win); |
| |
7955 |
| |
7956 testidea = gtk_vbox_new(FALSE, 0); |
| |
7957 |
| |
7958 /* Setup the menubar. */ |
| |
7959 menubar = setup_menubar(win); |
| |
7960 gtk_box_pack_start(GTK_BOX(testidea), menubar, FALSE, TRUE, 0); |
| |
7961 |
| |
7962 gtk_box_pack_start(GTK_BOX(testidea), win->notebook, TRUE, TRUE, 0); |
| |
7963 |
| |
7964 gtk_container_add(GTK_CONTAINER(win->window), testidea); |
| |
7965 |
| |
7966 gtk_widget_show(testidea); |
| |
7967 |
| |
7968 #ifdef _WIN32 |
| |
7969 g_signal_connect(G_OBJECT(win->window), "show", |
| |
7970 G_CALLBACK(winpidgin_ensure_onscreen), win->window); |
| |
7971 #endif |
| |
7972 |
| |
7973 return win; |
| |
7974 } |
| |
7975 |
| |
7976 void |
| |
7977 pidgin_conv_window_destroy(PidginWindow *win) |
| |
7978 { |
| |
7979 purple_prefs_disconnect_by_handle(win); |
| |
7980 window_list = g_list_remove(window_list, win); |
| |
7981 |
| |
7982 /* Close the "Find" dialog if it's open */ |
| |
7983 if (win->dialogs.search) |
| |
7984 gtk_widget_destroy(win->dialogs.search); |
| |
7985 |
| |
7986 gtk_widget_hide_all(win->window); |
| |
7987 |
| |
7988 if (win->gtkconvs) { |
| |
7989 while (win->gtkconvs) { |
| |
7990 GList *nextgtk = win->gtkconvs->next; |
| |
7991 PidginConversation *gtkconv = win->gtkconvs->data; |
| |
7992 GList *nextcore = gtkconv->convs->next; |
| |
7993 PurpleConversation *conv = gtkconv->convs->data; |
| |
7994 purple_conversation_destroy(conv); |
| |
7995 if (!nextgtk && !nextcore) |
| |
7996 /* we'll end up invoking ourselves when we destroy our last child */ |
| |
7997 /* so don't destroy ourselves right now */ |
| |
7998 return; |
| |
7999 } |
| |
8000 return; |
| |
8001 } |
| |
8002 gtk_widget_destroy(win->window); |
| |
8003 |
| |
8004 g_object_unref(G_OBJECT(win->menu.item_factory)); |
| |
8005 |
| |
8006 purple_notify_close_with_handle(win); |
| |
8007 |
| |
8008 g_free(win); |
| |
8009 } |
| |
8010 |
| |
8011 void |
| |
8012 pidgin_conv_window_show(PidginWindow *win) |
| |
8013 { |
| |
8014 gtk_widget_show(win->window); |
| |
8015 } |
| |
8016 |
| |
8017 void |
| |
8018 pidgin_conv_window_hide(PidginWindow *win) |
| |
8019 { |
| |
8020 gtk_widget_hide(win->window); |
| |
8021 } |
| |
8022 |
| |
8023 void |
| |
8024 pidgin_conv_window_raise(PidginWindow *win) |
| |
8025 { |
| |
8026 gdk_window_raise(GDK_WINDOW(win->window->window)); |
| |
8027 } |
| |
8028 |
| |
8029 void |
| |
8030 pidgin_conv_window_switch_gtkconv(PidginWindow *win, PidginConversation *gtkconv) |
| |
8031 { |
| |
8032 gtk_notebook_set_current_page(GTK_NOTEBOOK(win->notebook), |
| |
8033 gtk_notebook_page_num(GTK_NOTEBOOK(win->notebook), |
| |
8034 gtkconv->tab_cont)); |
| |
8035 } |
| |
8036 |
| |
8037 void |
| |
8038 pidgin_conv_window_add_gtkconv(PidginWindow *win, PidginConversation *gtkconv) |
| |
8039 { |
| |
8040 PurpleConversation *conv = gtkconv->active_conv; |
| |
8041 PidginConversation *focus_gtkconv; |
| |
8042 GtkWidget *tabby, *menu_tabby; |
| |
8043 GtkWidget *tab_cont = gtkconv->tab_cont; |
| |
8044 GtkWidget *close_image; |
| |
8045 PurpleConversationType conv_type; |
| |
8046 const gchar *tmp_lab; |
| |
8047 gint close_button_width, close_button_height, focus_width, focus_pad; |
| |
8048 gboolean tabs_side = FALSE; |
| |
8049 gint angle = 0; |
| |
8050 |
| |
8051 conv_type = purple_conversation_get_type(conv); |
| |
8052 |
| |
8053 |
| |
8054 win->gtkconvs = g_list_append(win->gtkconvs, gtkconv); |
| |
8055 gtkconv->win = win; |
| |
8056 |
| |
8057 if (purple_prefs_get_int(PIDGIN_PREFS_ROOT "/conversations/tab_side") == GTK_POS_LEFT || |
| |
8058 purple_prefs_get_int(PIDGIN_PREFS_ROOT "/conversations/tab_side") == GTK_POS_RIGHT) |
| |
8059 tabs_side = TRUE; |
| |
8060 else if (purple_prefs_get_int(PIDGIN_PREFS_ROOT "/conversations/tab_side") == (GTK_POS_LEFT|8)) |
| |
8061 angle = 90; |
| |
8062 else if (purple_prefs_get_int(PIDGIN_PREFS_ROOT "/conversations/tab_side") == (GTK_POS_RIGHT|8)) |
| |
8063 angle = 270; |
| |
8064 |
| |
8065 if (angle) |
| |
8066 gtkconv->tabby = tabby = gtk_vbox_new(FALSE, PIDGIN_HIG_BOX_SPACE); |
| |
8067 else |
| |
8068 gtkconv->tabby = tabby = gtk_hbox_new(FALSE, PIDGIN_HIG_BOX_SPACE); |
| |
8069 gtkconv->menu_tabby = menu_tabby = gtk_hbox_new(FALSE, PIDGIN_HIG_BOX_SPACE); |
| |
8070 |
| |
8071 /* Close button. */ |
| |
8072 gtkconv->close = gtk_button_new(); |
| |
8073 gtk_icon_size_lookup(GTK_ICON_SIZE_MENU, &close_button_width, &close_button_height); |
| |
8074 if (gtk_check_version(2, 4, 2) == NULL) { |
| |
8075 /* Need to account for extra padding around the gtkbutton */ |
| |
8076 gtk_widget_style_get(GTK_WIDGET(gtkconv->close), |
| |
8077 "focus-line-width", &focus_width, |
| |
8078 "focus-padding", &focus_pad, |
| |
8079 NULL); |
| |
8080 close_button_width += (focus_width + focus_pad) * 2; |
| |
8081 close_button_height += (focus_width + focus_pad) * 2; |
| |
8082 } |
| |
8083 gtk_widget_set_size_request(GTK_WIDGET(gtkconv->close), |
| |
8084 close_button_width, close_button_height); |
| |
8085 |
| |
8086 gtk_button_set_relief(GTK_BUTTON(gtkconv->close), GTK_RELIEF_NONE); |
| |
8087 close_image = gtk_image_new_from_stock(GTK_STOCK_CLOSE, GTK_ICON_SIZE_MENU); |
| |
8088 gtk_widget_show(close_image); |
| |
8089 gtk_container_add(GTK_CONTAINER(gtkconv->close), close_image); |
| |
8090 gtk_tooltips_set_tip(gtkconv->tooltips, gtkconv->close, |
| |
8091 _("Close conversation"), NULL); |
| |
8092 |
| |
8093 g_signal_connect(G_OBJECT(gtkconv->close), "clicked", |
| |
8094 G_CALLBACK(close_conv_cb), gtkconv); |
| |
8095 |
| |
8096 #if !GTK_CHECK_VERSION(2,6,0) |
| |
8097 /* |
| |
8098 * I love Galeon. They have a fix for that stupid annoying visible |
| |
8099 * border bug. I love you guys! -- ChipX86 |
| |
8100 */ |
| |
8101 /* This is fixed properly in some version of Gtk before 2.6.0 */ |
| |
8102 g_signal_connect(G_OBJECT(gtkconv->close), "state_changed", |
| |
8103 G_CALLBACK(tab_close_button_state_changed_cb), NULL); |
| |
8104 #endif |
| |
8105 |
| |
8106 /* Status icon. */ |
| |
8107 gtkconv->icon = gtk_image_new(); |
| |
8108 gtkconv->menu_icon = gtk_image_new(); |
| |
8109 update_tab_icon(conv); |
| |
8110 |
| |
8111 /* Tab label. */ |
| |
8112 gtkconv->tab_label = gtk_label_new(tmp_lab = purple_conversation_get_title(conv)); |
| |
8113 |
| |
8114 #if GTK_CHECK_VERSION(2,6,0) |
| |
8115 if (!angle) |
| |
8116 g_object_set(G_OBJECT(gtkconv->tab_label), "ellipsize", PANGO_ELLIPSIZE_END, NULL); |
| |
8117 gtk_label_set_width_chars(GTK_LABEL(gtkconv->tab_label), 6); |
| |
8118 if (tabs_side) { |
| |
8119 gtk_label_set_width_chars(GTK_LABEL(gtkconv->tab_label), MIN(g_utf8_strlen(tmp_lab, -1), 12)); |
| |
8120 } |
| |
8121 if (angle) |
| |
8122 gtk_label_set_angle(GTK_LABEL(gtkconv->tab_label), angle); |
| |
8123 #endif |
| |
8124 gtkconv->menu_label = gtk_label_new(purple_conversation_get_title(conv)); |
| |
8125 #if 0 |
| |
8126 gtk_misc_set_alignment(GTK_MISC(gtkconv->tab_label), 0.00, 0.5); |
| |
8127 gtk_misc_set_padding(GTK_MISC(gtkconv->tab_label), 4, 0); |
| |
8128 #endif |
| |
8129 |
| |
8130 /* Pack it all together. */ |
| |
8131 if (angle == 90) |
| |
8132 gtk_box_pack_start(GTK_BOX(tabby), gtkconv->close, FALSE, FALSE, 0); |
| |
8133 else |
| |
8134 gtk_box_pack_start(GTK_BOX(tabby), gtkconv->icon, FALSE, FALSE, 0); |
| |
8135 gtk_box_pack_start(GTK_BOX(menu_tabby), gtkconv->menu_icon, |
| |
8136 FALSE, FALSE, 0); |
| |
8137 |
| |
8138 gtk_widget_show_all(gtkconv->icon); |
| |
8139 gtk_widget_show_all(gtkconv->menu_icon); |
| |
8140 |
| |
8141 gtk_box_pack_start(GTK_BOX(tabby), gtkconv->tab_label, TRUE, TRUE, 0); |
| |
8142 gtk_box_pack_start(GTK_BOX(menu_tabby), gtkconv->menu_label, TRUE, TRUE, 0); |
| |
8143 gtk_widget_show(gtkconv->tab_label); |
| |
8144 gtk_widget_show(gtkconv->menu_label); |
| |
8145 gtk_misc_set_alignment(GTK_MISC(gtkconv->menu_label), 0, 0); |
| |
8146 |
| |
8147 if (angle == 90) |
| |
8148 gtk_box_pack_start(GTK_BOX(tabby), gtkconv->icon, FALSE, FALSE, 0); |
| |
8149 else |
| |
8150 gtk_box_pack_start(GTK_BOX(tabby), gtkconv->close, FALSE, FALSE, 0); |
| |
8151 if (purple_prefs_get_bool(PIDGIN_PREFS_ROOT "/conversations/close_on_tabs")) |
| |
8152 gtk_widget_show(gtkconv->close); |
| |
8153 |
| |
8154 gtk_widget_show(tabby); |
| |
8155 gtk_widget_show(menu_tabby); |
| |
8156 |
| |
8157 if (purple_conversation_get_type(conv) == PURPLE_CONV_TYPE_IM) |
| |
8158 pidgin_conv_update_buddy_icon(conv); |
| |
8159 |
| |
8160 /* Add this pane to the conversation's notebook. */ |
| |
8161 gtk_notebook_append_page_menu(GTK_NOTEBOOK(win->notebook), tab_cont, tabby, menu_tabby); |
| |
8162 gtk_notebook_set_tab_label_packing(GTK_NOTEBOOK(win->notebook), tab_cont, !tabs_side && !angle, TRUE, GTK_PACK_START); |
| |
8163 |
| |
8164 |
| |
8165 gtk_widget_show(tab_cont); |
| |
8166 |
| |
8167 if (pidgin_conv_window_get_gtkconv_count(win) == 1) { |
| |
8168 /* Er, bug in notebooks? Switch to the page manually. */ |
| |
8169 gtk_notebook_set_current_page(GTK_NOTEBOOK(win->notebook), 0); |
| |
8170 |
| |
8171 gtk_notebook_set_show_tabs(GTK_NOTEBOOK(win->notebook), |
| |
8172 purple_prefs_get_bool(PIDGIN_PREFS_ROOT "/conversations/tabs")); |
| |
8173 } else |
| |
8174 gtk_notebook_set_show_tabs(GTK_NOTEBOOK(win->notebook), TRUE); |
| |
8175 |
| |
8176 focus_gtkconv = g_list_nth_data(pidgin_conv_window_get_gtkconvs(win), |
| |
8177 gtk_notebook_get_current_page(GTK_NOTEBOOK(win->notebook))); |
| |
8178 gtk_widget_grab_focus(focus_gtkconv->entry); |
| |
8179 |
| |
8180 if (pidgin_conv_window_get_gtkconv_count(win) == 1) |
| |
8181 update_send_to_selection(win); |
| |
8182 } |
| |
8183 |
| |
8184 void |
| |
8185 pidgin_conv_window_remove_gtkconv(PidginWindow *win, PidginConversation *gtkconv) |
| |
8186 { |
| |
8187 unsigned int index; |
| |
8188 PurpleConversationType conv_type; |
| |
8189 |
| |
8190 conv_type = purple_conversation_get_type(gtkconv->active_conv); |
| |
8191 index = gtk_notebook_page_num(GTK_NOTEBOOK(win->notebook), gtkconv->tab_cont); |
| |
8192 |
| |
8193 g_object_ref(gtkconv->tab_cont); |
| |
8194 gtk_object_sink(GTK_OBJECT(gtkconv->tab_cont)); |
| |
8195 |
| |
8196 gtk_notebook_remove_page(GTK_NOTEBOOK(win->notebook), index); |
| |
8197 |
| |
8198 /* go back to tabless if need be */ |
| |
8199 if (pidgin_conv_window_get_gtkconv_count(win) <= 2) { |
| |
8200 gtk_notebook_set_show_tabs(GTK_NOTEBOOK(win->notebook), |
| |
8201 purple_prefs_get_bool(PIDGIN_PREFS_ROOT "/conversations/tabs")); |
| |
8202 } |
| |
8203 |
| |
8204 win->gtkconvs = g_list_remove(win->gtkconvs, gtkconv); |
| |
8205 |
| |
8206 if (!win->gtkconvs && win != hidden_convwin) |
| |
8207 pidgin_conv_window_destroy(win); |
| |
8208 } |
| |
8209 |
| |
8210 PidginConversation * |
| |
8211 pidgin_conv_window_get_gtkconv_at_index(const PidginWindow *win, int index) |
| |
8212 { |
| |
8213 GtkWidget *tab_cont; |
| |
8214 |
| |
8215 if (index == -1) |
| |
8216 index = 0; |
| |
8217 tab_cont = gtk_notebook_get_nth_page(GTK_NOTEBOOK(win->notebook), index); |
| |
8218 return tab_cont ? g_object_get_data(G_OBJECT(tab_cont), "PidginConversation") : NULL; |
| |
8219 } |
| |
8220 |
| |
8221 PidginConversation * |
| |
8222 pidgin_conv_window_get_active_gtkconv(const PidginWindow *win) |
| |
8223 { |
| |
8224 int index; |
| |
8225 GtkWidget *tab_cont; |
| |
8226 |
| |
8227 index = gtk_notebook_get_current_page(GTK_NOTEBOOK(win->notebook)); |
| |
8228 if (index == -1) |
| |
8229 index = 0; |
| |
8230 tab_cont = gtk_notebook_get_nth_page(GTK_NOTEBOOK(win->notebook), index); |
| |
8231 if (!tab_cont) |
| |
8232 return NULL; |
| |
8233 return g_object_get_data(G_OBJECT(tab_cont), "PidginConversation"); |
| |
8234 } |
| |
8235 |
| |
8236 |
| |
8237 PurpleConversation * |
| |
8238 pidgin_conv_window_get_active_conversation(const PidginWindow *win) |
| |
8239 { |
| |
8240 PidginConversation *gtkconv; |
| |
8241 |
| |
8242 gtkconv = pidgin_conv_window_get_active_gtkconv(win); |
| |
8243 return gtkconv ? gtkconv->active_conv : NULL; |
| |
8244 } |
| |
8245 |
| |
8246 gboolean |
| |
8247 pidgin_conv_window_is_active_conversation(const PurpleConversation *conv) |
| |
8248 { |
| |
8249 return conv == pidgin_conv_window_get_active_conversation(PIDGIN_CONVERSATION(conv)->win); |
| |
8250 } |
| |
8251 |
| |
8252 gboolean |
| |
8253 pidgin_conv_window_has_focus(PidginWindow *win) |
| |
8254 { |
| |
8255 gboolean has_focus = FALSE; |
| |
8256 |
| |
8257 g_object_get(G_OBJECT(win->window), "has-toplevel-focus", &has_focus, NULL); |
| |
8258 |
| |
8259 return has_focus; |
| |
8260 } |
| |
8261 |
| |
8262 PidginWindow * |
| |
8263 pidgin_conv_window_get_at_xy(int x, int y) |
| |
8264 { |
| |
8265 PidginWindow *win; |
| |
8266 GdkWindow *gdkwin; |
| |
8267 GList *l; |
| |
8268 |
| |
8269 gdkwin = gdk_window_at_pointer(&x, &y); |
| |
8270 |
| |
8271 if (gdkwin) |
| |
8272 gdkwin = gdk_window_get_toplevel(gdkwin); |
| |
8273 |
| |
8274 for (l = pidgin_conv_windows_get_list(); l != NULL; l = l->next) { |
| |
8275 win = l->data; |
| |
8276 |
| |
8277 if (gdkwin == win->window->window) |
| |
8278 return win; |
| |
8279 } |
| |
8280 |
| |
8281 return NULL; |
| |
8282 } |
| |
8283 |
| |
8284 GList * |
| |
8285 pidgin_conv_window_get_gtkconvs(PidginWindow *win) |
| |
8286 { |
| |
8287 return win->gtkconvs; |
| |
8288 } |
| |
8289 |
| |
8290 guint |
| |
8291 pidgin_conv_window_get_gtkconv_count(PidginWindow *win) |
| |
8292 { |
| |
8293 return g_list_length(win->gtkconvs); |
| |
8294 } |
| |
8295 |
| |
8296 PidginWindow * |
| |
8297 pidgin_conv_window_first_with_type(PurpleConversationType type) |
| |
8298 { |
| |
8299 GList *wins, *convs; |
| |
8300 PidginWindow *win; |
| |
8301 PidginConversation *conv; |
| |
8302 |
| |
8303 if (type == PURPLE_CONV_TYPE_UNKNOWN) |
| |
8304 return NULL; |
| |
8305 |
| |
8306 for (wins = pidgin_conv_windows_get_list(); wins != NULL; wins = wins->next) { |
| |
8307 win = wins->data; |
| |
8308 |
| |
8309 for (convs = win->gtkconvs; |
| |
8310 convs != NULL; |
| |
8311 convs = convs->next) { |
| |
8312 |
| |
8313 conv = convs->data; |
| |
8314 |
| |
8315 if (purple_conversation_get_type(conv->active_conv) == type) |
| |
8316 return win; |
| |
8317 } |
| |
8318 } |
| |
8319 |
| |
8320 return NULL; |
| |
8321 } |
| |
8322 |
| |
8323 PidginWindow * |
| |
8324 pidgin_conv_window_last_with_type(PurpleConversationType type) |
| |
8325 { |
| |
8326 GList *wins, *convs; |
| |
8327 PidginWindow *win; |
| |
8328 PidginConversation *conv; |
| |
8329 |
| |
8330 if (type == PURPLE_CONV_TYPE_UNKNOWN) |
| |
8331 return NULL; |
| |
8332 |
| |
8333 for (wins = g_list_last(pidgin_conv_windows_get_list()); |
| |
8334 wins != NULL; |
| |
8335 wins = wins->prev) { |
| |
8336 |
| |
8337 win = wins->data; |
| |
8338 |
| |
8339 for (convs = win->gtkconvs; |
| |
8340 convs != NULL; |
| |
8341 convs = convs->next) { |
| |
8342 |
| |
8343 conv = convs->data; |
| |
8344 |
| |
8345 if (purple_conversation_get_type(conv->active_conv) == type) |
| |
8346 return win; |
| |
8347 } |
| |
8348 } |
| |
8349 |
| |
8350 return NULL; |
| |
8351 } |
| |
8352 |
| |
8353 |
| |
8354 /************************************************************************** |
| |
8355 * Conversation placement functions |
| |
8356 **************************************************************************/ |
| |
8357 typedef struct |
| |
8358 { |
| |
8359 char *id; |
| |
8360 char *name; |
| |
8361 PidginConvPlacementFunc fnc; |
| |
8362 |
| |
8363 } ConvPlacementData; |
| |
8364 |
| |
8365 static GList *conv_placement_fncs = NULL; |
| |
8366 static PidginConvPlacementFunc place_conv = NULL; |
| |
8367 |
| |
8368 /* This one places conversations in the last made window. */ |
| |
8369 static void |
| |
8370 conv_placement_last_created_win(PidginConversation *conv) |
| |
8371 { |
| |
8372 PidginWindow *win; |
| |
8373 |
| |
8374 GList *l = g_list_last(pidgin_conv_windows_get_list()); |
| |
8375 win = l ? l->data : NULL;; |
| |
8376 |
| |
8377 if (win == NULL) { |
| |
8378 win = pidgin_conv_window_new(); |
| |
8379 |
| |
8380 pidgin_conv_window_add_gtkconv(win, conv); |
| |
8381 pidgin_conv_window_show(win); |
| |
8382 } else { |
| |
8383 pidgin_conv_window_add_gtkconv(win, conv); |
| |
8384 } |
| |
8385 } |
| |
8386 |
| |
8387 /* This one places conversations in the last made window of the same type. */ |
| |
8388 static void |
| |
8389 conv_placement_last_created_win_type(PidginConversation *conv) |
| |
8390 { |
| |
8391 PidginWindow *win; |
| |
8392 |
| |
8393 win = pidgin_conv_window_last_with_type(purple_conversation_get_type(conv->active_conv)); |
| |
8394 |
| |
8395 if (win == NULL) { |
| |
8396 win = pidgin_conv_window_new(); |
| |
8397 |
| |
8398 pidgin_conv_window_add_gtkconv(win, conv); |
| |
8399 pidgin_conv_window_show(win); |
| |
8400 } else |
| |
8401 pidgin_conv_window_add_gtkconv(win, conv); |
| |
8402 } |
| |
8403 |
| |
8404 /* This one places each conversation in its own window. */ |
| |
8405 static void |
| |
8406 conv_placement_new_window(PidginConversation *conv) |
| |
8407 { |
| |
8408 PidginWindow *win; |
| |
8409 |
| |
8410 win = pidgin_conv_window_new(); |
| |
8411 |
| |
8412 pidgin_conv_window_add_gtkconv(win, conv); |
| |
8413 |
| |
8414 pidgin_conv_window_show(win); |
| |
8415 } |
| |
8416 |
| |
8417 static PurpleGroup * |
| |
8418 conv_get_group(PidginConversation *conv) |
| |
8419 { |
| |
8420 PurpleGroup *group = NULL; |
| |
8421 |
| |
8422 if (purple_conversation_get_type(conv->active_conv) == PURPLE_CONV_TYPE_IM) { |
| |
8423 PurpleBuddy *buddy; |
| |
8424 |
| |
8425 buddy = purple_find_buddy(purple_conversation_get_account(conv->active_conv), |
| |
8426 purple_conversation_get_name(conv->active_conv)); |
| |
8427 |
| |
8428 if (buddy != NULL) |
| |
8429 group = purple_buddy_get_group(buddy); |
| |
8430 |
| |
8431 } else if (purple_conversation_get_type(conv->active_conv) == PURPLE_CONV_TYPE_CHAT) { |
| |
8432 PurpleChat *chat; |
| |
8433 |
| |
8434 chat = purple_blist_find_chat(purple_conversation_get_account(conv->active_conv), |
| |
8435 purple_conversation_get_name(conv->active_conv)); |
| |
8436 |
| |
8437 if (chat != NULL) |
| |
8438 group = purple_chat_get_group(chat); |
| |
8439 } |
| |
8440 |
| |
8441 return group; |
| |
8442 } |
| |
8443 |
| |
8444 /* |
| |
8445 * This groups things by, well, group. Buddies from groups will always be |
| |
8446 * grouped together, and a buddy from a group not belonging to any currently |
| |
8447 * open windows will get a new window. |
| |
8448 */ |
| |
8449 static void |
| |
8450 conv_placement_by_group(PidginConversation *conv) |
| |
8451 { |
| |
8452 PurpleConversationType type; |
| |
8453 PurpleGroup *group = NULL; |
| |
8454 GList *wl, *cl; |
| |
8455 |
| |
8456 type = purple_conversation_get_type(conv->active_conv); |
| |
8457 |
| |
8458 group = conv_get_group(conv); |
| |
8459 |
| |
8460 /* Go through the list of IMs and find one with this group. */ |
| |
8461 for (wl = pidgin_conv_windows_get_list(); wl != NULL; wl = wl->next) { |
| |
8462 PidginWindow *win2; |
| |
8463 PidginConversation *conv2; |
| |
8464 PurpleGroup *group2 = NULL; |
| |
8465 |
| |
8466 win2 = wl->data; |
| |
8467 |
| |
8468 for (cl = win2->gtkconvs; |
| |
8469 cl != NULL; |
| |
8470 cl = cl->next) { |
| |
8471 conv2 = cl->data; |
| |
8472 |
| |
8473 group2 = conv_get_group(conv2); |
| |
8474 |
| |
8475 if (group == group2) { |
| |
8476 pidgin_conv_window_add_gtkconv(win2, conv); |
| |
8477 |
| |
8478 return; |
| |
8479 } |
| |
8480 } |
| |
8481 } |
| |
8482 |
| |
8483 /* Make a new window. */ |
| |
8484 conv_placement_new_window(conv); |
| |
8485 } |
| |
8486 |
| |
8487 /* This groups things by account. Otherwise, the same semantics as above */ |
| |
8488 static void |
| |
8489 conv_placement_by_account(PidginConversation *conv) |
| |
8490 { |
| |
8491 PurpleConversationType type; |
| |
8492 GList *wins, *convs; |
| |
8493 PurpleAccount *account; |
| |
8494 |
| |
8495 account = purple_conversation_get_account(conv->active_conv); |
| |
8496 type = purple_conversation_get_type(conv->active_conv); |
| |
8497 |
| |
8498 /* Go through the list of IMs and find one with this group. */ |
| |
8499 for (wins = pidgin_conv_windows_get_list(); wins != NULL; wins = wins->next) { |
| |
8500 PidginWindow *win2; |
| |
8501 PidginConversation *conv2; |
| |
8502 |
| |
8503 win2 = wins->data; |
| |
8504 |
| |
8505 for (convs = win2->gtkconvs; |
| |
8506 convs != NULL; |
| |
8507 convs = convs->next) { |
| |
8508 conv2 = convs->data; |
| |
8509 |
| |
8510 if (account == purple_conversation_get_account(conv2->active_conv)) { |
| |
8511 pidgin_conv_window_add_gtkconv(win2, conv); |
| |
8512 return; |
| |
8513 } |
| |
8514 } |
| |
8515 } |
| |
8516 |
| |
8517 /* Make a new window. */ |
| |
8518 conv_placement_new_window(conv); |
| |
8519 } |
| |
8520 |
| |
8521 static ConvPlacementData * |
| |
8522 get_conv_placement_data(const char *id) |
| |
8523 { |
| |
8524 ConvPlacementData *data = NULL; |
| |
8525 GList *n; |
| |
8526 |
| |
8527 for (n = conv_placement_fncs; n; n = n->next) { |
| |
8528 data = n->data; |
| |
8529 if (!strcmp(data->id, id)) |
| |
8530 return data; |
| |
8531 } |
| |
8532 |
| |
8533 return NULL; |
| |
8534 } |
| |
8535 |
| |
8536 static void |
| |
8537 add_conv_placement_fnc(const char *id, const char *name, |
| |
8538 PidginConvPlacementFunc fnc) |
| |
8539 { |
| |
8540 ConvPlacementData *data; |
| |
8541 |
| |
8542 data = g_new(ConvPlacementData, 1); |
| |
8543 |
| |
8544 data->id = g_strdup(id); |
| |
8545 data->name = g_strdup(name); |
| |
8546 data->fnc = fnc; |
| |
8547 |
| |
8548 conv_placement_fncs = g_list_append(conv_placement_fncs, data); |
| |
8549 } |
| |
8550 |
| |
8551 static void |
| |
8552 ensure_default_funcs(void) |
| |
8553 { |
| |
8554 if (conv_placement_fncs == NULL) { |
| |
8555 add_conv_placement_fnc("last", _("Last created window"), |
| |
8556 conv_placement_last_created_win); |
| |
8557 add_conv_placement_fnc("im_chat", _("Separate IM and Chat windows"), |
| |
8558 conv_placement_last_created_win_type); |
| |
8559 add_conv_placement_fnc("new", _("New window"), |
| |
8560 conv_placement_new_window); |
| |
8561 add_conv_placement_fnc("group", _("By group"), |
| |
8562 conv_placement_by_group); |
| |
8563 add_conv_placement_fnc("account", _("By account"), |
| |
8564 conv_placement_by_account); |
| |
8565 } |
| |
8566 } |
| |
8567 |
| |
8568 GList * |
| |
8569 pidgin_conv_placement_get_options(void) |
| |
8570 { |
| |
8571 GList *n, *list = NULL; |
| |
8572 ConvPlacementData *data; |
| |
8573 |
| |
8574 ensure_default_funcs(); |
| |
8575 |
| |
8576 for (n = conv_placement_fncs; n; n = n->next) { |
| |
8577 data = n->data; |
| |
8578 list = g_list_append(list, data->name); |
| |
8579 list = g_list_append(list, data->id); |
| |
8580 } |
| |
8581 |
| |
8582 return list; |
| |
8583 } |
| |
8584 |
| |
8585 |
| |
8586 void |
| |
8587 pidgin_conv_placement_add_fnc(const char *id, const char *name, |
| |
8588 PidginConvPlacementFunc fnc) |
| |
8589 { |
| |
8590 g_return_if_fail(id != NULL); |
| |
8591 g_return_if_fail(name != NULL); |
| |
8592 g_return_if_fail(fnc != NULL); |
| |
8593 |
| |
8594 ensure_default_funcs(); |
| |
8595 |
| |
8596 add_conv_placement_fnc(id, name, fnc); |
| |
8597 } |
| |
8598 |
| |
8599 void |
| |
8600 pidgin_conv_placement_remove_fnc(const char *id) |
| |
8601 { |
| |
8602 ConvPlacementData *data = get_conv_placement_data(id); |
| |
8603 |
| |
8604 if (data == NULL) |
| |
8605 return; |
| |
8606 |
| |
8607 conv_placement_fncs = g_list_remove(conv_placement_fncs, data); |
| |
8608 |
| |
8609 g_free(data->id); |
| |
8610 g_free(data->name); |
| |
8611 g_free(data); |
| |
8612 } |
| |
8613 |
| |
8614 const char * |
| |
8615 pidgin_conv_placement_get_name(const char *id) |
| |
8616 { |
| |
8617 ConvPlacementData *data; |
| |
8618 |
| |
8619 ensure_default_funcs(); |
| |
8620 |
| |
8621 data = get_conv_placement_data(id); |
| |
8622 |
| |
8623 if (data == NULL) |
| |
8624 return NULL; |
| |
8625 |
| |
8626 return data->name; |
| |
8627 } |
| |
8628 |
| |
8629 PidginConvPlacementFunc |
| |
8630 pidgin_conv_placement_get_fnc(const char *id) |
| |
8631 { |
| |
8632 ConvPlacementData *data; |
| |
8633 |
| |
8634 ensure_default_funcs(); |
| |
8635 |
| |
8636 data = get_conv_placement_data(id); |
| |
8637 |
| |
8638 if (data == NULL) |
| |
8639 return NULL; |
| |
8640 |
| |
8641 return data->fnc; |
| |
8642 } |
| |
8643 |
| |
8644 void |
| |
8645 pidgin_conv_placement_set_current_func(PidginConvPlacementFunc func) |
| |
8646 { |
| |
8647 g_return_if_fail(func != NULL); |
| |
8648 |
| |
8649 /* If tabs are enabled, set the function, otherwise, NULL it out. */ |
| |
8650 if (purple_prefs_get_bool(PIDGIN_PREFS_ROOT "/conversations/tabs")) |
| |
8651 place_conv = func; |
| |
8652 else |
| |
8653 place_conv = NULL; |
| |
8654 } |
| |
8655 |
| |
8656 PidginConvPlacementFunc |
| |
8657 pidgin_conv_placement_get_current_func(void) |
| |
8658 { |
| |
8659 return place_conv; |
| |
8660 } |
| |
8661 |
| |
8662 void |
| |
8663 pidgin_conv_placement_place(PidginConversation *gtkconv) |
| |
8664 { |
| |
8665 if (place_conv) |
| |
8666 place_conv(gtkconv); |
| |
8667 else |
| |
8668 conv_placement_new_window(gtkconv); |
| |
8669 } |
| |
8670 |
| |
8671 gboolean |
| |
8672 pidgin_conv_is_hidden(PidginConversation *gtkconv) |
| |
8673 { |
| |
8674 g_return_val_if_fail(gtkconv != NULL, FALSE); |
| |
8675 |
| |
8676 return (gtkconv->win == hidden_convwin); |
| |
8677 } |
| |
8678 |
| |
8679 |
| |
8680 /* Algorithm from http://www.w3.org/TR/AERT#color-contrast */ |
| |
8681 static gboolean |
| |
8682 color_is_visible(GdkColor foreground, GdkColor background, int color_contrast, int brightness_contrast) |
| |
8683 { |
| |
8684 gulong fg_brightness; |
| |
8685 gulong bg_brightness; |
| |
8686 gulong br_diff; |
| |
8687 gulong col_diff; |
| |
8688 int fred, fgreen, fblue, bred, bgreen, bblue; |
| |
8689 |
| |
8690 /* this algorithm expects colors between 0 and 255 for each of red green and blue. |
| |
8691 * GTK on the other hand has values between 0 and 65535 |
| |
8692 * Err suggested I >> 8, which grabbed the high bits. |
| |
8693 */ |
| |
8694 |
| |
8695 fred = foreground.red >> 8 ; |
| |
8696 fgreen = foreground.green >> 8 ; |
| |
8697 fblue = foreground.blue >> 8 ; |
| |
8698 |
| |
8699 |
| |
8700 bred = background.red >> 8 ; |
| |
8701 bgreen = background.green >> 8 ; |
| |
8702 bblue = background.blue >> 8 ; |
| |
8703 |
| |
8704 fg_brightness = (fred * 299 + fgreen * 587 + fblue * 114) / 1000; |
| |
8705 bg_brightness = (bred * 299 + bgreen * 587 + bblue * 114) / 1000; |
| |
8706 br_diff = abs(fg_brightness - bg_brightness); |
| |
8707 |
| |
8708 col_diff = abs(fred - bred) + abs(fgreen - bgreen) + abs(fblue - bblue); |
| |
8709 |
| |
8710 return ((col_diff > color_contrast) && (br_diff > brightness_contrast)); |
| |
8711 } |
| |
8712 |
| |
8713 |
| |
8714 static GdkColor* |
| |
8715 generate_nick_colors(guint *color_count, GdkColor background) |
| |
8716 { |
| |
8717 guint numcolors = *color_count; |
| |
8718 guint i = 0, j = 0; |
| |
8719 GdkColor *colors = g_new(GdkColor, numcolors); |
| |
8720 GdkColor nick_highlight; |
| |
8721 GdkColor send_color; |
| |
8722 time_t breakout_time; |
| |
8723 |
| |
8724 gdk_color_parse(HIGHLIGHT_COLOR, &nick_highlight); |
| |
8725 gdk_color_parse(SEND_COLOR, &send_color); |
| |
8726 |
| |
8727 srand(background.red + background.green + background.blue + 1); |
| |
8728 |
| |
8729 breakout_time = time(NULL) + 3; |
| |
8730 |
| |
8731 /* first we look through the list of "good" colors: colors that differ from every other color in the |
| |
8732 * list. only some of them will differ from the background color though. lets see if we can find |
| |
8733 * numcolors of them that do |
| |
8734 */ |
| |
8735 while (i < numcolors && j < NUM_NICK_SEED_COLORS && time(NULL) < breakout_time) |
| |
8736 { |
| |
8737 GdkColor color = nick_seed_colors[j]; |
| |
8738 |
| |
8739 if (color_is_visible(color, background, MIN_COLOR_CONTRAST, MIN_BRIGHTNESS_CONTRAST) && |
| |
8740 color_is_visible(color, nick_highlight, MIN_COLOR_CONTRAST / 2, 0) && |
| |
8741 color_is_visible(color, send_color, MIN_COLOR_CONTRAST / 4, 0)) |
| |
8742 { |
| |
8743 colors[i] = color; |
| |
8744 i++; |
| |
8745 } |
| |
8746 j++; |
| |
8747 } |
| |
8748 |
| |
8749 /* we might not have found numcolors in the last loop. if we did, we'll never enter this one. |
| |
8750 * if we did not, lets just find some colors that don't conflict with the background. its |
| |
8751 * expensive to find colors that not only don't conflict with the background, but also do not |
| |
8752 * conflict with each other. |
| |
8753 */ |
| |
8754 while(i < numcolors && time(NULL) < breakout_time) |
| |
8755 { |
| |
8756 GdkColor color = { 0, rand() % 65536, rand() % 65536, rand() % 65536 }; |
| |
8757 |
| |
8758 if (color_is_visible(color, background, MIN_COLOR_CONTRAST, MIN_BRIGHTNESS_CONTRAST) && |
| |
8759 color_is_visible(color, nick_highlight, MIN_COLOR_CONTRAST / 2, 0) && |
| |
8760 color_is_visible(color, send_color, MIN_COLOR_CONTRAST / 4, 0)) |
| |
8761 { |
| |
8762 colors[i] = color; |
| |
8763 i++; |
| |
8764 } |
| |
8765 } |
| |
8766 |
| |
8767 if (i < numcolors) { |
| |
8768 GdkColor *c = colors; |
| |
8769 purple_debug_warning("gtkconv", "Unable to generate enough random colors before timeout. %u colors found.\n", i); |
| |
8770 colors = g_memdup(c, i * sizeof(GdkColor)); |
| |
8771 g_free(c); |
| |
8772 *color_count = i; |
| |
8773 } |
| |
8774 |
| |
8775 return colors; |
| |
8776 } |