pidgin/gtkconv.c

changeset 16238
33bf2fd32108
parent 12982
c8a26cf625fc
parent 16144
4e022531d1c9
equal deleted inserted replaced
13071:b98e72d4089a 16238:33bf2fd32108
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 "&lt;AUTO-REPLY&gt; : "
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 &lt;command&gt;\" 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, &gtkconv->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, &gtkconv->entry, &gtkconv->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, &gtkconv->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, &gtkconv->entry, &gtkconv->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 &lt;message&gt;: 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 &lt;action&gt;: 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 &lt;option&gt;: 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 &lt;command&gt;: 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 }

mercurial