pidgin/gtkconv.c

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

mercurial