2 * @file gtkconv.c GTK+ Conversation API
8 * Pidgin is the legal property of its developers, whose names are too numerous
9 * to list here. Please refer to the COPYRIGHT file distributed with this
10 * source distribution.
12 * This program is free software; you can redistribute it and/or modify
13 * it under the terms of the GNU General Public License as published by
14 * the Free Software Foundation; either version 2 of the License, or
15 * (at your option) any later version.
17 * This program is distributed in the hope that it will be useful,
18 * but WITHOUT ANY WARRANTY; without even the implied warranty of
19 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 * GNU General Public License for more details.
22 * You should have received a copy of the GNU General Public License
23 * along with this program; if not, write to the Free Software
24 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA
27 #define _PIDGIN_GTKCONV_C_
33 # include <X11/Xlib.h>
37 # include <gtkspell/gtkspell.h>
43 #include <gdk/gdkkeysyms.h>
58 #include "gtkdnd-hints.h"
61 #include "gtkconvwin.h"
62 #include "gtkdialogs.h"
63 #include "gtkimhtml.h"
64 #include "gtkimhtmltoolbar.h"
66 #include "gtkmenutray.h"
67 #include "gtkpounce.h"
69 #include "gtkprivacy.h"
70 #include "gtkthemes.h"
72 #include "pidginstock.h"
73 #include "pidgintooltip.h"
75 #include "gtknickcolors.h"
77 #define CLOSE_CONV_TIMEOUT_SECS (10 * 60)
79 #define AUTO_RESPONSE "<AUTO-REPLY> : "
83 PIDGIN_CONV_SET_TITLE
= 1 << 0,
84 PIDGIN_CONV_BUDDY_ICON
= 1 << 1,
85 PIDGIN_CONV_MENU
= 1 << 2,
86 PIDGIN_CONV_TAB_ICON
= 1 << 3,
87 PIDGIN_CONV_TOPIC
= 1 << 4,
88 PIDGIN_CONV_SMILEY_THEME
= 1 << 5,
89 PIDGIN_CONV_COLORIZE_TITLE
= 1 << 6
96 CONV_PROTOCOL_ICON_COLUMN
,
98 } PidginInfopaneColumns
;
100 #define PIDGIN_CONV_ALL ((1 << 7) - 1)
102 /* XXX: These color defines shouldn't really be here. But the nick-color
103 * generation algorithm uses them, so keeping these around until we fix that. */
104 #define DEFAULT_SEND_COLOR "#204a87"
105 #define DEFAULT_HIGHLIGHT_COLOR "#AF7F00"
107 #define BUDDYICON_SIZE_MIN 32
108 #define BUDDYICON_SIZE_MAX 96
110 /* Undef this to turn off "custom-smiley" debug messages */
111 #define DEBUG_CUSTOM_SMILEY
113 #define LUMINANCE(c) (float)((0.3*(c.red))+(0.59*(c.green))+(0.11*(c.blue)))
115 /* From http://www.w3.org/TR/AERT#color-contrast */
116 #define MIN_BRIGHTNESS_CONTRAST 75
117 #define MIN_COLOR_CONTRAST 200
119 #define NUM_NICK_COLORS 220
120 static GdkColor
*nick_colors
= NULL
;
121 static guint nbr_nick_colors
;
129 PurpleConversation
*conv
;
133 static GtkWidget
*invite_dialog
= NULL
;
134 static GtkWidget
*warn_close_dialog
= NULL
;
136 static PidginWindow
*hidden_convwin
= NULL
;
137 static GList
*window_list
= NULL
;
139 /* Lists of status icons at all available sizes for use as window icons */
140 static GList
*available_list
= NULL
;
141 static GList
*away_list
= NULL
;
142 static GList
*busy_list
= NULL
;
143 static GList
*xa_list
= NULL
;
144 static GList
*offline_list
= NULL
;
145 static GHashTable
*prpl_lists
= NULL
;
147 static gboolean
update_send_to_selection(PidginWindow
*win
);
148 static void generate_send_to_items(PidginWindow
*win
);
150 /* Prototypes. <-- because Paco-Paco hates this comment. */
151 static gboolean
infopane_entry_activate(PidginConversation
*gtkconv
);
152 static void got_typing_keypress(PidginConversation
*gtkconv
, gboolean first
);
153 static void gray_stuff_out(PidginConversation
*gtkconv
);
154 static void add_chat_buddy_common(PurpleConversation
*conv
, PurpleConvChatBuddy
*cb
, const char *old_name
);
155 static gboolean
tab_complete(PurpleConversation
*conv
);
156 static void pidgin_conv_updated(PurpleConversation
*conv
, PurpleConvUpdateType type
);
157 static void conv_set_unseen(PurpleConversation
*gtkconv
, PidginUnseenState state
);
158 static void gtkconv_set_unseen(PidginConversation
*gtkconv
, PidginUnseenState state
);
159 static void update_typing_icon(PidginConversation
*gtkconv
);
160 static void update_typing_message(PidginConversation
*gtkconv
, const char *message
);
161 static const char *item_factory_translate_func (const char *path
, gpointer func_data
);
162 gboolean
pidgin_conv_has_focus(PurpleConversation
*conv
);
163 static GdkColor
* generate_nick_colors(guint
*numcolors
, GdkColor background
);
164 static gboolean
color_is_visible(GdkColor foreground
, GdkColor background
, int color_contrast
, int brightness_contrast
);
165 static GtkTextTag
*get_buddy_tag(PurpleConversation
*conv
, const char *who
, PurpleMessageFlags flag
, gboolean create
);
166 static void pidgin_conv_update_fields(PurpleConversation
*conv
, PidginConvFields fields
);
167 static void focus_out_from_menubar(GtkWidget
*wid
, PidginWindow
*win
);
168 static void pidgin_conv_tab_pack(PidginWindow
*win
, PidginConversation
*gtkconv
);
169 static gboolean
infopane_press_cb(GtkWidget
*widget
, GdkEventButton
*e
, PidginConversation
*conv
);
170 static void hide_conv(PidginConversation
*gtkconv
, gboolean closetimer
);
172 static void pidgin_conv_set_position_size(PidginWindow
*win
, int x
, int y
,
173 int width
, int height
);
174 static gboolean
pidgin_conv_xy_to_right_infopane(PidginWindow
*win
, int x
, int y
);
176 static const GdkColor
*get_nick_color(PidginConversation
*gtkconv
, const char *name
)
179 GtkStyle
*style
= gtk_widget_get_style(gtkconv
->imhtml
);
182 col
= nick_colors
[g_str_hash(name
) % nbr_nick_colors
];
183 scale
= ((1-(LUMINANCE(style
->base
[GTK_STATE_NORMAL
]) / LUMINANCE(style
->white
))) *
184 (LUMINANCE(style
->white
)/MAX(MAX(col
.red
, col
.blue
), col
.green
)));
186 /* The colors are chosen to look fine on white; we should never have to darken */
196 static PurpleBlistNode
*
197 get_conversation_blist_node(PurpleConversation
*conv
)
199 PurpleBlistNode
*node
= NULL
;
201 switch (purple_conversation_get_type(conv
)) {
202 case PURPLE_CONV_TYPE_IM
:
203 node
= PURPLE_BLIST_NODE(purple_find_buddy(conv
->account
, conv
->name
));
204 node
= node
? node
->parent
: NULL
;
206 case PURPLE_CONV_TYPE_CHAT
:
207 node
= PURPLE_BLIST_NODE(purple_blist_find_chat(conv
->account
, conv
->name
));
215 /**************************************************************************
217 **************************************************************************/
220 close_this_sucker(gpointer data
)
222 PidginConversation
*gtkconv
= data
;
223 GList
*list
= g_list_copy(gtkconv
->convs
);
224 g_list_foreach(list
, (GFunc
)purple_conversation_destroy
, NULL
);
230 close_conv_cb(GtkButton
*button
, PidginConversation
*gtkconv
)
232 /* We are going to destroy the conversations immediately only if the 'close immediately'
233 * preference is selected. Otherwise, close the conversation after a reasonable timeout
234 * (I am going to consider 10 minutes as a 'reasonable timeout' here.
235 * For chats, close immediately if the chat is not in the buddylist, or if the chat is
236 * not marked 'Persistent' */
237 PurpleConversation
*conv
= gtkconv
->active_conv
;
238 PurpleAccount
*account
= purple_conversation_get_account(conv
);
239 const char *name
= purple_conversation_get_name(conv
);
241 switch (purple_conversation_get_type(conv
)) {
242 case PURPLE_CONV_TYPE_IM
:
244 if (purple_prefs_get_bool(PIDGIN_PREFS_ROOT
"/conversations/im/close_immediately"))
245 close_this_sucker(gtkconv
);
247 hide_conv(gtkconv
, TRUE
);
250 case PURPLE_CONV_TYPE_CHAT
:
252 PurpleChat
*chat
= purple_blist_find_chat(account
, name
);
254 !purple_blist_node_get_bool(&chat
->node
, "gtk-persistent"))
255 close_this_sucker(gtkconv
);
257 hide_conv(gtkconv
, FALSE
);
268 lbox_size_allocate_cb(GtkWidget
*w
, GtkAllocation
*allocation
, gpointer data
)
270 purple_prefs_set_int(PIDGIN_PREFS_ROOT
"/conversations/chat/userlist_width", allocation
->width
== 1 ? 0 : allocation
->width
);
276 default_formatize(PidginConversation
*c
)
278 PurpleConversation
*conv
= c
->active_conv
;
279 gtk_imhtml_setup_entry(GTK_IMHTML(c
->entry
), conv
->features
);
283 conversation_entry_clear(PidginConversation
*gtkconv
)
285 GtkIMHtml
*imhtml
= GTK_IMHTML(gtkconv
->entry
);
286 gtk_source_undo_manager_begin_not_undoable_action(imhtml
->undo_manager
);
287 gtk_imhtml_clear(imhtml
);
288 gtk_source_undo_manager_end_not_undoable_action(imhtml
->undo_manager
);
292 clear_formatting_cb(GtkIMHtml
*imhtml
, PidginConversation
*gtkconv
)
294 default_formatize(gtkconv
);
298 pidgin_get_cmd_prefix(void)
304 say_command_cb(PurpleConversation
*conv
,
305 const char *cmd
, char **args
, char **error
, void *data
)
307 if (purple_conversation_get_type(conv
) == PURPLE_CONV_TYPE_IM
)
308 purple_conv_im_send(PURPLE_CONV_IM(conv
), args
[0]);
309 else if (purple_conversation_get_type(conv
) == PURPLE_CONV_TYPE_CHAT
)
310 purple_conv_chat_send(PURPLE_CONV_CHAT(conv
), args
[0]);
312 return PURPLE_CMD_RET_OK
;
316 me_command_cb(PurpleConversation
*conv
,
317 const char *cmd
, char **args
, char **error
, void *data
)
321 tmp
= g_strdup_printf("/me %s", args
[0]);
323 if (purple_conversation_get_type(conv
) == PURPLE_CONV_TYPE_IM
)
324 purple_conv_im_send(PURPLE_CONV_IM(conv
), tmp
);
325 else if (purple_conversation_get_type(conv
) == PURPLE_CONV_TYPE_CHAT
)
326 purple_conv_chat_send(PURPLE_CONV_CHAT(conv
), tmp
);
329 return PURPLE_CMD_RET_OK
;
333 debug_command_cb(PurpleConversation
*conv
,
334 const char *cmd
, char **args
, char **error
, void *data
)
338 if (!g_ascii_strcasecmp(args
[0], "version")) {
339 tmp
= g_strdup_printf("Using Pidgin v%s with libpurple v%s.",
340 DISPLAY_VERSION
, purple_core_get_version());
341 } else if (!g_ascii_strcasecmp(args
[0], "plugins")) {
342 /* Show all the loaded plugins, including the protocol plugins and plugin loaders.
343 * This is intentional, since third party prpls are often sources of bugs, and some
344 * plugin loaders (e.g. mono) can also be buggy.
346 GString
*str
= g_string_new("Loaded Plugins: ");
347 const GList
*plugins
= purple_plugins_get_loaded();
349 for (; plugins
; plugins
= plugins
->next
) {
350 str
= g_string_append(str
, purple_plugin_get_name(plugins
->data
));
352 str
= g_string_append(str
, ", ");
355 str
= g_string_append(str
, "(none)");
358 tmp
= g_string_free(str
, FALSE
);
360 purple_conversation_write(conv
, NULL
, _("Supported debug options are: plugins version"),
361 PURPLE_MESSAGE_NO_LOG
|PURPLE_MESSAGE_ERROR
, time(NULL
));
362 return PURPLE_CMD_RET_OK
;
365 markup
= g_markup_escape_text(tmp
, -1);
366 if (purple_conversation_get_type(conv
) == PURPLE_CONV_TYPE_IM
)
367 purple_conv_im_send(PURPLE_CONV_IM(conv
), markup
);
368 else if (purple_conversation_get_type(conv
) == PURPLE_CONV_TYPE_CHAT
)
369 purple_conv_chat_send(PURPLE_CONV_CHAT(conv
), markup
);
373 return PURPLE_CMD_RET_OK
;
376 static void clear_conversation_scrollback_cb(PurpleConversation
*conv
,
379 PidginConversation
*gtkconv
= NULL
;
381 gtkconv
= PIDGIN_CONVERSATION(conv
);
383 gtk_imhtml_clear(GTK_IMHTML(gtkconv
->imhtml
));
387 clear_command_cb(PurpleConversation
*conv
,
388 const char *cmd
, char **args
, char **error
, void *data
)
390 purple_conversation_clear_message_history(conv
);
391 return PURPLE_CMD_RET_OK
;
395 clearall_command_cb(PurpleConversation
*conv
,
396 const char *cmd
, char **args
, char **error
, void *data
)
398 purple_conversation_foreach(purple_conversation_clear_message_history
);
399 return PURPLE_CMD_RET_OK
;
403 help_command_cb(PurpleConversation
*conv
,
404 const char *cmd
, char **args
, char **error
, void *data
)
409 if (args
[0] != NULL
) {
410 s
= g_string_new("");
411 text
= purple_cmd_help(conv
, args
[0]);
414 for (l
= text
; l
; l
= l
->next
)
416 g_string_append_printf(s
, "%s\n", (char *)l
->data
);
418 g_string_append_printf(s
, "%s", (char *)l
->data
);
420 g_string_append(s
, _("No such command (in this context)."));
423 s
= g_string_new(_("Use \"/help <command>\" for help on a specific command.\n"
424 "The following commands are available in this context:\n"));
426 text
= purple_cmd_list(conv
);
427 for (l
= text
; l
; l
= l
->next
)
429 g_string_append_printf(s
, "%s, ", (char *)l
->data
);
431 g_string_append_printf(s
, "%s.", (char *)l
->data
);
435 purple_conversation_write(conv
, NULL
, s
->str
, PURPLE_MESSAGE_NO_LOG
, time(NULL
));
436 g_string_free(s
, TRUE
);
438 return PURPLE_CMD_RET_OK
;
442 send_history_add(PidginConversation
*gtkconv
, const char *message
)
446 first
= g_list_first(gtkconv
->send_history
);
448 first
->data
= g_strdup(message
);
449 gtkconv
->send_history
= g_list_prepend(first
, NULL
);
453 check_for_and_do_command(PurpleConversation
*conv
)
455 PidginConversation
*gtkconv
;
459 gboolean retval
= FALSE
;
461 gtkconv
= PIDGIN_CONVERSATION(conv
);
462 prefix
= pidgin_get_cmd_prefix();
464 cmd
= gtk_imhtml_get_text(GTK_IMHTML(gtkconv
->entry
), NULL
, NULL
);
465 gtk_text_buffer_get_start_iter(GTK_IMHTML(gtkconv
->entry
)->text_buffer
, &start
);
467 if (cmd
&& (strncmp(cmd
, prefix
, strlen(prefix
)) == 0)
468 && !gtk_text_iter_get_child_anchor(&start
)) {
469 PurpleCmdStatus status
;
470 char *error
, *cmdline
, *markup
, *send_history
;
473 send_history
= gtk_imhtml_get_markup(GTK_IMHTML(gtkconv
->entry
));
474 send_history_add(gtkconv
, send_history
);
475 g_free(send_history
);
477 cmdline
= cmd
+ strlen(prefix
);
479 if (strcmp(cmdline
, "xyzzy") == 0) {
480 purple_conversation_write(conv
, "", "Nothing happens",
481 PURPLE_MESSAGE_NO_LOG
, time(NULL
));
486 gtk_text_iter_forward_chars(&start
, g_utf8_strlen(prefix
, -1));
487 gtk_text_buffer_get_end_iter(GTK_IMHTML(gtkconv
->entry
)->text_buffer
, &end
);
488 markup
= gtk_imhtml_get_markup_range(GTK_IMHTML(gtkconv
->entry
), &start
, &end
);
489 status
= purple_cmd_do_command(conv
, cmdline
, markup
, &error
);
493 case PURPLE_CMD_STATUS_OK
:
496 case PURPLE_CMD_STATUS_NOT_FOUND
:
498 PurplePluginProtocolInfo
*prpl_info
= NULL
;
499 PurpleConnection
*gc
;
501 if ((gc
= purple_conversation_get_gc(conv
)))
502 prpl_info
= PURPLE_PLUGIN_PROTOCOL_INFO(gc
->prpl
);
504 if ((prpl_info
!= NULL
) && (prpl_info
->options
& OPT_PROTO_SLASH_COMMANDS_NATIVE
)) {
507 /* If the first word in the entered text has a '/' in it, then the user
508 * probably didn't mean it as a command. So send the text as message. */
509 spaceslash
= cmdline
;
510 while (*spaceslash
&& *spaceslash
!= ' ' && *spaceslash
!= '/')
513 if (*spaceslash
!= '/') {
514 purple_conversation_write(conv
, "", _("Unknown command."), PURPLE_MESSAGE_NO_LOG
, time(NULL
));
520 case PURPLE_CMD_STATUS_WRONG_ARGS
:
521 purple_conversation_write(conv
, "", _("Syntax Error: You typed the wrong number of arguments "
523 PURPLE_MESSAGE_NO_LOG
, time(NULL
));
526 case PURPLE_CMD_STATUS_FAILED
:
527 purple_conversation_write(conv
, "", error
? error
: _("Your command failed for an unknown reason."),
528 PURPLE_MESSAGE_NO_LOG
, time(NULL
));
532 case PURPLE_CMD_STATUS_WRONG_TYPE
:
533 if(purple_conversation_get_type(conv
) == PURPLE_CONV_TYPE_IM
)
534 purple_conversation_write(conv
, "", _("That command only works in chats, not IMs."),
535 PURPLE_MESSAGE_NO_LOG
, time(NULL
));
537 purple_conversation_write(conv
, "", _("That command only works in IMs, not chats."),
538 PURPLE_MESSAGE_NO_LOG
, time(NULL
));
541 case PURPLE_CMD_STATUS_WRONG_PRPL
:
542 purple_conversation_write(conv
, "", _("That command doesn't work on this protocol."),
543 PURPLE_MESSAGE_NO_LOG
, time(NULL
));
554 send_cb(GtkWidget
*widget
, PidginConversation
*gtkconv
)
556 PurpleConversation
*conv
= gtkconv
->active_conv
;
557 PurpleAccount
*account
;
558 PurpleConnection
*gc
;
559 PurpleMessageFlags flags
= 0;
562 account
= purple_conversation_get_account(conv
);
564 if (check_for_and_do_command(conv
)) {
565 conversation_entry_clear(gtkconv
);
569 if ((purple_conversation_get_type(conv
) == PURPLE_CONV_TYPE_CHAT
) &&
570 purple_conv_chat_has_left(PURPLE_CONV_CHAT(conv
)))
573 if (!purple_account_is_connected(account
))
576 buf
= gtk_imhtml_get_markup(GTK_IMHTML(gtkconv
->entry
));
577 clean
= gtk_imhtml_get_text(GTK_IMHTML(gtkconv
->entry
), NULL
, NULL
);
579 gtk_widget_grab_focus(gtkconv
->entry
);
581 if (strlen(clean
) == 0) {
589 /* XXX: is there a better way to tell if the message has images? */
590 if (GTK_IMHTML(gtkconv
->entry
)->im_images
!= NULL
)
591 flags
|= PURPLE_MESSAGE_IMAGES
;
593 gc
= purple_account_get_connection(account
);
594 if (gc
&& (conv
->features
& PURPLE_CONNECTION_NO_NEWLINES
)) {
598 bufs
= gtk_imhtml_get_markup_lines(GTK_IMHTML(gtkconv
->entry
));
599 for (i
= 0; bufs
[i
]; i
++) {
600 send_history_add(gtkconv
, bufs
[i
]);
601 if (purple_conversation_get_type(conv
) == PURPLE_CONV_TYPE_IM
)
602 purple_conv_im_send_with_flags(PURPLE_CONV_IM(conv
), bufs
[i
], flags
);
603 else if (purple_conversation_get_type(conv
) == PURPLE_CONV_TYPE_CHAT
)
604 purple_conv_chat_send_with_flags(PURPLE_CONV_CHAT(conv
), bufs
[i
], flags
);
610 send_history_add(gtkconv
, buf
);
611 if (purple_conversation_get_type(conv
) == PURPLE_CONV_TYPE_IM
)
612 purple_conv_im_send_with_flags(PURPLE_CONV_IM(conv
), buf
, flags
);
613 else if (purple_conversation_get_type(conv
) == PURPLE_CONV_TYPE_CHAT
)
614 purple_conv_chat_send_with_flags(PURPLE_CONV_CHAT(conv
), buf
, flags
);
620 conversation_entry_clear(gtkconv
);
621 gtkconv_set_unseen(gtkconv
, PIDGIN_UNSEEN_NONE
);
625 add_remove_cb(GtkWidget
*widget
, PidginConversation
*gtkconv
)
627 PurpleAccount
*account
;
629 PurpleConversation
*conv
= gtkconv
->active_conv
;
631 account
= purple_conversation_get_account(conv
);
632 name
= purple_conversation_get_name(conv
);
634 if (purple_conversation_get_type(conv
) == PURPLE_CONV_TYPE_IM
) {
637 b
= purple_find_buddy(account
, name
);
639 pidgin_dialogs_remove_buddy(b
);
640 else if (account
!= NULL
&& purple_account_is_connected(account
))
641 purple_blist_request_add_buddy(account
, (char *)name
, NULL
, NULL
);
642 } else if (purple_conversation_get_type(conv
) == PURPLE_CONV_TYPE_CHAT
) {
645 c
= purple_blist_find_chat(account
, name
);
647 pidgin_dialogs_remove_chat(c
);
648 else if (account
!= NULL
&& purple_account_is_connected(account
))
649 purple_blist_request_add_chat(account
, NULL
, NULL
, name
);
652 gtk_widget_grab_focus(PIDGIN_CONVERSATION(conv
)->entry
);
655 static void chat_do_info(PidginConversation
*gtkconv
, const char *who
)
657 PurpleConversation
*conv
= gtkconv
->active_conv
;
658 PurpleConnection
*gc
;
660 if ((gc
= purple_conversation_get_gc(conv
))) {
661 pidgin_retrieve_user_info_in_chat(gc
, who
, purple_conv_chat_get_id(PURPLE_CONV_CHAT(conv
)));
667 info_cb(GtkWidget
*widget
, PidginConversation
*gtkconv
)
669 PurpleConversation
*conv
= gtkconv
->active_conv
;
671 if (purple_conversation_get_type(conv
) == PURPLE_CONV_TYPE_IM
) {
672 pidgin_retrieve_user_info(purple_conversation_get_gc(conv
),
673 purple_conversation_get_name(conv
));
674 gtk_widget_grab_focus(gtkconv
->entry
);
675 } else if (purple_conversation_get_type(conv
) == PURPLE_CONV_TYPE_CHAT
) {
676 /* Get info of the person currently selected in the GtkTreeView */
677 PidginChatPane
*gtkchat
;
680 GtkTreeSelection
*sel
;
683 gtkchat
= gtkconv
->u
.chat
;
685 model
= gtk_tree_view_get_model(GTK_TREE_VIEW(gtkchat
->list
));
686 sel
= gtk_tree_view_get_selection(GTK_TREE_VIEW(gtkchat
->list
));
688 if (gtk_tree_selection_get_selected(sel
, NULL
, &iter
))
689 gtk_tree_model_get(GTK_TREE_MODEL(model
), &iter
, CHAT_USERS_NAME_COLUMN
, &name
, -1);
693 chat_do_info(gtkconv
, name
);
699 block_cb(GtkWidget
*widget
, PidginConversation
*gtkconv
)
701 PurpleConversation
*conv
= gtkconv
->active_conv
;
702 PurpleAccount
*account
;
704 account
= purple_conversation_get_account(conv
);
706 if (account
!= NULL
&& purple_account_is_connected(account
))
707 pidgin_request_add_block(account
, purple_conversation_get_name(conv
));
709 gtk_widget_grab_focus(PIDGIN_CONVERSATION(conv
)->entry
);
713 unblock_cb(GtkWidget
*widget
, PidginConversation
*gtkconv
)
715 PurpleConversation
*conv
= gtkconv
->active_conv
;
716 PurpleAccount
*account
;
718 account
= purple_conversation_get_account(conv
);
720 if (account
!= NULL
&& purple_account_is_connected(account
))
721 pidgin_request_add_permit(account
, purple_conversation_get_name(conv
));
723 gtk_widget_grab_focus(PIDGIN_CONVERSATION(conv
)->entry
);
727 chat_invite_filter(const PidginBuddyCompletionEntry
*entry
, gpointer data
)
729 PurpleAccount
*filter_account
= data
;
730 PurpleAccount
*account
= NULL
;
732 if (entry
->is_buddy
) {
733 if (PURPLE_BUDDY_IS_ONLINE(entry
->entry
.buddy
))
734 account
= purple_buddy_get_account(entry
->entry
.buddy
);
738 account
= entry
->entry
.logged_buddy
->account
;
740 if (account
== filter_account
)
746 do_invite(GtkWidget
*w
, int resp
, InviteBuddyInfo
*info
)
748 const char *buddy
, *message
;
749 PurpleConversation
*conv
;
753 if (resp
== GTK_RESPONSE_OK
) {
754 buddy
= gtk_entry_get_text(GTK_ENTRY(info
->entry
));
755 message
= gtk_entry_get_text(GTK_ENTRY(info
->message
));
757 if (!g_ascii_strcasecmp(buddy
, ""))
760 serv_chat_invite(purple_conversation_get_gc(conv
),
761 purple_conv_chat_get_id(PURPLE_CONV_CHAT(conv
)),
765 gtk_widget_destroy(invite_dialog
);
766 invite_dialog
= NULL
;
772 invite_dnd_recv(GtkWidget
*widget
, GdkDragContext
*dc
, gint x
, gint y
,
773 GtkSelectionData
*sd
, guint inf
, guint t
, gpointer data
)
775 InviteBuddyInfo
*info
= (InviteBuddyInfo
*)data
;
776 const char *convprotocol
;
777 gboolean success
= TRUE
;
779 convprotocol
= purple_account_get_protocol_id(purple_conversation_get_account(info
->conv
));
781 if (sd
->target
== gdk_atom_intern("PURPLE_BLIST_NODE", FALSE
))
783 PurpleBlistNode
*node
= NULL
;
786 memcpy(&node
, sd
->data
, sizeof(node
));
788 if (PURPLE_BLIST_NODE_IS_CONTACT(node
))
789 buddy
= purple_contact_get_priority_buddy((PurpleContact
*)node
);
790 else if (PURPLE_BLIST_NODE_IS_BUDDY(node
))
791 buddy
= (PurpleBuddy
*)node
;
795 if (strcmp(convprotocol
, purple_account_get_protocol_id(buddy
->account
)))
797 purple_notify_error(PIDGIN_CONVERSATION(info
->conv
), NULL
,
798 _("That buddy is not on the same protocol as this "
803 gtk_entry_set_text(GTK_ENTRY(info
->entry
), purple_buddy_get_name(buddy
));
805 gtk_drag_finish(dc
, success
, (dc
->action
== GDK_ACTION_MOVE
), t
);
807 else if (sd
->target
== gdk_atom_intern("application/x-im-contact", FALSE
))
809 char *protocol
= NULL
;
810 char *username
= NULL
;
811 PurpleAccount
*account
;
813 if (pidgin_parse_x_im_contact((const char *)sd
->data
, FALSE
, &account
,
814 &protocol
, &username
, NULL
))
818 purple_notify_error(PIDGIN_CONVERSATION(info
->conv
), NULL
,
819 _("You are not currently signed on with an account that "
820 "can invite that buddy."), NULL
);
822 else if (strcmp(convprotocol
, purple_account_get_protocol_id(account
)))
824 purple_notify_error(PIDGIN_CONVERSATION(info
->conv
), NULL
,
825 _("That buddy is not on the same protocol as this "
831 gtk_entry_set_text(GTK_ENTRY(info
->entry
), username
);
838 gtk_drag_finish(dc
, success
, (dc
->action
== GDK_ACTION_MOVE
), t
);
842 static const GtkTargetEntry dnd_targets
[] =
844 {"PURPLE_BLIST_NODE", GTK_TARGET_SAME_APP
, 0},
845 {"application/x-im-contact", 0, 1}
849 invite_cb(GtkWidget
*widget
, PidginConversation
*gtkconv
)
851 PurpleConversation
*conv
= gtkconv
->active_conv
;
852 InviteBuddyInfo
*info
= NULL
;
854 if (invite_dialog
== NULL
) {
855 PidginWindow
*gtkwin
;
857 GtkWidget
*vbox
, *hbox
;
861 img
= gtk_image_new_from_stock(PIDGIN_STOCK_DIALOG_QUESTION
,
862 gtk_icon_size_from_name(PIDGIN_ICON_SIZE_TANGO_HUGE
));
864 info
= g_new0(InviteBuddyInfo
, 1);
867 gtkwin
= pidgin_conv_get_window(gtkconv
);
869 /* Create the new dialog. */
870 invite_dialog
= gtk_dialog_new_with_buttons(
871 _("Invite Buddy Into Chat Room"),
872 GTK_WINDOW(gtkwin
->window
), 0,
873 GTK_STOCK_CANCEL
, GTK_RESPONSE_CANCEL
,
874 PIDGIN_STOCK_INVITE
, GTK_RESPONSE_OK
, NULL
);
876 gtk_dialog_set_default_response(GTK_DIALOG(invite_dialog
),
878 gtk_container_set_border_width(GTK_CONTAINER(invite_dialog
), PIDGIN_HIG_BOX_SPACE
);
879 gtk_window_set_resizable(GTK_WINDOW(invite_dialog
), FALSE
);
880 gtk_dialog_set_has_separator(GTK_DIALOG(invite_dialog
), FALSE
);
882 info
->window
= GTK_WIDGET(invite_dialog
);
884 /* Setup the outside spacing. */
885 vbox
= GTK_DIALOG(invite_dialog
)->vbox
;
887 gtk_box_set_spacing(GTK_BOX(vbox
), PIDGIN_HIG_BORDER
);
888 gtk_container_set_border_width(GTK_CONTAINER(vbox
), PIDGIN_HIG_BOX_SPACE
);
890 /* Setup the inner hbox and put the dialog's icon in it. */
891 hbox
= gtk_hbox_new(FALSE
, PIDGIN_HIG_BORDER
);
892 gtk_container_add(GTK_CONTAINER(vbox
), hbox
);
893 gtk_box_pack_start(GTK_BOX(hbox
), img
, FALSE
, FALSE
, 0);
894 gtk_misc_set_alignment(GTK_MISC(img
), 0, 0);
896 /* Setup the right vbox. */
897 vbox
= gtk_vbox_new(FALSE
, 0);
898 gtk_container_add(GTK_CONTAINER(hbox
), vbox
);
900 /* Put our happy label in it. */
901 label
= gtk_label_new(_("Please enter the name of the user you wish "
902 "to invite, along with an optional invite "
904 gtk_widget_set_size_request(label
, 350, -1);
905 gtk_label_set_line_wrap(GTK_LABEL(label
), TRUE
);
906 gtk_misc_set_alignment(GTK_MISC(label
), 0, 0);
907 gtk_box_pack_start(GTK_BOX(vbox
), label
, FALSE
, FALSE
, 0);
909 /* hbox for the table, and to give it some spacing on the left. */
910 hbox
= gtk_hbox_new(FALSE
, PIDGIN_HIG_BOX_SPACE
);
911 gtk_container_add(GTK_CONTAINER(vbox
), hbox
);
913 /* Setup the table we're going to use to lay stuff out. */
914 table
= gtk_table_new(2, 2, FALSE
);
915 gtk_table_set_row_spacings(GTK_TABLE(table
), PIDGIN_HIG_BOX_SPACE
);
916 gtk_table_set_col_spacings(GTK_TABLE(table
), PIDGIN_HIG_BOX_SPACE
);
917 gtk_container_set_border_width(GTK_CONTAINER(table
), PIDGIN_HIG_BORDER
);
918 gtk_box_pack_start(GTK_BOX(vbox
), table
, FALSE
, FALSE
, 0);
920 /* Now the Buddy label */
921 label
= gtk_label_new(NULL
);
922 gtk_label_set_markup_with_mnemonic(GTK_LABEL(label
), _("_Buddy:"));
923 gtk_misc_set_alignment(GTK_MISC(label
), 0, 0);
924 gtk_table_attach_defaults(GTK_TABLE(table
), label
, 0, 1, 0, 1);
926 /* Now the Buddy drop-down entry field. */
927 info
->entry
= gtk_entry_new();
928 pidgin_setup_screenname_autocomplete_with_filter(info
->entry
, NULL
, chat_invite_filter
,
929 purple_conversation_get_account(conv
));
930 gtk_table_attach_defaults(GTK_TABLE(table
), info
->entry
, 1, 2, 0, 1);
931 gtk_label_set_mnemonic_widget(GTK_LABEL(label
), info
->entry
);
933 /* Now the label for "Message" */
934 label
= gtk_label_new(NULL
);
935 gtk_label_set_markup_with_mnemonic(GTK_LABEL(label
), _("_Message:"));
936 gtk_misc_set_alignment(GTK_MISC(label
), 0, 0);
937 gtk_table_attach_defaults(GTK_TABLE(table
), label
, 0, 1, 1, 2);
940 /* And finally, the Message entry field. */
941 info
->message
= gtk_entry_new();
942 gtk_entry_set_activates_default(GTK_ENTRY(info
->message
), TRUE
);
944 gtk_table_attach_defaults(GTK_TABLE(table
), info
->message
, 1, 2, 1, 2);
945 gtk_label_set_mnemonic_widget(GTK_LABEL(label
), info
->message
);
947 /* Connect the signals. */
948 g_signal_connect(G_OBJECT(invite_dialog
), "response",
949 G_CALLBACK(do_invite
), info
);
950 /* Setup drag-and-drop */
951 gtk_drag_dest_set(info
->window
,
952 GTK_DEST_DEFAULT_MOTION
|
953 GTK_DEST_DEFAULT_DROP
,
955 sizeof(dnd_targets
) / sizeof(GtkTargetEntry
),
957 gtk_drag_dest_set(info
->entry
,
958 GTK_DEST_DEFAULT_MOTION
|
959 GTK_DEST_DEFAULT_DROP
,
961 sizeof(dnd_targets
) / sizeof(GtkTargetEntry
),
964 g_signal_connect(G_OBJECT(info
->window
), "drag_data_received",
965 G_CALLBACK(invite_dnd_recv
), info
);
966 g_signal_connect(G_OBJECT(info
->entry
), "drag_data_received",
967 G_CALLBACK(invite_dnd_recv
), info
);
970 gtk_widget_show_all(invite_dialog
);
973 gtk_widget_grab_focus(info
->entry
);
977 menu_new_conv_cb(gpointer data
, guint action
, GtkWidget
*widget
)
983 menu_join_chat_cb(gpointer data
, guint action
, GtkWidget
*widget
)
985 pidgin_blist_joinchat_show();
989 savelog_writefile_cb(void *user_data
, const char *filename
)
991 PurpleConversation
*conv
= (PurpleConversation
*)user_data
;
997 if ((fp
= g_fopen(filename
, "w+")) == NULL
) {
998 purple_notify_error(PIDGIN_CONVERSATION(conv
), NULL
, _("Unable to open file."), NULL
);
1002 name
= purple_conversation_get_name(conv
);
1003 fprintf(fp
, "<html>\n<head>\n");
1004 fprintf(fp
, "<meta http-equiv=\"content-type\" content=\"text/html; charset=UTF-8\">\n");
1005 fprintf(fp
, "<title>%s</title>\n</head>\n<body>\n", name
);
1006 fprintf(fp
, _("<h1>Conversation with %s</h1>\n"), name
);
1008 lines
= gtk_imhtml_get_markup_lines(
1009 GTK_IMHTML(PIDGIN_CONVERSATION(conv
)->imhtml
));
1010 text
= g_strjoinv("<br>\n", lines
);
1011 fprintf(fp
, "%s", text
);
1015 fprintf(fp
, "\n</body>\n</html>\n");
1020 * It would be kinda cool if this gave the option of saving a
1021 * plaintext v. HTML file.
1024 menu_save_as_cb(gpointer data
, guint action
, GtkWidget
*widget
)
1026 PidginWindow
*win
= data
;
1027 PurpleConversation
*conv
= pidgin_conv_window_get_active_conversation(win
);
1028 PurpleBuddy
*buddy
= purple_find_buddy(conv
->account
, conv
->name
);
1034 name
= purple_buddy_get_contact_alias(buddy
);
1036 name
= purple_normalize(conv
->account
, conv
->name
);
1038 buf
= g_strdup_printf("%s.html", name
);
1039 for (c
= buf
; *c
; c
++)
1041 if (*c
== '/' || *c
== '\\')
1044 purple_request_file(PIDGIN_CONVERSATION(conv
), _("Save Conversation"),
1046 TRUE
, G_CALLBACK(savelog_writefile_cb
), NULL
,
1054 menu_view_log_cb(gpointer data
, guint action
, GtkWidget
*widget
)
1056 PidginWindow
*win
= data
;
1057 PurpleConversation
*conv
;
1059 PidginBuddyList
*gtkblist
;
1062 PurpleAccount
*account
;
1066 conv
= pidgin_conv_window_get_active_conversation(win
);
1068 if (purple_conversation_get_type(conv
) == PURPLE_CONV_TYPE_IM
)
1069 type
= PURPLE_LOG_IM
;
1070 else if (purple_conversation_get_type(conv
) == PURPLE_CONV_TYPE_CHAT
)
1071 type
= PURPLE_LOG_CHAT
;
1075 gtkblist
= pidgin_blist_get_default_gtk_blist();
1077 cursor
= gdk_cursor_new(GDK_WATCH
);
1078 gdk_window_set_cursor(gtkblist
->window
->window
, cursor
);
1079 gdk_window_set_cursor(win
->window
->window
, cursor
);
1080 gdk_cursor_unref(cursor
);
1081 gdk_display_flush(gdk_drawable_get_display(GDK_DRAWABLE(widget
->window
)));
1083 name
= purple_conversation_get_name(conv
);
1084 account
= purple_conversation_get_account(conv
);
1086 buddies
= purple_find_buddies(account
, name
);
1087 for (cur
= buddies
; cur
!= NULL
; cur
= cur
->next
)
1089 PurpleBlistNode
*node
= cur
->data
;
1090 if ((node
!= NULL
) && ((node
->prev
!= NULL
) || (node
->next
!= NULL
)))
1092 pidgin_log_show_contact((PurpleContact
*)node
->parent
);
1093 g_slist_free(buddies
);
1094 gdk_window_set_cursor(gtkblist
->window
->window
, NULL
);
1095 gdk_window_set_cursor(win
->window
->window
, NULL
);
1099 g_slist_free(buddies
);
1101 pidgin_log_show(type
, name
, account
);
1103 gdk_window_set_cursor(gtkblist
->window
->window
, NULL
);
1104 gdk_window_set_cursor(win
->window
->window
, NULL
);
1108 menu_clear_cb(gpointer data
, guint action
, GtkWidget
*widget
)
1110 PidginWindow
*win
= data
;
1111 PurpleConversation
*conv
;
1113 conv
= pidgin_conv_window_get_active_conversation(win
);
1114 purple_conversation_clear_message_history(conv
);
1118 menu_find_cb(gpointer data
, guint action
, GtkWidget
*widget
)
1120 PidginWindow
*gtkwin
= data
;
1121 PidginConversation
*gtkconv
= pidgin_conv_window_get_active_gtkconv(gtkwin
);
1122 gtk_widget_show_all(gtkconv
->quickfind
.container
);
1123 gtk_widget_grab_focus(gtkconv
->quickfind
.entry
);
1128 menu_initiate_media_call_cb(gpointer data
, guint action
, GtkWidget
*widget
)
1130 PidginWindow
*win
= (PidginWindow
*)data
;
1131 PurpleConversation
*conv
= pidgin_conv_window_get_active_conversation(win
);
1132 PurpleAccount
*account
= purple_conversation_get_account(conv
);
1134 purple_prpl_initiate_media(account
,
1135 purple_conversation_get_name(conv
),
1136 action
== 0 ? PURPLE_MEDIA_AUDIO
:
1137 action
== 1 ? PURPLE_MEDIA_VIDEO
:
1138 action
== 2 ? PURPLE_MEDIA_AUDIO
|
1139 PURPLE_MEDIA_VIDEO
: PURPLE_MEDIA_NONE
);
1144 menu_send_file_cb(gpointer data
, guint action
, GtkWidget
*widget
)
1146 PidginWindow
*win
= data
;
1147 PurpleConversation
*conv
= pidgin_conv_window_get_active_conversation(win
);
1149 if (purple_conversation_get_type(conv
) == PURPLE_CONV_TYPE_IM
) {
1150 serv_send_file(purple_conversation_get_gc(conv
), purple_conversation_get_name(conv
), NULL
);
1156 menu_get_attention_cb(gpointer data
, guint action
, GtkWidget
*widget
)
1158 PidginWindow
*win
= data
;
1159 PurpleConversation
*conv
= pidgin_conv_window_get_active_conversation(win
);
1161 if (purple_conversation_get_type(conv
) == PURPLE_CONV_TYPE_IM
) {
1162 purple_prpl_send_attention(purple_conversation_get_gc(conv
),
1163 purple_conversation_get_name(conv
), 0);
1168 menu_add_pounce_cb(gpointer data
, guint action
, GtkWidget
*widget
)
1170 PidginWindow
*win
= data
;
1171 PurpleConversation
*conv
;
1173 conv
= pidgin_conv_window_get_active_gtkconv(win
)->active_conv
;
1175 pidgin_pounce_editor_show(purple_conversation_get_account(conv
),
1176 purple_conversation_get_name(conv
), NULL
);
1180 menu_insert_link_cb(gpointer data
, guint action
, GtkWidget
*widget
)
1182 PidginWindow
*win
= data
;
1183 PidginConversation
*gtkconv
;
1184 GtkIMHtmlToolbar
*toolbar
;
1186 gtkconv
= pidgin_conv_window_get_active_gtkconv(win
);
1187 toolbar
= GTK_IMHTMLTOOLBAR(gtkconv
->toolbar
);
1189 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(toolbar
->link
),
1190 !gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(toolbar
->link
)));
1194 menu_insert_image_cb(gpointer data
, guint action
, GtkWidget
*widget
)
1196 PidginWindow
*win
= data
;
1197 PidginConversation
*gtkconv
;
1198 GtkIMHtmlToolbar
*toolbar
;
1200 gtkconv
= pidgin_conv_window_get_active_gtkconv(win
);
1201 toolbar
= GTK_IMHTMLTOOLBAR(gtkconv
->toolbar
);
1203 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(toolbar
->image
),
1204 !gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(toolbar
->image
)));
1209 menu_alias_cb(gpointer data
, guint action
, GtkWidget
*widget
)
1211 PidginWindow
*win
= data
;
1212 PurpleConversation
*conv
;
1213 PurpleAccount
*account
;
1216 conv
= pidgin_conv_window_get_active_conversation(win
);
1217 account
= purple_conversation_get_account(conv
);
1218 name
= purple_conversation_get_name(conv
);
1220 if (purple_conversation_get_type(conv
) == PURPLE_CONV_TYPE_IM
) {
1223 b
= purple_find_buddy(account
, name
);
1225 pidgin_dialogs_alias_buddy(b
);
1226 } else if (purple_conversation_get_type(conv
) == PURPLE_CONV_TYPE_CHAT
) {
1229 c
= purple_blist_find_chat(account
, name
);
1231 pidgin_dialogs_alias_chat(c
);
1236 menu_get_info_cb(gpointer data
, guint action
, GtkWidget
*widget
)
1238 PidginWindow
*win
= data
;
1239 PurpleConversation
*conv
;
1241 conv
= pidgin_conv_window_get_active_conversation(win
);
1243 info_cb(NULL
, PIDGIN_CONVERSATION(conv
));
1247 menu_invite_cb(gpointer data
, guint action
, GtkWidget
*widget
)
1249 PidginWindow
*win
= data
;
1250 PurpleConversation
*conv
;
1252 conv
= pidgin_conv_window_get_active_conversation(win
);
1254 invite_cb(NULL
, PIDGIN_CONVERSATION(conv
));
1258 menu_block_cb(gpointer data
, guint action
, GtkWidget
*widget
)
1260 PidginWindow
*win
= data
;
1261 PurpleConversation
*conv
;
1263 conv
= pidgin_conv_window_get_active_conversation(win
);
1265 block_cb(NULL
, PIDGIN_CONVERSATION(conv
));
1269 menu_unblock_cb(gpointer data
, guint action
, GtkWidget
*widget
)
1271 PidginWindow
*win
= data
;
1272 PurpleConversation
*conv
;
1274 conv
= pidgin_conv_window_get_active_conversation(win
);
1276 unblock_cb(NULL
, PIDGIN_CONVERSATION(conv
));
1280 menu_add_remove_cb(gpointer data
, guint action
, GtkWidget
*widget
)
1282 PidginWindow
*win
= data
;
1283 PurpleConversation
*conv
;
1285 conv
= pidgin_conv_window_get_active_conversation(win
);
1287 add_remove_cb(NULL
, PIDGIN_CONVERSATION(conv
));
1291 close_already(gpointer data
)
1293 purple_conversation_destroy(data
);
1298 hide_conv(PidginConversation
*gtkconv
, gboolean closetimer
)
1302 purple_signal_emit(pidgin_conversations_get_handle(),
1303 "conversation-hiding", gtkconv
);
1305 for (list
= g_list_copy(gtkconv
->convs
); list
; list
= g_list_delete_link(list
, list
)) {
1306 PurpleConversation
*conv
= list
->data
;
1308 guint timer
= GPOINTER_TO_INT(purple_conversation_get_data(conv
, "close-timer"));
1310 purple_timeout_remove(timer
);
1311 timer
= purple_timeout_add_seconds(CLOSE_CONV_TIMEOUT_SECS
, close_already
, conv
);
1312 purple_conversation_set_data(conv
, "close-timer", GINT_TO_POINTER(timer
));
1315 /* I will miss you */
1316 purple_conversation_set_ui_ops(conv
, NULL
);
1318 pidgin_conv_window_remove_gtkconv(gtkconv
->win
, gtkconv
);
1319 pidgin_conv_window_add_gtkconv(hidden_convwin
, gtkconv
);
1325 menu_close_conv_cb(gpointer data
, guint action
, GtkWidget
*widget
)
1327 PidginWindow
*win
= data
;
1329 close_conv_cb(NULL
, PIDGIN_CONVERSATION(pidgin_conv_window_get_active_conversation(win
)));
1333 menu_logging_cb(gpointer data
, guint action
, GtkWidget
*widget
)
1335 PidginWindow
*win
= data
;
1336 PurpleConversation
*conv
;
1338 PurpleBlistNode
*node
;
1340 conv
= pidgin_conv_window_get_active_conversation(win
);
1345 logging
= gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(widget
));
1347 if (logging
== purple_conversation_is_logging(conv
))
1350 node
= get_conversation_blist_node(conv
);
1354 /* Enable logging first so the message below can be logged. */
1355 purple_conversation_set_logging(conv
, TRUE
);
1357 purple_conversation_write(conv
, NULL
,
1358 _("Logging started. Future messages in this conversation will be logged."),
1359 conv
->logs
? (PURPLE_MESSAGE_SYSTEM
) :
1360 (PURPLE_MESSAGE_SYSTEM
| PURPLE_MESSAGE_NO_LOG
),
1365 purple_conversation_write(conv
, NULL
,
1366 _("Logging stopped. Future messages in this conversation will not be logged."),
1367 conv
->logs
? (PURPLE_MESSAGE_SYSTEM
) :
1368 (PURPLE_MESSAGE_SYSTEM
| PURPLE_MESSAGE_NO_LOG
),
1371 /* Disable the logging second, so that the above message can be logged. */
1372 purple_conversation_set_logging(conv
, FALSE
);
1375 /* Save the setting IFF it's different than the pref. */
1378 case PURPLE_CONV_TYPE_IM
:
1379 if (logging
== purple_prefs_get_bool("/purple/logging/log_ims"))
1380 purple_blist_node_remove_setting(node
, "enable-logging");
1382 purple_blist_node_set_bool(node
, "enable-logging", logging
);
1385 case PURPLE_CONV_TYPE_CHAT
:
1386 if (logging
== purple_prefs_get_bool("/purple/logging/log_chats"))
1387 purple_blist_node_remove_setting(node
, "enable-logging");
1389 purple_blist_node_set_bool(node
, "enable-logging", logging
);
1398 menu_toolbar_cb(gpointer data
, guint action
, GtkWidget
*widget
)
1400 purple_prefs_set_bool(PIDGIN_PREFS_ROOT
"/conversations/show_formatting_toolbar",
1401 gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(widget
)));
1405 menu_sounds_cb(gpointer data
, guint action
, GtkWidget
*widget
)
1407 PidginWindow
*win
= data
;
1408 PurpleConversation
*conv
;
1409 PidginConversation
*gtkconv
;
1410 PurpleBlistNode
*node
;
1412 conv
= pidgin_conv_window_get_active_conversation(win
);
1417 gtkconv
= PIDGIN_CONVERSATION(conv
);
1419 gtkconv
->make_sound
=
1420 gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(widget
));
1421 node
= get_conversation_blist_node(conv
);
1423 purple_blist_node_set_bool(node
, "gtk-mute-sound", !gtkconv
->make_sound
);
1427 menu_timestamps_cb(gpointer data
, guint action
, GtkWidget
*widget
)
1429 purple_prefs_set_bool(PIDGIN_PREFS_ROOT
"/conversations/show_timestamps",
1430 gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(widget
)));
1434 chat_do_im(PidginConversation
*gtkconv
, const char *who
)
1436 PurpleConversation
*conv
= gtkconv
->active_conv
;
1437 PurpleAccount
*account
;
1438 PurpleConnection
*gc
;
1439 PurplePluginProtocolInfo
*prpl_info
= NULL
;
1440 gchar
*real_who
= NULL
;
1442 account
= purple_conversation_get_account(conv
);
1443 g_return_if_fail(account
!= NULL
);
1445 gc
= purple_account_get_connection(account
);
1446 g_return_if_fail(gc
!= NULL
);
1448 prpl_info
= PURPLE_PLUGIN_PROTOCOL_INFO(gc
->prpl
);
1450 if (prpl_info
&& prpl_info
->get_cb_real_name
)
1451 real_who
= prpl_info
->get_cb_real_name(gc
,
1452 purple_conv_chat_get_id(PURPLE_CONV_CHAT(conv
)), who
);
1454 if(!who
&& !real_who
)
1457 pidgin_dialogs_im_with_user(account
, real_who
? real_who
: who
);
1462 static void pidgin_conv_chat_update_user(PurpleConversation
*conv
, const char *user
);
1465 ignore_cb(GtkWidget
*w
, PidginConversation
*gtkconv
)
1467 PurpleConversation
*conv
= gtkconv
->active_conv
;
1468 PurpleConvChat
*chat
;
1471 chat
= PURPLE_CONV_CHAT(conv
);
1472 name
= g_object_get_data(G_OBJECT(w
), "user_data");
1477 if (purple_conv_chat_is_user_ignored(chat
, name
))
1478 purple_conv_chat_unignore(chat
, name
);
1480 purple_conv_chat_ignore(chat
, name
);
1482 pidgin_conv_chat_update_user(conv
, name
);
1486 menu_chat_im_cb(GtkWidget
*w
, PidginConversation
*gtkconv
)
1488 const char *who
= g_object_get_data(G_OBJECT(w
), "user_data");
1490 chat_do_im(gtkconv
, who
);
1494 menu_chat_send_file_cb(GtkWidget
*w
, PidginConversation
*gtkconv
)
1496 PurplePluginProtocolInfo
*prpl_info
;
1497 PurpleConversation
*conv
= gtkconv
->active_conv
;
1498 const char *who
= g_object_get_data(G_OBJECT(w
), "user_data");
1499 PurpleConnection
*gc
= purple_conversation_get_gc(conv
);
1500 gchar
*real_who
= NULL
;
1502 g_return_if_fail(gc
!= NULL
);
1504 prpl_info
= PURPLE_PLUGIN_PROTOCOL_INFO(gc
->prpl
);
1506 if (prpl_info
&& prpl_info
->get_cb_real_name
)
1507 real_who
= prpl_info
->get_cb_real_name(gc
,
1508 purple_conv_chat_get_id(PURPLE_CONV_CHAT(conv
)), who
);
1510 serv_send_file(gc
, real_who
? real_who
: who
, NULL
);
1515 menu_chat_info_cb(GtkWidget
*w
, PidginConversation
*gtkconv
)
1519 who
= g_object_get_data(G_OBJECT(w
), "user_data");
1521 chat_do_info(gtkconv
, who
);
1525 menu_chat_get_away_cb(GtkWidget
*w
, PidginConversation
*gtkconv
)
1527 PurpleConversation
*conv
= gtkconv
->active_conv
;
1528 PurplePluginProtocolInfo
*prpl_info
= NULL
;
1529 PurpleConnection
*gc
;
1532 gc
= purple_conversation_get_gc(conv
);
1533 who
= g_object_get_data(G_OBJECT(w
), "user_data");
1536 prpl_info
= PURPLE_PLUGIN_PROTOCOL_INFO(gc
->prpl
);
1539 * May want to expand this to work similarly to menu_info_cb?
1542 if (prpl_info
->get_cb_away
!= NULL
)
1544 prpl_info
->get_cb_away(gc
,
1545 purple_conv_chat_get_id(PURPLE_CONV_CHAT(conv
)), who
);
1551 menu_chat_add_remove_cb(GtkWidget
*w
, PidginConversation
*gtkconv
)
1553 PurpleConversation
*conv
= gtkconv
->active_conv
;
1554 PurpleAccount
*account
;
1558 account
= purple_conversation_get_account(conv
);
1559 name
= g_object_get_data(G_OBJECT(w
), "user_data");
1560 b
= purple_find_buddy(account
, name
);
1563 pidgin_dialogs_remove_buddy(b
);
1564 else if (account
!= NULL
&& purple_account_is_connected(account
))
1565 purple_blist_request_add_buddy(account
, name
, NULL
, NULL
);
1567 gtk_widget_grab_focus(PIDGIN_CONVERSATION(conv
)->entry
);
1570 static GtkTextMark
*
1571 get_mark_for_user(PidginConversation
*gtkconv
, const char *who
)
1573 GtkTextBuffer
*buf
= gtk_text_view_get_buffer(GTK_TEXT_VIEW(gtkconv
->imhtml
));
1574 char *tmp
= g_strconcat("user:", who
, NULL
);
1575 GtkTextMark
*mark
= gtk_text_buffer_get_mark(buf
, tmp
);
1582 menu_last_said_cb(GtkWidget
*w
, PidginConversation
*gtkconv
)
1587 who
= g_object_get_data(G_OBJECT(w
), "user_data");
1588 mark
= get_mark_for_user(gtkconv
, who
);
1591 gtk_text_view_scroll_to_mark(GTK_TEXT_VIEW(gtkconv
->imhtml
), mark
, 0.1, FALSE
, 0, 0);
1593 g_return_if_reached();
1597 create_chat_menu(PurpleConversation
*conv
, const char *who
, PurpleConnection
*gc
)
1599 static GtkWidget
*menu
= NULL
;
1600 PurplePluginProtocolInfo
*prpl_info
= NULL
;
1601 PurpleConvChat
*chat
= PURPLE_CONV_CHAT(conv
);
1602 gboolean is_me
= FALSE
;
1604 PurpleBuddy
*buddy
= NULL
;
1607 prpl_info
= PURPLE_PLUGIN_PROTOCOL_INFO(gc
->prpl
);
1610 * If a menu already exists, destroy it before creating a new one,
1611 * thus freeing-up the memory it occupied.
1614 gtk_widget_destroy(menu
);
1616 if (!strcmp(chat
->nick
, purple_normalize(conv
->account
, who
)))
1619 menu
= gtk_menu_new();
1622 button
= pidgin_new_item_from_stock(menu
, _("IM"), PIDGIN_STOCK_TOOLBAR_MESSAGE_NEW
,
1623 G_CALLBACK(menu_chat_im_cb
), PIDGIN_CONVERSATION(conv
), 0, 0, NULL
);
1626 gtk_widget_set_sensitive(button
, FALSE
);
1628 g_object_set_data_full(G_OBJECT(button
), "user_data", g_strdup(who
), g_free
);
1631 if (prpl_info
&& prpl_info
->send_file
)
1633 gboolean can_receive_file
= TRUE
;
1635 button
= pidgin_new_item_from_stock(menu
, _("Send File"),
1636 PIDGIN_STOCK_TOOLBAR_SEND_FILE
, G_CALLBACK(menu_chat_send_file_cb
),
1637 PIDGIN_CONVERSATION(conv
), 0, 0, NULL
);
1639 if (gc
== NULL
|| prpl_info
== NULL
)
1640 can_receive_file
= FALSE
;
1642 gchar
*real_who
= NULL
;
1643 if (prpl_info
->get_cb_real_name
)
1644 real_who
= prpl_info
->get_cb_real_name(gc
,
1645 purple_conv_chat_get_id(PURPLE_CONV_CHAT(conv
)), who
);
1646 if (!(!prpl_info
->can_receive_file
|| prpl_info
->can_receive_file(gc
, real_who
? real_who
: who
)))
1647 can_receive_file
= FALSE
;
1651 if (!can_receive_file
)
1652 gtk_widget_set_sensitive(button
, FALSE
);
1654 g_object_set_data_full(G_OBJECT(button
), "user_data", g_strdup(who
), g_free
);
1658 if (purple_conv_chat_is_user_ignored(PURPLE_CONV_CHAT(conv
), who
))
1659 button
= pidgin_new_item_from_stock(menu
, _("Un-Ignore"), PIDGIN_STOCK_IGNORE
,
1660 G_CALLBACK(ignore_cb
), PIDGIN_CONVERSATION(conv
), 0, 0, NULL
);
1662 button
= pidgin_new_item_from_stock(menu
, _("Ignore"), PIDGIN_STOCK_IGNORE
,
1663 G_CALLBACK(ignore_cb
), PIDGIN_CONVERSATION(conv
), 0, 0, NULL
);
1666 gtk_widget_set_sensitive(button
, FALSE
);
1668 g_object_set_data_full(G_OBJECT(button
), "user_data", g_strdup(who
), g_free
);
1671 if (prpl_info
&& (prpl_info
->get_info
|| prpl_info
->get_cb_info
)) {
1672 button
= pidgin_new_item_from_stock(menu
, _("Info"), PIDGIN_STOCK_TOOLBAR_USER_INFO
,
1673 G_CALLBACK(menu_chat_info_cb
), PIDGIN_CONVERSATION(conv
), 0, 0, NULL
);
1676 gtk_widget_set_sensitive(button
, FALSE
);
1678 g_object_set_data_full(G_OBJECT(button
), "user_data", g_strdup(who
), g_free
);
1681 if (prpl_info
&& prpl_info
->get_cb_away
) {
1682 button
= pidgin_new_item_from_stock(menu
, _("Get Away Message"), PIDGIN_STOCK_AWAY
,
1683 G_CALLBACK(menu_chat_get_away_cb
), PIDGIN_CONVERSATION(conv
), 0, 0, NULL
);
1686 gtk_widget_set_sensitive(button
, FALSE
);
1688 g_object_set_data_full(G_OBJECT(button
), "user_data", g_strdup(who
), g_free
);
1691 if (!is_me
&& prpl_info
&& !(prpl_info
->options
& OPT_PROTO_UNIQUE_CHATNAME
)) {
1692 if ((buddy
= purple_find_buddy(conv
->account
, who
)) != NULL
)
1693 button
= pidgin_new_item_from_stock(menu
, _("Remove"), GTK_STOCK_REMOVE
,
1694 G_CALLBACK(menu_chat_add_remove_cb
), PIDGIN_CONVERSATION(conv
), 0, 0, NULL
);
1696 button
= pidgin_new_item_from_stock(menu
, _("Add"), GTK_STOCK_ADD
,
1697 G_CALLBACK(menu_chat_add_remove_cb
), PIDGIN_CONVERSATION(conv
), 0, 0, NULL
);
1700 gtk_widget_set_sensitive(button
, FALSE
);
1702 g_object_set_data_full(G_OBJECT(button
), "user_data", g_strdup(who
), g_free
);
1705 button
= pidgin_new_item_from_stock(menu
, _("Last Said"), GTK_STOCK_INDEX
,
1706 G_CALLBACK(menu_last_said_cb
), PIDGIN_CONVERSATION(conv
), 0, 0, NULL
);
1707 g_object_set_data_full(G_OBJECT(button
), "user_data", g_strdup(who
), g_free
);
1708 if (!get_mark_for_user(PIDGIN_CONVERSATION(conv
), who
))
1709 gtk_widget_set_sensitive(button
, FALSE
);
1713 if (purple_account_is_connected(conv
->account
))
1714 pidgin_append_blist_node_proto_menu(menu
, conv
->account
->gc
,
1715 (PurpleBlistNode
*)buddy
);
1716 pidgin_append_blist_node_extended_menu(menu
, (PurpleBlistNode
*)buddy
);
1717 gtk_widget_show_all(menu
);
1725 gtkconv_chat_popup_menu_cb(GtkWidget
*widget
, PidginConversation
*gtkconv
)
1727 PurpleConversation
*conv
= gtkconv
->active_conv
;
1728 PidginChatPane
*gtkchat
;
1729 PurpleConnection
*gc
;
1730 PurpleAccount
*account
;
1731 GtkTreeSelection
*sel
;
1733 GtkTreeModel
*model
;
1737 gtkconv
= PIDGIN_CONVERSATION(conv
);
1738 gtkchat
= gtkconv
->u
.chat
;
1739 account
= purple_conversation_get_account(conv
);
1742 model
= gtk_tree_view_get_model(GTK_TREE_VIEW(gtkchat
->list
));
1744 sel
= gtk_tree_view_get_selection(GTK_TREE_VIEW(gtkchat
->list
));
1745 if(!gtk_tree_selection_get_selected(sel
, NULL
, &iter
))
1748 gtk_tree_model_get(GTK_TREE_MODEL(model
), &iter
, CHAT_USERS_NAME_COLUMN
, &who
, -1);
1749 menu
= create_chat_menu (conv
, who
, gc
);
1750 gtk_menu_popup(GTK_MENU(menu
), NULL
, NULL
,
1751 pidgin_treeview_popup_menu_position_func
, widget
,
1752 0, GDK_CURRENT_TIME
);
1760 right_click_chat_cb(GtkWidget
*widget
, GdkEventButton
*event
,
1761 PidginConversation
*gtkconv
)
1763 PurpleConversation
*conv
= gtkconv
->active_conv
;
1764 PidginChatPane
*gtkchat
;
1765 PurpleConnection
*gc
;
1766 PurpleAccount
*account
;
1769 GtkTreeModel
*model
;
1770 GtkTreeViewColumn
*column
;
1774 gtkchat
= gtkconv
->u
.chat
;
1775 account
= purple_conversation_get_account(conv
);
1778 model
= gtk_tree_view_get_model(GTK_TREE_VIEW(gtkchat
->list
));
1780 gtk_tree_view_get_path_at_pos(GTK_TREE_VIEW(gtkchat
->list
),
1781 event
->x
, event
->y
, &path
, &column
, &x
, &y
);
1786 gtk_tree_selection_select_path(GTK_TREE_SELECTION(
1787 gtk_tree_view_get_selection(GTK_TREE_VIEW(gtkchat
->list
))), path
);
1788 gtk_tree_view_set_cursor(GTK_TREE_VIEW(gtkchat
->list
),
1790 gtk_widget_grab_focus(GTK_WIDGET(gtkchat
->list
));
1792 gtk_tree_model_get_iter(GTK_TREE_MODEL(model
), &iter
, path
);
1793 gtk_tree_model_get(GTK_TREE_MODEL(model
), &iter
, CHAT_USERS_NAME_COLUMN
, &who
, -1);
1795 /* emit chat-nick-clicked signal */
1796 if (event
->type
== GDK_BUTTON_PRESS
) {
1797 gint plugin_return
= GPOINTER_TO_INT(purple_signal_emit_return_1(
1798 pidgin_conversations_get_handle(), "chat-nick-clicked",
1799 conv
, who
, event
->button
));
1804 if (event
->button
== 1 && event
->type
== GDK_2BUTTON_PRESS
) {
1805 chat_do_im(gtkconv
, who
);
1806 } else if (event
->button
== 2 && event
->type
== GDK_BUTTON_PRESS
) {
1807 /* Move to user's anchor */
1808 GtkTextMark
*mark
= get_mark_for_user(gtkconv
, who
);
1811 gtk_text_view_scroll_to_mark(GTK_TEXT_VIEW(gtkconv
->imhtml
), mark
, 0.1, FALSE
, 0, 0);
1812 } else if (event
->button
== 3 && event
->type
== GDK_BUTTON_PRESS
) {
1813 GtkWidget
*menu
= create_chat_menu (conv
, who
, gc
);
1814 gtk_menu_popup(GTK_MENU(menu
), NULL
, NULL
, NULL
, NULL
,
1815 event
->button
, event
->time
);
1820 gtk_tree_path_free(path
);
1826 activate_list_cb(GtkTreeView
*list
, GtkTreePath
*path
, GtkTreeViewColumn
*column
, PidginConversation
*gtkconv
)
1829 GtkTreeModel
*model
;
1832 model
= gtk_tree_view_get_model(GTK_TREE_VIEW(list
));
1834 gtk_tree_model_get_iter(GTK_TREE_MODEL(model
), &iter
, path
);
1835 gtk_tree_model_get(GTK_TREE_MODEL(model
), &iter
, CHAT_USERS_NAME_COLUMN
, &who
, -1);
1836 chat_do_im(gtkconv
, who
);
1842 move_to_next_unread_tab(PidginConversation
*gtkconv
, gboolean forward
)
1844 PidginConversation
*next_gtkconv
= NULL
, *most_active
= NULL
;
1845 PidginUnseenState unseen_state
= PIDGIN_UNSEEN_NONE
;
1847 int initial
, i
, total
, diff
;
1850 initial
= gtk_notebook_page_num(GTK_NOTEBOOK(win
->notebook
),
1852 total
= pidgin_conv_window_get_gtkconv_count(win
);
1853 /* By adding total here, the moduli calculated later will always have two
1854 * positive arguments. x % y where x < 0 is not guaranteed to return a
1857 diff
= (forward
? 1 : -1) + total
;
1859 for (i
= (initial
+ diff
) % total
; i
!= initial
; i
= (i
+ diff
) % total
) {
1860 next_gtkconv
= pidgin_conv_window_get_gtkconv_at_index(win
, i
);
1861 if (next_gtkconv
->unseen_state
> unseen_state
) {
1862 most_active
= next_gtkconv
;
1863 unseen_state
= most_active
->unseen_state
;
1864 if(PIDGIN_UNSEEN_NICK
== unseen_state
) /* highest possible state */
1869 if (most_active
== NULL
) { /* no new messages */
1870 i
= (i
+ diff
) % total
;
1871 most_active
= pidgin_conv_window_get_gtkconv_at_index(win
, i
);
1874 if (most_active
!= NULL
&& most_active
!= gtkconv
)
1875 pidgin_conv_window_switch_gtkconv(win
, most_active
);
1879 gtkconv_cycle_focus(PidginConversation
*gtkconv
, GtkDirectionType dir
)
1881 PurpleConversation
*conv
= gtkconv
->active_conv
;
1882 gboolean chat
= purple_conversation_get_type(conv
) == PURPLE_CONV_TYPE_CHAT
;
1883 GtkWidget
*next
= NULL
;
1888 {gtkconv
->entry
, gtkconv
->imhtml
},
1889 {gtkconv
->imhtml
, chat
? gtkconv
->u
.chat
->list
: gtkconv
->entry
},
1890 {chat
? gtkconv
->u
.chat
->list
: NULL
, gtkconv
->entry
},
1894 for (ptr
= transitions
; !next
&& ptr
->from
; ptr
++) {
1895 GtkWidget
*from
, *to
;
1896 if (dir
== GTK_DIR_TAB_FORWARD
) {
1903 if (gtk_widget_is_focus(from
))
1908 gtk_widget_grab_focus(next
);
1913 conv_keypress_common(PidginConversation
*gtkconv
, GdkEventKey
*event
)
1919 curconv
= gtk_notebook_get_current_page(GTK_NOTEBOOK(win
->notebook
));
1921 /* clear any tooltips */
1922 pidgin_tooltip_destroy();
1924 /* If CTRL was held down... */
1925 if (event
->state
& GDK_CONTROL_MASK
) {
1926 switch (event
->keyval
) {
1928 case GDK_KP_Page_Down
:
1930 if (!pidgin_conv_window_get_gtkconv_at_index(win
, curconv
+ 1))
1931 gtk_notebook_set_current_page(GTK_NOTEBOOK(win
->notebook
), 0);
1933 gtk_notebook_set_current_page(GTK_NOTEBOOK(win
->notebook
), curconv
+ 1);
1938 case GDK_KP_Page_Up
:
1940 if (!pidgin_conv_window_get_gtkconv_at_index(win
, curconv
- 1))
1941 gtk_notebook_set_current_page(GTK_NOTEBOOK(win
->notebook
), -1);
1943 gtk_notebook_set_current_page(GTK_NOTEBOOK(win
->notebook
), curconv
- 1);
1949 case GDK_ISO_Left_Tab
:
1950 if (event
->state
& GDK_SHIFT_MASK
) {
1951 move_to_next_unread_tab(gtkconv
, FALSE
);
1953 move_to_next_unread_tab(gtkconv
, TRUE
);
1960 gtk_notebook_reorder_child(GTK_NOTEBOOK(win
->notebook
),
1961 gtk_notebook_get_nth_page(GTK_NOTEBOOK(win
->notebook
), curconv
),
1967 gtk_notebook_reorder_child(GTK_NOTEBOOK(win
->notebook
),
1968 gtk_notebook_get_nth_page(GTK_NOTEBOOK(win
->notebook
), curconv
),
1969 (curconv
+ 1) % gtk_notebook_get_n_pages(GTK_NOTEBOOK(win
->notebook
)));
1973 if (gtkconv_cycle_focus(gtkconv
, event
->state
& GDK_SHIFT_MASK
? GTK_DIR_TAB_BACKWARD
: GTK_DIR_TAB_FORWARD
))
1976 } /* End of switch */
1979 /* If ALT (or whatever) was held down... */
1980 else if (event
->state
& GDK_MOD1_MASK
)
1982 if (event
->keyval
> '0' && event
->keyval
<= '9')
1984 guint switchto
= event
->keyval
- '1';
1985 if (switchto
< pidgin_conv_window_get_gtkconv_count(win
))
1986 gtk_notebook_set_current_page(GTK_NOTEBOOK(win
->notebook
), switchto
);
1992 /* If neither CTRL nor ALT were held down... */
1995 switch (event
->keyval
) {
1997 if (gtk_widget_is_focus(GTK_WIDGET(win
->notebook
))) {
1998 infopane_entry_activate(gtkconv
);
2003 if (gtkconv_cycle_focus(gtkconv
, event
->state
& GDK_SHIFT_MASK
? GTK_DIR_TAB_BACKWARD
: GTK_DIR_TAB_FORWARD
))
2012 entry_key_press_cb(GtkWidget
*entry
, GdkEventKey
*event
, gpointer data
)
2014 PurpleConversation
*conv
;
2015 PidginConversation
*gtkconv
;
2017 gtkconv
= (PidginConversation
*)data
;
2018 conv
= gtkconv
->active_conv
;
2020 if (conv_keypress_common(gtkconv
, event
))
2023 /* If CTRL was held down... */
2024 if (event
->state
& GDK_CONTROL_MASK
) {
2025 switch (event
->keyval
) {
2027 if (!gtkconv
->send_history
)
2030 if (gtkconv
->entry
!= entry
)
2033 if (!gtkconv
->send_history
->prev
) {
2034 GtkTextIter start
, end
;
2036 g_free(gtkconv
->send_history
->data
);
2038 gtk_text_buffer_get_start_iter(gtkconv
->entry_buffer
,
2040 gtk_text_buffer_get_end_iter(gtkconv
->entry_buffer
, &end
);
2042 gtkconv
->send_history
->data
=
2043 gtk_imhtml_get_markup(GTK_IMHTML(gtkconv
->entry
));
2046 if (gtkconv
->send_history
->next
&& gtkconv
->send_history
->next
->data
) {
2049 GtkTextBuffer
*buffer
= gtk_text_view_get_buffer(GTK_TEXT_VIEW(gtkconv
->entry
));
2051 gtkconv
->send_history
= gtkconv
->send_history
->next
;
2053 /* Block the signal to prevent application of default formatting. */
2054 object
= g_object_ref(G_OBJECT(gtkconv
->entry
));
2055 g_signal_handlers_block_matched(object
, G_SIGNAL_MATCH_DATA
, 0, 0, NULL
,
2057 /* Clear the formatting. */
2058 gtk_imhtml_clear_formatting(GTK_IMHTML(gtkconv
->entry
));
2059 /* Unblock the signal. */
2060 g_signal_handlers_unblock_matched(object
, G_SIGNAL_MATCH_DATA
, 0, 0, NULL
,
2062 g_object_unref(object
);
2064 gtk_imhtml_clear(GTK_IMHTML(gtkconv
->entry
));
2065 gtk_imhtml_append_text_with_images(
2066 GTK_IMHTML(gtkconv
->entry
), gtkconv
->send_history
->data
,
2068 /* this is mainly just a hack so the formatting at the
2069 * cursor gets picked up. */
2070 gtk_text_buffer_get_end_iter(buffer
, &iter
);
2071 gtk_text_buffer_move_mark_by_name(buffer
, "insert", &iter
);
2078 if (!gtkconv
->send_history
)
2081 if (gtkconv
->entry
!= entry
)
2084 if (gtkconv
->send_history
->prev
&& gtkconv
->send_history
->prev
->data
) {
2087 GtkTextBuffer
*buffer
= gtk_text_view_get_buffer(GTK_TEXT_VIEW(gtkconv
->entry
));
2089 gtkconv
->send_history
= gtkconv
->send_history
->prev
;
2091 /* Block the signal to prevent application of default formatting. */
2092 object
= g_object_ref(G_OBJECT(gtkconv
->entry
));
2093 g_signal_handlers_block_matched(object
, G_SIGNAL_MATCH_DATA
, 0, 0, NULL
,
2095 /* Clear the formatting. */
2096 gtk_imhtml_clear_formatting(GTK_IMHTML(gtkconv
->entry
));
2097 /* Unblock the signal. */
2098 g_signal_handlers_unblock_matched(object
, G_SIGNAL_MATCH_DATA
, 0, 0, NULL
,
2100 g_object_unref(object
);
2102 gtk_imhtml_clear(GTK_IMHTML(gtkconv
->entry
));
2103 gtk_imhtml_append_text_with_images(
2104 GTK_IMHTML(gtkconv
->entry
), gtkconv
->send_history
->data
,
2106 /* this is mainly just a hack so the formatting at the
2107 * cursor gets picked up. */
2108 if (*(char *)gtkconv
->send_history
->data
) {
2109 gtk_text_buffer_get_end_iter(buffer
, &iter
);
2110 gtk_text_buffer_move_mark_by_name(buffer
, "insert", &iter
);
2112 /* Restore the default formatting */
2113 default_formatize(gtkconv
);
2119 } /* End of switch */
2122 /* If ALT (or whatever) was held down... */
2123 else if (event
->state
& GDK_MOD1_MASK
) {
2127 /* If neither CTRL nor ALT were held down... */
2129 switch (event
->keyval
) {
2132 case GDK_ISO_Left_Tab
:
2133 if (gtkconv
->entry
!= entry
)
2137 plugin_return
= GPOINTER_TO_INT(purple_signal_emit_return_1(
2138 pidgin_conversations_get_handle(), "chat-nick-autocomplete",
2139 conv
, event
->state
& GDK_SHIFT_MASK
));
2140 return plugin_return
? TRUE
: tab_complete(conv
);
2145 case GDK_KP_Page_Up
:
2146 gtk_imhtml_page_up(GTK_IMHTML(gtkconv
->imhtml
));
2151 case GDK_KP_Page_Down
:
2152 gtk_imhtml_page_down(GTK_IMHTML(gtkconv
->imhtml
));
2163 * This guy just kills a single right click from being propagated any
2164 * further. I have no idea *why* we need this, but we do ... It
2165 * prevents right clicks on the GtkTextView in a convo dialog from
2166 * going all the way down to the notebook. I suspect a bug in
2167 * GtkTextView, but I'm not ready to point any fingers yet.
2170 entry_stop_rclick_cb(GtkWidget
*widget
, GdkEventButton
*event
, gpointer data
)
2172 if (event
->button
== 3 && event
->type
== GDK_BUTTON_PRESS
) {
2173 /* Right single click */
2174 g_signal_stop_emission_by_name(G_OBJECT(widget
), "button_press_event");
2183 * If someone tries to type into the conversation backlog of a
2184 * conversation window then we yank focus from the conversation backlog
2185 * and give it to the text entry box so that people can type
2186 * all the live long day and it will get entered into the entry box.
2189 refocus_entry_cb(GtkWidget
*widget
, GdkEventKey
*event
, gpointer data
)
2191 PidginConversation
*gtkconv
= data
;
2193 /* If we have a valid key for the conversation display, then exit */
2194 if ((event
->state
& GDK_CONTROL_MASK
) ||
2195 (event
->keyval
== GDK_F6
) ||
2196 (event
->keyval
== GDK_F10
) ||
2197 (event
->keyval
== GDK_Shift_L
) ||
2198 (event
->keyval
== GDK_Shift_R
) ||
2199 (event
->keyval
== GDK_Control_L
) ||
2200 (event
->keyval
== GDK_Control_R
) ||
2201 (event
->keyval
== GDK_Escape
) ||
2202 (event
->keyval
== GDK_Up
) ||
2203 (event
->keyval
== GDK_Down
) ||
2204 (event
->keyval
== GDK_Left
) ||
2205 (event
->keyval
== GDK_Right
) ||
2206 (event
->keyval
== GDK_Page_Up
) ||
2207 (event
->keyval
== GDK_KP_Page_Up
) ||
2208 (event
->keyval
== GDK_Page_Down
) ||
2209 (event
->keyval
== GDK_KP_Page_Down
) ||
2210 (event
->keyval
== GDK_Home
) ||
2211 (event
->keyval
== GDK_End
) ||
2212 (event
->keyval
== GDK_Tab
) ||
2213 (event
->keyval
== GDK_KP_Tab
) ||
2214 (event
->keyval
== GDK_ISO_Left_Tab
))
2216 if (event
->type
== GDK_KEY_PRESS
)
2217 return conv_keypress_common(gtkconv
, event
);
2221 if (event
->type
== GDK_KEY_RELEASE
)
2222 gtk_widget_grab_focus(gtkconv
->entry
);
2224 gtk_widget_event(gtkconv
->entry
, (GdkEvent
*)event
);
2230 regenerate_options_items(PidginWindow
*win
);
2233 pidgin_conv_switch_active_conversation(PurpleConversation
*conv
)
2235 PidginConversation
*gtkconv
;
2236 PurpleConversation
*old_conv
;
2238 const char *protocol_name
;
2240 g_return_if_fail(conv
!= NULL
);
2242 gtkconv
= PIDGIN_CONVERSATION(conv
);
2243 old_conv
= gtkconv
->active_conv
;
2245 purple_debug_info("gtkconv", "setting active conversation on toolbar %p\n",
2247 gtk_imhtmltoolbar_switch_active_conversation(GTK_IMHTMLTOOLBAR(gtkconv
->toolbar
),
2250 if (old_conv
== conv
)
2253 purple_conversation_close_logs(old_conv
);
2254 gtkconv
->active_conv
= conv
;
2256 purple_conversation_set_logging(conv
,
2257 gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(gtkconv
->win
->menu
.logging
)));
2259 entry
= GTK_IMHTML(gtkconv
->entry
);
2260 protocol_name
= purple_account_get_protocol_name(conv
->account
);
2261 gtk_imhtml_set_protocol_name(entry
, protocol_name
);
2262 gtk_imhtml_set_protocol_name(GTK_IMHTML(gtkconv
->imhtml
), protocol_name
);
2264 if (!(conv
->features
& PURPLE_CONNECTION_HTML
))
2265 gtk_imhtml_clear_formatting(GTK_IMHTML(gtkconv
->entry
));
2266 else if (conv
->features
& PURPLE_CONNECTION_FORMATTING_WBFO
&&
2267 !(old_conv
->features
& PURPLE_CONNECTION_FORMATTING_WBFO
))
2269 /* The old conversation allowed formatting on parts of the
2270 * buffer, but the new one only allows it on the whole
2271 * buffer. This code saves the formatting from the current
2272 * position of the cursor, clears the formatting, then
2273 * applies the saved formatting to the entire buffer. */
2278 char *fontface
= gtk_imhtml_get_current_fontface(entry
);
2279 char *forecolor
= gtk_imhtml_get_current_forecolor(entry
);
2280 char *backcolor
= gtk_imhtml_get_current_backcolor(entry
);
2281 char *background
= gtk_imhtml_get_current_background(entry
);
2282 gint fontsize
= gtk_imhtml_get_current_fontsize(entry
);
2285 gboolean underline2
;
2287 gtk_imhtml_get_current_format(entry
, &bold
, &italic
, &underline
);
2289 /* Clear existing formatting */
2290 gtk_imhtml_clear_formatting(entry
);
2292 /* Apply saved formatting to the whole buffer. */
2294 gtk_imhtml_get_current_format(entry
, &bold2
, &italic2
, &underline2
);
2297 gtk_imhtml_toggle_bold(entry
);
2299 if (italic
!= italic2
)
2300 gtk_imhtml_toggle_italic(entry
);
2302 if (underline
!= underline2
)
2303 gtk_imhtml_toggle_underline(entry
);
2305 gtk_imhtml_toggle_fontface(entry
, fontface
);
2307 if (!(conv
->features
& PURPLE_CONNECTION_NO_FONTSIZE
))
2308 gtk_imhtml_font_set_size(entry
, fontsize
);
2310 gtk_imhtml_toggle_forecolor(entry
, forecolor
);
2312 if (!(conv
->features
& PURPLE_CONNECTION_NO_BGCOLOR
))
2314 gtk_imhtml_toggle_backcolor(entry
, backcolor
);
2315 gtk_imhtml_toggle_background(entry
, background
);
2325 /* This is done in default_formatize, which is called from clear_formatting_cb,
2326 * which is (obviously) a clear_formatting signal handler. However, if we're
2327 * here, we didn't call gtk_imhtml_clear_formatting() (because we want to
2328 * preserve the formatting exactly as it is), so we have to do this now. */
2329 gtk_imhtml_set_whole_buffer_formatting_only(entry
,
2330 (conv
->features
& PURPLE_CONNECTION_FORMATTING_WBFO
));
2333 purple_signal_emit(pidgin_conversations_get_handle(), "conversation-switched", conv
);
2335 gray_stuff_out(gtkconv
);
2336 update_typing_icon(gtkconv
);
2337 g_object_set_data(G_OBJECT(entry
), "transient_buddy", NULL
);
2338 regenerate_options_items(gtkconv
->win
);
2340 gtk_window_set_title(GTK_WINDOW(gtkconv
->win
->window
),
2341 gtk_label_get_text(GTK_LABEL(gtkconv
->tab_label
)));
2345 menu_conv_sel_send_cb(GObject
*m
, gpointer data
)
2347 PurpleAccount
*account
= g_object_get_data(m
, "purple_account");
2348 gchar
*name
= g_object_get_data(m
, "purple_buddy_name");
2349 PurpleConversation
*conv
;
2351 if (gtk_check_menu_item_get_active((GtkCheckMenuItem
*) m
) == FALSE
)
2354 conv
= purple_conversation_new(PURPLE_CONV_TYPE_IM
, account
, name
);
2355 pidgin_conv_switch_active_conversation(conv
);
2359 insert_text_cb(GtkTextBuffer
*textbuffer
, GtkTextIter
*position
,
2360 gchar
*new_text
, gint new_text_length
, gpointer user_data
)
2362 PidginConversation
*gtkconv
= (PidginConversation
*)user_data
;
2364 g_return_if_fail(gtkconv
!= NULL
);
2366 if (!purple_prefs_get_bool("/purple/conversations/im/send_typing"))
2369 got_typing_keypress(gtkconv
, (gtk_text_iter_is_start(position
) &&
2370 gtk_text_iter_is_end(position
)));
2374 delete_text_cb(GtkTextBuffer
*textbuffer
, GtkTextIter
*start_pos
,
2375 GtkTextIter
*end_pos
, gpointer user_data
)
2377 PidginConversation
*gtkconv
= (PidginConversation
*)user_data
;
2378 PurpleConversation
*conv
;
2381 g_return_if_fail(gtkconv
!= NULL
);
2383 conv
= gtkconv
->active_conv
;
2385 if (!purple_prefs_get_bool("/purple/conversations/im/send_typing"))
2388 im
= PURPLE_CONV_IM(conv
);
2390 if (gtk_text_iter_is_start(start_pos
) && gtk_text_iter_is_end(end_pos
)) {
2392 /* We deleted all the text, so turn off typing. */
2393 purple_conv_im_stop_send_typed_timeout(im
);
2395 serv_send_typing(purple_conversation_get_gc(conv
),
2396 purple_conversation_get_name(conv
),
2400 /* We're deleting, but not all of it, so it counts as typing. */
2401 got_typing_keypress(gtkconv
, FALSE
);
2405 /**************************************************************************
2406 * A bunch of buddy icon functions
2407 **************************************************************************/
2409 static GList
*get_prpl_icon_list(PurpleAccount
*account
)
2412 PurplePlugin
*prpl
= purple_find_prpl(purple_account_get_protocol_id(account
));
2413 PurplePluginProtocolInfo
*prpl_info
= PURPLE_PLUGIN_PROTOCOL_INFO(prpl
);
2414 const char *prplname
= prpl_info
->list_icon(account
, NULL
);
2415 l
= g_hash_table_lookup(prpl_lists
, prplname
);
2419 l
= g_list_prepend(l
, pidgin_create_prpl_icon(account
, PIDGIN_PRPL_ICON_LARGE
));
2420 l
= g_list_prepend(l
, pidgin_create_prpl_icon(account
, PIDGIN_PRPL_ICON_MEDIUM
));
2421 l
= g_list_prepend(l
, pidgin_create_prpl_icon(account
, PIDGIN_PRPL_ICON_SMALL
));
2423 g_hash_table_insert(prpl_lists
, g_strdup(prplname
), l
);
2428 pidgin_conv_get_tab_icons(PurpleConversation
*conv
)
2430 PurpleAccount
*account
= NULL
;
2431 const char *name
= NULL
;
2433 g_return_val_if_fail(conv
!= NULL
, NULL
);
2435 account
= purple_conversation_get_account(conv
);
2436 name
= purple_conversation_get_name(conv
);
2438 g_return_val_if_fail(account
!= NULL
, NULL
);
2439 g_return_val_if_fail(name
!= NULL
, NULL
);
2441 /* Use the buddy icon, if possible */
2442 if (purple_conversation_get_type(conv
) == PURPLE_CONV_TYPE_IM
) {
2443 PurpleBuddy
*b
= purple_find_buddy(account
, name
);
2446 p
= purple_buddy_get_presence(b
);
2447 if (purple_presence_is_status_primitive_active(p
, PURPLE_STATUS_AWAY
))
2449 if (purple_presence_is_status_primitive_active(p
, PURPLE_STATUS_UNAVAILABLE
))
2451 if (purple_presence_is_status_primitive_active(p
, PURPLE_STATUS_EXTENDED_AWAY
))
2453 if (purple_presence_is_status_primitive_active(p
, PURPLE_STATUS_OFFLINE
))
2454 return offline_list
;
2456 return available_list
;
2460 return get_prpl_icon_list(account
);
2464 pidgin_conv_get_icon_stock(PurpleConversation
*conv
)
2466 PurpleAccount
*account
= NULL
;
2467 const char *stock
= NULL
;
2469 g_return_val_if_fail(conv
!= NULL
, NULL
);
2471 account
= purple_conversation_get_account(conv
);
2472 g_return_val_if_fail(account
!= NULL
, NULL
);
2474 /* Use the buddy icon, if possible */
2475 if (purple_conversation_get_type(conv
) == PURPLE_CONV_TYPE_IM
) {
2476 const char *name
= NULL
;
2478 name
= purple_conversation_get_name(conv
);
2479 b
= purple_find_buddy(account
, name
);
2481 PurplePresence
*p
= purple_buddy_get_presence(b
);
2482 PurpleStatus
*active
= purple_presence_get_active_status(p
);
2483 PurpleStatusType
*type
= purple_status_get_type(active
);
2484 PurpleStatusPrimitive prim
= purple_status_type_get_primitive(type
);
2485 stock
= pidgin_stock_id_from_status_primitive(prim
);
2487 stock
= PIDGIN_STOCK_STATUS_PERSON
;
2490 stock
= PIDGIN_STOCK_STATUS_CHAT
;
2497 pidgin_conv_get_icon(PurpleConversation
*conv
, GtkWidget
*parent
, const char *icon_size
)
2499 PurpleAccount
*account
= NULL
;
2500 const char *name
= NULL
;
2501 const char *stock
= NULL
;
2502 GdkPixbuf
*status
= NULL
;
2503 PurpleBlistUiOps
*ops
= purple_blist_get_ui_ops();
2506 g_return_val_if_fail(conv
!= NULL
, NULL
);
2508 account
= purple_conversation_get_account(conv
);
2509 name
= purple_conversation_get_name(conv
);
2511 g_return_val_if_fail(account
!= NULL
, NULL
);
2512 g_return_val_if_fail(name
!= NULL
, NULL
);
2514 /* Use the buddy icon, if possible */
2515 if (purple_conversation_get_type(conv
) == PURPLE_CONV_TYPE_IM
) {
2516 PurpleBuddy
*b
= purple_find_buddy(account
, name
);
2518 /* I hate this hack. It fixes a bug where the pending message icon
2519 * displays in the conv tab even though it shouldn't.
2520 * A better solution would be great. */
2521 if (ops
&& ops
->update
)
2522 ops
->update(NULL
, (PurpleBlistNode
*)b
);
2526 stock
= pidgin_conv_get_icon_stock(conv
);
2527 size
= gtk_icon_size_from_name(icon_size
);
2528 status
= gtk_widget_render_icon (parent
, stock
, size
, "GtkWidget");
2533 pidgin_conv_get_tab_icon(PurpleConversation
*conv
, gboolean small_icon
)
2535 const char *icon_size
= small_icon
? PIDGIN_ICON_SIZE_TANGO_MICROSCOPIC
: PIDGIN_ICON_SIZE_TANGO_EXTRA_SMALL
;
2536 return pidgin_conv_get_icon(conv
, PIDGIN_CONVERSATION(conv
)->icon
, icon_size
);
2541 update_tab_icon(PurpleConversation
*conv
)
2543 PidginConversation
*gtkconv
;
2546 GdkPixbuf
*emblem
= NULL
;
2547 const char *status
= NULL
;
2548 const char *infopane_status
= NULL
;
2550 g_return_if_fail(conv
!= NULL
);
2552 gtkconv
= PIDGIN_CONVERSATION(conv
);
2554 if (conv
!= gtkconv
->active_conv
)
2557 status
= infopane_status
= pidgin_conv_get_icon_stock(conv
);
2559 if (purple_conversation_get_type(conv
) == PURPLE_CONV_TYPE_IM
) {
2560 PurpleBuddy
*b
= purple_find_buddy(conv
->account
, conv
->name
);
2562 emblem
= pidgin_blist_get_emblem((PurpleBlistNode
*)b
);
2565 g_return_if_fail(status
!= NULL
);
2567 g_object_set(G_OBJECT(gtkconv
->icon
), "stock", status
, NULL
);
2568 g_object_set(G_OBJECT(gtkconv
->menu_icon
), "stock", status
, NULL
);
2570 gtk_list_store_set(GTK_LIST_STORE(gtkconv
->infopane_model
),
2571 &(gtkconv
->infopane_iter
),
2572 CONV_ICON_COLUMN
, infopane_status
, -1);
2574 gtk_list_store_set(GTK_LIST_STORE(gtkconv
->infopane_model
),
2575 &(gtkconv
->infopane_iter
),
2576 CONV_EMBLEM_COLUMN
, emblem
, -1);
2578 g_object_unref(emblem
);
2580 if (purple_prefs_get_bool(PIDGIN_PREFS_ROOT
"/blist/show_protocol_icons")) {
2581 emblem
= pidgin_create_prpl_icon(gtkconv
->active_conv
->account
, PIDGIN_PRPL_ICON_SMALL
);
2586 gtk_list_store_set(GTK_LIST_STORE(gtkconv
->infopane_model
),
2587 &(gtkconv
->infopane_iter
),
2588 CONV_PROTOCOL_ICON_COLUMN
, emblem
, -1);
2590 g_object_unref(emblem
);
2592 /* XXX seanegan Why do I have to do this? */
2593 gtk_widget_queue_resize(gtkconv
->infopane
);
2594 gtk_widget_queue_draw(gtkconv
->infopane
);
2596 if (pidgin_conv_window_is_active_conversation(conv
) &&
2597 (purple_conversation_get_type(conv
) != PURPLE_CONV_TYPE_IM
||
2598 gtkconv
->u
.im
->anim
== NULL
))
2600 l
= pidgin_conv_get_tab_icons(conv
);
2602 gtk_window_set_icon_list(GTK_WINDOW(win
->window
), l
);
2607 /* This gets added as an idle handler when doing something that
2608 * redraws the icon. It sets the auto_resize gboolean to TRUE.
2609 * This way, when the size_allocate callback gets triggered, it notices
2610 * that this is an autoresize, and after the main loop iterates, it
2611 * gets set back to FALSE
2613 static gboolean
reset_auto_resize_cb(gpointer data
)
2615 PidginConversation
*gtkconv
= (PidginConversation
*)data
;
2616 gtkconv
->auto_resize
= FALSE
;
2622 redraw_icon(gpointer data
)
2624 PidginConversation
*gtkconv
= (PidginConversation
*)data
;
2625 PurpleConversation
*conv
= gtkconv
->active_conv
;
2626 PurpleAccount
*account
;
2631 int scale_width
, scale_height
;
2634 gtkconv
= PIDGIN_CONVERSATION(conv
);
2635 account
= purple_conversation_get_account(conv
);
2637 if (!(account
&& account
->gc
)) {
2638 gtkconv
->u
.im
->icon_timer
= 0;
2642 gdk_pixbuf_animation_iter_advance(gtkconv
->u
.im
->iter
, NULL
);
2643 buf
= gdk_pixbuf_animation_iter_get_pixbuf(gtkconv
->u
.im
->iter
);
2645 scale_width
= gdk_pixbuf_get_width(buf
);
2646 scale_height
= gdk_pixbuf_get_height(buf
);
2648 gtk_widget_get_size_request(gtkconv
->u
.im
->icon_container
, NULL
, &size
);
2649 size
= MIN(size
, MIN(scale_width
, scale_height
));
2650 size
= CLAMP(size
, BUDDYICON_SIZE_MIN
, BUDDYICON_SIZE_MAX
);
2652 if (scale_width
== scale_height
) {
2653 scale_width
= scale_height
= size
;
2654 } else if (scale_height
> scale_width
) {
2655 scale_width
= size
* scale_width
/ scale_height
;
2656 scale_height
= size
;
2658 scale_height
= size
* scale_height
/ scale_width
;
2662 scale
= gdk_pixbuf_scale_simple(buf
, scale_width
, scale_height
,
2663 GDK_INTERP_BILINEAR
);
2664 if (pidgin_gdk_pixbuf_is_opaque(scale
))
2665 pidgin_gdk_pixbuf_make_round(scale
);
2667 gtk_image_set_from_pixbuf(GTK_IMAGE(gtkconv
->u
.im
->icon
), scale
);
2668 g_object_unref(G_OBJECT(scale
));
2669 gtk_widget_queue_draw(gtkconv
->u
.im
->icon
);
2671 delay
= gdk_pixbuf_animation_iter_get_delay_time(gtkconv
->u
.im
->iter
);
2676 gtkconv
->u
.im
->icon_timer
= g_timeout_add(delay
, redraw_icon
, gtkconv
);
2682 start_anim(GtkObject
*obj
, PidginConversation
*gtkconv
)
2686 if (gtkconv
->u
.im
->anim
== NULL
)
2689 if (gtkconv
->u
.im
->icon_timer
!= 0)
2692 if (gdk_pixbuf_animation_is_static_image(gtkconv
->u
.im
->anim
))
2695 delay
= gdk_pixbuf_animation_iter_get_delay_time(gtkconv
->u
.im
->iter
);
2700 gtkconv
->u
.im
->icon_timer
= g_timeout_add(delay
, redraw_icon
, gtkconv
);
2704 remove_icon(GtkWidget
*widget
, PidginConversation
*gtkconv
)
2708 PurpleConversation
*conv
= gtkconv
->active_conv
;
2710 g_return_if_fail(conv
!= NULL
);
2712 gtk_widget_set_size_request(gtkconv
->u
.im
->icon_container
, -1, BUDDYICON_SIZE_MIN
);
2713 children
= gtk_container_get_children(GTK_CONTAINER(gtkconv
->u
.im
->icon_container
));
2715 /* We know there's only one child here. It'd be nice to shortcut to the
2716 event box, but we can't change the PidginConversation until 3.0 */
2717 event
= (GtkWidget
*)children
->data
;
2718 gtk_container_remove(GTK_CONTAINER(gtkconv
->u
.im
->icon_container
), event
);
2719 g_list_free(children
);
2722 if (gtkconv
->u
.im
->anim
!= NULL
)
2723 g_object_unref(G_OBJECT(gtkconv
->u
.im
->anim
));
2725 if (gtkconv
->u
.im
->icon_timer
!= 0)
2726 g_source_remove(gtkconv
->u
.im
->icon_timer
);
2728 if (gtkconv
->u
.im
->iter
!= NULL
)
2729 g_object_unref(G_OBJECT(gtkconv
->u
.im
->iter
));
2731 gtkconv
->u
.im
->icon_timer
= 0;
2732 gtkconv
->u
.im
->icon
= NULL
;
2733 gtkconv
->u
.im
->anim
= NULL
;
2734 gtkconv
->u
.im
->iter
= NULL
;
2735 gtkconv
->u
.im
->show_icon
= FALSE
;
2739 saveicon_writefile_cb(void *user_data
, const char *filename
)
2741 PidginConversation
*gtkconv
= (PidginConversation
*)user_data
;
2742 PurpleConversation
*conv
= gtkconv
->active_conv
;
2743 PurpleBuddyIcon
*icon
;
2747 icon
= purple_conv_im_get_icon(PURPLE_CONV_IM(conv
));
2748 data
= purple_buddy_icon_get_data(icon
, &len
);
2750 if ((len
<= 0) || (data
== NULL
) || !purple_util_write_data_to_file_absolute(filename
, data
, len
)) {
2751 purple_notify_error(gtkconv
, NULL
, _("Unable to save icon file to disk."), NULL
);
2756 custom_icon_sel_cb(const char *filename
, gpointer data
)
2761 PurpleContact
*contact
;
2762 PidginConversation
*gtkconv
= data
;
2763 PurpleConversation
*conv
= gtkconv
->active_conv
;
2764 PurpleAccount
*account
= purple_conversation_get_account(conv
);
2766 name
= purple_conversation_get_name(conv
);
2767 buddy
= purple_find_buddy(account
, name
);
2769 purple_debug_info("custom-icon", "You can only set custom icons for people on your buddylist.\n");
2772 contact
= purple_buddy_get_contact(buddy
);
2774 purple_buddy_icons_node_set_custom_icon_from_file((PurpleBlistNode
*)contact
, filename
);
2779 set_custom_icon_cb(GtkWidget
*widget
, PidginConversation
*gtkconv
)
2781 GtkWidget
*win
= pidgin_buddy_icon_chooser_new(GTK_WINDOW(gtkconv
->win
->window
),
2782 custom_icon_sel_cb
, gtkconv
);
2783 gtk_widget_show_all(win
);
2787 change_size_cb(GtkWidget
*widget
, PidginConversation
*gtkconv
)
2790 PurpleConversation
*conv
= gtkconv
->active_conv
;
2793 gtk_widget_get_size_request(gtkconv
->u
.im
->icon_container
, NULL
, &size
);
2795 if (size
== BUDDYICON_SIZE_MAX
) {
2796 size
= BUDDYICON_SIZE_MIN
;
2798 size
= BUDDYICON_SIZE_MAX
;
2801 gtk_widget_set_size_request(gtkconv
->u
.im
->icon_container
, -1, size
);
2802 pidgin_conv_update_buddy_icon(conv
);
2804 buddies
= purple_find_buddies(purple_conversation_get_account(conv
),
2805 purple_conversation_get_name(conv
));
2806 for (; buddies
; buddies
= g_slist_delete_link(buddies
, buddies
)) {
2807 PurpleBuddy
*buddy
= buddies
->data
;
2808 PurpleContact
*contact
= purple_buddy_get_contact(buddy
);
2809 purple_blist_node_set_int((PurpleBlistNode
*)contact
, "pidgin-infopane-iconsize", size
);
2814 remove_custom_icon_cb(GtkWidget
*widget
, PidginConversation
*gtkconv
)
2818 PurpleAccount
*account
;
2819 PurpleContact
*contact
;
2820 PurpleConversation
*conv
= gtkconv
->active_conv
;
2822 account
= purple_conversation_get_account(conv
);
2823 name
= purple_conversation_get_name(conv
);
2824 buddy
= purple_find_buddy(account
, name
);
2828 contact
= purple_buddy_get_contact(buddy
);
2830 purple_buddy_icons_node_set_custom_icon_from_file((PurpleBlistNode
*)contact
, NULL
);
2834 icon_menu_save_cb(GtkWidget
*widget
, PidginConversation
*gtkconv
)
2836 PurpleConversation
*conv
= gtkconv
->active_conv
;
2840 g_return_if_fail(conv
!= NULL
);
2842 ext
= purple_buddy_icon_get_extension(purple_conv_im_get_icon(PURPLE_CONV_IM(conv
)));
2844 buf
= g_strdup_printf("%s.%s", purple_normalize(conv
->account
, conv
->name
), ext
);
2846 purple_request_file(gtkconv
, _("Save Icon"), buf
, TRUE
,
2847 G_CALLBACK(saveicon_writefile_cb
), NULL
,
2848 conv
->account
, NULL
, conv
,
2855 stop_anim(GtkObject
*obj
, PidginConversation
*gtkconv
)
2857 if (gtkconv
->u
.im
->icon_timer
!= 0)
2858 g_source_remove(gtkconv
->u
.im
->icon_timer
);
2860 gtkconv
->u
.im
->icon_timer
= 0;
2865 toggle_icon_animate_cb(GtkWidget
*w
, PidginConversation
*gtkconv
)
2867 gtkconv
->u
.im
->animate
=
2868 gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(w
));
2870 if (gtkconv
->u
.im
->animate
)
2871 start_anim(NULL
, gtkconv
);
2873 stop_anim(NULL
, gtkconv
);
2877 icon_menu(GtkObject
*obj
, GdkEventButton
*e
, PidginConversation
*gtkconv
)
2879 static GtkWidget
*menu
= NULL
;
2880 PurpleConversation
*conv
;
2883 if (e
->button
== 1 && e
->type
== GDK_BUTTON_PRESS
) {
2884 change_size_cb(NULL
, gtkconv
);
2888 if (e
->button
!= 3 || e
->type
!= GDK_BUTTON_PRESS
) {
2893 * If a menu already exists, destroy it before creating a new one,
2894 * thus freeing-up the memory it occupied.
2897 gtk_widget_destroy(menu
);
2899 menu
= gtk_menu_new();
2901 if (gtkconv
->u
.im
->anim
&&
2902 !(gdk_pixbuf_animation_is_static_image(gtkconv
->u
.im
->anim
)))
2904 pidgin_new_check_item(menu
, _("Animate"),
2905 G_CALLBACK(toggle_icon_animate_cb
), gtkconv
,
2906 gtkconv
->u
.im
->icon_timer
);
2909 pidgin_new_item_from_stock(menu
, _("Hide Icon"), NULL
, G_CALLBACK(remove_icon
),
2910 gtkconv
, 0, 0, NULL
);
2912 pidgin_new_item_from_stock(menu
, _("Save Icon As..."), GTK_STOCK_SAVE_AS
,
2913 G_CALLBACK(icon_menu_save_cb
), gtkconv
,
2916 pidgin_new_item_from_stock(menu
, _("Set Custom Icon..."), NULL
,
2917 G_CALLBACK(set_custom_icon_cb
), gtkconv
,
2920 pidgin_new_item_from_stock(menu
, _("Change Size"), NULL
,
2921 G_CALLBACK(change_size_cb
), gtkconv
,
2924 /* Is there a custom icon for this person? */
2925 conv
= gtkconv
->active_conv
;
2926 buddy
= purple_find_buddy(purple_conversation_get_account(conv
),
2927 purple_conversation_get_name(conv
));
2930 PurpleContact
*contact
= purple_buddy_get_contact(buddy
);
2931 if (contact
&& purple_buddy_icons_node_has_custom_icon((PurpleBlistNode
*)contact
))
2933 pidgin_new_item_from_stock(menu
, _("Remove Custom Icon"), NULL
,
2934 G_CALLBACK(remove_custom_icon_cb
), gtkconv
,
2939 gtk_menu_popup(GTK_MENU(menu
), NULL
, NULL
, NULL
, NULL
, e
->button
, e
->time
);
2944 /**************************************************************************
2945 * End of the bunch of buddy icon functions
2946 **************************************************************************/
2948 pidgin_conv_present_conversation(PurpleConversation
*conv
)
2950 PidginConversation
*gtkconv
;
2951 GdkModifierType state
;
2953 pidgin_conv_attach_to_conversation(conv
);
2954 gtkconv
= PIDGIN_CONVERSATION(conv
);
2956 pidgin_conv_switch_active_conversation(conv
);
2957 /* Switch the tab only if the user initiated the event by pressing
2958 * a button or hitting a key. */
2959 if (gtk_get_current_event_state(&state
))
2960 pidgin_conv_window_switch_gtkconv(gtkconv
->win
, gtkconv
);
2961 gtk_window_present(GTK_WINDOW(gtkconv
->win
->window
));
2965 pidgin_conversations_find_unseen_list(PurpleConversationType type
,
2966 PidginUnseenState min_state
,
2967 gboolean hidden_only
,
2974 if (type
== PURPLE_CONV_TYPE_IM
) {
2975 l
= purple_get_ims();
2976 } else if (type
== PURPLE_CONV_TYPE_CHAT
) {
2977 l
= purple_get_chats();
2979 l
= purple_get_conversations();
2982 for (; l
!= NULL
&& (max_count
== 0 || c
< max_count
); l
= l
->next
) {
2983 PurpleConversation
*conv
= (PurpleConversation
*)l
->data
;
2984 PidginConversation
*gtkconv
= PIDGIN_CONVERSATION(conv
);
2986 if(gtkconv
== NULL
|| gtkconv
->active_conv
!= conv
)
2989 if (gtkconv
->unseen_state
>= min_state
2991 (hidden_only
&& gtkconv
->win
== hidden_convwin
))) {
2993 r
= g_list_prepend(r
, conv
);
3002 unseen_conv_menu_cb(GtkMenuItem
*item
, PurpleConversation
*conv
)
3004 g_return_if_fail(conv
!= NULL
);
3005 pidgin_conv_present_conversation(conv
);
3009 unseen_all_conv_menu_cb(GtkMenuItem
*item
, GList
*list
)
3011 g_return_if_fail(list
!= NULL
);
3012 /* Do not free the list from here. It will be freed from the
3013 * 'destroy' callback on the menuitem. */
3015 pidgin_conv_present_conversation(list
->data
);
3021 pidgin_conversations_fill_menu(GtkWidget
*menu
, GList
*convs
)
3026 g_return_val_if_fail(menu
!= NULL
, 0);
3027 g_return_val_if_fail(convs
!= NULL
, 0);
3029 for (l
= convs
; l
!= NULL
; l
= l
->next
) {
3030 PurpleConversation
*conv
= (PurpleConversation
*)l
->data
;
3031 PidginConversation
*gtkconv
= PIDGIN_CONVERSATION(conv
);
3033 GtkWidget
*icon
= gtk_image_new_from_stock(pidgin_conv_get_icon_stock(conv
),
3034 gtk_icon_size_from_name(PIDGIN_ICON_SIZE_TANGO_MICROSCOPIC
));
3036 gchar
*text
= g_strdup_printf("%s (%d)",
3037 gtk_label_get_text(GTK_LABEL(gtkconv
->tab_label
)),
3038 gtkconv
->unseen_count
);
3040 item
= gtk_image_menu_item_new_with_label(text
);
3041 gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(item
), icon
);
3042 g_signal_connect(G_OBJECT(item
), "activate", G_CALLBACK(unseen_conv_menu_cb
), conv
);
3043 gtk_menu_shell_append(GTK_MENU_SHELL(menu
), item
);
3049 /* There are more than one conversation. Add an option to show all conversations. */
3051 GList
*list
= g_list_copy(convs
);
3053 pidgin_separator(menu
);
3055 item
= gtk_menu_item_new_with_label(_("Show All"));
3056 g_signal_connect(G_OBJECT(item
), "activate", G_CALLBACK(unseen_all_conv_menu_cb
), list
);
3057 g_signal_connect_swapped(G_OBJECT(item
), "destroy", G_CALLBACK(g_list_free
), list
);
3058 gtk_menu_shell_append(GTK_MENU_SHELL(menu
), item
);
3065 pidgin_conv_get_window(PidginConversation
*gtkconv
)
3067 g_return_val_if_fail(gtkconv
!= NULL
, NULL
);
3068 return gtkconv
->win
;
3071 static GtkItemFactoryEntry menu_items
[] =
3073 /* Conversation menu */
3074 { N_("/_Conversation"), NULL
, NULL
, 0, "<Branch>", NULL
},
3076 { N_("/Conversation/New Instant _Message..."), "<CTL>M", menu_new_conv_cb
,
3077 0, "<StockItem>", PIDGIN_STOCK_TOOLBAR_MESSAGE_NEW
},
3078 { N_("/Conversation/Join a _Chat..."), NULL
, menu_join_chat_cb
,
3079 0, "<StockItem>", PIDGIN_STOCK_CHAT
},
3081 { "/Conversation/sep0", NULL
, NULL
, 0, "<Separator>", NULL
},
3083 { N_("/Conversation/_Find..."), NULL
, menu_find_cb
, 0,
3084 "<StockItem>", GTK_STOCK_FIND
},
3085 { N_("/Conversation/View _Log"), NULL
, menu_view_log_cb
, 0, "<Item>", NULL
},
3086 { N_("/Conversation/_Save As..."), NULL
, menu_save_as_cb
, 0,
3087 "<StockItem>", GTK_STOCK_SAVE_AS
},
3088 { N_("/Conversation/Clea_r Scrollback"), "<CTL>L", menu_clear_cb
, 0, "<StockItem>", GTK_STOCK_CLEAR
},
3090 { "/Conversation/sep1", NULL
, NULL
, 0, "<Separator>", NULL
},
3093 { N_("/Conversation/M_edia"), NULL
, NULL
, 0, "<Branch>", NULL
},
3095 { N_("/Conversation/Media/_Audio Call"), NULL
, menu_initiate_media_call_cb
, 0,
3096 "<StockItem>", PIDGIN_STOCK_TOOLBAR_AUDIO_CALL
},
3097 { N_("/Conversation/Media/_Video Call"), NULL
, menu_initiate_media_call_cb
, 1,
3098 "<StockItem>", PIDGIN_STOCK_TOOLBAR_VIDEO_CALL
},
3099 { N_("/Conversation/Media/Audio\\/Video _Call"), NULL
, menu_initiate_media_call_cb
, 2,
3100 "<StockItem>", PIDGIN_STOCK_TOOLBAR_VIDEO_CALL
},
3103 { N_("/Conversation/Se_nd File..."), NULL
, menu_send_file_cb
, 0, "<StockItem>", PIDGIN_STOCK_TOOLBAR_SEND_FILE
},
3104 { N_("/Conversation/Get _Attention"), NULL
, menu_get_attention_cb
, 0, "<StockItem>", PIDGIN_STOCK_TOOLBAR_SEND_ATTENTION
},
3105 { N_("/Conversation/Add Buddy _Pounce..."), NULL
, menu_add_pounce_cb
,
3106 0, "<Item>", NULL
},
3107 { N_("/Conversation/_Get Info"), "<CTL>O", menu_get_info_cb
, 0,
3108 "<StockItem>", PIDGIN_STOCK_TOOLBAR_USER_INFO
},
3109 { N_("/Conversation/In_vite..."), NULL
, menu_invite_cb
, 0,
3111 { N_("/Conversation/M_ore"), NULL
, NULL
, 0, "<Branch>", NULL
},
3113 { "/Conversation/sep2", NULL
, NULL
, 0, "<Separator>", NULL
},
3115 { N_("/Conversation/Al_ias..."), NULL
, menu_alias_cb
, 0,
3117 { N_("/Conversation/_Block..."), NULL
, menu_block_cb
, 0,
3118 "<StockItem>", PIDGIN_STOCK_TOOLBAR_BLOCK
},
3119 { N_("/Conversation/_Unblock..."), NULL
, menu_unblock_cb
, 0,
3120 "<StockItem>", PIDGIN_STOCK_TOOLBAR_UNBLOCK
},
3121 { N_("/Conversation/_Add..."), NULL
, menu_add_remove_cb
, 0,
3122 "<StockItem>", GTK_STOCK_ADD
},
3123 { N_("/Conversation/_Remove..."), NULL
, menu_add_remove_cb
, 0,
3124 "<StockItem>", GTK_STOCK_REMOVE
},
3126 { "/Conversation/sep3", NULL
, NULL
, 0, "<Separator>", NULL
},
3128 { N_("/Conversation/Insert Lin_k..."), NULL
, menu_insert_link_cb
, 0,
3129 "<StockItem>", PIDGIN_STOCK_TOOLBAR_INSERT_LINK
},
3130 { N_("/Conversation/Insert Imag_e..."), NULL
, menu_insert_image_cb
, 0,
3131 "<StockItem>", PIDGIN_STOCK_TOOLBAR_INSERT_IMAGE
},
3133 { "/Conversation/sep4", NULL
, NULL
, 0, "<Separator>", NULL
},
3136 { N_("/Conversation/_Close"), NULL
, menu_close_conv_cb
, 0,
3137 "<StockItem>", GTK_STOCK_CLOSE
},
3140 { N_("/_Options"), NULL
, NULL
, 0, "<Branch>", NULL
},
3141 { N_("/Options/Enable _Logging"), NULL
, menu_logging_cb
, 0, "<CheckItem>", NULL
},
3142 { N_("/Options/Enable _Sounds"), NULL
, menu_sounds_cb
, 0, "<CheckItem>", NULL
},
3143 { "/Options/sep0", NULL
, NULL
, 0, "<Separator>", NULL
},
3144 { N_("/Options/Show Formatting _Toolbars"), NULL
, menu_toolbar_cb
, 0, "<CheckItem>", NULL
},
3145 { N_("/Options/Show Ti_mestamps"), NULL
, menu_timestamps_cb
, 0, "<CheckItem>", NULL
},
3148 static const int menu_item_count
=
3149 sizeof(menu_items
) / sizeof(*menu_items
);
3152 item_factory_translate_func (const char *path
, gpointer func_data
)
3158 sound_method_pref_changed_cb(const char *name
, PurplePrefType type
,
3159 gconstpointer value
, gpointer data
)
3161 PidginWindow
*win
= data
;
3162 const char *method
= value
;
3164 if (!strcmp(method
, "none"))
3166 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(win
->menu
.sounds
),
3168 gtk_widget_set_sensitive(win
->menu
.sounds
, FALSE
);
3172 PidginConversation
*gtkconv
= pidgin_conv_window_get_active_gtkconv(win
);
3174 if (gtkconv
!= NULL
)
3175 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(win
->menu
.sounds
),
3176 gtkconv
->make_sound
);
3177 gtk_widget_set_sensitive(win
->menu
.sounds
, TRUE
);
3182 /* Returns TRUE if some items were added to the menu, FALSE otherwise */
3184 populate_menu_with_options(GtkWidget
*menu
, PidginConversation
*gtkconv
, gboolean all
)
3187 PurpleConversation
*conv
;
3188 PurpleBlistNode
*node
= NULL
;
3189 PurpleChat
*chat
= NULL
;
3190 PurpleBuddy
*buddy
= NULL
;
3193 conv
= gtkconv
->active_conv
;
3195 if (purple_conversation_get_type(conv
) == PURPLE_CONV_TYPE_CHAT
) {
3196 chat
= purple_blist_find_chat(conv
->account
, conv
->name
);
3198 if ((chat
== NULL
) && (gtkconv
->imhtml
!= NULL
)) {
3199 chat
= g_object_get_data(G_OBJECT(gtkconv
->imhtml
), "transient_chat");
3202 if ((chat
== NULL
) && (gtkconv
->imhtml
!= NULL
)) {
3203 GHashTable
*components
;
3204 PurpleAccount
*account
= purple_conversation_get_account(conv
);
3205 PurplePlugin
*prpl
= purple_find_prpl(purple_account_get_protocol_id(account
));
3206 PurplePluginProtocolInfo
*prpl_info
= PURPLE_PLUGIN_PROTOCOL_INFO(prpl
);
3207 if (purple_account_get_connection(account
) != NULL
&&
3208 PURPLE_PROTOCOL_PLUGIN_HAS_FUNC(prpl_info
, chat_info_defaults
)) {
3209 components
= prpl_info
->chat_info_defaults(purple_account_get_connection(account
),
3210 purple_conversation_get_name(conv
));
3212 components
= g_hash_table_new_full(g_str_hash
, g_str_equal
,
3214 g_hash_table_replace(components
, g_strdup("channel"),
3215 g_strdup(purple_conversation_get_name(conv
)));
3217 chat
= purple_chat_new(conv
->account
, NULL
, components
);
3218 purple_blist_node_set_flags((PurpleBlistNode
*)chat
,
3219 PURPLE_BLIST_NODE_FLAG_NO_SAVE
);
3220 g_object_set_data_full(G_OBJECT(gtkconv
->imhtml
), "transient_chat",
3221 chat
, (GDestroyNotify
)purple_blist_remove_chat
);
3224 if (!purple_account_is_connected(conv
->account
))
3227 buddy
= purple_find_buddy(conv
->account
, conv
->name
);
3229 /* gotta remain bug-compatible :( libpurple < 2.0.2 didn't handle
3230 * removing "isolated" buddy nodes well */
3231 if (purple_version_check(2, 0, 2) == NULL
) {
3232 if ((buddy
== NULL
) && (gtkconv
->imhtml
!= NULL
)) {
3233 buddy
= g_object_get_data(G_OBJECT(gtkconv
->imhtml
), "transient_buddy");
3236 if ((buddy
== NULL
) && (gtkconv
->imhtml
!= NULL
)) {
3237 buddy
= purple_buddy_new(conv
->account
, conv
->name
, NULL
);
3238 purple_blist_node_set_flags((PurpleBlistNode
*)buddy
,
3239 PURPLE_BLIST_NODE_FLAG_NO_SAVE
);
3240 g_object_set_data_full(G_OBJECT(gtkconv
->imhtml
), "transient_buddy",
3241 buddy
, (GDestroyNotify
)purple_buddy_destroy
);
3247 node
= (PurpleBlistNode
*)chat
;
3249 node
= (PurpleBlistNode
*)buddy
;
3251 /* Now add the stuff */
3254 pidgin_blist_make_buddy_menu(menu
, buddy
, TRUE
);
3259 if (purple_account_is_connected(conv
->account
))
3260 pidgin_append_blist_node_proto_menu(menu
, conv
->account
->gc
, node
);
3261 pidgin_append_blist_node_extended_menu(menu
, node
);
3264 if ((list
= gtk_container_get_children(GTK_CONTAINER(menu
))) == NULL
) {
3274 regenerate_media_items(PidginWindow
*win
)
3277 PurpleAccount
*account
;
3278 PurpleConversation
*conv
;
3280 conv
= pidgin_conv_window_get_active_conversation(win
);
3283 purple_debug_error("gtkconv", "couldn't get active conversation"
3284 " when regenerating media items\n");
3288 account
= purple_conversation_get_account(conv
);
3290 if (account
== NULL
) {
3291 purple_debug_error("gtkconv", "couldn't get account when"
3292 " regenerating media items\n");
3297 * Check if account support voice and/or calls, and
3298 * if the current buddy supports it.
3300 if (account
!= NULL
&& purple_conversation_get_type(conv
)
3301 == PURPLE_CONV_TYPE_IM
) {
3302 PurpleMediaCaps caps
=
3303 purple_prpl_get_media_caps(account
,
3304 purple_conversation_get_name(conv
));
3306 gtk_widget_set_sensitive(win
->audio_call
,
3307 caps
& PURPLE_MEDIA_CAPS_AUDIO
3309 gtk_widget_set_sensitive(win
->video_call
,
3310 caps
& PURPLE_MEDIA_CAPS_VIDEO
3312 gtk_widget_set_sensitive(win
->audio_video_call
,
3313 caps
& PURPLE_MEDIA_CAPS_AUDIO_VIDEO
3315 } else if (purple_conversation_get_type(conv
)
3316 == PURPLE_CONV_TYPE_CHAT
) {
3317 /* for now, don't care about chats... */
3318 gtk_widget_set_sensitive(win
->audio_call
, FALSE
);
3319 gtk_widget_set_sensitive(win
->video_call
, FALSE
);
3320 gtk_widget_set_sensitive(win
->audio_video_call
, FALSE
);
3322 gtk_widget_set_sensitive(win
->audio_call
, FALSE
);
3323 gtk_widget_set_sensitive(win
->video_call
, FALSE
);
3324 gtk_widget_set_sensitive(win
->audio_video_call
, FALSE
);
3330 regenerate_options_items(PidginWindow
*win
)
3333 PidginConversation
*gtkconv
;
3336 gtkconv
= pidgin_conv_window_get_active_gtkconv(win
);
3337 menu
= gtk_item_factory_get_widget(win
->menu
.item_factory
, N_("/Conversation/More"));
3339 /* Remove the previous entries */
3340 for (list
= gtk_container_get_children(GTK_CONTAINER(menu
)); list
; )
3342 GtkWidget
*w
= list
->data
;
3343 list
= g_list_delete_link(list
, list
);
3344 gtk_widget_destroy(w
);
3347 if (!populate_menu_with_options(menu
, gtkconv
, FALSE
))
3349 GtkWidget
*item
= gtk_menu_item_new_with_label(_("No actions available"));
3350 gtk_menu_shell_append(GTK_MENU_SHELL(menu
), item
);
3351 gtk_widget_set_sensitive(item
, FALSE
);
3354 gtk_widget_show_all(menu
);
3358 remove_from_list(GtkWidget
*widget
, PidginWindow
*win
)
3360 GList
*list
= g_object_get_data(G_OBJECT(win
->window
), "plugin-actions");
3361 list
= g_list_remove(list
, widget
);
3362 g_object_set_data(G_OBJECT(win
->window
), "plugin-actions", list
);
3366 regenerate_plugins_items(PidginWindow
*win
)
3368 GList
*action_items
;
3371 PidginConversation
*gtkconv
;
3372 PurpleConversation
*conv
;
3375 if (win
->window
== NULL
|| win
== hidden_convwin
)
3378 gtkconv
= pidgin_conv_window_get_active_gtkconv(win
);
3379 if (gtkconv
== NULL
)
3382 conv
= gtkconv
->active_conv
;
3383 action_items
= g_object_get_data(G_OBJECT(win
->window
), "plugin-actions");
3385 /* Remove the old menuitems */
3386 while (action_items
) {
3387 g_signal_handlers_disconnect_by_func(G_OBJECT(action_items
->data
),
3388 G_CALLBACK(remove_from_list
), win
);
3389 gtk_widget_destroy(action_items
->data
);
3390 action_items
= g_list_delete_link(action_items
, action_items
);
3393 menu
= gtk_item_factory_get_widget(win
->menu
.item_factory
, N_("/Options"));
3395 list
= purple_conversation_get_extended_menu(conv
);
3397 action_items
= g_list_prepend(NULL
, (item
= pidgin_separator(menu
)));
3398 g_signal_connect(G_OBJECT(item
), "destroy", G_CALLBACK(remove_from_list
), win
);
3401 for(; list
; list
= g_list_delete_link(list
, list
)) {
3402 PurpleMenuAction
*act
= (PurpleMenuAction
*) list
->data
;
3403 item
= pidgin_append_menu_action(menu
, act
, conv
);
3404 action_items
= g_list_prepend(action_items
, item
);
3405 gtk_widget_show_all(item
);
3406 g_signal_connect(G_OBJECT(item
), "destroy", G_CALLBACK(remove_from_list
), win
);
3408 g_object_set_data(G_OBJECT(win
->window
), "plugin-actions", action_items
);
3411 static void menubar_activated(GtkWidget
*item
, gpointer data
)
3413 PidginWindow
*win
= data
;
3414 regenerate_media_items(win
);
3415 regenerate_options_items(win
);
3416 regenerate_plugins_items(win
);
3418 /* The following are to make sure the 'More' submenu is not regenerated every time
3419 * the focus shifts from 'Conversations' to some other menu and back. */
3420 g_signal_handlers_block_by_func(G_OBJECT(item
), G_CALLBACK(menubar_activated
), data
);
3421 g_signal_connect(G_OBJECT(win
->menu
.menubar
), "deactivate", G_CALLBACK(focus_out_from_menubar
), data
);
3425 focus_out_from_menubar(GtkWidget
*wid
, PidginWindow
*win
)
3427 /* The menubar has been deactivated. Make sure the 'More' submenu is regenerated next time
3428 * the 'Conversation' menu pops up. */
3429 GtkWidget
*menuitem
= gtk_item_factory_get_item(win
->menu
.item_factory
, N_("/Conversation"));
3430 g_signal_handlers_unblock_by_func(G_OBJECT(menuitem
), G_CALLBACK(menubar_activated
), win
);
3431 g_signal_handlers_disconnect_by_func(G_OBJECT(win
->menu
.menubar
),
3432 G_CALLBACK(focus_out_from_menubar
), win
);
3436 setup_menubar(PidginWindow
*win
)
3438 GtkAccelGroup
*accel_group
;
3440 GtkWidget
*menuitem
;
3442 accel_group
= gtk_accel_group_new ();
3443 gtk_window_add_accel_group(GTK_WINDOW(win
->window
), accel_group
);
3444 g_object_unref(accel_group
);
3446 win
->menu
.item_factory
=
3447 gtk_item_factory_new(GTK_TYPE_MENU_BAR
, "<main>", accel_group
);
3449 gtk_item_factory_set_translate_func(win
->menu
.item_factory
,
3450 (GtkTranslateFunc
)item_factory_translate_func
,
3453 gtk_item_factory_create_items(win
->menu
.item_factory
, menu_item_count
,
3455 g_signal_connect(G_OBJECT(accel_group
), "accel-changed",
3456 G_CALLBACK(pidgin_save_accels_cb
), NULL
);
3458 /* Make sure the 'Conversation -> More' menuitems are regenerated whenever
3459 * the 'Conversation' menu pops up because the entries can change after the
3460 * conversation is created. */
3461 menuitem
= gtk_item_factory_get_item(win
->menu
.item_factory
, N_("/Conversation"));
3462 g_signal_connect(G_OBJECT(menuitem
), "activate", G_CALLBACK(menubar_activated
), win
);
3465 gtk_item_factory_get_widget(win
->menu
.item_factory
, "<main>");
3467 win
->menu
.view_log
=
3468 gtk_item_factory_get_widget(win
->menu
.item_factory
,
3469 N_("/Conversation/View Log"));
3473 gtk_item_factory_get_widget(win
->menu
.item_factory
,
3474 N_("/Conversation/Media/Audio Call"));
3476 gtk_item_factory_get_widget(win
->menu
.item_factory
,
3477 N_("/Conversation/Media/Video Call"));
3478 win
->audio_video_call
=
3479 gtk_item_factory_get_widget(win
->menu
.item_factory
,
3480 N_("/Conversation/Media/Audio\\/Video Call"));
3485 win
->menu
.send_file
=
3486 gtk_item_factory_get_widget(win
->menu
.item_factory
,
3487 N_("/Conversation/Send File..."));
3489 g_object_set_data(G_OBJECT(win
->window
), "get_attention",
3490 gtk_item_factory_get_widget(win
->menu
.item_factory
,
3491 N_("/Conversation/Get Attention")));
3492 win
->menu
.add_pounce
=
3493 gtk_item_factory_get_widget(win
->menu
.item_factory
,
3494 N_("/Conversation/Add Buddy Pounce..."));
3498 win
->menu
.get_info
=
3499 gtk_item_factory_get_widget(win
->menu
.item_factory
,
3500 N_("/Conversation/Get Info"));
3503 gtk_item_factory_get_widget(win
->menu
.item_factory
,
3504 N_("/Conversation/Invite..."));
3509 gtk_item_factory_get_widget(win
->menu
.item_factory
,
3510 N_("/Conversation/Alias..."));
3513 gtk_item_factory_get_widget(win
->menu
.item_factory
,
3514 N_("/Conversation/Block..."));
3517 gtk_item_factory_get_widget(win
->menu
.item_factory
,
3518 N_("/Conversation/Unblock..."));
3521 gtk_item_factory_get_widget(win
->menu
.item_factory
,
3522 N_("/Conversation/Add..."));
3525 gtk_item_factory_get_widget(win
->menu
.item_factory
,
3526 N_("/Conversation/Remove..."));
3530 win
->menu
.insert_link
=
3531 gtk_item_factory_get_widget(win
->menu
.item_factory
,
3532 N_("/Conversation/Insert Link..."));
3534 win
->menu
.insert_image
=
3535 gtk_item_factory_get_widget(win
->menu
.item_factory
,
3536 N_("/Conversation/Insert Image..."));
3541 gtk_item_factory_get_widget(win
->menu
.item_factory
,
3542 N_("/Options/Enable Logging"));
3544 gtk_item_factory_get_widget(win
->menu
.item_factory
,
3545 N_("/Options/Enable Sounds"));
3546 method
= purple_prefs_get_string(PIDGIN_PREFS_ROOT
"/sound/method");
3547 if (method
!= NULL
&& !strcmp(method
, "none"))
3549 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(win
->menu
.sounds
),
3551 gtk_widget_set_sensitive(win
->menu
.sounds
, FALSE
);
3553 purple_prefs_connect_callback(win
, PIDGIN_PREFS_ROOT
"/sound/method",
3554 sound_method_pref_changed_cb
, win
);
3556 win
->menu
.show_formatting_toolbar
=
3557 gtk_item_factory_get_widget(win
->menu
.item_factory
,
3558 N_("/Options/Show Formatting Toolbars"));
3559 win
->menu
.show_timestamps
=
3560 gtk_item_factory_get_widget(win
->menu
.item_factory
,
3561 N_("/Options/Show Timestamps"));
3562 win
->menu
.show_icon
= NULL
;
3564 win
->menu
.tray
= pidgin_menu_tray_new();
3565 gtk_menu_shell_append(GTK_MENU_SHELL(win
->menu
.menubar
),
3567 gtk_widget_show(win
->menu
.tray
);
3569 gtk_widget_show(win
->menu
.menubar
);
3571 return win
->menu
.menubar
;
3575 /**************************************************************************
3577 **************************************************************************/
3580 got_typing_keypress(PidginConversation
*gtkconv
, gboolean first
)
3582 PurpleConversation
*conv
= gtkconv
->active_conv
;
3586 * We know we got something, so we at least have to make sure we don't
3587 * send PURPLE_TYPED any time soon.
3590 im
= PURPLE_CONV_IM(conv
);
3592 purple_conv_im_stop_send_typed_timeout(im
);
3593 purple_conv_im_start_send_typed_timeout(im
);
3595 /* Check if we need to send another PURPLE_TYPING message */
3596 if (first
|| (purple_conv_im_get_type_again(im
) != 0 &&
3597 time(NULL
) > purple_conv_im_get_type_again(im
)))
3599 unsigned int timeout
;
3600 timeout
= serv_send_typing(purple_conversation_get_gc(conv
),
3601 purple_conversation_get_name(conv
),
3603 purple_conv_im_set_type_again(im
, timeout
);
3609 typing_animation(gpointer data
) {
3610 PidginConversation
*gtkconv
= data
;
3611 PidginWindow
*gtkwin
= gtkconv
->win
;
3612 const char *stock_id
= NULL
;
3614 if(gtkconv
!= pidgin_conv_window_get_active_gtkconv(gtkwin
)) {
3618 switch (rand() % 5) {
3620 stock_id
= PIDGIN_STOCK_ANIMATION_TYPING0
;
3623 stock_id
= PIDGIN_STOCK_ANIMATION_TYPING1
;
3626 stock_id
= PIDGIN_STOCK_ANIMATION_TYPING2
;
3629 stock_id
= PIDGIN_STOCK_ANIMATION_TYPING3
;
3632 stock_id
= PIDGIN_STOCK_ANIMATION_TYPING4
;
3635 if (gtkwin
->menu
.typing_icon
== NULL
) {
3636 gtkwin
->menu
.typing_icon
= gtk_image_new_from_stock(stock_id
, GTK_ICON_SIZE_MENU
);
3637 pidgin_menu_tray_append(PIDGIN_MENU_TRAY(gtkwin
->menu
.tray
),
3638 gtkwin
->menu
.typing_icon
,
3639 _("User is typing..."));
3641 gtk_image_set_from_stock(GTK_IMAGE(gtkwin
->menu
.typing_icon
), stock_id
, GTK_ICON_SIZE_MENU
);
3643 gtk_widget_show(gtkwin
->menu
.typing_icon
);
3649 update_typing_message(PidginConversation
*gtkconv
, const char *message
)
3651 GtkTextBuffer
*buffer
;
3652 GtkTextMark
*stmark
, *enmark
;
3654 if (g_object_get_data(G_OBJECT(gtkconv
->imhtml
), "disable-typing-notification"))
3657 buffer
= gtk_text_view_get_buffer(GTK_TEXT_VIEW(gtkconv
->imhtml
));
3658 stmark
= gtk_text_buffer_get_mark(buffer
, "typing-notification-start");
3659 enmark
= gtk_text_buffer_get_mark(buffer
, "typing-notification-end");
3660 if (stmark
&& enmark
) {
3661 GtkTextIter start
, end
;
3662 gtk_text_buffer_get_iter_at_mark(buffer
, &start
, stmark
);
3663 gtk_text_buffer_get_iter_at_mark(buffer
, &end
, enmark
);
3664 gtk_text_buffer_delete_mark(buffer
, stmark
);
3665 gtk_text_buffer_delete_mark(buffer
, enmark
);
3666 gtk_text_buffer_delete(buffer
, &start
, &end
);
3667 } else if (message
&& *message
== '\n' && message
[1] == ' ' && message
[2] == '\0')
3672 message
= "\n "; /* The blank space is required to avoid a GTK+/Pango bug */
3677 gtk_text_buffer_get_end_iter(buffer
, &iter
);
3678 gtk_text_buffer_create_mark(buffer
, "typing-notification-start", &iter
, TRUE
);
3679 gtk_text_buffer_insert_with_tags_by_name(buffer
, &iter
, message
, -1, "TYPING-NOTIFICATION", NULL
);
3680 gtk_text_buffer_get_end_iter(buffer
, &iter
);
3681 gtk_text_buffer_create_mark(buffer
, "typing-notification-end", &iter
, TRUE
);
3686 update_typing_icon(PidginConversation
*gtkconv
)
3688 PurpleConvIm
*im
= NULL
;
3689 PurpleConversation
*conv
= gtkconv
->active_conv
;
3690 char *message
= NULL
;
3692 if (purple_conversation_get_type(conv
) == PURPLE_CONV_TYPE_IM
)
3693 im
= PURPLE_CONV_IM(conv
);
3698 if (purple_conv_im_get_typing_state(im
) == PURPLE_NOT_TYPING
) {
3700 update_typing_message(gtkconv
, NULL
);
3702 update_typing_message(gtkconv
, "\n ");
3707 if (purple_conv_im_get_typing_state(im
) == PURPLE_TYPING
) {
3708 message
= g_strdup_printf(_("\n%s is typing..."), purple_conversation_get_title(conv
));
3710 message
= g_strdup_printf(_("\n%s has stopped typing"), purple_conversation_get_title(conv
));
3713 update_typing_message(gtkconv
, message
);
3718 update_send_to_selection(PidginWindow
*win
)
3720 PurpleAccount
*account
;
3721 PurpleConversation
*conv
;
3726 conv
= pidgin_conv_window_get_active_conversation(win
);
3731 account
= purple_conversation_get_account(conv
);
3733 if (account
== NULL
)
3736 if (win
->menu
.send_to
== NULL
)
3739 if (!(b
= purple_find_buddy(account
, conv
->name
)))
3743 gtk_widget_show(win
->menu
.send_to
);
3745 menu
= gtk_menu_item_get_submenu(GTK_MENU_ITEM(win
->menu
.send_to
));
3747 for (child
= gtk_container_get_children(GTK_CONTAINER(menu
));
3749 child
= g_list_delete_link(child
, child
)) {
3751 GtkWidget
*item
= child
->data
;
3752 PurpleBuddy
*item_buddy
;
3753 PurpleAccount
*item_account
= g_object_get_data(G_OBJECT(item
), "purple_account");
3754 gchar
*buddy_name
= g_object_get_data(G_OBJECT(item
),
3755 "purple_buddy_name");
3756 item_buddy
= purple_find_buddy(item_account
, buddy_name
);
3758 if (b
== item_buddy
) {
3759 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(item
), TRUE
);
3769 send_to_item_enter_notify_cb(GtkWidget
*menuitem
, GdkEventCrossing
*event
, GtkWidget
*label
)
3771 gtk_widget_set_sensitive(GTK_WIDGET(label
), TRUE
);
3776 send_to_item_leave_notify_cb(GtkWidget
*menuitem
, GdkEventCrossing
*event
, GtkWidget
*label
)
3778 gtk_widget_set_sensitive(GTK_WIDGET(label
), FALSE
);
3783 create_sendto_item(GtkWidget
*menu
, GtkSizeGroup
*sg
, GSList
**group
, PurpleBuddy
*buddy
, PurpleAccount
*account
, const char *name
)
3788 GtkWidget
*menuitem
;
3792 /* Create a pixmap for the protocol icon. */
3793 pixbuf
= pidgin_create_prpl_icon(account
, PIDGIN_PRPL_ICON_SMALL
);
3795 /* Now convert it to GtkImage */
3797 image
= gtk_image_new();
3800 image
= gtk_image_new_from_pixbuf(pixbuf
);
3801 g_object_unref(G_OBJECT(pixbuf
));
3804 gtk_size_group_add_widget(sg
, image
);
3806 /* Make our menu item */
3807 text
= g_strdup_printf("%s (%s)", name
, purple_account_get_name_for_display(account
));
3808 menuitem
= gtk_radio_menu_item_new_with_label(*group
, text
);
3810 *group
= gtk_radio_menu_item_get_group(GTK_RADIO_MENU_ITEM(menuitem
));
3812 /* Do some evil, see some evil, speak some evil. */
3813 box
= gtk_hbox_new(FALSE
, 0);
3815 label
= gtk_bin_get_child(GTK_BIN(menuitem
));
3816 g_object_ref(label
);
3817 gtk_container_remove(GTK_CONTAINER(menuitem
), label
);
3819 gtk_box_pack_start(GTK_BOX(box
), image
, FALSE
, FALSE
, 0);
3820 gtk_box_pack_start(GTK_BOX(box
), label
, TRUE
, TRUE
, 4);
3822 if (buddy
!= NULL
&&
3823 !purple_presence_is_online(purple_buddy_get_presence(buddy
)))
3825 gtk_widget_set_sensitive(label
, FALSE
);
3827 /* Set the label sensitive when the menuitem is highlighted and
3828 * insensitive again when the mouse leaves it. This way, it
3829 * doesn't appear weird from the highlighting of the embossed
3830 * (insensitive style) text.*/
3831 g_signal_connect(menuitem
, "enter-notify-event",
3832 G_CALLBACK(send_to_item_enter_notify_cb
), label
);
3833 g_signal_connect(menuitem
, "leave-notify-event",
3834 G_CALLBACK(send_to_item_leave_notify_cb
), label
);
3837 g_object_unref(label
);
3839 gtk_container_add(GTK_CONTAINER(menuitem
), box
);
3841 gtk_widget_show(label
);
3842 gtk_widget_show(image
);
3843 gtk_widget_show(box
);
3845 /* Set our data and callbacks. */
3846 g_object_set_data(G_OBJECT(menuitem
), "purple_account", account
);
3847 g_object_set_data_full(G_OBJECT(menuitem
), "purple_buddy_name", g_strdup(name
), g_free
);
3849 g_signal_connect(G_OBJECT(menuitem
), "activate",
3850 G_CALLBACK(menu_conv_sel_send_cb
), NULL
);
3852 gtk_widget_show(menuitem
);
3853 gtk_menu_shell_append(GTK_MENU_SHELL(menu
), menuitem
);
3857 compare_buddy_presence(PurplePresence
*p1
, PurplePresence
*p2
)
3859 /* This is necessary because multiple PurpleBuddy's don't share the same
3860 * PurplePresence anymore.
3862 PurpleBuddy
*b1
= purple_presence_get_buddy(p1
);
3863 PurpleBuddy
*b2
= purple_presence_get_buddy(p2
);
3864 if (purple_buddy_get_account(b1
) == purple_buddy_get_account(b2
) &&
3865 strcmp(purple_buddy_get_name(b1
), purple_buddy_get_name(b2
)) == 0)
3871 generate_send_to_items(PidginWindow
*win
)
3874 GSList
*group
= NULL
;
3875 GtkSizeGroup
*sg
= gtk_size_group_new(GTK_SIZE_GROUP_HORIZONTAL
);
3876 PidginConversation
*gtkconv
;
3879 g_return_if_fail(win
!= NULL
);
3881 gtkconv
= pidgin_conv_window_get_active_gtkconv(win
);
3883 g_return_if_fail(gtkconv
!= NULL
);
3885 if (win
->menu
.send_to
!= NULL
)
3886 gtk_widget_destroy(win
->menu
.send_to
);
3888 /* Build the Send To menu */
3889 win
->menu
.send_to
= gtk_menu_item_new_with_mnemonic(_("S_end To"));
3890 gtk_widget_show(win
->menu
.send_to
);
3892 menu
= gtk_menu_new();
3893 gtk_menu_shell_insert(GTK_MENU_SHELL(win
->menu
.menubar
),
3894 win
->menu
.send_to
, 2);
3895 gtk_menu_item_set_submenu(GTK_MENU_ITEM(win
->menu
.send_to
), menu
);
3897 gtk_widget_show(menu
);
3899 if (gtkconv
->active_conv
->type
== PURPLE_CONV_TYPE_IM
) {
3900 buds
= purple_find_buddies(gtkconv
->active_conv
->account
, gtkconv
->active_conv
->name
);
3904 /* The user isn't on the buddy list. So we don't create any sendto menu. */
3908 GList
*list
= NULL
, *iter
;
3909 for (l
= buds
; l
!= NULL
; l
= l
->next
)
3911 PurpleBlistNode
*node
;
3913 node
= PURPLE_BLIST_NODE(purple_buddy_get_contact(PURPLE_BUDDY(l
->data
)));
3915 for (node
= node
->child
; node
!= NULL
; node
= node
->next
)
3917 PurpleBuddy
*buddy
= (PurpleBuddy
*)node
;
3918 PurpleAccount
*account
;
3920 if (!PURPLE_BLIST_NODE_IS_BUDDY(node
))
3923 account
= purple_buddy_get_account(buddy
);
3924 if (purple_account_is_connected(account
) || account
== gtkconv
->active_conv
->account
)
3926 /* Use the PurplePresence to get unique buddies. */
3927 PurplePresence
*presence
= purple_buddy_get_presence(buddy
);
3928 if (!g_list_find_custom(list
, presence
, (GCompareFunc
)compare_buddy_presence
))
3929 list
= g_list_prepend(list
, presence
);
3934 /* Create the sendto menu only if it has more than one item to show */
3935 if (list
&& list
->next
) {
3936 /* Loop over the list backwards so we get the items in the right order,
3937 * since we did a g_list_prepend() earlier. */
3938 for (iter
= g_list_last(list
); iter
!= NULL
; iter
= iter
->prev
) {
3939 PurplePresence
*pre
= iter
->data
;
3940 PurpleBuddy
*buddy
= purple_presence_get_buddy(pre
);
3941 create_sendto_item(menu
, sg
, &group
, buddy
,
3942 purple_buddy_get_account(buddy
), purple_buddy_get_name(buddy
));
3952 gtk_widget_show(win
->menu
.send_to
);
3953 /* TODO: This should never be insensitive. Possibly hidden or not. */
3955 gtk_widget_set_sensitive(win
->menu
.send_to
, FALSE
);
3956 update_send_to_selection(win
);
3960 get_chat_buddy_status_icon(PurpleConvChat
*chat
, const char *name
, PurpleConvChatBuddyFlags flags
)
3962 const char *image
= NULL
;
3964 if (flags
& PURPLE_CBFLAGS_FOUNDER
) {
3965 image
= PIDGIN_STOCK_STATUS_FOUNDER
;
3966 } else if (flags
& PURPLE_CBFLAGS_OP
) {
3967 image
= PIDGIN_STOCK_STATUS_OPERATOR
;
3968 } else if (flags
& PURPLE_CBFLAGS_HALFOP
) {
3969 image
= PIDGIN_STOCK_STATUS_HALFOP
;
3970 } else if (flags
& PURPLE_CBFLAGS_VOICE
) {
3971 image
= PIDGIN_STOCK_STATUS_VOICE
;
3972 } else if ((!flags
) && purple_conv_chat_is_user_ignored(chat
, name
)) {
3973 image
= PIDGIN_STOCK_STATUS_IGNORED
;
3981 deleting_chat_buddy_cb(PurpleConvChatBuddy
*cb
)
3984 GtkTreeRowReference
*ref
= cb
->ui_data
;
3985 gtk_tree_row_reference_free(ref
);
3991 add_chat_buddy_common(PurpleConversation
*conv
, PurpleConvChatBuddy
*cb
, const char *old_name
)
3993 PidginConversation
*gtkconv
;
3994 PidginChatPane
*gtkchat
;
3995 PurpleConvChat
*chat
;
3996 PurpleConnection
*gc
;
3997 PurplePluginProtocolInfo
*prpl_info
;
4000 GtkTreePath
*newpath
;
4003 gboolean is_me
= FALSE
;
4005 gchar
*tmp
, *alias_key
, *name
, *alias
;
4006 PurpleConvChatBuddyFlags flags
;
4007 GdkColor
*color
= NULL
;
4013 chat
= PURPLE_CONV_CHAT(conv
);
4014 gtkconv
= PIDGIN_CONVERSATION(conv
);
4015 gtkchat
= gtkconv
->u
.chat
;
4016 gc
= purple_conversation_get_gc(conv
);
4018 if (!gc
|| !(prpl_info
= PURPLE_PLUGIN_PROTOCOL_INFO(gc
->prpl
)))
4021 tm
= gtk_tree_view_get_model(GTK_TREE_VIEW(gtkchat
->list
));
4022 ls
= GTK_LIST_STORE(tm
);
4024 stock
= get_chat_buddy_status_icon(chat
, name
, flags
);
4026 if (!strcmp(chat
->nick
, purple_normalize(conv
->account
, old_name
!= NULL
? old_name
: name
)))
4029 is_buddy
= cb
->buddy
;
4031 tmp
= g_utf8_casefold(alias
, -1);
4032 alias_key
= g_utf8_collate_key(tmp
, -1);
4036 GtkTextTag
*tag
= gtk_text_tag_table_lookup(
4037 gtk_text_buffer_get_tag_table(GTK_IMHTML(gtkconv
->imhtml
)->text_buffer
),
4039 g_object_get(tag
, "foreground-gdk", &color
, NULL
);
4042 if ((tag
= get_buddy_tag(conv
, name
, 0, FALSE
)))
4043 g_object_set(G_OBJECT(tag
), "style", PANGO_STYLE_NORMAL
, NULL
);
4044 if ((tag
= get_buddy_tag(conv
, name
, PURPLE_MESSAGE_NICK
, FALSE
)))
4045 g_object_set(G_OBJECT(tag
), "style", PANGO_STYLE_NORMAL
, NULL
);
4046 color
= (GdkColor
*)get_nick_color(gtkconv
, name
);
4049 gtk_list_store_insert_with_values(ls
, &iter
,
4051 * The GTK docs are mute about the effects of the "row" value for performance.
4052 * X-Chat hardcodes their value to 0 (prepend) and -1 (append), so we will too.
4053 * It *might* be faster to search the gtk_list_store and set row accurately,
4054 * but no one in #gtk+ seems to know anything about it either.
4055 * Inserting in the "wrong" location has no visible ill effects. - F.P.
4058 CHAT_USERS_ICON_STOCK_COLUMN
, stock
,
4059 CHAT_USERS_ALIAS_COLUMN
, alias
,
4060 CHAT_USERS_ALIAS_KEY_COLUMN
, alias_key
,
4061 CHAT_USERS_NAME_COLUMN
, name
,
4062 CHAT_USERS_FLAGS_COLUMN
, flags
,
4063 CHAT_USERS_COLOR_COLUMN
, color
,
4064 CHAT_USERS_WEIGHT_COLUMN
, is_buddy
? PANGO_WEIGHT_BOLD
: PANGO_WEIGHT_NORMAL
,
4068 GtkTreeRowReference
*ref
= cb
->ui_data
;
4069 gtk_tree_row_reference_free(ref
);
4072 newpath
= gtk_tree_model_get_path(tm
, &iter
);
4073 cb
->ui_data
= gtk_tree_row_reference_new(tm
, newpath
);
4074 gtk_tree_path_free(newpath
);
4077 gdk_color_free(color
);
4082 * @param most_matched Used internally by this function.
4083 * @param entered The partial string that the user types before hitting the
4085 * @param entered_bytes The length of entered.
4086 * @param partial This is a return variable. This will be set to a string
4087 * containing the largest common string between all matches. This will
4088 * be inserted into the input box at the start of the word that the
4089 * user is tab completing. For example, if a chat room contains
4090 * "AlfFan" and "AlfHater" and the user types "a<TAB>" then this will
4092 * @param nick_partial Used internally by this function. Shoudl be a
4093 * temporary buffer that is entered_bytes+1 bytes long.
4094 * @param matches This is a return variable. If the given name is a potential
4095 * match for the entered string, then add a copy of the name to this
4096 * list. The caller is responsible for g_free'ing the data in this
4098 * @param name The buddy name or alias or slash command name that we're
4099 * checking for a match.
4102 tab_complete_process_item(int *most_matched
, const char *entered
, gsize entered_bytes
, char **partial
, char *nick_partial
,
4103 GList
**matches
, char *name
)
4105 memcpy(nick_partial
, name
, entered_bytes
);
4106 if (purple_utf8_strcasecmp(nick_partial
, entered
))
4109 /* if we're here, it's a possible completion */
4111 if (*most_matched
== -1) {
4113 * this will only get called once, since from now
4114 * on *most_matched is >= 0
4116 *most_matched
= strlen(name
);
4117 *partial
= g_strdup(name
);
4119 else if (*most_matched
) {
4120 char *tmp
= g_strdup(name
);
4122 while (purple_utf8_strcasecmp(tmp
, *partial
)) {
4123 (*partial
)[*most_matched
] = '\0';
4124 if (*most_matched
< strlen(tmp
))
4125 tmp
[*most_matched
] = '\0';
4133 *matches
= g_list_insert_sorted(*matches
, g_strdup(name
),
4134 (GCompareFunc
)purple_utf8_strcasecmp
);
4138 tab_complete(PurpleConversation
*conv
)
4140 PidginConversation
*gtkconv
;
4141 GtkTextIter cursor
, word_start
, start_buffer
;
4143 int most_matched
= -1;
4144 char *entered
, *partial
= NULL
;
4148 GList
*matches
= NULL
;
4149 gboolean command
= FALSE
;
4150 gsize entered_bytes
= 0;
4152 gtkconv
= PIDGIN_CONVERSATION(conv
);
4154 gtk_text_buffer_get_start_iter(gtkconv
->entry_buffer
, &start_buffer
);
4155 gtk_text_buffer_get_iter_at_mark(gtkconv
->entry_buffer
, &cursor
,
4156 gtk_text_buffer_get_insert(gtkconv
->entry_buffer
));
4158 word_start
= cursor
;
4160 /* if there's nothing there just return */
4161 if (!gtk_text_iter_compare(&cursor
, &start_buffer
))
4162 return (purple_conversation_get_type(conv
) == PURPLE_CONV_TYPE_CHAT
) ? TRUE
: FALSE
;
4164 text
= gtk_text_buffer_get_text(gtkconv
->entry_buffer
, &start_buffer
,
4167 /* if we're at the end of ": " we need to move back 2 spaces */
4168 start
= strlen(text
) - 1;
4170 if (start
>= 1 && !strncmp(&text
[start
-1], ": ", 2)) {
4171 gtk_text_iter_backward_chars(&word_start
, 2);
4174 /* find the start of the word that we're tabbing.
4175 * Using gtk_text_iter_backward_word_start won't work, because a nick can contain
4176 * characters (e.g. '.', '/' etc.) that Pango may think are word separators. */
4177 while (gtk_text_iter_backward_char(&word_start
)) {
4178 if (gtk_text_iter_get_char(&word_start
) == ' ') {
4179 /* Reached the whitespace before the start of the word. Move forward once */
4180 gtk_text_iter_forward_char(&word_start
);
4185 prefix
= pidgin_get_cmd_prefix();
4186 if (gtk_text_iter_get_offset(&word_start
) == 0 &&
4187 (strlen(text
) >= strlen(prefix
)) && !strncmp(text
, prefix
, strlen(prefix
))) {
4189 gtk_text_iter_forward_chars(&word_start
, strlen(prefix
));
4194 entered
= gtk_text_buffer_get_text(gtkconv
->entry_buffer
, &word_start
,
4196 entered_bytes
= strlen(entered
);
4198 if (!g_utf8_strlen(entered
, -1)) {
4200 return (purple_conversation_get_type(conv
) == PURPLE_CONV_TYPE_CHAT
) ? TRUE
: FALSE
;
4203 nick_partial
= g_malloc0(entered_bytes
+ 1);
4206 GList
*list
= purple_cmd_list(conv
);
4210 for (l
= list
; l
!= NULL
; l
= l
->next
) {
4211 tab_complete_process_item(&most_matched
, entered
, entered_bytes
, &partial
, nick_partial
,
4215 } else if (purple_conversation_get_type(conv
) == PURPLE_CONV_TYPE_CHAT
) {
4216 PurpleConvChat
*chat
= PURPLE_CONV_CHAT(conv
);
4217 GList
*l
= purple_conv_chat_get_users(chat
);
4218 GtkTreeModel
*model
= gtk_tree_view_get_model(GTK_TREE_VIEW(PIDGIN_CONVERSATION(conv
)->u
.chat
->list
));
4223 for (; l
!= NULL
; l
= l
->next
) {
4224 tab_complete_process_item(&most_matched
, entered
, entered_bytes
, &partial
, nick_partial
,
4225 &matches
, ((PurpleConvChatBuddy
*)l
->data
)->name
);
4230 if (gtk_tree_model_get_iter_first(GTK_TREE_MODEL(model
), &iter
))
4236 gtk_tree_model_get(model
, &iter
,
4237 CHAT_USERS_NAME_COLUMN
, &name
,
4238 CHAT_USERS_ALIAS_COLUMN
, &alias
,
4241 if (name
&& alias
&& strcmp(name
, alias
))
4242 tab_complete_process_item(&most_matched
, entered
, entered_bytes
, &partial
, nick_partial
,
4247 f
= gtk_tree_model_iter_next(model
, &iter
);
4251 g_free(nick_partial
);
4256 g_free(nick_partial
);
4258 /* we're only here if we're doing new style */
4260 /* if there weren't any matches, return */
4262 /* if matches isn't set partials won't be either */
4264 return (purple_conversation_get_type(conv
) == PURPLE_CONV_TYPE_CHAT
) ? TRUE
: FALSE
;
4267 gtk_text_buffer_delete(gtkconv
->entry_buffer
, &word_start
, &cursor
);
4269 if (!matches
->next
) {
4270 /* there was only one match. fill it in. */
4271 gtk_text_buffer_get_start_iter(gtkconv
->entry_buffer
, &start_buffer
);
4272 gtk_text_buffer_get_iter_at_mark(gtkconv
->entry_buffer
, &cursor
,
4273 gtk_text_buffer_get_insert(gtkconv
->entry_buffer
));
4275 if (!gtk_text_iter_compare(&cursor
, &start_buffer
)) {
4276 char *tmp
= g_strdup_printf("%s: ", (char *)matches
->data
);
4277 gtk_text_buffer_insert_at_cursor(gtkconv
->entry_buffer
, tmp
, -1);
4281 gtk_text_buffer_insert_at_cursor(gtkconv
->entry_buffer
,
4284 g_free(matches
->data
);
4285 matches
= g_list_remove(matches
, matches
->data
);
4289 * there were lots of matches, fill in as much as possible
4290 * and display all of them
4292 char *addthis
= g_malloc0(1);
4295 char *tmp
= addthis
;
4296 addthis
= g_strconcat(tmp
, matches
->data
, " ", NULL
);
4298 g_free(matches
->data
);
4299 matches
= g_list_remove(matches
, matches
->data
);
4302 purple_conversation_write(conv
, NULL
, addthis
, PURPLE_MESSAGE_NO_LOG
,
4304 gtk_text_buffer_insert_at_cursor(gtkconv
->entry_buffer
, partial
, -1);
4314 static void topic_callback(GtkWidget
*w
, PidginConversation
*gtkconv
)
4316 PurplePluginProtocolInfo
*prpl_info
= NULL
;
4317 PurpleConnection
*gc
;
4318 PurpleConversation
*conv
= gtkconv
->active_conv
;
4319 PidginChatPane
*gtkchat
;
4321 const char *current_topic
;
4323 gc
= purple_conversation_get_gc(conv
);
4325 if(!gc
|| !(prpl_info
= PURPLE_PLUGIN_PROTOCOL_INFO(gc
->prpl
)))
4328 if(prpl_info
->set_chat_topic
== NULL
)
4331 gtkconv
= PIDGIN_CONVERSATION(conv
);
4332 gtkchat
= gtkconv
->u
.chat
;
4333 new_topic
= g_strdup(gtk_entry_get_text(GTK_ENTRY(gtkchat
->topic_text
)));
4334 current_topic
= purple_conv_chat_get_topic(PURPLE_CONV_CHAT(conv
));
4336 if(current_topic
&& !g_utf8_collate(new_topic
, current_topic
)){
4342 gtk_entry_set_text(GTK_ENTRY(gtkchat
->topic_text
), current_topic
);
4344 gtk_entry_set_text(GTK_ENTRY(gtkchat
->topic_text
), "");
4346 prpl_info
->set_chat_topic(gc
, purple_conv_chat_get_id(PURPLE_CONV_CHAT(conv
)),
4353 sort_chat_users(GtkTreeModel
*model
, GtkTreeIter
*a
, GtkTreeIter
*b
, gpointer userdata
)
4355 PurpleConvChatBuddyFlags f1
= 0, f2
= 0;
4356 char *user1
= NULL
, *user2
= NULL
;
4357 gboolean buddy1
= FALSE
, buddy2
= FALSE
;
4360 gtk_tree_model_get(model
, a
,
4361 CHAT_USERS_ALIAS_KEY_COLUMN
, &user1
,
4362 CHAT_USERS_FLAGS_COLUMN
, &f1
,
4363 CHAT_USERS_WEIGHT_COLUMN
, &buddy1
,
4365 gtk_tree_model_get(model
, b
,
4366 CHAT_USERS_ALIAS_KEY_COLUMN
, &user2
,
4367 CHAT_USERS_FLAGS_COLUMN
, &f2
,
4368 CHAT_USERS_WEIGHT_COLUMN
, &buddy2
,
4371 /* Only sort by membership levels */
4372 f1
&= PURPLE_CBFLAGS_VOICE
| PURPLE_CBFLAGS_HALFOP
| PURPLE_CBFLAGS_OP
|
4373 PURPLE_CBFLAGS_FOUNDER
;
4374 f2
&= PURPLE_CBFLAGS_VOICE
| PURPLE_CBFLAGS_HALFOP
| PURPLE_CBFLAGS_OP
|
4375 PURPLE_CBFLAGS_FOUNDER
;
4377 if (user1
== NULL
|| user2
== NULL
) {
4378 if (!(user1
== NULL
&& user2
== NULL
))
4379 ret
= (user1
== NULL
) ? -1: 1;
4380 } else if (f1
!= f2
) {
4381 /* sort more important users first */
4382 ret
= (f1
> f2
) ? -1 : 1;
4383 } else if (buddy1
!= buddy2
) {
4384 ret
= (buddy1
> buddy2
) ? -1 : 1;
4386 ret
= strcmp(user1
, user2
);
4396 update_chat_alias(PurpleBuddy
*buddy
, PurpleConversation
*conv
, PurpleConnection
*gc
, PurplePluginProtocolInfo
*prpl_info
)
4398 PidginConversation
*gtkconv
= PIDGIN_CONVERSATION(conv
);
4399 PurpleConvChat
*chat
= PURPLE_CONV_CHAT(conv
);
4400 GtkTreeModel
*model
;
4401 char *normalized_name
;
4405 g_return_if_fail(buddy
!= NULL
);
4406 g_return_if_fail(conv
!= NULL
);
4408 /* This is safe because this callback is only used in chats, not IMs. */
4409 model
= gtk_tree_view_get_model(GTK_TREE_VIEW(gtkconv
->u
.chat
->list
));
4411 if (!gtk_tree_model_get_iter_first(GTK_TREE_MODEL(model
), &iter
))
4414 normalized_name
= g_strdup(purple_normalize(conv
->account
, buddy
->name
));
4419 gtk_tree_model_get(model
, &iter
, CHAT_USERS_NAME_COLUMN
, &name
, -1);
4421 if (!strcmp(normalized_name
, purple_normalize(conv
->account
, name
))) {
4422 const char *alias
= name
;
4424 char *alias_key
= NULL
;
4425 PurpleBuddy
*buddy2
;
4427 if (strcmp(chat
->nick
, purple_normalize(conv
->account
, name
))) {
4428 /* This user is not me, so look into updating the alias. */
4430 if ((buddy2
= purple_find_buddy(conv
->account
, name
)) != NULL
) {
4431 alias
= purple_buddy_get_contact_alias(buddy2
);
4434 tmp
= g_utf8_casefold(alias
, -1);
4435 alias_key
= g_utf8_collate_key(tmp
, -1);
4438 gtk_list_store_set(GTK_LIST_STORE(model
), &iter
,
4439 CHAT_USERS_ALIAS_COLUMN
, alias
,
4440 CHAT_USERS_ALIAS_KEY_COLUMN
, alias_key
,
4448 f
= gtk_tree_model_iter_next(model
, &iter
);
4453 g_free(normalized_name
);
4457 blist_node_aliased_cb(PurpleBlistNode
*node
, const char *old_alias
, PurpleConversation
*conv
)
4459 PurpleConnection
*gc
;
4460 PurplePluginProtocolInfo
*prpl_info
;
4462 g_return_if_fail(node
!= NULL
);
4463 g_return_if_fail(conv
!= NULL
);
4465 gc
= purple_conversation_get_gc(conv
);
4466 g_return_if_fail(gc
!= NULL
);
4467 g_return_if_fail(gc
->prpl
!= NULL
);
4468 prpl_info
= PURPLE_PLUGIN_PROTOCOL_INFO(gc
->prpl
);
4470 if (prpl_info
->options
& OPT_PROTO_UNIQUE_CHATNAME
)
4473 if (PURPLE_BLIST_NODE_IS_CONTACT(node
))
4475 PurpleBlistNode
*bnode
;
4477 for(bnode
= node
->child
; bnode
; bnode
= bnode
->next
) {
4479 if(!PURPLE_BLIST_NODE_IS_BUDDY(bnode
))
4482 update_chat_alias((PurpleBuddy
*)bnode
, conv
, gc
, prpl_info
);
4485 else if (PURPLE_BLIST_NODE_IS_BUDDY(node
))
4486 update_chat_alias((PurpleBuddy
*)node
, conv
, gc
, prpl_info
);
4487 else if (PURPLE_BLIST_NODE_IS_CHAT(node
) &&
4488 purple_conversation_get_account(conv
) == ((PurpleChat
*)node
)->account
)
4490 if (old_alias
== NULL
|| g_utf8_collate(old_alias
, purple_conversation_get_title(conv
)) == 0)
4491 pidgin_conv_update_fields(conv
, PIDGIN_CONV_SET_TITLE
);
4496 buddy_cb_common(PurpleBuddy
*buddy
, PurpleConversation
*conv
, gboolean is_buddy
)
4498 GtkTreeModel
*model
;
4499 char *normalized_name
;
4501 GtkTextTag
*texttag
;
4504 g_return_if_fail(buddy
!= NULL
);
4505 g_return_if_fail(conv
!= NULL
);
4507 /* Do nothing if the buddy does not belong to the conv's account */
4508 if (purple_buddy_get_account(buddy
) != purple_conversation_get_account(conv
))
4511 /* This is safe because this callback is only used in chats, not IMs. */
4512 model
= gtk_tree_view_get_model(GTK_TREE_VIEW(PIDGIN_CONVERSATION(conv
)->u
.chat
->list
));
4514 if (!gtk_tree_model_get_iter_first(GTK_TREE_MODEL(model
), &iter
))
4517 normalized_name
= g_strdup(purple_normalize(conv
->account
, buddy
->name
));
4522 gtk_tree_model_get(model
, &iter
, CHAT_USERS_NAME_COLUMN
, &name
, -1);
4524 if (!strcmp(normalized_name
, purple_normalize(conv
->account
, name
))) {
4525 gtk_list_store_set(GTK_LIST_STORE(model
), &iter
,
4526 CHAT_USERS_WEIGHT_COLUMN
, is_buddy
? PANGO_WEIGHT_BOLD
: PANGO_WEIGHT_NORMAL
, -1);
4531 f
= gtk_tree_model_iter_next(model
, &iter
);
4536 g_free(normalized_name
);
4538 blist_node_aliased_cb((PurpleBlistNode
*)buddy
, NULL
, conv
);
4540 texttag
= get_buddy_tag(conv
, purple_buddy_get_name(buddy
), 0, FALSE
); /* XXX: do we want the normalized name? */
4542 g_object_set(texttag
, "weight", is_buddy
? PANGO_WEIGHT_BOLD
: PANGO_WEIGHT_NORMAL
, NULL
);
4547 buddy_added_cb(PurpleBlistNode
*node
, PurpleConversation
*conv
)
4549 if (!PURPLE_BLIST_NODE_IS_BUDDY(node
))
4552 buddy_cb_common(PURPLE_BUDDY(node
), conv
, TRUE
);
4556 buddy_removed_cb(PurpleBlistNode
*node
, PurpleConversation
*conv
)
4558 if (!PURPLE_BLIST_NODE_IS_BUDDY(node
))
4561 /* If there's another buddy for the same "dude" on the list, do nothing. */
4562 if (purple_find_buddy(purple_buddy_get_account(PURPLE_BUDDY(node
)),
4563 purple_buddy_get_name(PURPLE_BUDDY(node
))) != NULL
)
4566 buddy_cb_common(PURPLE_BUDDY(node
), conv
, FALSE
);
4569 static void send_menu_cb(GtkWidget
*widget
, PidginConversation
*gtkconv
)
4571 g_signal_emit_by_name(gtkconv
->entry
, "message_send");
4575 entry_popup_menu_cb(GtkIMHtml
*imhtml
, GtkMenu
*menu
, gpointer data
)
4577 GtkWidget
*menuitem
;
4578 PidginConversation
*gtkconv
= data
;
4580 g_return_if_fail(menu
!= NULL
);
4581 g_return_if_fail(gtkconv
!= NULL
);
4583 menuitem
= pidgin_new_item_from_stock(NULL
, _("_Send"), NULL
,
4584 G_CALLBACK(send_menu_cb
), gtkconv
,
4586 if (gtk_text_buffer_get_char_count(imhtml
->text_buffer
) == 0)
4587 gtk_widget_set_sensitive(menuitem
, FALSE
);
4588 gtk_menu_shell_insert(GTK_MENU_SHELL(menu
), menuitem
, 0);
4590 menuitem
= gtk_separator_menu_item_new();
4591 gtk_widget_show(menuitem
);
4592 gtk_menu_shell_insert(GTK_MENU_SHELL(menu
), menuitem
, 1);
4595 static gboolean
resize_imhtml_cb(PidginConversation
*gtkconv
)
4597 GtkTextBuffer
*buffer
;
4600 GdkRectangle oneline
;
4602 int pad_top
, pad_inside
, pad_bottom
;
4603 int total_height
= (gtkconv
->imhtml
->allocation
.height
+ gtkconv
->entry
->allocation
.height
);
4604 int max_height
= total_height
/ 2;
4605 int min_lines
= purple_prefs_get_int(PIDGIN_PREFS_ROOT
"/conversations/minimum_entry_lines");
4607 gboolean interior_focus
;
4610 pad_top
= gtk_text_view_get_pixels_above_lines(GTK_TEXT_VIEW(gtkconv
->entry
));
4611 pad_bottom
= gtk_text_view_get_pixels_below_lines(GTK_TEXT_VIEW(gtkconv
->entry
));
4612 pad_inside
= gtk_text_view_get_pixels_inside_wrap(GTK_TEXT_VIEW(gtkconv
->entry
));
4614 buffer
= gtk_text_view_get_buffer(GTK_TEXT_VIEW(gtkconv
->entry
));
4615 gtk_text_buffer_get_start_iter(buffer
, &iter
);
4616 gtk_text_view_get_iter_location(GTK_TEXT_VIEW(gtkconv
->entry
), &iter
, &oneline
);
4618 lines
= gtk_text_buffer_get_line_count(buffer
);
4623 gtk_text_view_get_line_yrange(GTK_TEXT_VIEW(gtkconv
->entry
), &iter
, NULL
, &lineheight
);
4624 height
+= lineheight
;
4626 } while (gtk_text_iter_forward_line(&iter
));
4627 height
+= lines
* (oneline
.height
+ pad_top
+ pad_bottom
);
4629 /* Make sure there's enough room for at least min_lines. Allocate enough space to
4630 * prevent scrolling when the second line is a continuation of the first line, or
4631 * is the beginning of a new paragraph. */
4632 min_height
= min_lines
* (oneline
.height
+ MAX(pad_inside
, pad_top
+ pad_bottom
));
4633 height
= CLAMP(height
, MIN(min_height
, max_height
), max_height
);
4635 gtk_widget_style_get(gtkconv
->entry
,
4636 "interior-focus", &interior_focus
,
4637 "focus-line-width", &focus_width
,
4639 if (!interior_focus
)
4640 height
+= 2 * focus_width
;
4642 diff
= height
- gtkconv
->entry
->allocation
.height
;
4643 if (ABS(diff
) < oneline
.height
/ 2)
4646 gtk_widget_set_size_request(gtkconv
->lower_hbox
, -1,
4647 diff
+ gtkconv
->lower_hbox
->allocation
.height
);
4653 minimum_entry_lines_pref_cb(const char *name
,
4654 PurplePrefType type
,
4655 gconstpointer value
,
4658 GList
*l
= purple_get_conversations();
4659 PurpleConversation
*conv
;
4662 conv
= (PurpleConversation
*)l
->data
;
4664 if (PIDGIN_IS_PIDGIN_CONVERSATION(conv
))
4665 resize_imhtml_cb(PIDGIN_CONVERSATION(conv
));
4672 setup_chat_topic(PidginConversation
*gtkconv
, GtkWidget
*vbox
)
4674 PurpleConversation
*conv
= gtkconv
->active_conv
;
4675 PurpleConnection
*gc
= purple_conversation_get_gc(conv
);
4676 PurplePluginProtocolInfo
*prpl_info
= PURPLE_PLUGIN_PROTOCOL_INFO(gc
->prpl
);
4677 if (prpl_info
->options
& OPT_PROTO_CHAT_TOPIC
)
4679 GtkWidget
*hbox
, *label
;
4680 PidginChatPane
*gtkchat
= gtkconv
->u
.chat
;
4682 hbox
= gtk_hbox_new(FALSE
, PIDGIN_HIG_BOX_SPACE
);
4683 gtk_box_pack_start(GTK_BOX(vbox
), hbox
, FALSE
, FALSE
, 0);
4685 label
= gtk_label_new(_("Topic:"));
4686 gtk_box_pack_start(GTK_BOX(hbox
), label
, FALSE
, FALSE
, 0);
4688 gtkchat
->topic_text
= gtk_entry_new();
4689 gtk_widget_set_size_request(gtkchat
->topic_text
, -1, BUDDYICON_SIZE_MIN
);
4691 if(prpl_info
->set_chat_topic
== NULL
) {
4692 gtk_editable_set_editable(GTK_EDITABLE(gtkchat
->topic_text
), FALSE
);
4694 g_signal_connect(GTK_OBJECT(gtkchat
->topic_text
), "activate",
4695 G_CALLBACK(topic_callback
), gtkconv
);
4698 gtk_box_pack_start(GTK_BOX(hbox
), gtkchat
->topic_text
, TRUE
, TRUE
, 0);
4699 g_signal_connect(G_OBJECT(gtkchat
->topic_text
), "key_press_event",
4700 G_CALLBACK(entry_key_press_cb
), gtkconv
);
4705 pidgin_conv_userlist_create_tooltip(GtkWidget
*tipwindow
, GtkTreePath
*path
,
4706 gpointer userdata
, int *w
, int *h
)
4708 PidginConversation
*gtkconv
= userdata
;
4710 GtkTreeModel
*model
= gtk_tree_view_get_model(GTK_TREE_VIEW(gtkconv
->u
.chat
->list
));
4711 PurpleConversation
*conv
= gtkconv
->active_conv
;
4712 PurpleBlistNode
*node
;
4713 PurplePluginProtocolInfo
*prpl_info
;
4714 PurpleAccount
*account
= purple_conversation_get_account(conv
);
4717 if (account
->gc
== NULL
)
4720 if (!gtk_tree_model_get_iter(GTK_TREE_MODEL(model
), &iter
, path
))
4723 gtk_tree_model_get(GTK_TREE_MODEL(model
), &iter
, CHAT_USERS_NAME_COLUMN
, &who
, -1);
4725 prpl_info
= PURPLE_PLUGIN_PROTOCOL_INFO(account
->gc
->prpl
);
4726 node
= (PurpleBlistNode
*)(purple_find_buddy(conv
->account
, who
));
4727 if (node
&& prpl_info
&& (prpl_info
->options
& OPT_PROTO_UNIQUE_CHATNAME
))
4728 pidgin_blist_draw_tooltip(node
, gtkconv
->infopane
);
4735 setup_chat_userlist(PidginConversation
*gtkconv
, GtkWidget
*hpaned
)
4737 PidginChatPane
*gtkchat
= gtkconv
->u
.chat
;
4738 GtkWidget
*lbox
, *list
;
4740 GtkCellRenderer
*rend
;
4741 GtkTreeViewColumn
*col
;
4743 void *blist_handle
= purple_blist_get_handle();
4744 PurpleConversation
*conv
= gtkconv
->active_conv
;
4746 /* Build the right pane. */
4747 lbox
= gtk_vbox_new(FALSE
, PIDGIN_HIG_BOX_SPACE
);
4748 gtk_paned_pack2(GTK_PANED(hpaned
), lbox
, FALSE
, TRUE
);
4749 gtk_widget_show(lbox
);
4751 /* Setup the label telling how many people are in the room. */
4752 gtkchat
->count
= gtk_label_new(_("0 people in room"));
4753 gtk_label_set_ellipsize(GTK_LABEL(gtkchat
->count
), PANGO_ELLIPSIZE_END
);
4754 gtk_box_pack_start(GTK_BOX(lbox
), gtkchat
->count
, FALSE
, FALSE
, 0);
4755 gtk_widget_show(gtkchat
->count
);
4757 /* Setup the list of users. */
4759 ls
= gtk_list_store_new(CHAT_USERS_COLUMNS
, GDK_TYPE_PIXBUF
, G_TYPE_STRING
,
4760 G_TYPE_STRING
, G_TYPE_STRING
, G_TYPE_INT
,
4761 GDK_TYPE_COLOR
, G_TYPE_INT
, G_TYPE_STRING
);
4762 gtk_tree_sortable_set_sort_func(GTK_TREE_SORTABLE(ls
), CHAT_USERS_ALIAS_KEY_COLUMN
,
4763 sort_chat_users
, NULL
, NULL
);
4765 list
= gtk_tree_view_new_with_model(GTK_TREE_MODEL(ls
));
4767 /* Allow a user to specify gtkrc settings for the chat userlist only */
4768 gtk_widget_set_name(list
, "pidgin_conv_userlist");
4770 rend
= gtk_cell_renderer_pixbuf_new();
4771 g_object_set(G_OBJECT(rend
),
4772 "stock-size", gtk_icon_size_from_name(PIDGIN_ICON_SIZE_TANGO_EXTRA_SMALL
),
4774 col
= gtk_tree_view_column_new_with_attributes(NULL
, rend
,
4775 "stock-id", CHAT_USERS_ICON_STOCK_COLUMN
, NULL
);
4776 gtk_tree_view_column_set_sizing(col
, GTK_TREE_VIEW_COLUMN_AUTOSIZE
);
4777 gtk_tree_view_append_column(GTK_TREE_VIEW(list
), col
);
4778 ul_width
= purple_prefs_get_int(PIDGIN_PREFS_ROOT
"/conversations/chat/userlist_width");
4779 gtk_widget_set_size_request(lbox
, ul_width
, -1);
4781 /* Hack to prevent completely collapsed userlist coming back with a 1 pixel width.
4782 * I would have liked to use the GtkPaned "max-position", but for some reason that didn't work */
4784 gtk_paned_set_position(GTK_PANED(hpaned
), 999999);
4786 g_signal_connect(G_OBJECT(list
), "button_press_event",
4787 G_CALLBACK(right_click_chat_cb
), gtkconv
);
4788 g_signal_connect(G_OBJECT(list
), "row-activated",
4789 G_CALLBACK(activate_list_cb
), gtkconv
);
4790 g_signal_connect(G_OBJECT(list
), "popup-menu",
4791 G_CALLBACK(gtkconv_chat_popup_menu_cb
), gtkconv
);
4792 g_signal_connect(G_OBJECT(lbox
), "size-allocate", G_CALLBACK(lbox_size_allocate_cb
), gtkconv
);
4794 pidgin_tooltip_setup_for_treeview(list
, gtkconv
,
4795 pidgin_conv_userlist_create_tooltip
, NULL
);
4797 rend
= gtk_cell_renderer_text_new();
4799 "foreground-set", TRUE
,
4802 g_object_set(G_OBJECT(rend
), "editable", TRUE
, NULL
);
4804 col
= gtk_tree_view_column_new_with_attributes(NULL
, rend
,
4805 "text", CHAT_USERS_ALIAS_COLUMN
,
4806 "foreground-gdk", CHAT_USERS_COLOR_COLUMN
,
4807 "weight", CHAT_USERS_WEIGHT_COLUMN
,
4810 purple_signal_connect(blist_handle
, "blist-node-added",
4811 gtkchat
, PURPLE_CALLBACK(buddy_added_cb
), conv
);
4812 purple_signal_connect(blist_handle
, "blist-node-removed",
4813 gtkchat
, PURPLE_CALLBACK(buddy_removed_cb
), conv
);
4814 purple_signal_connect(blist_handle
, "blist-node-aliased",
4815 gtkchat
, PURPLE_CALLBACK(blist_node_aliased_cb
), conv
);
4817 gtk_tree_view_column_set_expand(col
, TRUE
);
4818 g_object_set(rend
, "ellipsize", PANGO_ELLIPSIZE_END
, NULL
);
4820 gtk_tree_view_append_column(GTK_TREE_VIEW(list
), col
);
4822 gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(list
), FALSE
);
4823 gtk_widget_show(list
);
4825 gtkchat
->list
= list
;
4827 gtk_box_pack_start(GTK_BOX(lbox
),
4828 pidgin_make_scrollable(list
, GTK_POLICY_AUTOMATIC
, GTK_POLICY_AUTOMATIC
, GTK_SHADOW_IN
, -1, -1),
4833 pidgin_conv_create_tooltip(GtkWidget
*tipwindow
, gpointer userdata
, int *w
, int *h
)
4835 PurpleBlistNode
*node
= NULL
;
4836 PurpleConversation
*conv
;
4837 PidginConversation
*gtkconv
= userdata
;
4839 conv
= gtkconv
->active_conv
;
4840 if (purple_conversation_get_type(conv
) == PURPLE_CONV_TYPE_CHAT
) {
4841 node
= (PurpleBlistNode
*)(purple_blist_find_chat(conv
->account
, conv
->name
));
4843 node
= g_object_get_data(G_OBJECT(gtkconv
->imhtml
), "transient_chat");
4845 node
= (PurpleBlistNode
*)(purple_find_buddy(conv
->account
, conv
->name
));
4847 /* Using the transient blist nodes to show the tooltip doesn't quite work yet. */
4849 node
= g_object_get_data(G_OBJECT(gtkconv
->imhtml
), "transient_buddy");
4854 pidgin_blist_draw_tooltip(node
, gtkconv
->infopane
);
4858 /* Quick Find {{{ */
4860 pidgin_conv_end_quickfind(PidginConversation
*gtkconv
)
4862 gtk_widget_modify_base(gtkconv
->quickfind
.entry
, GTK_STATE_NORMAL
, NULL
);
4864 gtk_imhtml_search_clear(GTK_IMHTML(gtkconv
->imhtml
));
4865 gtk_widget_hide_all(gtkconv
->quickfind
.container
);
4867 gtk_widget_grab_focus(gtkconv
->entry
);
4872 quickfind_process_input(GtkWidget
*entry
, GdkEventKey
*event
, PidginConversation
*gtkconv
)
4874 switch (event
->keyval
) {
4877 if (gtk_imhtml_search_find(GTK_IMHTML(gtkconv
->imhtml
), gtk_entry_get_text(GTK_ENTRY(entry
)))) {
4878 gtk_widget_modify_base(gtkconv
->quickfind
.entry
, GTK_STATE_NORMAL
, NULL
);
4884 gtk_widget_modify_base(gtkconv
->quickfind
.entry
, GTK_STATE_NORMAL
, &col
);
4888 pidgin_conv_end_quickfind(gtkconv
);
4897 pidgin_conv_setup_quickfind(PidginConversation
*gtkconv
, GtkWidget
*container
)
4899 GtkWidget
*widget
= gtk_hbox_new(FALSE
, 0);
4900 GtkWidget
*label
, *entry
, *close
;
4902 gtk_box_pack_start(GTK_BOX(container
), widget
, FALSE
, FALSE
, 0);
4904 close
= pidgin_create_small_button(gtk_label_new("×"));
4905 gtk_box_pack_start(GTK_BOX(widget
), close
, FALSE
, FALSE
, 0);
4906 gtk_tooltips_set_tip(gtkconv
->tooltips
, close
,
4907 _("Close Find bar"), NULL
);
4909 label
= gtk_label_new(_("Find:"));
4910 gtk_box_pack_start(GTK_BOX(widget
), label
, FALSE
, FALSE
, 10);
4912 entry
= gtk_entry_new();
4913 gtk_box_pack_start(GTK_BOX(widget
), entry
, TRUE
, TRUE
, 0);
4915 gtkconv
->quickfind
.entry
= entry
;
4916 gtkconv
->quickfind
.container
= widget
;
4918 /* Hook to signals and stuff */
4919 g_signal_connect(G_OBJECT(entry
), "key_press_event",
4920 G_CALLBACK(quickfind_process_input
), gtkconv
);
4921 g_signal_connect_swapped(G_OBJECT(close
), "button-press-event",
4922 G_CALLBACK(pidgin_conv_end_quickfind
), gtkconv
);
4928 setup_common_pane(PidginConversation
*gtkconv
)
4930 GtkWidget
*vbox
, *frame
, *imhtml_sw
, *event_box
;
4931 GtkCellRenderer
*rend
;
4933 PurpleConversation
*conv
= gtkconv
->active_conv
;
4935 gboolean chat
= (conv
->type
== PURPLE_CONV_TYPE_CHAT
);
4936 int buddyicon_size
= 0;
4938 /* Setup the top part of the pane */
4939 vbox
= gtk_vbox_new(FALSE
, PIDGIN_HIG_BOX_SPACE
);
4940 gtk_widget_show(vbox
);
4942 /* Setup the info pane */
4943 event_box
= gtk_event_box_new();
4944 gtk_event_box_set_visible_window(GTK_EVENT_BOX(event_box
), FALSE
);
4945 gtk_widget_show(event_box
);
4946 gtkconv
->infopane_hbox
= gtk_hbox_new(FALSE
, 0);
4947 gtk_box_pack_start(GTK_BOX(vbox
), event_box
, FALSE
, FALSE
, 0);
4948 gtk_container_add(GTK_CONTAINER(event_box
), gtkconv
->infopane_hbox
);
4949 gtk_widget_show(gtkconv
->infopane_hbox
);
4950 gtk_widget_add_events(event_box
,
4951 GDK_POINTER_MOTION_MASK
| GDK_LEAVE_NOTIFY_MASK
);
4952 g_signal_connect(G_OBJECT(event_box
), "button-press-event",
4953 G_CALLBACK(infopane_press_cb
), gtkconv
);
4955 pidgin_tooltip_setup_for_widget(event_box
, gtkconv
,
4956 pidgin_conv_create_tooltip
, NULL
);
4958 gtkconv
->infopane
= gtk_cell_view_new();
4959 gtkconv
->infopane_model
= gtk_list_store_new(CONV_NUM_COLUMNS
, G_TYPE_STRING
, G_TYPE_STRING
, GDK_TYPE_PIXBUF
, GDK_TYPE_PIXBUF
);
4960 gtk_cell_view_set_model(GTK_CELL_VIEW(gtkconv
->infopane
),
4961 GTK_TREE_MODEL(gtkconv
->infopane_model
));
4962 g_object_unref(gtkconv
->infopane_model
);
4963 gtk_list_store_append(gtkconv
->infopane_model
, &(gtkconv
->infopane_iter
));
4964 gtk_box_pack_start(GTK_BOX(gtkconv
->infopane_hbox
), gtkconv
->infopane
, TRUE
, TRUE
, 0);
4965 path
= gtk_tree_path_new_from_string("0");
4966 gtk_cell_view_set_displayed_row(GTK_CELL_VIEW(gtkconv
->infopane
), path
);
4967 gtk_tree_path_free(path
);
4970 /* This empty widget is used to ensure that the infopane is consistently
4971 sized for chat windows. The correct fix is to put an icon in the chat
4972 window as well, because that would make "Set Custom Icon" consistent
4973 for both the buddy list and the chat window, but PidginConversation
4974 is pretty much stuck until 3.0. */
4975 GtkWidget
*sizing_vbox
;
4976 sizing_vbox
= gtk_vbox_new(FALSE
, 0);
4977 gtk_widget_set_size_request(sizing_vbox
, -1, BUDDYICON_SIZE_MIN
);
4978 gtk_box_pack_start(GTK_BOX(gtkconv
->infopane_hbox
), sizing_vbox
, FALSE
, FALSE
, 0);
4979 gtk_widget_show(sizing_vbox
);
4982 gtkconv
->u
.im
->icon_container
= gtk_vbox_new(FALSE
, 0);
4984 if ((buddy
= purple_find_buddy(purple_conversation_get_account(conv
),
4985 purple_conversation_get_name(conv
))) != NULL
) {
4986 PurpleContact
*contact
= purple_buddy_get_contact(buddy
);
4988 buddyicon_size
= purple_blist_node_get_int((PurpleBlistNode
*)contact
, "pidgin-infopane-iconsize");
4991 buddyicon_size
= CLAMP(buddyicon_size
, BUDDYICON_SIZE_MIN
, BUDDYICON_SIZE_MAX
);
4992 gtk_widget_set_size_request(gtkconv
->u
.im
->icon_container
, -1, buddyicon_size
);
4994 gtk_box_pack_start(GTK_BOX(gtkconv
->infopane_hbox
),
4995 gtkconv
->u
.im
->icon_container
, FALSE
, FALSE
, 0);
4997 gtk_widget_show(gtkconv
->u
.im
->icon_container
);
5000 gtk_widget_show(gtkconv
->infopane
);
5002 rend
= gtk_cell_renderer_pixbuf_new();
5003 gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(gtkconv
->infopane
), rend
, FALSE
);
5004 gtk_cell_layout_set_attributes(GTK_CELL_LAYOUT(gtkconv
->infopane
), rend
, "stock-id", CONV_ICON_COLUMN
, NULL
);
5005 g_object_set(rend
, "xalign", 0.0, "xpad", 6, "ypad", 0,
5006 "stock-size", gtk_icon_size_from_name(PIDGIN_ICON_SIZE_TANGO_EXTRA_SMALL
),
5009 rend
= gtk_cell_renderer_text_new();
5010 gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(gtkconv
->infopane
), rend
, TRUE
);
5011 gtk_cell_layout_set_attributes(GTK_CELL_LAYOUT(gtkconv
->infopane
), rend
, "markup", CONV_TEXT_COLUMN
, NULL
);
5012 g_object_set(rend
, "ypad", 0, "yalign", 0.5, NULL
);
5014 g_object_set(rend
, "ellipsize", PANGO_ELLIPSIZE_END
, NULL
);
5016 rend
= gtk_cell_renderer_pixbuf_new();
5017 gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(gtkconv
->infopane
), rend
, FALSE
);
5018 gtk_cell_layout_set_attributes(GTK_CELL_LAYOUT(gtkconv
->infopane
), rend
, "pixbuf", CONV_PROTOCOL_ICON_COLUMN
, NULL
);
5019 g_object_set(rend
, "xalign", 0.0, "xpad", 3, "ypad", 0, NULL
);
5021 rend
= gtk_cell_renderer_pixbuf_new();
5022 gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(gtkconv
->infopane
), rend
, FALSE
);
5023 gtk_cell_layout_set_attributes(GTK_CELL_LAYOUT(gtkconv
->infopane
), rend
, "pixbuf", CONV_EMBLEM_COLUMN
, NULL
);
5024 g_object_set(rend
, "xalign", 0.0, "xpad", 6, "ypad", 0, NULL
);
5026 /* Setup the gtkimhtml widget */
5027 frame
= pidgin_create_imhtml(FALSE
, >kconv
->imhtml
, NULL
, &imhtml_sw
);
5028 gtk_widget_set_size_request(gtkconv
->imhtml
, -1, 0);
5033 setup_chat_topic(gtkconv
, vbox
);
5035 /* Add the gtkimhtml frame */
5036 hpaned
= gtk_hpaned_new();
5037 gtk_box_pack_start(GTK_BOX(vbox
), hpaned
, TRUE
, TRUE
, 0);
5038 gtk_widget_show(hpaned
);
5039 gtk_paned_pack1(GTK_PANED(hpaned
), frame
, TRUE
, TRUE
);
5041 /* Now add the userlist */
5042 setup_chat_userlist(gtkconv
, hpaned
);
5044 gtk_box_pack_start(GTK_BOX(vbox
), frame
, TRUE
, TRUE
, 0);
5046 gtk_widget_show(frame
);
5048 gtk_widget_set_name(gtkconv
->imhtml
, "pidgin_conv_imhtml");
5049 gtk_imhtml_show_comments(GTK_IMHTML(gtkconv
->imhtml
),TRUE
);
5050 g_object_set_data(G_OBJECT(gtkconv
->imhtml
), "gtkconv", gtkconv
);
5052 g_object_set(G_OBJECT(imhtml_sw
), "vscrollbar-policy", GTK_POLICY_ALWAYS
, NULL
);
5054 g_signal_connect_after(G_OBJECT(gtkconv
->imhtml
), "button_press_event",
5055 G_CALLBACK(entry_stop_rclick_cb
), NULL
);
5056 g_signal_connect(G_OBJECT(gtkconv
->imhtml
), "key_press_event",
5057 G_CALLBACK(refocus_entry_cb
), gtkconv
);
5058 g_signal_connect(G_OBJECT(gtkconv
->imhtml
), "key_release_event",
5059 G_CALLBACK(refocus_entry_cb
), gtkconv
);
5061 pidgin_conv_setup_quickfind(gtkconv
, vbox
);
5063 gtkconv
->lower_hbox
= gtk_hbox_new(FALSE
, PIDGIN_HIG_BOX_SPACE
);
5064 gtk_box_pack_start(GTK_BOX(vbox
), gtkconv
->lower_hbox
, FALSE
, FALSE
, 0);
5065 gtk_widget_show(gtkconv
->lower_hbox
);
5067 /* Setup the toolbar, entry widget and all signals */
5068 frame
= pidgin_create_imhtml(TRUE
, >kconv
->entry
, >kconv
->toolbar
, NULL
);
5069 gtk_box_pack_start(GTK_BOX(gtkconv
->lower_hbox
), frame
, TRUE
, TRUE
, 0);
5070 gtk_widget_show(frame
);
5072 gtk_widget_set_name(gtkconv
->entry
, "pidgin_conv_entry");
5073 gtk_imhtml_set_protocol_name(GTK_IMHTML(gtkconv
->entry
),
5074 purple_account_get_protocol_name(conv
->account
));
5076 g_signal_connect(G_OBJECT(gtkconv
->entry
), "populate-popup",
5077 G_CALLBACK(entry_popup_menu_cb
), gtkconv
);
5078 g_signal_connect(G_OBJECT(gtkconv
->entry
), "key_press_event",
5079 G_CALLBACK(entry_key_press_cb
), gtkconv
);
5080 g_signal_connect_after(G_OBJECT(gtkconv
->entry
), "message_send",
5081 G_CALLBACK(send_cb
), gtkconv
);
5082 g_signal_connect_after(G_OBJECT(gtkconv
->entry
), "button_press_event",
5083 G_CALLBACK(entry_stop_rclick_cb
), NULL
);
5085 gtkconv
->entry_buffer
=
5086 gtk_text_view_get_buffer(GTK_TEXT_VIEW(gtkconv
->entry
));
5087 g_object_set_data(G_OBJECT(gtkconv
->entry_buffer
), "user_data", gtkconv
);
5090 /* For sending typing notifications for IMs */
5091 g_signal_connect(G_OBJECT(gtkconv
->entry_buffer
), "insert_text",
5092 G_CALLBACK(insert_text_cb
), gtkconv
);
5093 g_signal_connect(G_OBJECT(gtkconv
->entry_buffer
), "delete_range",
5094 G_CALLBACK(delete_text_cb
), gtkconv
);
5095 gtkconv
->u
.im
->typing_timer
= 0;
5096 gtkconv
->u
.im
->animate
= purple_prefs_get_bool(PIDGIN_PREFS_ROOT
"/conversations/im/animate_buddy_icons");
5097 gtkconv
->u
.im
->show_icon
= TRUE
;
5100 g_signal_connect_swapped(G_OBJECT(gtkconv
->entry_buffer
), "changed",
5101 G_CALLBACK(resize_imhtml_cb
), gtkconv
);
5102 g_signal_connect_swapped(G_OBJECT(gtkconv
->entry
), "size-allocate",
5103 G_CALLBACK(resize_imhtml_cb
), gtkconv
);
5105 default_formatize(gtkconv
);
5106 g_signal_connect_after(G_OBJECT(gtkconv
->entry
), "format_function_clear",
5107 G_CALLBACK(clear_formatting_cb
), gtkconv
);
5112 conv_dnd_recv(GtkWidget
*widget
, GdkDragContext
*dc
, guint x
, guint y
,
5113 GtkSelectionData
*sd
, guint info
, guint t
,
5114 PidginConversation
*gtkconv
)
5116 PurpleConversation
*conv
= gtkconv
->active_conv
;
5117 PidginWindow
*win
= gtkconv
->win
;
5118 PurpleConversation
*c
;
5119 PurpleAccount
*convaccount
= purple_conversation_get_account(conv
);
5120 PurpleConnection
*gc
= purple_account_get_connection(convaccount
);
5121 PurplePluginProtocolInfo
*prpl_info
= gc
? PURPLE_PLUGIN_PROTOCOL_INFO(gc
->prpl
) : NULL
;
5123 if (sd
->target
== gdk_atom_intern("PURPLE_BLIST_NODE", FALSE
))
5125 PurpleBlistNode
*n
= NULL
;
5127 PidginConversation
*gtkconv
= NULL
;
5128 PurpleAccount
*buddyaccount
;
5129 const char *buddyname
;
5131 n
= *(PurpleBlistNode
**)sd
->data
;
5133 if (PURPLE_BLIST_NODE_IS_CONTACT(n
))
5134 b
= purple_contact_get_priority_buddy((PurpleContact
*)n
);
5135 else if (PURPLE_BLIST_NODE_IS_BUDDY(n
))
5136 b
= (PurpleBuddy
*)n
;
5140 buddyaccount
= purple_buddy_get_account(b
);
5141 buddyname
= purple_buddy_get_name(b
);
5143 * If a buddy is dragged to a chat window of the same protocol,
5144 * invite him to the chat.
5146 if (purple_conversation_get_type(conv
) == PURPLE_CONV_TYPE_CHAT
&&
5147 prpl_info
&& PURPLE_PROTOCOL_PLUGIN_HAS_FUNC(prpl_info
, chat_invite
) &&
5148 strcmp(purple_account_get_protocol_id(convaccount
),
5149 purple_account_get_protocol_id(buddyaccount
)) == 0) {
5150 purple_conv_chat_invite_user(PURPLE_CONV_CHAT(conv
), buddyname
, NULL
, TRUE
);
5153 * If we already have an open conversation with this buddy, then
5154 * just move the conv to this window. Otherwise, create a new
5155 * conv and add it to this window.
5157 c
= purple_find_conversation_with_account(PURPLE_CONV_TYPE_IM
, buddyname
, buddyaccount
);
5159 PidginWindow
*oldwin
;
5160 gtkconv
= PIDGIN_CONVERSATION(c
);
5161 oldwin
= gtkconv
->win
;
5162 if (oldwin
!= win
) {
5163 pidgin_conv_window_remove_gtkconv(oldwin
, gtkconv
);
5164 pidgin_conv_window_add_gtkconv(win
, gtkconv
);
5167 c
= purple_conversation_new(PURPLE_CONV_TYPE_IM
, buddyaccount
, buddyname
);
5168 gtkconv
= PIDGIN_CONVERSATION(c
);
5169 if (gtkconv
->win
!= win
) {
5170 pidgin_conv_window_remove_gtkconv(gtkconv
->win
, gtkconv
);
5171 pidgin_conv_window_add_gtkconv(win
, gtkconv
);
5175 /* Make this conversation the active conversation */
5176 pidgin_conv_window_switch_gtkconv(win
, gtkconv
);
5179 gtk_drag_finish(dc
, TRUE
, (dc
->action
== GDK_ACTION_MOVE
), t
);
5181 else if (sd
->target
== gdk_atom_intern("application/x-im-contact", FALSE
))
5183 char *protocol
= NULL
;
5184 char *username
= NULL
;
5185 PurpleAccount
*account
;
5186 PidginConversation
*gtkconv
;
5188 if (pidgin_parse_x_im_contact((const char *)sd
->data
, FALSE
, &account
,
5189 &protocol
, &username
, NULL
))
5191 if (account
== NULL
)
5193 purple_notify_error(win
, NULL
,
5194 _("You are not currently signed on with an account that "
5195 "can add that buddy."), NULL
);
5198 * If a buddy is dragged to a chat window of the same protocol,
5199 * invite him to the chat.
5201 if (purple_conversation_get_type(conv
) == PURPLE_CONV_TYPE_CHAT
&&
5202 prpl_info
&& PURPLE_PROTOCOL_PLUGIN_HAS_FUNC(prpl_info
, chat_invite
) &&
5203 strcmp(purple_account_get_protocol_id(convaccount
), protocol
) == 0) {
5204 purple_conv_chat_invite_user(PURPLE_CONV_CHAT(conv
), username
, NULL
, TRUE
);
5206 c
= purple_conversation_new(PURPLE_CONV_TYPE_IM
, account
, username
);
5207 gtkconv
= PIDGIN_CONVERSATION(c
);
5208 if (gtkconv
->win
!= win
) {
5209 pidgin_conv_window_remove_gtkconv(gtkconv
->win
, gtkconv
);
5210 pidgin_conv_window_add_gtkconv(win
, gtkconv
);
5219 gtk_drag_finish(dc
, TRUE
, (dc
->action
== GDK_ACTION_MOVE
), t
);
5221 else if (sd
->target
== gdk_atom_intern("text/uri-list", FALSE
)) {
5222 if (purple_conversation_get_type(conv
) == PURPLE_CONV_TYPE_IM
)
5223 pidgin_dnd_file_manage(sd
, convaccount
, purple_conversation_get_name(conv
));
5224 gtk_drag_finish(dc
, TRUE
, (dc
->action
== GDK_ACTION_MOVE
), t
);
5227 gtk_drag_finish(dc
, FALSE
, FALSE
, t
);
5231 static const GtkTargetEntry te
[] =
5233 GTK_IMHTML_DND_TARGETS
,
5234 {"PURPLE_BLIST_NODE", GTK_TARGET_SAME_APP
, GTK_IMHTML_DRAG_NUM
},
5235 {"application/x-im-contact", 0, GTK_IMHTML_DRAG_NUM
+ 1}
5238 static PidginConversation
*
5239 pidgin_conv_find_gtkconv(PurpleConversation
* conv
)
5241 PurpleBuddy
*bud
= purple_find_buddy(conv
->account
, conv
->name
);
5243 PurpleBlistNode
*cn
, *bn
;
5248 if (!(c
= purple_buddy_get_contact(bud
)))
5251 cn
= PURPLE_BLIST_NODE(c
);
5252 for (bn
= purple_blist_node_get_first_child(cn
); bn
; bn
= purple_blist_node_get_sibling_next(bn
)) {
5253 PurpleBuddy
*b
= PURPLE_BUDDY(bn
);
5254 PurpleConversation
*conv
;
5255 if ((conv
= purple_find_conversation_with_account(PURPLE_CONV_TYPE_IM
, b
->name
, b
->account
))) {
5257 return conv
->ui_data
;
5265 buddy_update_cb(PurpleBlistNode
*bnode
, gpointer null
)
5269 g_return_if_fail(bnode
);
5270 if (!PURPLE_BLIST_NODE_IS_BUDDY(bnode
))
5273 for (list
= pidgin_conv_windows_get_list(); list
; list
= list
->next
)
5275 PidginWindow
*win
= list
->data
;
5276 PurpleConversation
*conv
= pidgin_conv_window_get_active_conversation(win
);
5278 if (purple_conversation_get_type(conv
) != PURPLE_CONV_TYPE_IM
)
5281 pidgin_conv_update_fields(conv
, PIDGIN_CONV_MENU
);
5286 ignore_middle_click(GtkWidget
*widget
, GdkEventButton
*e
, gpointer null
)
5288 /* A click on the pane is propagated to the notebook containing the pane.
5289 * So if Stu accidentally aims high and middle clicks on the pane-handle,
5290 * it causes a conversation tab to close. Let's stop that from happening.
5292 if (e
->button
== 2 && e
->type
== GDK_BUTTON_PRESS
)
5297 static void set_typing_font(GtkWidget
*widget
, GtkStyle
*style
, PidginConversation
*gtkconv
)
5299 static PangoFontDescription
*font_desc
= NULL
;
5300 static GdkColor
*color
= NULL
;
5301 static gboolean enable
= TRUE
;
5303 if (font_desc
== NULL
) {
5304 char *string
= NULL
;
5305 gtk_widget_style_get(widget
,
5306 "typing-notification-font", &string
,
5307 "typing-notification-color", &color
,
5308 "typing-notification-enable", &enable
,
5310 font_desc
= pango_font_description_from_string(string
);
5312 if (color
== NULL
) {
5313 GdkColor def
= {0, 0x8888, 0x8888, 0x8888};
5314 color
= gdk_color_copy(&def
);
5318 gtk_text_buffer_create_tag(GTK_IMHTML(widget
)->text_buffer
, "TYPING-NOTIFICATION",
5319 "foreground-gdk", color
,
5320 "font-desc", font_desc
,
5324 g_object_set_data(G_OBJECT(widget
), "disable-typing-notification", GINT_TO_POINTER(TRUE
));
5325 /* or may be 'gtkconv->disable_typing = TRUE;' instead? */
5328 g_signal_handlers_disconnect_by_func(G_OBJECT(widget
), set_typing_font
, gtkconv
);
5331 /**************************************************************************
5332 * Conversation UI operations
5333 **************************************************************************/
5335 private_gtkconv_new(PurpleConversation
*conv
, gboolean hidden
)
5337 PidginConversation
*gtkconv
;
5338 PurpleConversationType conv_type
= purple_conversation_get_type(conv
);
5339 GtkWidget
*pane
= NULL
;
5340 GtkWidget
*tab_cont
;
5341 PurpleBlistNode
*convnode
;
5344 if (conv_type
== PURPLE_CONV_TYPE_IM
&& (gtkconv
= pidgin_conv_find_gtkconv(conv
))) {
5345 conv
->ui_data
= gtkconv
;
5346 if (!g_list_find(gtkconv
->convs
, conv
))
5347 gtkconv
->convs
= g_list_prepend(gtkconv
->convs
, conv
);
5348 pidgin_conv_switch_active_conversation(conv
);
5352 gtkconv
= g_new0(PidginConversation
, 1);
5353 conv
->ui_data
= gtkconv
;
5354 gtkconv
->active_conv
= conv
;
5355 gtkconv
->convs
= g_list_prepend(gtkconv
->convs
, conv
);
5356 gtkconv
->send_history
= g_list_append(NULL
, NULL
);
5358 /* Setup some initial variables. */
5359 gtkconv
->tooltips
= gtk_tooltips_new();
5360 gtkconv
->unseen_state
= PIDGIN_UNSEEN_NONE
;
5361 gtkconv
->unseen_count
= 0;
5363 if (conv_type
== PURPLE_CONV_TYPE_IM
) {
5364 gtkconv
->u
.im
= g_malloc0(sizeof(PidginImPane
));
5365 } else if (conv_type
== PURPLE_CONV_TYPE_CHAT
) {
5366 gtkconv
->u
.chat
= g_malloc0(sizeof(PidginChatPane
));
5368 pane
= setup_common_pane(gtkconv
);
5370 gtk_imhtml_set_format_functions(GTK_IMHTML(gtkconv
->imhtml
),
5371 gtk_imhtml_get_format_functions(GTK_IMHTML(gtkconv
->imhtml
)) | GTK_IMHTML_IMAGE
);
5374 if (conv_type
== PURPLE_CONV_TYPE_CHAT
)
5375 g_free(gtkconv
->u
.chat
);
5376 else if (conv_type
== PURPLE_CONV_TYPE_IM
)
5377 g_free(gtkconv
->u
.im
);
5380 conv
->ui_data
= NULL
;
5384 /* Setup drag-and-drop */
5385 gtk_drag_dest_set(pane
,
5386 GTK_DEST_DEFAULT_MOTION
|
5387 GTK_DEST_DEFAULT_DROP
,
5388 te
, sizeof(te
) / sizeof(GtkTargetEntry
),
5390 gtk_drag_dest_set(pane
,
5391 GTK_DEST_DEFAULT_MOTION
|
5392 GTK_DEST_DEFAULT_DROP
,
5393 te
, sizeof(te
) / sizeof(GtkTargetEntry
),
5395 gtk_drag_dest_set(gtkconv
->imhtml
, 0,
5396 te
, sizeof(te
) / sizeof(GtkTargetEntry
),
5399 gtk_drag_dest_set(gtkconv
->entry
, 0,
5400 te
, sizeof(te
) / sizeof(GtkTargetEntry
),
5403 g_signal_connect(G_OBJECT(pane
), "button_press_event",
5404 G_CALLBACK(ignore_middle_click
), NULL
);
5405 g_signal_connect(G_OBJECT(pane
), "drag_data_received",
5406 G_CALLBACK(conv_dnd_recv
), gtkconv
);
5407 g_signal_connect(G_OBJECT(gtkconv
->imhtml
), "drag_data_received",
5408 G_CALLBACK(conv_dnd_recv
), gtkconv
);
5409 g_signal_connect(G_OBJECT(gtkconv
->entry
), "drag_data_received",
5410 G_CALLBACK(conv_dnd_recv
), gtkconv
);
5412 g_signal_connect(gtkconv
->imhtml
, "style-set", G_CALLBACK(set_typing_font
), gtkconv
);
5414 /* Setup the container for the tab. */
5415 gtkconv
->tab_cont
= tab_cont
= gtk_vbox_new(FALSE
, PIDGIN_HIG_BOX_SPACE
);
5416 g_object_set_data(G_OBJECT(tab_cont
), "PidginConversation", gtkconv
);
5417 gtk_container_set_border_width(GTK_CONTAINER(tab_cont
), PIDGIN_HIG_BOX_SPACE
);
5418 gtk_container_add(GTK_CONTAINER(tab_cont
), pane
);
5419 gtk_widget_show(pane
);
5421 convnode
= get_conversation_blist_node(conv
);
5422 if (convnode
== NULL
|| !purple_blist_node_get_bool(convnode
, "gtk-mute-sound"))
5423 gtkconv
->make_sound
= TRUE
;
5425 if (convnode
!= NULL
&&
5426 (value
= g_hash_table_lookup(convnode
->settings
, "enable-logging")) &&
5427 purple_value_get_type(value
) == PURPLE_TYPE_BOOLEAN
)
5429 purple_conversation_set_logging(conv
, purple_value_get_boolean(value
));
5432 if (purple_prefs_get_bool(PIDGIN_PREFS_ROOT
"/conversations/show_formatting_toolbar"))
5433 gtk_widget_show(gtkconv
->toolbar
);
5435 gtk_widget_hide(gtkconv
->toolbar
);
5437 if (purple_prefs_get_bool(PIDGIN_PREFS_ROOT
"/conversations/im/show_buddy_icons"))
5438 gtk_widget_show(gtkconv
->infopane_hbox
);
5440 gtk_widget_hide(gtkconv
->infopane_hbox
);
5442 gtk_imhtml_show_comments(GTK_IMHTML(gtkconv
->imhtml
),
5443 purple_prefs_get_bool(PIDGIN_PREFS_ROOT
"/conversations/show_timestamps"));
5444 gtk_imhtml_set_protocol_name(GTK_IMHTML(gtkconv
->imhtml
),
5445 purple_account_get_protocol_name(conv
->account
));
5447 g_signal_connect_swapped(G_OBJECT(pane
), "focus",
5448 G_CALLBACK(gtk_widget_grab_focus
),
5452 pidgin_conv_window_add_gtkconv(hidden_convwin
, gtkconv
);
5454 pidgin_conv_placement_place(gtkconv
);
5456 if (nick_colors
== NULL
) {
5457 nbr_nick_colors
= NUM_NICK_COLORS
;
5458 nick_colors
= generate_nick_colors(&nbr_nick_colors
, gtk_widget_get_style(gtkconv
->imhtml
)->base
[GTK_STATE_NORMAL
]);
5461 if (conv
->features
& PURPLE_CONNECTION_ALLOW_CUSTOM_SMILEY
)
5462 pidgin_themes_smiley_themeize_custom(gtkconv
->entry
);
5466 pidgin_conv_new_hidden(PurpleConversation
*conv
)
5468 private_gtkconv_new(conv
, TRUE
);
5472 pidgin_conv_new(PurpleConversation
*conv
)
5474 private_gtkconv_new(conv
, FALSE
);
5475 if (PIDGIN_IS_PIDGIN_CONVERSATION(conv
))
5476 purple_signal_emit(pidgin_conversations_get_handle(),
5477 "conversation-displayed", PIDGIN_CONVERSATION(conv
));
5481 received_im_msg_cb(PurpleAccount
*account
, char *sender
, char *message
,
5482 PurpleConversation
*conv
, PurpleMessageFlags flags
)
5484 PurpleConversationUiOps
*ui_ops
= pidgin_conversations_get_conv_ui_ops();
5485 gboolean hide
= FALSE
;
5488 /* create hidden conv if hide_new pref is always */
5489 if (strcmp(purple_prefs_get_string(PIDGIN_PREFS_ROOT
"/conversations/im/hide_new"), "always") == 0)
5492 /* create hidden conv if hide_new pref is away and account is away */
5493 if (strcmp(purple_prefs_get_string(PIDGIN_PREFS_ROOT
"/conversations/im/hide_new"), "away") == 0 &&
5494 !purple_status_is_available(purple_account_get_active_status(account
)))
5497 if (conv
&& PIDGIN_IS_PIDGIN_CONVERSATION(conv
) && !hide
) {
5498 PidginConversation
*gtkconv
= PIDGIN_CONVERSATION(conv
);
5499 if (gtkconv
->win
== hidden_convwin
) {
5500 pidgin_conv_attach_to_conversation(gtkconv
->active_conv
);
5506 ui_ops
->create_conversation
= pidgin_conv_new_hidden
;
5507 purple_conversation_new(PURPLE_CONV_TYPE_IM
, account
, sender
);
5508 ui_ops
->create_conversation
= pidgin_conv_new
;
5511 /* Somebody wants to keep this conversation around, so don't time it out */
5513 timer
= GPOINTER_TO_INT(purple_conversation_get_data(conv
, "close-timer"));
5515 purple_timeout_remove(timer
);
5516 purple_conversation_set_data(conv
, "close-timer", GINT_TO_POINTER(0));
5522 pidgin_conv_destroy(PurpleConversation
*conv
)
5524 PidginConversation
*gtkconv
= PIDGIN_CONVERSATION(conv
);
5526 gtkconv
->convs
= g_list_remove(gtkconv
->convs
, conv
);
5527 /* Don't destroy ourselves until all our convos are gone */
5528 if (gtkconv
->convs
) {
5529 /* Make sure the destroyed conversation is not the active one */
5530 if (gtkconv
->active_conv
== conv
) {
5531 gtkconv
->active_conv
= gtkconv
->convs
->data
;
5532 purple_conversation_update(gtkconv
->active_conv
, PURPLE_CONV_UPDATE_FEATURES
);
5537 pidgin_conv_window_remove_gtkconv(gtkconv
->win
, gtkconv
);
5539 /* If the "Save Conversation" or "Save Icon" dialogs are open then close them */
5540 purple_request_close_with_handle(gtkconv
);
5541 purple_notify_close_with_handle(gtkconv
);
5543 gtk_widget_destroy(gtkconv
->tab_cont
);
5544 g_object_unref(gtkconv
->tab_cont
);
5546 if (purple_conversation_get_type(conv
) == PURPLE_CONV_TYPE_IM
) {
5547 if (gtkconv
->u
.im
->icon_timer
!= 0)
5548 g_source_remove(gtkconv
->u
.im
->icon_timer
);
5550 if (gtkconv
->u
.im
->anim
!= NULL
)
5551 g_object_unref(G_OBJECT(gtkconv
->u
.im
->anim
));
5553 if (gtkconv
->u
.im
->typing_timer
!= 0)
5554 g_source_remove(gtkconv
->u
.im
->typing_timer
);
5556 g_free(gtkconv
->u
.im
);
5557 } else if (purple_conversation_get_type(conv
) == PURPLE_CONV_TYPE_CHAT
) {
5558 purple_signals_disconnect_by_handle(gtkconv
->u
.chat
);
5559 g_free(gtkconv
->u
.chat
);
5562 gtk_object_sink(GTK_OBJECT(gtkconv
->tooltips
));
5564 gtkconv
->send_history
= g_list_first(gtkconv
->send_history
);
5565 g_list_foreach(gtkconv
->send_history
, (GFunc
)g_free
, NULL
);
5566 g_list_free(gtkconv
->send_history
);
5568 if (gtkconv
->attach
.timer
) {
5569 g_source_remove(gtkconv
->attach
.timer
);
5577 pidgin_conv_write_im(PurpleConversation
*conv
, const char *who
,
5578 const char *message
, PurpleMessageFlags flags
,
5581 PidginConversation
*gtkconv
;
5583 gtkconv
= PIDGIN_CONVERSATION(conv
);
5585 if (conv
!= gtkconv
->active_conv
&&
5586 flags
& PURPLE_MESSAGE_ACTIVE_ONLY
)
5588 /* Plugins that want these messages suppressed should be
5589 * calling purple_conv_im_write(), so they get suppressed here,
5590 * before being written to the log. */
5591 purple_debug_info("gtkconv",
5592 "Suppressing message for an inactive conversation in pidgin_conv_write_im()\n");
5596 purple_conversation_write(conv
, who
, message
, flags
, mtime
);
5600 get_text_tag_color(GtkTextTag
*tag
)
5602 GdkColor
*color
= NULL
;
5603 gboolean set
= FALSE
;
5604 static char colcode
[] = "#XXXXXX";
5606 g_object_get(G_OBJECT(tag
), "foreground-set", &set
, "foreground-gdk", &color
, NULL
);
5608 g_snprintf(colcode
, sizeof(colcode
), "#%02x%02x%02x",
5609 color
->red
>> 8, color
->green
>> 8, color
->blue
>> 8);
5613 gdk_color_free(color
);
5617 /* The callback for an event on a link tag. */
5618 static gboolean
buddytag_event(GtkTextTag
*tag
, GObject
*imhtml
,
5619 GdkEvent
*event
, GtkTextIter
*arg2
, gpointer data
)
5621 if (event
->type
== GDK_BUTTON_PRESS
5622 || event
->type
== GDK_2BUTTON_PRESS
) {
5623 GdkEventButton
*btn_event
= (GdkEventButton
*) event
;
5624 PurpleConversation
*conv
= data
;
5627 /* strlen("BUDDY " or "HILIT ") == 6 */
5628 g_return_val_if_fail((tag
->name
!= NULL
)
5629 && (strlen(tag
->name
) > 6), FALSE
);
5631 buddyname
= (tag
->name
) + 6;
5633 /* emit chat-nick-clicked signal */
5634 if (event
->type
== GDK_BUTTON_PRESS
) {
5635 gint plugin_return
= GPOINTER_TO_INT(purple_signal_emit_return_1(
5636 pidgin_conversations_get_handle(), "chat-nick-clicked",
5637 data
, buddyname
, btn_event
->button
));
5642 if (btn_event
->button
== 1 &&
5643 event
->type
== GDK_2BUTTON_PRESS
) {
5644 chat_do_im(PIDGIN_CONVERSATION(conv
), buddyname
);
5646 } else if (btn_event
->button
== 2
5647 && event
->type
== GDK_2BUTTON_PRESS
) {
5648 chat_do_info(PIDGIN_CONVERSATION(conv
), buddyname
);
5651 } else if (btn_event
->button
== 3
5652 && event
->type
== GDK_BUTTON_PRESS
) {
5653 GtkTextIter start
, end
;
5655 /* we shouldn't display the popup
5656 * if the user has selected something: */
5657 if (!gtk_text_buffer_get_selection_bounds(
5658 gtk_text_iter_get_buffer(arg2
),
5660 GtkWidget
*menu
= NULL
;
5661 PurpleConnection
*gc
=
5662 purple_conversation_get_gc(conv
);
5665 menu
= create_chat_menu(conv
, buddyname
, gc
);
5666 gtk_menu_popup(GTK_MENU(menu
), NULL
, NULL
,
5667 NULL
, GTK_WIDGET(imhtml
),
5671 /* Don't propagate the event any further */
5680 static GtkTextTag
*get_buddy_tag(PurpleConversation
*conv
, const char *who
, PurpleMessageFlags flag
,
5683 PidginConversation
*gtkconv
= PIDGIN_CONVERSATION(conv
);
5684 GtkTextTag
*buddytag
;
5686 gboolean highlight
= (flag
& PURPLE_MESSAGE_NICK
);
5687 GtkTextBuffer
*buffer
= GTK_IMHTML(gtkconv
->imhtml
)->text_buffer
;
5689 str
= g_strdup_printf(highlight
? "HILIT %s" : "BUDDY %s", who
);
5691 buddytag
= gtk_text_tag_table_lookup(
5692 gtk_text_buffer_get_tag_table(buffer
), str
);
5694 if (buddytag
== NULL
&& create
) {
5696 buddytag
= gtk_text_buffer_create_tag(buffer
, str
,
5697 "foreground", get_text_tag_color(gtk_text_tag_table_lookup(
5698 gtk_text_buffer_get_tag_table(buffer
), "highlight-name")),
5699 "weight", PANGO_WEIGHT_BOLD
,
5702 buddytag
= gtk_text_buffer_create_tag(
5704 "foreground-gdk", get_nick_color(gtkconv
, who
),
5705 "weight", purple_find_buddy(purple_conversation_get_account(conv
), who
) ? PANGO_WEIGHT_BOLD
: PANGO_WEIGHT_NORMAL
,
5708 g_object_set_data(G_OBJECT(buddytag
), "cursor", "");
5709 g_signal_connect(G_OBJECT(buddytag
), "event",
5710 G_CALLBACK(buddytag_event
), conv
);
5718 static void pidgin_conv_calculate_newday(PidginConversation
*gtkconv
, time_t mtime
)
5720 struct tm
*tm
= localtime(&mtime
);
5722 tm
->tm_hour
= tm
->tm_min
= tm
->tm_sec
= 0;
5725 gtkconv
->newday
= mktime(tm
);
5728 /* Detect string direction and encapsulate the string in RLE/LRE/PDF unicode characters
5729 str - pointer to string (string is re-allocated and the pointer updated) */
5731 str_embed_direction_chars(char **str
)
5738 if (PANGO_DIRECTION_RTL
== pango_find_base_dir(*str
, -1))
5740 sprintf(pre_str
, "%c%c%c",
5741 0xE2, 0x80, 0xAB); /* RLE */
5742 sprintf(post_str
, "%c%c%c%c%c%c%c%c%c",
5743 0xE2, 0x80, 0xAC, /* PDF */
5744 0xE2, 0x80, 0x8E, /* LRM */
5745 0xE2, 0x80, 0xAC); /* PDF */
5749 sprintf(pre_str
, "%c%c%c",
5750 0xE2, 0x80, 0xAA); /* LRE */
5751 sprintf(post_str
, "%c%c%c%c%c%c%c%c%c",
5752 0xE2, 0x80, 0xAC, /* PDF */
5753 0xE2, 0x80, 0x8F, /* RLM */
5754 0xE2, 0x80, 0xAC); /* PDF */
5757 ret
= g_strconcat(pre_str
, *str
, post_str
, NULL
);
5765 pidgin_conv_write_conv(PurpleConversation
*conv
, const char *name
, const char *alias
,
5766 const char *message
, PurpleMessageFlags flags
,
5769 PidginConversation
*gtkconv
;
5770 PurpleConnection
*gc
;
5771 PurpleAccount
*account
;
5772 int gtk_font_options
= 0;
5773 int gtk_font_options_all
= 0;
5774 int max_scrollback_lines
;
5776 char buf2
[BUF_LONG
];
5780 char *with_font_tag
;
5781 char *sml_attrib
= NULL
;
5783 PurpleConversationType type
;
5785 gboolean plugin_return
;
5788 gboolean is_rtl_message
= FALSE
;
5790 g_return_if_fail(conv
!= NULL
);
5791 gtkconv
= PIDGIN_CONVERSATION(conv
);
5792 g_return_if_fail(gtkconv
!= NULL
);
5794 if (gtkconv
->attach
.timer
) {
5795 /* We are currently in the process of filling up the buffer with the message
5796 * history of the conversation. So we do not need to add the message here.
5797 * Instead, this message will be added to the message-list, which in turn will
5798 * be processed and displayed by the attach-callback.
5803 if (conv
!= gtkconv
->active_conv
)
5805 if (flags
& PURPLE_MESSAGE_ACTIVE_ONLY
)
5807 /* Unless this had PURPLE_MESSAGE_NO_LOG, this message
5808 * was logged. Plugin writers: if this isn't what
5809 * you wanted, call purple_conv_im_write() instead of
5810 * purple_conversation_write(). */
5811 purple_debug_info("gtkconv",
5812 "Suppressing message for an inactive conversation in pidgin_conv_write_conv()\n");
5816 /* Set the active conversation to the one that just messaged us. */
5817 /* TODO: consider not doing this if the account is offline or something */
5818 if (flags
& (PURPLE_MESSAGE_SEND
| PURPLE_MESSAGE_RECV
))
5819 pidgin_conv_switch_active_conversation(conv
);
5822 type
= purple_conversation_get_type(conv
);
5823 account
= purple_conversation_get_account(conv
);
5824 g_return_if_fail(account
!= NULL
);
5825 gc
= purple_account_get_connection(account
);
5826 g_return_if_fail(gc
!= NULL
|| !(flags
& (PURPLE_MESSAGE_SEND
| PURPLE_MESSAGE_RECV
)));
5828 /* Make sure URLs are clickable */
5829 if(flags
& PURPLE_MESSAGE_NO_LINKIFY
)
5830 displaying
= g_strdup(message
);
5832 displaying
= purple_markup_linkify(message
);
5834 plugin_return
= GPOINTER_TO_INT(purple_signal_emit_return_1(
5835 pidgin_conversations_get_handle(), (type
== PURPLE_CONV_TYPE_IM
?
5836 "displaying-im-msg" : "displaying-chat-msg"),
5837 account
, name
, &displaying
, conv
, flags
));
5843 length
= strlen(displaying
) + 1;
5845 /* Awful hack to work around GtkIMHtml's inefficient rendering of messages with lots of formatting changes.
5846 * If a message has over 100 '<' characters, strip formatting before appending it. Hopefully nobody actually
5847 * needs that much formatting, anyway.
5849 for (bracket
= strchr(displaying
, '<'); bracket
&& *(bracket
+ 1); bracket
= strchr(bracket
+ 1, '<'))
5852 if (tag_count
> 100) {
5853 char *tmp
= displaying
;
5854 displaying
= purple_markup_strip_html(tmp
);
5858 line_count
= gtk_text_buffer_get_line_count(
5859 gtk_text_view_get_buffer(GTK_TEXT_VIEW(
5862 max_scrollback_lines
= purple_prefs_get_int(
5863 PIDGIN_PREFS_ROOT
"/conversations/scrollback_lines");
5864 /* If we're sitting at more than 100 lines more than the
5865 max scrollback, trim down to max scrollback */
5866 if (max_scrollback_lines
> 0
5867 && line_count
> (max_scrollback_lines
+ 100)) {
5868 GtkTextBuffer
*text_buffer
= gtk_text_view_get_buffer(
5869 GTK_TEXT_VIEW(gtkconv
->imhtml
));
5870 GtkTextIter start
, end
;
5872 gtk_text_buffer_get_start_iter(text_buffer
, &start
);
5873 gtk_text_buffer_get_iter_at_line(text_buffer
, &end
,
5874 (line_count
- max_scrollback_lines
));
5875 gtk_imhtml_delete(GTK_IMHTML(gtkconv
->imhtml
), &start
, &end
);
5878 if (type
== PURPLE_CONV_TYPE_CHAT
)
5880 /* Create anchor for user */
5882 char *tmp
= g_strconcat("user:", name
, NULL
);
5884 gtk_text_buffer_get_end_iter(gtk_text_view_get_buffer(GTK_TEXT_VIEW(gtkconv
->imhtml
)), &iter
);
5885 gtk_text_buffer_create_mark(gtk_text_view_get_buffer(GTK_TEXT_VIEW(gtkconv
->imhtml
)),
5890 if (purple_prefs_get_bool(PIDGIN_PREFS_ROOT
"/conversations/use_smooth_scrolling"))
5891 gtk_font_options_all
|= GTK_IMHTML_USE_SMOOTHSCROLLING
;
5893 if (gtk_text_buffer_get_char_count(gtk_text_view_get_buffer(GTK_TEXT_VIEW(gtkconv
->imhtml
))))
5894 gtk_imhtml_append_text(GTK_IMHTML(gtkconv
->imhtml
), "<BR>", gtk_font_options_all
| GTK_IMHTML_NO_SCROLL
);
5896 /* First message in a conversation. */
5897 if (gtkconv
->newday
== 0)
5898 pidgin_conv_calculate_newday(gtkconv
, mtime
);
5900 /* Show the date on the first message in a new day, or if the message is
5901 * older than 20 minutes. */
5902 show_date
= (mtime
>= gtkconv
->newday
) || (time(NULL
) > mtime
+ 20*60);
5904 mdate
= purple_signal_emit_return_1(pidgin_conversations_get_handle(),
5905 "conversation-timestamp",
5906 conv
, mtime
, show_date
);
5910 struct tm
*tm
= localtime(&mtime
);
5913 tmp
= purple_date_format_long(tm
);
5915 tmp
= purple_time_format(tm
);
5916 mdate
= g_strdup_printf("(%s)", tmp
);
5919 /* Bi-Directional support - set timestamp direction using unicode characters */
5920 is_rtl_message
= purple_markup_is_rtl(message
);
5921 /* Enforce direction only if message is RTL - doesn't effect LTR users */
5923 str_embed_direction_chars(&mdate
);
5925 if (mtime
>= gtkconv
->newday
)
5926 pidgin_conv_calculate_newday(gtkconv
, mtime
);
5928 sml_attrib
= g_strdup_printf("sml=\"%s\"", purple_account_get_protocol_name(account
));
5930 gtk_font_options
|= GTK_IMHTML_NO_COMMENTS
;
5932 if ((flags
& PURPLE_MESSAGE_RECV
) &&
5933 !purple_prefs_get_bool(PIDGIN_PREFS_ROOT
"/conversations/show_incoming_formatting"))
5934 gtk_font_options
|= GTK_IMHTML_NO_COLOURS
| GTK_IMHTML_NO_FONTS
| GTK_IMHTML_NO_SIZES
| GTK_IMHTML_NO_FORMATTING
;
5936 /* this is gonna crash one day, I can feel it. */
5937 if (PURPLE_PLUGIN_PROTOCOL_INFO(purple_find_prpl(purple_account_get_protocol_id(conv
->account
)))->options
&
5938 OPT_PROTO_USE_POINTSIZE
) {
5939 gtk_font_options
|= GTK_IMHTML_USE_POINTSIZE
;
5942 if (!(flags
& PURPLE_MESSAGE_RECV
) && (conv
->features
& PURPLE_CONNECTION_ALLOW_CUSTOM_SMILEY
))
5944 /* We want to see our own smileys. Need to revert it after send*/
5945 pidgin_themes_smiley_themeize_custom(gtkconv
->imhtml
);
5948 /* TODO: These colors should not be hardcoded so log.c can use them */
5949 if (flags
& PURPLE_MESSAGE_RAW
) {
5950 gtk_imhtml_append_text(GTK_IMHTML(gtkconv
->imhtml
), message
, gtk_font_options_all
);
5951 } else if (flags
& PURPLE_MESSAGE_SYSTEM
) {
5952 g_snprintf(buf2
, sizeof(buf2
),
5953 "<FONT %s><FONT SIZE=\"2\"><!--%s --></FONT><B>%s</B></FONT>",
5954 sml_attrib
? sml_attrib
: "", mdate
, displaying
);
5956 gtk_imhtml_append_text(GTK_IMHTML(gtkconv
->imhtml
), buf2
, gtk_font_options_all
);
5958 } else if (flags
& PURPLE_MESSAGE_ERROR
) {
5959 g_snprintf(buf2
, sizeof(buf2
),
5960 "<FONT COLOR=\"#ff0000\"><FONT %s><FONT SIZE=\"2\"><!--%s --></FONT><B>%s</B></FONT></FONT>",
5961 sml_attrib
? sml_attrib
: "", mdate
, displaying
);
5963 gtk_imhtml_append_text(GTK_IMHTML(gtkconv
->imhtml
), buf2
, gtk_font_options_all
);
5965 } else if (flags
& PURPLE_MESSAGE_NO_LOG
) {
5966 g_snprintf(buf2
, BUF_LONG
,
5967 "<B><FONT %s COLOR=\"#777777\">%s</FONT></B>",
5968 sml_attrib
? sml_attrib
: "", displaying
);
5970 gtk_imhtml_append_text(GTK_IMHTML(gtkconv
->imhtml
), buf2
, gtk_font_options_all
);
5972 char *new_message
= g_memdup(displaying
, length
);
5973 char *alias_escaped
= (alias
? g_markup_escape_text(alias
, strlen(alias
)) : g_strdup(""));
5974 /* The initial offset is to deal with
5975 * escaped entities making the string longer */
5976 int tag_start_offset
= 0;
5977 int tag_end_offset
= 0;
5978 const char *tagname
= NULL
;
5980 GtkTextIter start
, end
;
5983 GtkTextBuffer
*buffer
= GTK_IMHTML(gtkconv
->imhtml
)->text_buffer
;
5985 /* Enforce direction on alias */
5987 str_embed_direction_chars(&alias_escaped
);
5989 str
= g_malloc(1024);
5990 if (flags
& PURPLE_MESSAGE_WHISPER
) {
5991 /* If we're whispering, it's not an autoresponse. */
5992 if (purple_message_meify(new_message
, -1 )) {
5993 g_snprintf(str
, 1024, "***%s", alias_escaped
);
5994 tag_start_offset
+= 3;
5995 tagname
= "whisper-action-name";
5998 g_snprintf(str
, 1024, "*%s*:", alias_escaped
);
5999 tag_start_offset
+= 1;
6001 tagname
= "whisper-name";
6004 if (purple_message_meify(new_message
, -1)) {
6005 if (flags
& PURPLE_MESSAGE_AUTO_RESP
) {
6006 g_snprintf(str
, 1024, "%s ***%s", AUTO_RESPONSE
, alias_escaped
);
6007 tag_start_offset
+= strlen(AUTO_RESPONSE
) - 6 + 4;
6009 g_snprintf(str
, 1024, "***%s", alias_escaped
);
6010 tag_start_offset
+= 3;
6013 if (flags
& PURPLE_MESSAGE_NICK
)
6014 tagname
= "highlight-name";
6016 tagname
= "action-name";
6018 if (flags
& PURPLE_MESSAGE_AUTO_RESP
) {
6019 g_snprintf(str
, 1024, "%s %s", alias_escaped
, AUTO_RESPONSE
);
6020 tag_start_offset
+= strlen(AUTO_RESPONSE
) - 6 + 1;
6022 g_snprintf(str
, 1024, "%s:", alias_escaped
);
6026 if (flags
& PURPLE_MESSAGE_NICK
) {
6027 if (type
== PURPLE_CONV_TYPE_IM
) {
6028 tagname
= "highlight-name";
6030 } else if (flags
& PURPLE_MESSAGE_RECV
) {
6031 /* The tagname for chats is handled by get_buddy_tag */
6032 if (type
== PURPLE_CONV_TYPE_IM
) {
6033 tagname
= "receive-name";
6035 } else if (flags
& PURPLE_MESSAGE_SEND
) {
6036 tagname
= "send-name";
6038 purple_debug_error("gtkconv", "message missing flags\n");
6043 g_free(alias_escaped
);
6046 tag
= gtk_text_tag_table_lookup(gtk_text_buffer_get_tag_table(buffer
), tagname
);
6048 tag
= get_buddy_tag(conv
, name
, flags
, TRUE
);
6050 if (GTK_IMHTML(gtkconv
->imhtml
)->show_comments
) {
6051 /* The color for the timestamp has to be set in the font-tags, unfortunately.
6052 * Applying the nick-tag to timestamps would work, but that can make it
6053 * bold. I thought applying the "comment" tag again, which has "weight" set
6054 * to PANGO_WEIGHT_NORMAL, would remove the boldness. But it doesn't. So
6055 * this will have to do. I don't terribly like it. -- sadrul */
6056 const char *color
= get_text_tag_color(tag
);
6057 g_snprintf(buf2
, BUF_LONG
, "<FONT %s%s%s SIZE=\"2\"><!--%s --></FONT>",
6058 color
? "COLOR=\"" : "", color
? color
: "", color
? "\"" : "", mdate
);
6059 gtk_imhtml_append_text(GTK_IMHTML(gtkconv
->imhtml
), buf2
, gtk_font_options_all
| GTK_IMHTML_NO_SCROLL
);
6062 gtk_text_buffer_get_end_iter(buffer
, &end
);
6063 mark
= gtk_text_buffer_create_mark(buffer
, NULL
, &end
, TRUE
);
6065 g_snprintf(buf2
, BUF_LONG
, "<FONT %s>%s</FONT> ", sml_attrib
? sml_attrib
: "", str
);
6066 gtk_imhtml_append_text(GTK_IMHTML(gtkconv
->imhtml
), buf2
, gtk_font_options_all
| GTK_IMHTML_NO_SCROLL
);
6068 gtk_text_buffer_get_end_iter(buffer
, &end
);
6069 gtk_text_buffer_get_iter_at_mark(buffer
, &start
, mark
);
6070 gtk_text_buffer_apply_tag(buffer
, tag
, &start
, &end
);
6071 gtk_text_buffer_delete_mark(buffer
, mark
);
6076 char *pre
= g_strdup_printf("<font %s>", sml_attrib
? sml_attrib
: "");
6077 char *post
= "</font>";
6078 int pre_len
= strlen(pre
);
6079 int post_len
= strlen(post
);
6081 with_font_tag
= g_malloc(length
+ pre_len
+ post_len
+ 1);
6083 strcpy(with_font_tag
, pre
);
6084 memcpy(with_font_tag
+ pre_len
, new_message
, length
);
6085 strcpy(with_font_tag
+ pre_len
+ length
, post
);
6087 length
+= pre_len
+ post_len
;
6090 with_font_tag
= g_memdup(new_message
, length
);
6092 gtk_imhtml_append_text(GTK_IMHTML(gtkconv
->imhtml
),
6093 with_font_tag
, gtk_font_options
| gtk_font_options_all
);
6095 g_free(with_font_tag
);
6096 g_free(new_message
);
6102 /* Tab highlighting stuff */
6103 if (!(flags
& PURPLE_MESSAGE_SEND
) && !pidgin_conv_has_focus(conv
))
6105 PidginUnseenState unseen
= PIDGIN_UNSEEN_NONE
;
6107 if ((flags
& PURPLE_MESSAGE_NICK
) == PURPLE_MESSAGE_NICK
)
6108 unseen
= PIDGIN_UNSEEN_NICK
;
6109 else if (((flags
& PURPLE_MESSAGE_SYSTEM
) == PURPLE_MESSAGE_SYSTEM
) ||
6110 ((flags
& PURPLE_MESSAGE_ERROR
) == PURPLE_MESSAGE_ERROR
))
6111 unseen
= PIDGIN_UNSEEN_EVENT
;
6112 else if ((flags
& PURPLE_MESSAGE_NO_LOG
) == PURPLE_MESSAGE_NO_LOG
)
6113 unseen
= PIDGIN_UNSEEN_NO_LOG
;
6115 unseen
= PIDGIN_UNSEEN_TEXT
;
6117 gtkconv_set_unseen(gtkconv
, unseen
);
6120 if (!(flags
& PURPLE_MESSAGE_RECV
) && (conv
->features
& PURPLE_CONNECTION_ALLOW_CUSTOM_SMILEY
))
6122 /* Restore the smiley-data */
6123 pidgin_themes_smiley_themeize(gtkconv
->imhtml
);
6126 purple_signal_emit(pidgin_conversations_get_handle(),
6127 (type
== PURPLE_CONV_TYPE_IM
? "displayed-im-msg" : "displayed-chat-msg"),
6128 account
, name
, displaying
, conv
, flags
);
6130 update_typing_message(gtkconv
, NULL
);
6133 static gboolean
get_iter_from_chatbuddy(PurpleConvChatBuddy
*cb
, GtkTreeIter
*iter
)
6135 GtkTreeRowReference
*ref
;
6137 GtkTreeModel
*model
;
6139 g_return_val_if_fail(cb
!= NULL
, FALSE
);
6145 if ((path
= gtk_tree_row_reference_get_path(ref
)) == NULL
)
6148 model
= gtk_tree_row_reference_get_model(ref
);
6149 if (!gtk_tree_model_get_iter(GTK_TREE_MODEL(model
), iter
, path
)) {
6150 gtk_tree_path_free(path
);
6154 gtk_tree_path_free(path
);
6159 pidgin_conv_chat_add_users(PurpleConversation
*conv
, GList
*cbuddies
, gboolean new_arrivals
)
6161 PurpleConvChat
*chat
;
6162 PidginConversation
*gtkconv
;
6163 PidginChatPane
*gtkchat
;
6170 chat
= PURPLE_CONV_CHAT(conv
);
6171 gtkconv
= PIDGIN_CONVERSATION(conv
);
6172 gtkchat
= gtkconv
->u
.chat
;
6174 num_users
= g_list_length(purple_conv_chat_get_users(chat
));
6176 g_snprintf(tmp
, sizeof(tmp
),
6177 ngettext("%d person in room", "%d people in room",
6181 gtk_label_set_text(GTK_LABEL(gtkchat
->count
), tmp
);
6183 ls
= GTK_LIST_STORE(gtk_tree_view_get_model(GTK_TREE_VIEW(gtkchat
->list
)));
6185 gtk_tree_sortable_set_sort_column_id(GTK_TREE_SORTABLE(ls
), GTK_TREE_SORTABLE_UNSORTED_SORT_COLUMN_ID
,
6186 GTK_TREE_SORTABLE_UNSORTED_SORT_COLUMN_ID
);
6190 add_chat_buddy_common(conv
, (PurpleConvChatBuddy
*)l
->data
, NULL
);
6194 /* Currently GTK+ maintains our sorted list after it's in the tree.
6195 * This may change if it turns out we can manage it faster ourselves.
6197 gtk_tree_sortable_set_sort_column_id(GTK_TREE_SORTABLE(ls
), CHAT_USERS_ALIAS_KEY_COLUMN
,
6198 GTK_SORT_ASCENDING
);
6202 pidgin_conv_chat_rename_user(PurpleConversation
*conv
, const char *old_name
,
6203 const char *new_name
, const char *new_alias
)
6205 PurpleConvChat
*chat
;
6206 PidginConversation
*gtkconv
;
6207 PidginChatPane
*gtkchat
;
6208 PurpleConvChatBuddy
*old_cbuddy
, *new_cbuddy
;
6210 GtkTreeModel
*model
;
6213 chat
= PURPLE_CONV_CHAT(conv
);
6214 gtkconv
= PIDGIN_CONVERSATION(conv
);
6215 gtkchat
= gtkconv
->u
.chat
;
6217 model
= gtk_tree_view_get_model(GTK_TREE_VIEW(gtkchat
->list
));
6219 if (!gtk_tree_model_get_iter_first(GTK_TREE_MODEL(model
), &iter
))
6222 if ((tag
= get_buddy_tag(conv
, old_name
, 0, FALSE
)))
6223 g_object_set(G_OBJECT(tag
), "style", PANGO_STYLE_ITALIC
, NULL
);
6224 if ((tag
= get_buddy_tag(conv
, old_name
, PURPLE_MESSAGE_NICK
, FALSE
)))
6225 g_object_set(G_OBJECT(tag
), "style", PANGO_STYLE_ITALIC
, NULL
);
6227 old_cbuddy
= purple_conv_chat_cb_find(chat
, old_name
);
6231 if (get_iter_from_chatbuddy(old_cbuddy
, &iter
)) {
6232 GtkTreeRowReference
*ref
= old_cbuddy
->ui_data
;
6234 gtk_list_store_remove(GTK_LIST_STORE(model
), &iter
);
6235 gtk_tree_row_reference_free(ref
);
6236 old_cbuddy
->ui_data
= NULL
;
6239 g_return_if_fail(new_alias
!= NULL
);
6241 new_cbuddy
= purple_conv_chat_cb_find(chat
, new_name
);
6243 add_chat_buddy_common(conv
, new_cbuddy
, old_name
);
6247 pidgin_conv_chat_remove_users(PurpleConversation
*conv
, GList
*users
)
6249 PurpleConvChat
*chat
;
6250 PidginConversation
*gtkconv
;
6251 PidginChatPane
*gtkchat
;
6253 GtkTreeModel
*model
;
6260 chat
= PURPLE_CONV_CHAT(conv
);
6261 gtkconv
= PIDGIN_CONVERSATION(conv
);
6262 gtkchat
= gtkconv
->u
.chat
;
6264 num_users
= g_list_length(purple_conv_chat_get_users(chat
));
6266 for (l
= users
; l
!= NULL
; l
= l
->next
) {
6267 model
= gtk_tree_view_get_model(GTK_TREE_VIEW(gtkchat
->list
));
6269 if (!gtk_tree_model_get_iter_first(GTK_TREE_MODEL(model
), &iter
))
6276 gtk_tree_model_get(GTK_TREE_MODEL(model
), &iter
,
6277 CHAT_USERS_NAME_COLUMN
, &val
, -1);
6279 if (!purple_utf8_strcasecmp((char *)l
->data
, val
)) {
6280 f
= gtk_list_store_remove(GTK_LIST_STORE(model
), &iter
);
6283 f
= gtk_tree_model_iter_next(GTK_TREE_MODEL(model
), &iter
);
6288 if ((tag
= get_buddy_tag(conv
, l
->data
, 0, FALSE
)))
6289 g_object_set(G_OBJECT(tag
), "style", PANGO_STYLE_ITALIC
, NULL
);
6290 if ((tag
= get_buddy_tag(conv
, l
->data
, PURPLE_MESSAGE_NICK
, FALSE
)))
6291 g_object_set(G_OBJECT(tag
), "style", PANGO_STYLE_ITALIC
, NULL
);
6294 g_snprintf(tmp
, sizeof(tmp
),
6295 ngettext("%d person in room", "%d people in room",
6296 num_users
), num_users
);
6298 gtk_label_set_text(GTK_LABEL(gtkchat
->count
), tmp
);
6302 pidgin_conv_chat_update_user(PurpleConversation
*conv
, const char *user
)
6304 PurpleConvChat
*chat
;
6305 PurpleConvChatBuddy
*cbuddy
;
6306 PidginConversation
*gtkconv
;
6307 PidginChatPane
*gtkchat
;
6309 GtkTreeModel
*model
;
6311 chat
= PURPLE_CONV_CHAT(conv
);
6312 gtkconv
= PIDGIN_CONVERSATION(conv
);
6313 gtkchat
= gtkconv
->u
.chat
;
6315 model
= gtk_tree_view_get_model(GTK_TREE_VIEW(gtkchat
->list
));
6317 if (!gtk_tree_model_get_iter_first(GTK_TREE_MODEL(model
), &iter
))
6320 cbuddy
= purple_conv_chat_cb_find(chat
, user
);
6324 if (get_iter_from_chatbuddy(cbuddy
, &iter
)) {
6325 GtkTreeRowReference
*ref
= cbuddy
->ui_data
;
6326 gtk_list_store_remove(GTK_LIST_STORE(model
), &iter
);
6327 gtk_tree_row_reference_free(ref
);
6328 cbuddy
->ui_data
= NULL
;
6332 add_chat_buddy_common(conv
, cbuddy
, NULL
);
6336 pidgin_conv_has_focus(PurpleConversation
*conv
)
6338 PidginConversation
*gtkconv
= PIDGIN_CONVERSATION(conv
);
6344 g_object_get(G_OBJECT(win
->window
), "has-toplevel-focus", &has_focus
, NULL
);
6346 if (has_focus
&& pidgin_conv_window_is_active_conversation(conv
))
6353 add_custom_smiley_for_imhtml(GtkIMHtml
*imhtml
, const char *sml
, const char *smile
)
6355 GtkIMHtmlSmiley
*smiley
;
6357 smiley
= gtk_imhtml_smiley_get(imhtml
, sml
, smile
);
6360 if (!(smiley
->flags
& GTK_IMHTML_SMILEY_CUSTOM
)) {
6363 gtk_imhtml_smiley_reload(smiley
);
6367 smiley
= gtk_imhtml_smiley_create(NULL
, smile
, FALSE
, GTK_IMHTML_SMILEY_CUSTOM
);
6368 gtk_imhtml_associate_smiley(imhtml
, sml
, smiley
);
6369 g_signal_connect_swapped(imhtml
, "destroy", G_CALLBACK(gtk_imhtml_smiley_destroy
), smiley
);
6375 pidgin_conv_custom_smiley_add(PurpleConversation
*conv
, const char *smile
, gboolean remote
)
6377 PidginConversation
*gtkconv
;
6378 struct smiley_list
*list
;
6379 const char *sml
= NULL
, *conv_sml
;
6381 if (!conv
|| !smile
|| !*smile
) {
6385 /* If smileys are off, return false */
6386 if (pidgin_themes_smileys_disabled())
6389 /* If possible add this smiley to the current theme.
6390 * The addition is only temporary: custom smilies aren't saved to disk. */
6391 conv_sml
= purple_account_get_protocol_name(conv
->account
);
6392 gtkconv
= PIDGIN_CONVERSATION(conv
);
6394 for (list
= (struct smiley_list
*)current_smiley_theme
->list
; list
; list
= list
->next
) {
6395 if (!strcmp(list
->sml
, conv_sml
)) {
6401 if (!add_custom_smiley_for_imhtml(GTK_IMHTML(gtkconv
->imhtml
), sml
, smile
))
6404 if (!remote
) /* If it's a local custom smiley, then add it for the entry */
6405 if (!add_custom_smiley_for_imhtml(GTK_IMHTML(gtkconv
->entry
), sml
, smile
))
6412 pidgin_conv_custom_smiley_write(PurpleConversation
*conv
, const char *smile
,
6413 const guchar
*data
, gsize size
)
6415 PidginConversation
*gtkconv
;
6416 GtkIMHtmlSmiley
*smiley
;
6418 GError
*error
= NULL
;
6420 sml
= purple_account_get_protocol_name(conv
->account
);
6421 gtkconv
= PIDGIN_CONVERSATION(conv
);
6422 smiley
= gtk_imhtml_smiley_get(GTK_IMHTML(gtkconv
->imhtml
), sml
, smile
);
6427 smiley
->data
= g_realloc(smiley
->data
, smiley
->datasize
+ size
);
6428 g_memmove((guchar
*)smiley
->data
+ smiley
->datasize
, data
, size
);
6429 smiley
->datasize
+= size
;
6431 if (!smiley
->loader
)
6434 if (!gdk_pixbuf_loader_write(smiley
->loader
, data
, size
, &error
) || error
) {
6435 purple_debug_warning("gtkconv", "gdk_pixbuf_loader_write() "
6436 "failed with size=%zu: %s\n", size
,
6437 error
? error
->message
: "(no error message)");
6439 g_error_free(error
);
6440 /* We must stop using the GdkPixbufLoader because trying to load
6441 certain invalid GIFs with at least gdk-pixbuf 2.23.3 can return
6442 a GdkPixbuf that will cause some operations (like
6443 gdk_pixbuf_scale_simple()) to consume memory in an infinite loop.
6444 But we also don't want to set smiley->loader to NULL because our
6445 code might expect it to be set. So create a new loader. */
6446 g_object_unref(G_OBJECT(smiley
->loader
));
6447 smiley
->loader
= gdk_pixbuf_loader_new();
6452 pidgin_conv_custom_smiley_close(PurpleConversation
*conv
, const char *smile
)
6454 PidginConversation
*gtkconv
;
6455 GtkIMHtmlSmiley
*smiley
;
6457 GError
*error
= NULL
;
6459 g_return_if_fail(conv
!= NULL
);
6460 g_return_if_fail(smile
!= NULL
);
6462 sml
= purple_account_get_protocol_name(conv
->account
);
6463 gtkconv
= PIDGIN_CONVERSATION(conv
);
6464 smiley
= gtk_imhtml_smiley_get(GTK_IMHTML(gtkconv
->imhtml
), sml
, smile
);
6469 if (!smiley
->loader
)
6472 purple_debug_info("gtkconv", "About to close the smiley pixbuf\n");
6474 if (!gdk_pixbuf_loader_close(smiley
->loader
, &error
) || error
) {
6475 purple_debug_warning("gtkconv", "gdk_pixbuf_loader_close() "
6477 error
? error
->message
: "(no error message)");
6479 g_error_free(error
);
6480 /* We must stop using the GdkPixbufLoader because if we tried to
6481 load certain invalid GIFs with all current versions of GDK (as
6482 of 2011-06-15) then it's possible the loader will contain data
6483 that could cause some operations (like gdk_pixbuf_scale_simple())
6484 to consume memory in an infinite loop. But we also don't want
6485 to set smiley->loader to NULL because our code might expect it
6486 to be set. So create a new loader. */
6487 g_object_unref(G_OBJECT(smiley
->loader
));
6488 smiley
->loader
= gdk_pixbuf_loader_new();
6493 pidgin_conv_send_confirm(PurpleConversation
*conv
, const char *message
)
6495 PidginConversation
*gtkconv
= PIDGIN_CONVERSATION(conv
);
6497 gtk_imhtml_append_text(GTK_IMHTML(gtkconv
->entry
), message
, 0);
6501 * Makes sure all the menu items and all the buttons are hidden/shown and
6502 * sensitive/insensitive. This is called after changing tabs and when an
6503 * account signs on or off.
6506 gray_stuff_out(PidginConversation
*gtkconv
)
6509 PurpleConversation
*conv
= gtkconv
->active_conv
;
6510 PurpleConnection
*gc
;
6511 PurplePluginProtocolInfo
*prpl_info
= NULL
;
6512 GdkPixbuf
*window_icon
= NULL
;
6513 GtkIMHtmlButtons buttons
;
6514 PurpleAccount
*account
;
6516 win
= pidgin_conv_get_window(gtkconv
);
6517 gc
= purple_conversation_get_gc(conv
);
6518 account
= purple_conversation_get_account(conv
);
6521 prpl_info
= PURPLE_PLUGIN_PROTOCOL_INFO(gc
->prpl
);
6523 if (win
->menu
.send_to
!= NULL
)
6524 update_send_to_selection(win
);
6527 * Handle hiding and showing stuff based on what type of conv this is.
6528 * Stuff that Purple IMs support in general should be shown for IM
6529 * conversations. Stuff that Purple chats support in general should be
6530 * shown for chat conversations. It doesn't matter whether the PRPL
6531 * supports it or not--that only affects if the button or menu item
6532 * is sensitive or not.
6534 if (purple_conversation_get_type(conv
) == PURPLE_CONV_TYPE_IM
) {
6535 /* Show stuff that applies to IMs, hide stuff that applies to chats */
6537 /* Deal with menu items */
6538 gtk_widget_show(win
->menu
.view_log
);
6539 gtk_widget_show(win
->menu
.send_file
);
6540 gtk_widget_show(g_object_get_data(G_OBJECT(win
->window
), "get_attention"));
6541 gtk_widget_show(win
->menu
.add_pounce
);
6542 gtk_widget_show(win
->menu
.get_info
);
6543 gtk_widget_hide(win
->menu
.invite
);
6544 gtk_widget_show(win
->menu
.alias
);
6545 if (purple_privacy_check(account
, purple_conversation_get_name(conv
))) {
6546 gtk_widget_hide(win
->menu
.unblock
);
6547 gtk_widget_show(win
->menu
.block
);
6549 gtk_widget_hide(win
->menu
.block
);
6550 gtk_widget_show(win
->menu
.unblock
);
6553 if ((account
== NULL
) || purple_find_buddy(account
, purple_conversation_get_name(conv
)) == NULL
) {
6554 gtk_widget_show(win
->menu
.add
);
6555 gtk_widget_hide(win
->menu
.remove
);
6557 gtk_widget_show(win
->menu
.remove
);
6558 gtk_widget_hide(win
->menu
.add
);
6561 gtk_widget_show(win
->menu
.insert_link
);
6562 gtk_widget_show(win
->menu
.insert_image
);
6563 } else if (purple_conversation_get_type(conv
) == PURPLE_CONV_TYPE_CHAT
) {
6564 /* Show stuff that applies to Chats, hide stuff that applies to IMs */
6566 /* Deal with menu items */
6567 gtk_widget_show(win
->menu
.view_log
);
6568 gtk_widget_hide(win
->menu
.send_file
);
6569 gtk_widget_hide(g_object_get_data(G_OBJECT(win
->window
), "get_attention"));
6570 gtk_widget_hide(win
->menu
.add_pounce
);
6571 gtk_widget_hide(win
->menu
.get_info
);
6572 gtk_widget_show(win
->menu
.invite
);
6573 gtk_widget_show(win
->menu
.alias
);
6574 gtk_widget_hide(win
->menu
.block
);
6575 gtk_widget_hide(win
->menu
.unblock
);
6577 if ((account
== NULL
) || purple_blist_find_chat(account
, purple_conversation_get_name(conv
)) == NULL
) {
6578 /* If the chat is NOT in the buddy list */
6579 gtk_widget_show(win
->menu
.add
);
6580 gtk_widget_hide(win
->menu
.remove
);
6582 /* If the chat IS in the buddy list */
6583 gtk_widget_hide(win
->menu
.add
);
6584 gtk_widget_show(win
->menu
.remove
);
6587 gtk_widget_show(win
->menu
.insert_link
);
6588 gtk_widget_show(win
->menu
.insert_image
);
6592 * Handle graying stuff out based on whether an account is connected
6593 * and what features that account supports.
6596 ((purple_conversation_get_type(conv
) != PURPLE_CONV_TYPE_CHAT
) ||
6597 !purple_conv_chat_has_left(PURPLE_CONV_CHAT(conv
)) ))
6599 /* Account is online */
6600 /* Deal with the toolbar */
6601 if (conv
->features
& PURPLE_CONNECTION_HTML
)
6603 buttons
= GTK_IMHTML_ALL
; /* Everything on */
6604 if (conv
->features
& PURPLE_CONNECTION_NO_BGCOLOR
)
6605 buttons
&= ~GTK_IMHTML_BACKCOLOR
;
6606 if (conv
->features
& PURPLE_CONNECTION_NO_FONTSIZE
)
6608 buttons
&= ~GTK_IMHTML_GROW
;
6609 buttons
&= ~GTK_IMHTML_SHRINK
;
6611 if (conv
->features
& PURPLE_CONNECTION_NO_URLDESC
)
6612 buttons
&= ~GTK_IMHTML_LINKDESC
;
6614 buttons
= GTK_IMHTML_SMILEY
| GTK_IMHTML_IMAGE
;
6617 if (!(prpl_info
->options
& OPT_PROTO_IM_IMAGE
))
6618 conv
->features
|= PURPLE_CONNECTION_NO_IMAGES
;
6620 if(conv
->features
& PURPLE_CONNECTION_NO_IMAGES
)
6621 buttons
&= ~GTK_IMHTML_IMAGE
;
6623 if (conv
->features
& PURPLE_CONNECTION_ALLOW_CUSTOM_SMILEY
)
6624 buttons
|= GTK_IMHTML_CUSTOM_SMILEY
;
6626 buttons
&= ~GTK_IMHTML_CUSTOM_SMILEY
;
6628 gtk_imhtml_set_format_functions(GTK_IMHTML(gtkconv
->entry
), buttons
);
6629 if (account
!= NULL
)
6630 gtk_imhtmltoolbar_associate_smileys(GTK_IMHTMLTOOLBAR(gtkconv
->toolbar
), purple_account_get_protocol_id(account
));
6632 /* Deal with menu items */
6633 gtk_widget_set_sensitive(win
->menu
.view_log
, TRUE
);
6634 gtk_widget_set_sensitive(win
->menu
.add_pounce
, TRUE
);
6635 gtk_widget_set_sensitive(win
->menu
.get_info
, (prpl_info
->get_info
!= NULL
));
6636 gtk_widget_set_sensitive(win
->menu
.invite
, (prpl_info
->chat_invite
!= NULL
));
6637 gtk_widget_set_sensitive(win
->menu
.insert_link
, (conv
->features
& PURPLE_CONNECTION_HTML
));
6638 gtk_widget_set_sensitive(win
->menu
.insert_image
, !(conv
->features
& PURPLE_CONNECTION_NO_IMAGES
));
6640 if (purple_conversation_get_type(conv
) == PURPLE_CONV_TYPE_IM
)
6642 gtk_widget_set_sensitive(win
->menu
.add
, (prpl_info
->add_buddy
!= NULL
) || (prpl_info
->add_buddy_with_invite
!= NULL
));
6643 gtk_widget_set_sensitive(win
->menu
.remove
, (prpl_info
->remove_buddy
!= NULL
));
6644 gtk_widget_set_sensitive(win
->menu
.send_file
,
6645 (prpl_info
->send_file
!= NULL
&& (!prpl_info
->can_receive_file
||
6646 prpl_info
->can_receive_file(gc
, purple_conversation_get_name(conv
)))));
6647 gtk_widget_set_sensitive(g_object_get_data(G_OBJECT(win
->window
), "get_attention"), (prpl_info
->send_attention
!= NULL
));
6648 gtk_widget_set_sensitive(win
->menu
.alias
,
6649 (account
!= NULL
) &&
6650 (purple_find_buddy(account
, purple_conversation_get_name(conv
)) != NULL
));
6652 else if (purple_conversation_get_type(conv
) == PURPLE_CONV_TYPE_CHAT
)
6654 gtk_widget_set_sensitive(win
->menu
.add
, (prpl_info
->join_chat
!= NULL
));
6655 gtk_widget_set_sensitive(win
->menu
.remove
, (prpl_info
->join_chat
!= NULL
));
6656 gtk_widget_set_sensitive(win
->menu
.alias
,
6657 (account
!= NULL
) &&
6658 (purple_blist_find_chat(account
, purple_conversation_get_name(conv
)) != NULL
));
6662 /* Account is offline */
6663 /* Or it's a chat that we've left. */
6665 /* Then deal with menu items */
6666 gtk_widget_set_sensitive(win
->menu
.view_log
, TRUE
);
6667 gtk_widget_set_sensitive(win
->menu
.send_file
, FALSE
);
6668 gtk_widget_set_sensitive(g_object_get_data(G_OBJECT(win
->window
),
6669 "get_attention"), FALSE
);
6670 gtk_widget_set_sensitive(win
->menu
.add_pounce
, TRUE
);
6671 gtk_widget_set_sensitive(win
->menu
.get_info
, FALSE
);
6672 gtk_widget_set_sensitive(win
->menu
.invite
, FALSE
);
6673 gtk_widget_set_sensitive(win
->menu
.alias
, FALSE
);
6674 gtk_widget_set_sensitive(win
->menu
.add
, FALSE
);
6675 gtk_widget_set_sensitive(win
->menu
.remove
, FALSE
);
6676 gtk_widget_set_sensitive(win
->menu
.insert_link
, TRUE
);
6677 gtk_widget_set_sensitive(win
->menu
.insert_image
, FALSE
);
6681 * Update the window's icon
6683 if (pidgin_conv_window_is_active_conversation(conv
))
6686 if ((purple_conversation_get_type(conv
) == PURPLE_CONV_TYPE_IM
) &&
6687 (gtkconv
->u
.im
->anim
))
6689 PurpleBuddy
*buddy
= purple_find_buddy(conv
->account
, conv
->name
);
6691 gdk_pixbuf_animation_get_static_image(gtkconv
->u
.im
->anim
);
6693 if (buddy
&& !PURPLE_BUDDY_IS_ONLINE(buddy
))
6694 gdk_pixbuf_saturate_and_pixelate(window_icon
, window_icon
, 0.0, FALSE
);
6696 g_object_ref(window_icon
);
6697 l
= g_list_append(l
, window_icon
);
6699 l
= pidgin_conv_get_tab_icons(conv
);
6701 gtk_window_set_icon_list(GTK_WINDOW(win
->window
), l
);
6702 if (window_icon
!= NULL
) {
6703 g_object_unref(G_OBJECT(window_icon
));
6710 pidgin_conv_update_fields(PurpleConversation
*conv
, PidginConvFields fields
)
6712 PidginConversation
*gtkconv
;
6715 gtkconv
= PIDGIN_CONVERSATION(conv
);
6718 win
= pidgin_conv_get_window(gtkconv
);
6722 if (fields
& PIDGIN_CONV_SET_TITLE
)
6724 purple_conversation_autoset_title(conv
);
6727 if (fields
& PIDGIN_CONV_BUDDY_ICON
)
6729 if (purple_conversation_get_type(conv
) == PURPLE_CONV_TYPE_IM
)
6730 pidgin_conv_update_buddy_icon(conv
);
6733 if (fields
& PIDGIN_CONV_MENU
)
6735 gray_stuff_out(PIDGIN_CONVERSATION(conv
));
6736 generate_send_to_items(win
);
6739 if (fields
& PIDGIN_CONV_TAB_ICON
)
6741 update_tab_icon(conv
);
6742 generate_send_to_items(win
); /* To update the icons in SendTo menu */
6745 if ((fields
& PIDGIN_CONV_TOPIC
) &&
6746 purple_conversation_get_type(conv
) == PURPLE_CONV_TYPE_CHAT
)
6749 PurpleConvChat
*chat
= PURPLE_CONV_CHAT(conv
);
6750 PidginChatPane
*gtkchat
= gtkconv
->u
.chat
;
6752 if (gtkchat
->topic_text
!= NULL
)
6754 topic
= purple_conv_chat_get_topic(chat
);
6756 gtk_entry_set_text(GTK_ENTRY(gtkchat
->topic_text
), topic
? topic
: "");
6757 gtk_tooltips_set_tip(gtkconv
->tooltips
, gtkchat
->topic_text
,
6758 topic
? topic
: "", NULL
);
6762 if (fields
& PIDGIN_CONV_SMILEY_THEME
)
6763 pidgin_themes_smiley_themeize(PIDGIN_CONVERSATION(conv
)->imhtml
);
6765 if ((fields
& PIDGIN_CONV_COLORIZE_TITLE
) ||
6766 (fields
& PIDGIN_CONV_SET_TITLE
) ||
6767 (fields
& PIDGIN_CONV_TOPIC
))
6770 PurpleConvIm
*im
= NULL
;
6771 PurpleAccount
*account
= purple_conversation_get_account(conv
);
6772 PurpleBuddy
*buddy
= NULL
;
6773 char *markup
= NULL
;
6774 AtkObject
*accessibility_obj
;
6775 /* I think this is a little longer than it needs to be but I'm lazy. */
6778 if (purple_conversation_get_type(conv
) == PURPLE_CONV_TYPE_IM
)
6779 im
= PURPLE_CONV_IM(conv
);
6781 if ((account
== NULL
) ||
6782 !purple_account_is_connected(account
) ||
6783 ((purple_conversation_get_type(conv
) == PURPLE_CONV_TYPE_CHAT
)
6784 && purple_conv_chat_has_left(PURPLE_CONV_CHAT(conv
))))
6785 title
= g_strdup_printf("(%s)", purple_conversation_get_title(conv
));
6787 title
= g_strdup(purple_conversation_get_title(conv
));
6789 if (purple_conversation_get_type(conv
) == PURPLE_CONV_TYPE_IM
) {
6790 buddy
= purple_find_buddy(account
, conv
->name
);
6792 markup
= pidgin_blist_get_name_markup(buddy
, FALSE
, FALSE
);
6796 } else if (purple_conversation_get_type(conv
) == PURPLE_CONV_TYPE_CHAT
) {
6797 const char *topic
= gtkconv
->u
.chat
->topic_text
6798 ? gtk_entry_get_text(GTK_ENTRY(gtkconv
->u
.chat
->topic_text
))
6800 char *esc
= NULL
, *tmp
;
6801 esc
= topic
? g_markup_escape_text(topic
, -1) : NULL
;
6802 tmp
= g_markup_escape_text(purple_conversation_get_title(conv
), -1);
6803 markup
= g_strdup_printf("%s%s<span color='%s' size='smaller'>%s</span>",
6804 tmp
, esc
&& *esc
? "\n" : "",
6805 pidgin_get_dim_grey_string(gtkconv
->infopane
),
6810 gtk_list_store_set(gtkconv
->infopane_model
, &(gtkconv
->infopane_iter
),
6811 CONV_TEXT_COLUMN
, markup
, -1);
6812 /* XXX seanegan Why do I have to do this? */
6813 gtk_widget_queue_draw(gtkconv
->infopane
);
6815 if (title
!= markup
)
6818 if (!GTK_WIDGET_REALIZED(gtkconv
->tab_label
))
6819 gtk_widget_realize(gtkconv
->tab_label
);
6821 accessibility_obj
= gtk_widget_get_accessible(gtkconv
->tab_cont
);
6823 purple_conv_im_get_typing_state(im
) == PURPLE_TYPING
) {
6824 atk_object_set_description(accessibility_obj
, _("Typing"));
6825 style
= "tab-label-typing";
6826 } else if (im
!= NULL
&&
6827 purple_conv_im_get_typing_state(im
) == PURPLE_TYPED
) {
6828 atk_object_set_description(accessibility_obj
, _("Stopped Typing"));
6829 style
= "tab-label-typed";
6830 } else if (gtkconv
->unseen_state
== PIDGIN_UNSEEN_NICK
) {
6831 atk_object_set_description(accessibility_obj
, _("Nick Said"));
6832 style
= "tab-label-attention";
6833 } else if (gtkconv
->unseen_state
== PIDGIN_UNSEEN_TEXT
) {
6834 atk_object_set_description(accessibility_obj
, _("Unread Messages"));
6835 if (gtkconv
->active_conv
->type
== PURPLE_CONV_TYPE_CHAT
)
6836 style
= "tab-label-unreadchat";
6838 style
= "tab-label-attention";
6839 } else if (gtkconv
->unseen_state
== PIDGIN_UNSEEN_EVENT
) {
6840 atk_object_set_description(accessibility_obj
, _("New Event"));
6841 style
= "tab-label-event";
6843 style
= "tab-label";
6846 gtk_widget_set_name(gtkconv
->tab_label
, style
);
6847 gtk_label_set_text(GTK_LABEL(gtkconv
->tab_label
), title
);
6848 gtk_widget_set_state(gtkconv
->tab_label
, GTK_STATE_ACTIVE
);
6850 if (gtkconv
->unseen_state
== PIDGIN_UNSEEN_TEXT
||
6851 gtkconv
->unseen_state
== PIDGIN_UNSEEN_NICK
||
6852 gtkconv
->unseen_state
== PIDGIN_UNSEEN_EVENT
) {
6853 PangoAttrList
*list
= pango_attr_list_new();
6854 PangoAttribute
*attr
= pango_attr_weight_new(PANGO_WEIGHT_BOLD
);
6855 attr
->start_index
= 0;
6856 attr
->end_index
= -1;
6857 pango_attr_list_insert(list
, attr
);
6858 gtk_label_set_attributes(GTK_LABEL(gtkconv
->tab_label
), list
);
6859 pango_attr_list_unref(list
);
6861 gtk_label_set_attributes(GTK_LABEL(gtkconv
->tab_label
), NULL
);
6863 if (pidgin_conv_window_is_active_conversation(conv
))
6864 update_typing_icon(gtkconv
);
6866 gtk_label_set_text(GTK_LABEL(gtkconv
->menu_label
), title
);
6867 if (pidgin_conv_window_is_active_conversation(conv
)) {
6868 const char* current_title
= gtk_window_get_title(GTK_WINDOW(win
->window
));
6869 if (current_title
== NULL
|| strcmp(current_title
, title
) != 0)
6870 gtk_window_set_title(GTK_WINDOW(win
->window
), title
);
6878 pidgin_conv_updated(PurpleConversation
*conv
, PurpleConvUpdateType type
)
6880 PidginConvFields flags
= 0;
6882 g_return_if_fail(conv
!= NULL
);
6884 if (type
== PURPLE_CONV_UPDATE_ACCOUNT
)
6886 flags
= PIDGIN_CONV_ALL
;
6888 else if (type
== PURPLE_CONV_UPDATE_TYPING
||
6889 type
== PURPLE_CONV_UPDATE_UNSEEN
||
6890 type
== PURPLE_CONV_UPDATE_TITLE
)
6892 flags
= PIDGIN_CONV_COLORIZE_TITLE
;
6894 else if (type
== PURPLE_CONV_UPDATE_TOPIC
)
6896 flags
= PIDGIN_CONV_TOPIC
;
6898 else if (type
== PURPLE_CONV_ACCOUNT_ONLINE
||
6899 type
== PURPLE_CONV_ACCOUNT_OFFLINE
)
6901 flags
= PIDGIN_CONV_MENU
| PIDGIN_CONV_TAB_ICON
| PIDGIN_CONV_SET_TITLE
;
6903 else if (type
== PURPLE_CONV_UPDATE_AWAY
)
6905 flags
= PIDGIN_CONV_TAB_ICON
;
6907 else if (type
== PURPLE_CONV_UPDATE_ADD
||
6908 type
== PURPLE_CONV_UPDATE_REMOVE
||
6909 type
== PURPLE_CONV_UPDATE_CHATLEFT
)
6911 flags
= PIDGIN_CONV_SET_TITLE
| PIDGIN_CONV_MENU
;
6913 else if (type
== PURPLE_CONV_UPDATE_ICON
)
6915 flags
= PIDGIN_CONV_BUDDY_ICON
;
6917 else if (type
== PURPLE_CONV_UPDATE_FEATURES
)
6919 flags
= PIDGIN_CONV_MENU
;
6922 pidgin_conv_update_fields(conv
, flags
);
6926 wrote_msg_update_unseen_cb(PurpleAccount
*account
, const char *who
, const char *message
,
6927 PurpleConversation
*conv
, PurpleMessageFlags flags
, gpointer null
)
6929 PidginConversation
*gtkconv
= conv
? PIDGIN_CONVERSATION(conv
) : NULL
;
6930 if (conv
== NULL
|| (gtkconv
&& gtkconv
->win
!= hidden_convwin
))
6932 if (flags
& (PURPLE_MESSAGE_SEND
| PURPLE_MESSAGE_RECV
)) {
6933 PidginUnseenState unseen
= PIDGIN_UNSEEN_NONE
;
6935 if ((flags
& PURPLE_MESSAGE_NICK
) == PURPLE_MESSAGE_NICK
)
6936 unseen
= PIDGIN_UNSEEN_NICK
;
6937 else if (((flags
& PURPLE_MESSAGE_SYSTEM
) == PURPLE_MESSAGE_SYSTEM
) ||
6938 ((flags
& PURPLE_MESSAGE_ERROR
) == PURPLE_MESSAGE_ERROR
))
6939 unseen
= PIDGIN_UNSEEN_EVENT
;
6940 else if ((flags
& PURPLE_MESSAGE_NO_LOG
) == PURPLE_MESSAGE_NO_LOG
)
6941 unseen
= PIDGIN_UNSEEN_NO_LOG
;
6943 unseen
= PIDGIN_UNSEEN_TEXT
;
6945 conv_set_unseen(conv
, unseen
);
6949 static PurpleConversationUiOps conversation_ui_ops
=
6952 pidgin_conv_destroy
, /* destroy_conversation */
6953 NULL
, /* write_chat */
6954 pidgin_conv_write_im
, /* write_im */
6955 pidgin_conv_write_conv
, /* write_conv */
6956 pidgin_conv_chat_add_users
, /* chat_add_users */
6957 pidgin_conv_chat_rename_user
, /* chat_rename_user */
6958 pidgin_conv_chat_remove_users
, /* chat_remove_users */
6959 pidgin_conv_chat_update_user
, /* chat_update_user */
6960 pidgin_conv_present_conversation
, /* present */
6961 pidgin_conv_has_focus
, /* has_focus */
6962 pidgin_conv_custom_smiley_add
, /* custom_smiley_add */
6963 pidgin_conv_custom_smiley_write
, /* custom_smiley_write */
6964 pidgin_conv_custom_smiley_close
, /* custom_smiley_close */
6965 pidgin_conv_send_confirm
, /* send_confirm */
6972 PurpleConversationUiOps
*
6973 pidgin_conversations_get_conv_ui_ops(void)
6975 return &conversation_ui_ops
;
6978 /**************************************************************************
6979 * Public conversation utility functions
6980 **************************************************************************/
6982 pidgin_conv_update_buddy_icon(PurpleConversation
*conv
)
6984 PidginConversation
*gtkconv
;
6989 PurpleStoredImage
*custom_img
= NULL
;
6990 gconstpointer data
= NULL
;
6998 int scale_width
, scale_height
;
7001 PurpleAccount
*account
;
7003 PurpleBuddyIcon
*icon
;
7005 g_return_if_fail(conv
!= NULL
);
7006 g_return_if_fail(PIDGIN_IS_PIDGIN_CONVERSATION(conv
));
7007 g_return_if_fail(purple_conversation_get_type(conv
) == PURPLE_CONV_TYPE_IM
);
7009 gtkconv
= PIDGIN_CONVERSATION(conv
);
7011 if (conv
!= gtkconv
->active_conv
)
7014 if (!gtkconv
->u
.im
->show_icon
)
7017 account
= purple_conversation_get_account(conv
);
7019 /* Remove the current icon stuff */
7020 children
= gtk_container_get_children(GTK_CONTAINER(gtkconv
->u
.im
->icon_container
));
7022 /* We know there's only one child here. It'd be nice to shortcut to the
7023 event box, but we can't change the PidginConversation until 3.0 */
7024 event
= (GtkWidget
*)children
->data
;
7025 gtk_container_remove(GTK_CONTAINER(gtkconv
->u
.im
->icon_container
), event
);
7026 g_list_free(children
);
7029 if (gtkconv
->u
.im
->anim
!= NULL
)
7030 g_object_unref(G_OBJECT(gtkconv
->u
.im
->anim
));
7032 gtkconv
->u
.im
->anim
= NULL
;
7034 if (gtkconv
->u
.im
->icon_timer
!= 0)
7035 g_source_remove(gtkconv
->u
.im
->icon_timer
);
7037 gtkconv
->u
.im
->icon_timer
= 0;
7039 if (gtkconv
->u
.im
->iter
!= NULL
)
7040 g_object_unref(G_OBJECT(gtkconv
->u
.im
->iter
));
7042 gtkconv
->u
.im
->iter
= NULL
;
7044 if (!purple_prefs_get_bool(PIDGIN_PREFS_ROOT
"/conversations/im/show_buddy_icons"))
7047 if (purple_conversation_get_gc(conv
) == NULL
)
7050 buddy
= purple_find_buddy(account
, purple_conversation_get_name(conv
));
7053 PurpleContact
*contact
= purple_buddy_get_contact(buddy
);
7055 custom_img
= purple_buddy_icons_node_find_custom_icon((PurpleBlistNode
*)contact
);
7057 /* There is a custom icon for this user */
7058 data
= purple_imgstore_get_data(custom_img
);
7059 len
= purple_imgstore_get_size(custom_img
);
7065 icon
= purple_conv_im_get_icon(PURPLE_CONV_IM(conv
));
7068 gtk_widget_set_size_request(gtkconv
->u
.im
->icon_container
,
7069 -1, BUDDYICON_SIZE_MIN
);
7073 data
= purple_buddy_icon_get_data(icon
, &len
);
7076 gtk_widget_set_size_request(gtkconv
->u
.im
->icon_container
,
7077 -1, BUDDYICON_SIZE_MIN
);
7082 gtkconv
->u
.im
->anim
= pidgin_pixbuf_anim_from_data(data
, len
);
7083 purple_imgstore_unref(custom_img
);
7085 if (!gtkconv
->u
.im
->anim
) {
7086 purple_debug_error("gtkconv", "Couldn't load icon for conv %s\n",
7087 purple_conversation_get_name(conv
));
7091 if (gdk_pixbuf_animation_is_static_image(gtkconv
->u
.im
->anim
)) {
7093 gtkconv
->u
.im
->iter
= NULL
;
7094 stat
= gdk_pixbuf_animation_get_static_image(gtkconv
->u
.im
->anim
);
7095 buf
= gdk_pixbuf_add_alpha(stat
, FALSE
, 0, 0, 0);
7098 gtkconv
->u
.im
->iter
=
7099 gdk_pixbuf_animation_get_iter(gtkconv
->u
.im
->anim
, NULL
); /* LEAK */
7100 stat
= gdk_pixbuf_animation_iter_get_pixbuf(gtkconv
->u
.im
->iter
);
7101 buf
= gdk_pixbuf_add_alpha(stat
, FALSE
, 0, 0, 0);
7102 if (gtkconv
->u
.im
->animate
)
7103 start_anim(NULL
, gtkconv
);
7106 scale_width
= gdk_pixbuf_get_width(buf
);
7107 scale_height
= gdk_pixbuf_get_height(buf
);
7109 gtk_widget_get_size_request(gtkconv
->u
.im
->icon_container
, NULL
, &size
);
7110 size
= MIN(size
, MIN(scale_width
, scale_height
));
7112 /* Some sanity checks */
7113 size
= CLAMP(size
, BUDDYICON_SIZE_MIN
, BUDDYICON_SIZE_MAX
);
7114 if (scale_width
== scale_height
) {
7115 scale_width
= scale_height
= size
;
7116 } else if (scale_height
> scale_width
) {
7117 scale_width
= size
* scale_width
/ scale_height
;
7118 scale_height
= size
;
7120 scale_height
= size
* scale_height
/ scale_width
;
7123 scale
= gdk_pixbuf_scale_simple(buf
, scale_width
, scale_height
,
7124 GDK_INTERP_BILINEAR
);
7125 g_object_unref(buf
);
7126 if (pidgin_gdk_pixbuf_is_opaque(scale
))
7127 pidgin_gdk_pixbuf_make_round(scale
);
7129 event
= gtk_event_box_new();
7130 gtk_container_add(GTK_CONTAINER(gtkconv
->u
.im
->icon_container
), event
);
7131 gtk_event_box_set_visible_window(GTK_EVENT_BOX(event
), FALSE
);
7132 gtk_widget_add_events(event
,
7133 GDK_POINTER_MOTION_MASK
| GDK_LEAVE_NOTIFY_MASK
);
7134 g_signal_connect(G_OBJECT(event
), "button-press-event",
7135 G_CALLBACK(icon_menu
), gtkconv
);
7137 pidgin_tooltip_setup_for_widget(event
, gtkconv
, pidgin_conv_create_tooltip
, NULL
);
7138 gtk_widget_show(event
);
7140 gtkconv
->u
.im
->icon
= gtk_image_new_from_pixbuf(scale
);
7141 gtk_container_add(GTK_CONTAINER(event
), gtkconv
->u
.im
->icon
);
7142 gtk_widget_show(gtkconv
->u
.im
->icon
);
7144 g_object_unref(G_OBJECT(scale
));
7146 /* The buddy icon code needs badly to be fixed. */
7147 if(pidgin_conv_window_is_active_conversation(conv
))
7149 buf
= gdk_pixbuf_animation_get_static_image(gtkconv
->u
.im
->anim
);
7150 if (buddy
&& !PURPLE_BUDDY_IS_ONLINE(buddy
))
7151 gdk_pixbuf_saturate_and_pixelate(buf
, buf
, 0.0, FALSE
);
7152 gtk_window_set_icon(GTK_WINDOW(win
->window
), buf
);
7157 pidgin_conv_update_buttons_by_protocol(PurpleConversation
*conv
)
7161 if (!PIDGIN_IS_PIDGIN_CONVERSATION(conv
))
7164 win
= PIDGIN_CONVERSATION(conv
)->win
;
7166 if (win
!= NULL
&& pidgin_conv_window_is_active_conversation(conv
))
7167 gray_stuff_out(PIDGIN_CONVERSATION(conv
));
7171 pidgin_conv_xy_to_right_infopane(PidginWindow
*win
, int x
, int y
)
7173 gint pane_x
, pane_y
, x_rel
;
7174 PidginConversation
*gtkconv
;
7176 gdk_window_get_origin(win
->notebook
->window
, &pane_x
, &pane_y
);
7178 gtkconv
= pidgin_conv_window_get_active_gtkconv(win
);
7179 return (x_rel
> gtkconv
->infopane
->allocation
.x
+ gtkconv
->infopane
->allocation
.width
/ 2);
7183 pidgin_conv_get_tab_at_xy(PidginWindow
*win
, int x
, int y
, gboolean
*to_right
)
7185 gint nb_x
, nb_y
, x_rel
, y_rel
;
7186 GtkNotebook
*notebook
;
7187 GtkWidget
*page
, *tab
;
7188 gint i
, page_num
= -1;
7195 notebook
= GTK_NOTEBOOK(win
->notebook
);
7197 gdk_window_get_origin(win
->notebook
->window
, &nb_x
, &nb_y
);
7201 horiz
= (gtk_notebook_get_tab_pos(notebook
) == GTK_POS_TOP
||
7202 gtk_notebook_get_tab_pos(notebook
) == GTK_POS_BOTTOM
);
7204 count
= gtk_notebook_get_n_pages(GTK_NOTEBOOK(notebook
));
7206 for (i
= 0; i
< count
; i
++) {
7208 page
= gtk_notebook_get_nth_page(GTK_NOTEBOOK(notebook
), i
);
7209 tab
= gtk_notebook_get_tab_label(GTK_NOTEBOOK(notebook
), page
);
7211 /* Make sure the tab is not hidden beyond an arrow */
7212 if (!GTK_WIDGET_DRAWABLE(tab
) && gtk_notebook_get_show_tabs(notebook
))
7216 if (x_rel
>= tab
->allocation
.x
- PIDGIN_HIG_BOX_SPACE
&&
7217 x_rel
<= tab
->allocation
.x
+ tab
->allocation
.width
+ PIDGIN_HIG_BOX_SPACE
) {
7220 if (to_right
&& x_rel
>= tab
->allocation
.x
+ tab
->allocation
.width
/2)
7226 if (y_rel
>= tab
->allocation
.y
- PIDGIN_HIG_BOX_SPACE
&&
7227 y_rel
<= tab
->allocation
.y
+ tab
->allocation
.height
+ PIDGIN_HIG_BOX_SPACE
) {
7230 if (to_right
&& y_rel
>= tab
->allocation
.y
+ tab
->allocation
.height
/2)
7238 if (page_num
== -1) {
7239 /* Add after the last tab */
7240 page_num
= count
- 1;
7247 close_on_tabs_pref_cb(const char *name
, PurplePrefType type
,
7248 gconstpointer value
, gpointer data
)
7251 PurpleConversation
*conv
;
7252 PidginConversation
*gtkconv
;
7254 for (l
= purple_get_conversations(); l
!= NULL
; l
= l
->next
) {
7255 conv
= (PurpleConversation
*)l
->data
;
7257 if (!PIDGIN_IS_PIDGIN_CONVERSATION(conv
))
7260 gtkconv
= PIDGIN_CONVERSATION(conv
);
7263 gtk_widget_show(gtkconv
->close
);
7265 gtk_widget_hide(gtkconv
->close
);
7270 spellcheck_pref_cb(const char *name
, PurplePrefType type
,
7271 gconstpointer value
, gpointer data
)
7275 PurpleConversation
*conv
;
7276 PidginConversation
*gtkconv
;
7279 for (cl
= purple_get_conversations(); cl
!= NULL
; cl
= cl
->next
) {
7281 conv
= (PurpleConversation
*)cl
->data
;
7283 if (!PIDGIN_IS_PIDGIN_CONVERSATION(conv
))
7286 gtkconv
= PIDGIN_CONVERSATION(conv
);
7289 pidgin_setup_gtkspell(GTK_TEXT_VIEW(gtkconv
->entry
));
7291 spell
= gtkspell_get_from_text_view(GTK_TEXT_VIEW(gtkconv
->entry
));
7293 gtkspell_detach(spell
);
7300 tab_side_pref_cb(const char *name
, PurplePrefType type
,
7301 gconstpointer value
, gpointer data
)
7303 GList
*gtkwins
, *gtkconvs
;
7304 GtkPositionType pos
;
7305 PidginWindow
*gtkwin
;
7307 pos
= GPOINTER_TO_INT(value
);
7309 for (gtkwins
= pidgin_conv_windows_get_list(); gtkwins
!= NULL
; gtkwins
= gtkwins
->next
) {
7310 gtkwin
= gtkwins
->data
;
7311 gtk_notebook_set_tab_pos(GTK_NOTEBOOK(gtkwin
->notebook
), pos
&~8);
7312 for (gtkconvs
= gtkwin
->gtkconvs
; gtkconvs
!= NULL
; gtkconvs
= gtkconvs
->next
) {
7313 pidgin_conv_tab_pack(gtkwin
, gtkconvs
->data
);
7319 show_timestamps_pref_cb(const char *name
, PurplePrefType type
,
7320 gconstpointer value
, gpointer data
)
7323 PurpleConversation
*conv
;
7324 PidginConversation
*gtkconv
;
7327 for (l
= purple_get_conversations(); l
!= NULL
; l
= l
->next
)
7329 conv
= (PurpleConversation
*)l
->data
;
7331 if (!PIDGIN_IS_PIDGIN_CONVERSATION(conv
))
7334 gtkconv
= PIDGIN_CONVERSATION(conv
);
7337 gtk_check_menu_item_set_active(
7338 GTK_CHECK_MENU_ITEM(win
->menu
.show_timestamps
),
7339 (gboolean
)GPOINTER_TO_INT(value
));
7341 gtk_imhtml_show_comments(GTK_IMHTML(gtkconv
->imhtml
),
7342 (gboolean
)GPOINTER_TO_INT(value
));
7347 show_formatting_toolbar_pref_cb(const char *name
, PurplePrefType type
,
7348 gconstpointer value
, gpointer data
)
7351 PurpleConversation
*conv
;
7352 PidginConversation
*gtkconv
;
7355 for (l
= purple_get_conversations(); l
!= NULL
; l
= l
->next
)
7357 conv
= (PurpleConversation
*)l
->data
;
7359 if (!PIDGIN_IS_PIDGIN_CONVERSATION(conv
))
7362 gtkconv
= PIDGIN_CONVERSATION(conv
);
7365 gtk_check_menu_item_set_active(
7366 GTK_CHECK_MENU_ITEM(win
->menu
.show_formatting_toolbar
),
7367 (gboolean
)GPOINTER_TO_INT(value
));
7369 if ((gboolean
)GPOINTER_TO_INT(value
))
7370 gtk_widget_show(gtkconv
->toolbar
);
7372 gtk_widget_hide(gtkconv
->toolbar
);
7374 g_idle_add((GSourceFunc
)resize_imhtml_cb
,gtkconv
);
7379 animate_buddy_icons_pref_cb(const char *name
, PurplePrefType type
,
7380 gconstpointer value
, gpointer data
)
7383 PurpleConversation
*conv
;
7384 PidginConversation
*gtkconv
;
7387 if (!purple_prefs_get_bool(PIDGIN_PREFS_ROOT
"/conversations/im/show_buddy_icons"))
7390 /* Set the "animate" flag for each icon based on the new preference */
7391 for (l
= purple_get_ims(); l
!= NULL
; l
= l
->next
) {
7392 conv
= (PurpleConversation
*)l
->data
;
7393 gtkconv
= PIDGIN_CONVERSATION(conv
);
7395 gtkconv
->u
.im
->animate
= GPOINTER_TO_INT(value
);
7398 /* Now either stop or start animation for the active conversation in each window */
7399 for (l
= pidgin_conv_windows_get_list(); l
!= NULL
; l
= l
->next
) {
7401 conv
= pidgin_conv_window_get_active_conversation(win
);
7402 pidgin_conv_update_buddy_icon(conv
);
7407 show_buddy_icons_pref_cb(const char *name
, PurplePrefType type
,
7408 gconstpointer value
, gpointer data
)
7412 for (l
= purple_get_conversations(); l
!= NULL
; l
= l
->next
) {
7413 PurpleConversation
*conv
= l
->data
;
7414 if (!PIDGIN_CONVERSATION(conv
))
7416 if (GPOINTER_TO_INT(value
))
7417 gtk_widget_show(PIDGIN_CONVERSATION(conv
)->infopane_hbox
);
7419 gtk_widget_hide(PIDGIN_CONVERSATION(conv
)->infopane_hbox
);
7421 if (purple_conversation_get_type(conv
) == PURPLE_CONV_TYPE_IM
) {
7422 pidgin_conv_update_buddy_icon(conv
);
7426 /* Make the tabs show/hide correctly */
7427 for (l
= pidgin_conv_windows_get_list(); l
!= NULL
; l
= l
->next
) {
7428 PidginWindow
*win
= l
->data
;
7429 if (pidgin_conv_window_get_gtkconv_count(win
) == 1)
7430 gtk_notebook_set_show_tabs(GTK_NOTEBOOK(win
->notebook
),
7431 GPOINTER_TO_INT(value
) == 0);
7436 show_protocol_icons_pref_cb(const char *name
, PurplePrefType type
,
7437 gconstpointer value
, gpointer data
)
7440 for (l
= purple_get_conversations(); l
!= NULL
; l
= l
->next
) {
7441 PurpleConversation
*conv
= l
->data
;
7442 if (PIDGIN_CONVERSATION(conv
))
7443 update_tab_icon(conv
);
7448 conv_placement_usetabs_cb(const char *name
, PurplePrefType type
,
7449 gconstpointer value
, gpointer data
)
7451 purple_prefs_trigger_callback(PIDGIN_PREFS_ROOT
"/conversations/placement");
7455 account_status_changed_cb(PurpleAccount
*account
, PurpleStatus
*oldstatus
,
7456 PurpleStatus
*newstatus
)
7459 PurpleConversation
*conv
= NULL
;
7460 PidginConversation
*gtkconv
;
7462 if(strcmp(purple_prefs_get_string(PIDGIN_PREFS_ROOT
"/conversations/im/hide_new"), "away")!=0)
7465 if(purple_status_is_available(oldstatus
) || !purple_status_is_available(newstatus
))
7468 for (l
= hidden_convwin
->gtkconvs
; l
; ) {
7472 conv
= gtkconv
->active_conv
;
7473 if (purple_conversation_get_type(conv
) == PURPLE_CONV_TYPE_CHAT
||
7474 account
!= purple_conversation_get_account(conv
))
7477 pidgin_conv_attach_to_conversation(conv
);
7479 /* TODO: do we need to do anything for any other conversations that are in the same gtkconv here?
7480 * I'm a little concerned that not doing so will cause the "pending" indicator in the gtkblist not to be cleared. -DAA*/
7481 purple_conversation_update(conv
, PURPLE_CONV_UPDATE_UNSEEN
);
7486 hide_new_pref_cb(const char *name
, PurplePrefType type
,
7487 gconstpointer value
, gpointer data
)
7490 PurpleConversation
*conv
= NULL
;
7491 PidginConversation
*gtkconv
;
7492 gboolean when_away
= FALSE
;
7497 if(strcmp(purple_prefs_get_string(PIDGIN_PREFS_ROOT
"/conversations/im/hide_new"), "always")==0)
7500 if(strcmp(purple_prefs_get_string(PIDGIN_PREFS_ROOT
"/conversations/im/hide_new"), "away")==0)
7503 for (l
= hidden_convwin
->gtkconvs
; l
; )
7508 conv
= gtkconv
->active_conv
;
7510 if (conv
->type
== PURPLE_CONV_TYPE_CHAT
||
7511 gtkconv
->unseen_count
== 0 ||
7512 (when_away
&& !purple_status_is_available(
7513 purple_account_get_active_status(
7514 purple_conversation_get_account(conv
)))))
7517 pidgin_conv_attach_to_conversation(conv
);
7523 conv_placement_pref_cb(const char *name
, PurplePrefType type
,
7524 gconstpointer value
, gpointer data
)
7526 PidginConvPlacementFunc func
;
7528 if (strcmp(name
, PIDGIN_PREFS_ROOT
"/conversations/placement"))
7531 func
= pidgin_conv_placement_get_fnc(value
);
7536 pidgin_conv_placement_set_current_func(func
);
7539 static PidginConversation
*
7540 get_gtkconv_with_contact(PurpleContact
*contact
)
7542 PurpleBlistNode
*node
;
7544 node
= ((PurpleBlistNode
*)contact
)->child
;
7546 for (; node
; node
= node
->next
)
7548 PurpleBuddy
*buddy
= (PurpleBuddy
*)node
;
7549 PurpleConversation
*conv
;
7550 conv
= purple_find_conversation_with_account(PURPLE_CONV_TYPE_IM
, buddy
->name
, buddy
->account
);
7552 return PIDGIN_CONVERSATION(conv
);
7558 account_signed_off_cb(PurpleConnection
*gc
, gpointer event
)
7562 for (iter
= purple_get_conversations(); iter
; iter
= iter
->next
)
7564 PurpleConversation
*conv
= iter
->data
;
7566 /* This seems fine in theory, but we also need to cover the
7567 * case of this account matching one of the other buddies in
7568 * one of the contacts containing the buddy corresponding to
7569 * a conversation. It's easier to just update them all. */
7570 /* if (purple_conversation_get_account(conv) == account) */
7571 pidgin_conv_update_fields(conv
, PIDGIN_CONV_TAB_ICON
|
7572 PIDGIN_CONV_MENU
| PIDGIN_CONV_COLORIZE_TITLE
);
7574 if (PURPLE_CONNECTION_IS_CONNECTED(gc
) &&
7575 conv
->type
== PURPLE_CONV_TYPE_CHAT
&&
7576 conv
->account
== gc
->account
&&
7577 purple_conversation_get_data(conv
, "want-to-rejoin")) {
7578 GHashTable
*comps
= NULL
;
7579 PurpleChat
*chat
= purple_blist_find_chat(conv
->account
, conv
->name
);
7581 if (PURPLE_PLUGIN_PROTOCOL_INFO(gc
->prpl
)->chat_info_defaults
!= NULL
)
7582 comps
= PURPLE_PLUGIN_PROTOCOL_INFO(gc
->prpl
)->chat_info_defaults(gc
, conv
->name
);
7584 comps
= chat
->components
;
7586 serv_join_chat(gc
, comps
);
7587 if (chat
== NULL
&& comps
!= NULL
)
7588 g_hash_table_destroy(comps
);
7594 account_signing_off(PurpleConnection
*gc
)
7596 GList
*list
= purple_get_chats();
7597 PurpleAccount
*account
= purple_connection_get_account(gc
);
7599 /* We are about to sign off. See which chats we are currently in, and mark
7600 * them for rejoin on reconnect. */
7602 PurpleConversation
*conv
= list
->data
;
7603 if (!purple_conv_chat_has_left(PURPLE_CONV_CHAT(conv
)) &&
7604 purple_conversation_get_account(conv
) == account
) {
7605 purple_conversation_set_data(conv
, "want-to-rejoin", GINT_TO_POINTER(TRUE
));
7606 purple_conversation_write(conv
, NULL
, _("The account has disconnected and you are no "
7607 "longer in this chat. You will automatically rejoin the chat when "
7608 "the account reconnects."),
7609 PURPLE_MESSAGE_SYSTEM
, time(NULL
));
7616 update_buddy_status_changed(PurpleBuddy
*buddy
, PurpleStatus
*old
, PurpleStatus
*newstatus
)
7618 PidginConversation
*gtkconv
;
7619 PurpleConversation
*conv
;
7621 gtkconv
= get_gtkconv_with_contact(purple_buddy_get_contact(buddy
));
7624 conv
= gtkconv
->active_conv
;
7625 pidgin_conv_update_fields(conv
, PIDGIN_CONV_TAB_ICON
7626 | PIDGIN_CONV_COLORIZE_TITLE
7627 | PIDGIN_CONV_BUDDY_ICON
);
7628 if ((purple_status_is_online(old
) ^ purple_status_is_online(newstatus
)) != 0)
7629 pidgin_conv_update_fields(conv
, PIDGIN_CONV_MENU
);
7634 update_buddy_privacy_changed(PurpleBuddy
*buddy
)
7636 PidginConversation
*gtkconv
;
7637 PurpleConversation
*conv
;
7639 gtkconv
= get_gtkconv_with_contact(purple_buddy_get_contact(buddy
));
7641 conv
= gtkconv
->active_conv
;
7642 pidgin_conv_update_fields(conv
, PIDGIN_CONV_TAB_ICON
| PIDGIN_CONV_MENU
);
7647 update_buddy_idle_changed(PurpleBuddy
*buddy
, gboolean old
, gboolean newidle
)
7649 PurpleConversation
*conv
;
7651 conv
= purple_find_conversation_with_account(PURPLE_CONV_TYPE_IM
, buddy
->name
, buddy
->account
);
7653 pidgin_conv_update_fields(conv
, PIDGIN_CONV_TAB_ICON
);
7657 update_buddy_icon(PurpleBuddy
*buddy
)
7659 PurpleConversation
*conv
;
7661 conv
= purple_find_conversation_with_account(PURPLE_CONV_TYPE_IM
, buddy
->name
, buddy
->account
);
7663 pidgin_conv_update_fields(conv
, PIDGIN_CONV_BUDDY_ICON
);
7667 update_buddy_sign(PurpleBuddy
*buddy
, const char *which
)
7669 PurplePresence
*presence
;
7670 PurpleStatus
*on
, *off
;
7672 presence
= purple_buddy_get_presence(buddy
);
7675 off
= purple_presence_get_status(presence
, "offline");
7676 on
= purple_presence_get_status(presence
, "available");
7678 if (*(which
+1) == 'f')
7679 update_buddy_status_changed(buddy
, on
, off
);
7681 update_buddy_status_changed(buddy
, off
, on
);
7685 update_conversation_switched(PurpleConversation
*conv
)
7687 pidgin_conv_update_fields(conv
, PIDGIN_CONV_TAB_ICON
| PIDGIN_CONV_SET_TITLE
|
7688 PIDGIN_CONV_MENU
| PIDGIN_CONV_BUDDY_ICON
);
7692 update_buddy_typing(PurpleAccount
*account
, const char *who
)
7694 PurpleConversation
*conv
;
7695 PidginConversation
*gtkconv
;
7697 conv
= purple_find_conversation_with_account(PURPLE_CONV_TYPE_IM
, who
, account
);
7701 gtkconv
= PIDGIN_CONVERSATION(conv
);
7702 if (gtkconv
&& gtkconv
->active_conv
== conv
)
7703 pidgin_conv_update_fields(conv
, PIDGIN_CONV_COLORIZE_TITLE
);
7707 update_chat(PurpleConversation
*conv
)
7709 pidgin_conv_update_fields(conv
, PIDGIN_CONV_TOPIC
|
7710 PIDGIN_CONV_MENU
| PIDGIN_CONV_SET_TITLE
);
7714 update_chat_topic(PurpleConversation
*conv
, const char *old
, const char *new)
7716 pidgin_conv_update_fields(conv
, PIDGIN_CONV_TOPIC
);
7719 /* Message history stuff */
7721 /* Compare two PurpleConvMessage's, according to time in ascending order. */
7723 message_compare(gconstpointer p1
, gconstpointer p2
)
7725 const PurpleConvMessage
*m1
= p1
, *m2
= p2
;
7726 return (m1
->when
> m2
->when
);
7729 /* Adds some message history to the gtkconv. This happens in a idle-callback. */
7731 add_message_history_to_gtkconv(gpointer data
)
7733 PidginConversation
*gtkconv
= data
;
7735 int timer
= gtkconv
->attach
.timer
;
7736 time_t when
= GPOINTER_TO_INT(g_object_get_data(G_OBJECT(gtkconv
->entry
), "attach-start-time"));
7737 gboolean im
= (gtkconv
->active_conv
->type
== PURPLE_CONV_TYPE_IM
);
7739 gtkconv
->attach
.timer
= 0;
7740 while (gtkconv
->attach
.current
&& count
< 100) { /* XXX: 100 is a random value here */
7741 PurpleConvMessage
*msg
= gtkconv
->attach
.current
->data
;
7742 if (!im
&& when
&& when
< msg
->when
) {
7743 gtk_imhtml_append_text(GTK_IMHTML(gtkconv
->imhtml
), "<BR><HR>", 0);
7744 g_object_set_data(G_OBJECT(gtkconv
->entry
), "attach-start-time", NULL
);
7746 pidgin_conv_write_conv(msg
->conv
, msg
->who
, msg
->alias
, msg
->what
, msg
->flags
, msg
->when
);
7748 gtkconv
->attach
.current
= g_list_delete_link(gtkconv
->attach
.current
, gtkconv
->attach
.current
);
7750 gtkconv
->attach
.current
= gtkconv
->attach
.current
->prev
;
7754 gtkconv
->attach
.timer
= timer
;
7755 if (gtkconv
->attach
.current
)
7758 g_source_remove(gtkconv
->attach
.timer
);
7759 gtkconv
->attach
.timer
= 0;
7761 /* Print any message that was sent while the old history was being added back. */
7763 GList
*iter
= gtkconv
->convs
;
7764 for (; iter
; iter
= iter
->next
) {
7765 PurpleConversation
*conv
= iter
->data
;
7766 GList
*history
= purple_conversation_get_message_history(conv
);
7767 for (; history
; history
= history
->next
) {
7768 PurpleConvMessage
*msg
= history
->data
;
7769 if (msg
->when
> when
)
7770 msgs
= g_list_prepend(msgs
, msg
);
7773 msgs
= g_list_sort(msgs
, message_compare
);
7774 for (; msgs
; msgs
= g_list_delete_link(msgs
, msgs
)) {
7775 PurpleConvMessage
*msg
= msgs
->data
;
7776 pidgin_conv_write_conv(msg
->conv
, msg
->who
, msg
->alias
, msg
->what
, msg
->flags
, msg
->when
);
7778 gtk_imhtml_append_text(GTK_IMHTML(gtkconv
->imhtml
), "<BR><HR>", 0);
7779 g_object_set_data(G_OBJECT(gtkconv
->entry
), "attach-start-time", NULL
);
7782 g_object_set_data(G_OBJECT(gtkconv
->entry
), "attach-start-time", NULL
);
7783 purple_signal_emit(pidgin_conversations_get_handle(),
7784 "conversation-displayed", gtkconv
);
7789 pidgin_conv_attach(PurpleConversation
*conv
)
7792 purple_conversation_set_data(conv
, "unseen-count", NULL
);
7793 purple_conversation_set_data(conv
, "unseen-state", NULL
);
7794 purple_conversation_set_ui_ops(conv
, pidgin_conversations_get_conv_ui_ops());
7795 if (!PIDGIN_CONVERSATION(conv
))
7796 private_gtkconv_new(conv
, FALSE
);
7797 timer
= GPOINTER_TO_INT(purple_conversation_get_data(conv
, "close-timer"));
7799 purple_timeout_remove(timer
);
7800 purple_conversation_set_data(conv
, "close-timer", NULL
);
7804 gboolean
pidgin_conv_attach_to_conversation(PurpleConversation
*conv
)
7807 PidginConversation
*gtkconv
;
7809 if (PIDGIN_IS_PIDGIN_CONVERSATION(conv
)) {
7810 /* This is pretty much always the case now. */
7811 gtkconv
= PIDGIN_CONVERSATION(conv
);
7812 if (gtkconv
->win
!= hidden_convwin
)
7814 pidgin_conv_window_remove_gtkconv(hidden_convwin
, gtkconv
);
7815 pidgin_conv_placement_place(gtkconv
);
7816 purple_signal_emit(pidgin_conversations_get_handle(),
7817 "conversation-displayed", gtkconv
);
7818 list
= gtkconv
->convs
;
7820 pidgin_conv_attach(list
->data
);
7826 pidgin_conv_attach(conv
);
7827 gtkconv
= PIDGIN_CONVERSATION(conv
);
7829 list
= purple_conversation_get_message_history(conv
);
7831 switch (purple_conversation_get_type(conv
)) {
7832 case PURPLE_CONV_TYPE_IM
:
7835 list
= g_list_copy(list
);
7836 for (convs
= purple_get_ims(); convs
; convs
= convs
->next
)
7837 if (convs
->data
!= conv
&&
7838 pidgin_conv_find_gtkconv(convs
->data
) == gtkconv
) {
7839 pidgin_conv_attach(convs
->data
);
7840 list
= g_list_concat(list
, g_list_copy(purple_conversation_get_message_history(convs
->data
)));
7842 list
= g_list_sort(list
, message_compare
);
7843 gtkconv
->attach
.current
= list
;
7844 list
= g_list_last(list
);
7847 case PURPLE_CONV_TYPE_CHAT
:
7848 gtkconv
->attach
.current
= g_list_last(list
);
7851 g_return_val_if_reached(TRUE
);
7853 g_object_set_data(G_OBJECT(gtkconv
->entry
), "attach-start-time",
7854 GINT_TO_POINTER(((PurpleConvMessage
*)(list
->data
))->when
));
7855 gtkconv
->attach
.timer
= g_idle_add(add_message_history_to_gtkconv
, gtkconv
);
7857 purple_signal_emit(pidgin_conversations_get_handle(),
7858 "conversation-displayed", gtkconv
);
7861 if (conv
->type
== PURPLE_CONV_TYPE_CHAT
) {
7862 pidgin_conv_update_fields(conv
, PIDGIN_CONV_TOPIC
);
7863 pidgin_conv_chat_add_users(conv
, PURPLE_CONV_CHAT(conv
)->in_room
, TRUE
);
7870 pidgin_conversations_get_handle(void)
7878 pidgin_conversations_init(void)
7880 void *handle
= pidgin_conversations_get_handle();
7881 void *blist_handle
= purple_blist_get_handle();
7884 purple_prefs_add_none(PIDGIN_PREFS_ROOT
"/conversations");
7885 purple_prefs_add_bool(PIDGIN_PREFS_ROOT
"/conversations/use_smooth_scrolling", TRUE
);
7886 purple_prefs_add_bool(PIDGIN_PREFS_ROOT
"/conversations/close_on_tabs", TRUE
);
7887 purple_prefs_add_bool(PIDGIN_PREFS_ROOT
"/conversations/send_bold", FALSE
);
7888 purple_prefs_add_bool(PIDGIN_PREFS_ROOT
"/conversations/send_italic", FALSE
);
7889 purple_prefs_add_bool(PIDGIN_PREFS_ROOT
"/conversations/send_underline", FALSE
);
7890 purple_prefs_add_bool(PIDGIN_PREFS_ROOT
"/conversations/spellcheck", TRUE
);
7891 purple_prefs_add_bool(PIDGIN_PREFS_ROOT
"/conversations/show_incoming_formatting", TRUE
);
7892 purple_prefs_add_bool(PIDGIN_PREFS_ROOT
"/conversations/resize_custom_smileys", TRUE
);
7893 purple_prefs_add_int(PIDGIN_PREFS_ROOT
"/conversations/custom_smileys_size", 96);
7894 purple_prefs_add_int(PIDGIN_PREFS_ROOT
"/conversations/minimum_entry_lines", 2);
7896 purple_prefs_add_bool(PIDGIN_PREFS_ROOT
"/conversations/show_timestamps", TRUE
);
7897 purple_prefs_add_bool(PIDGIN_PREFS_ROOT
"/conversations/show_formatting_toolbar", TRUE
);
7899 purple_prefs_add_string(PIDGIN_PREFS_ROOT
"/conversations/placement", "last");
7900 purple_prefs_add_int(PIDGIN_PREFS_ROOT
"/conversations/placement_number", 1);
7901 purple_prefs_add_string(PIDGIN_PREFS_ROOT
"/conversations/bgcolor", "");
7902 purple_prefs_add_string(PIDGIN_PREFS_ROOT
"/conversations/fgcolor", "");
7903 purple_prefs_add_string(PIDGIN_PREFS_ROOT
"/conversations/font_face", "");
7904 purple_prefs_add_int(PIDGIN_PREFS_ROOT
"/conversations/font_size", 3);
7905 purple_prefs_add_bool(PIDGIN_PREFS_ROOT
"/conversations/tabs", TRUE
);
7906 purple_prefs_add_int(PIDGIN_PREFS_ROOT
"/conversations/tab_side", GTK_POS_TOP
);
7907 purple_prefs_add_int(PIDGIN_PREFS_ROOT
"/conversations/scrollback_lines", 4000);
7910 purple_prefs_add_bool(PIDGIN_PREFS_ROOT
"/conversations/use_theme_font", TRUE
);
7911 purple_prefs_add_string(PIDGIN_PREFS_ROOT
"/conversations/custom_font", "");
7914 /* Conversations -> Chat */
7915 purple_prefs_add_none(PIDGIN_PREFS_ROOT
"/conversations/chat");
7916 purple_prefs_add_int(PIDGIN_PREFS_ROOT
"/conversations/chat/entry_height", 54);
7917 purple_prefs_add_int(PIDGIN_PREFS_ROOT
"/conversations/chat/userlist_width", 80);
7918 purple_prefs_add_int(PIDGIN_PREFS_ROOT
"/conversations/chat/x", 0);
7919 purple_prefs_add_int(PIDGIN_PREFS_ROOT
"/conversations/chat/y", 0);
7920 purple_prefs_add_int(PIDGIN_PREFS_ROOT
"/conversations/chat/width", 340);
7921 purple_prefs_add_int(PIDGIN_PREFS_ROOT
"/conversations/chat/height", 390);
7923 /* Conversations -> IM */
7924 purple_prefs_add_none(PIDGIN_PREFS_ROOT
"/conversations/im");
7925 purple_prefs_add_int(PIDGIN_PREFS_ROOT
"/conversations/im/x", 0);
7926 purple_prefs_add_int(PIDGIN_PREFS_ROOT
"/conversations/im/y", 0);
7927 purple_prefs_add_int(PIDGIN_PREFS_ROOT
"/conversations/im/width", 340);
7928 purple_prefs_add_int(PIDGIN_PREFS_ROOT
"/conversations/im/height", 390);
7930 purple_prefs_add_bool(PIDGIN_PREFS_ROOT
"/conversations/im/animate_buddy_icons", TRUE
);
7932 purple_prefs_add_int(PIDGIN_PREFS_ROOT
"/conversations/im/entry_height", 54);
7933 purple_prefs_add_bool(PIDGIN_PREFS_ROOT
"/conversations/im/show_buddy_icons", TRUE
);
7935 purple_prefs_add_string(PIDGIN_PREFS_ROOT
"/conversations/im/hide_new", "never");
7936 purple_prefs_add_bool(PIDGIN_PREFS_ROOT
"/conversations/im/close_immediately", TRUE
);
7939 purple_prefs_add_bool(PIDGIN_PREFS_ROOT
"/win32/minimize_new_convs", FALSE
);
7942 /* Connect callbacks. */
7943 purple_prefs_connect_callback(handle
, PIDGIN_PREFS_ROOT
"/conversations/close_on_tabs",
7944 close_on_tabs_pref_cb
, NULL
);
7945 purple_prefs_connect_callback(handle
, PIDGIN_PREFS_ROOT
"/conversations/show_timestamps",
7946 show_timestamps_pref_cb
, NULL
);
7947 purple_prefs_connect_callback(handle
, PIDGIN_PREFS_ROOT
"/conversations/show_formatting_toolbar",
7948 show_formatting_toolbar_pref_cb
, NULL
);
7949 purple_prefs_connect_callback(handle
, PIDGIN_PREFS_ROOT
"/conversations/spellcheck",
7950 spellcheck_pref_cb
, NULL
);
7951 purple_prefs_connect_callback(handle
, PIDGIN_PREFS_ROOT
"/conversations/tab_side",
7952 tab_side_pref_cb
, NULL
);
7954 purple_prefs_connect_callback(handle
, PIDGIN_PREFS_ROOT
"/conversations/tabs",
7955 conv_placement_usetabs_cb
, NULL
);
7957 purple_prefs_connect_callback(handle
, PIDGIN_PREFS_ROOT
"/conversations/placement",
7958 conv_placement_pref_cb
, NULL
);
7959 purple_prefs_trigger_callback(PIDGIN_PREFS_ROOT
"/conversations/placement");
7961 purple_prefs_connect_callback(handle
, PIDGIN_PREFS_ROOT
"/conversations/minimum_entry_lines",
7962 minimum_entry_lines_pref_cb
, NULL
);
7965 purple_prefs_connect_callback(handle
, PIDGIN_PREFS_ROOT
"/conversations/im/animate_buddy_icons",
7966 animate_buddy_icons_pref_cb
, NULL
);
7967 purple_prefs_connect_callback(handle
, PIDGIN_PREFS_ROOT
"/conversations/im/show_buddy_icons",
7968 show_buddy_icons_pref_cb
, NULL
);
7969 purple_prefs_connect_callback(handle
, PIDGIN_PREFS_ROOT
"/blist/show_protocol_icons",
7970 show_protocol_icons_pref_cb
, NULL
);
7971 purple_prefs_connect_callback(handle
, PIDGIN_PREFS_ROOT
"/conversations/im/hide_new",
7972 hide_new_pref_cb
, NULL
);
7976 /**********************************************************************
7978 **********************************************************************/
7979 purple_signal_register(handle
, "conversation-dragging",
7980 purple_marshal_VOID__POINTER_POINTER
, NULL
, 2,
7981 purple_value_new(PURPLE_TYPE_BOXED
,
7983 purple_value_new(PURPLE_TYPE_BOXED
,
7986 purple_signal_register(handle
, "conversation-timestamp",
7987 #if SIZEOF_TIME_T == 4
7988 purple_marshal_POINTER__POINTER_INT_BOOLEAN
,
7989 #elif SIZEOF_TIME_T == 8
7990 purple_marshal_POINTER__POINTER_INT64_BOOLEAN
,
7992 #error Unkown size of time_t
7994 purple_value_new(PURPLE_TYPE_STRING
), 3,
7995 purple_value_new(PURPLE_TYPE_SUBTYPE
,
7996 PURPLE_SUBTYPE_CONVERSATION
),
7997 #if SIZEOF_TIME_T == 4
7998 purple_value_new(PURPLE_TYPE_INT
),
7999 #elif SIZEOF_TIME_T == 8
8000 purple_value_new(PURPLE_TYPE_INT64
),
8002 # error Unknown size of time_t
8004 purple_value_new(PURPLE_TYPE_BOOLEAN
));
8006 purple_signal_register(handle
, "displaying-im-msg",
8007 purple_marshal_BOOLEAN__POINTER_POINTER_POINTER_POINTER_POINTER
,
8008 purple_value_new(PURPLE_TYPE_BOOLEAN
), 5,
8009 purple_value_new(PURPLE_TYPE_SUBTYPE
,
8010 PURPLE_SUBTYPE_ACCOUNT
),
8011 purple_value_new(PURPLE_TYPE_STRING
),
8012 purple_value_new_outgoing(PURPLE_TYPE_STRING
),
8013 purple_value_new(PURPLE_TYPE_SUBTYPE
,
8014 PURPLE_SUBTYPE_CONVERSATION
),
8015 purple_value_new(PURPLE_TYPE_INT
));
8017 purple_signal_register(handle
, "displayed-im-msg",
8018 purple_marshal_VOID__POINTER_POINTER_POINTER_POINTER_UINT
,
8020 purple_value_new(PURPLE_TYPE_SUBTYPE
,
8021 PURPLE_SUBTYPE_ACCOUNT
),
8022 purple_value_new(PURPLE_TYPE_STRING
),
8023 purple_value_new(PURPLE_TYPE_STRING
),
8024 purple_value_new(PURPLE_TYPE_SUBTYPE
,
8025 PURPLE_SUBTYPE_CONVERSATION
),
8026 purple_value_new(PURPLE_TYPE_INT
));
8028 purple_signal_register(handle
, "displaying-chat-msg",
8029 purple_marshal_BOOLEAN__POINTER_POINTER_POINTER_POINTER_POINTER
,
8030 purple_value_new(PURPLE_TYPE_BOOLEAN
), 5,
8031 purple_value_new(PURPLE_TYPE_SUBTYPE
,
8032 PURPLE_SUBTYPE_ACCOUNT
),
8033 purple_value_new(PURPLE_TYPE_STRING
),
8034 purple_value_new_outgoing(PURPLE_TYPE_STRING
),
8035 purple_value_new(PURPLE_TYPE_SUBTYPE
,
8036 PURPLE_SUBTYPE_CONVERSATION
),
8037 purple_value_new(PURPLE_TYPE_INT
));
8039 purple_signal_register(handle
, "displayed-chat-msg",
8040 purple_marshal_VOID__POINTER_POINTER_POINTER_POINTER_UINT
,
8042 purple_value_new(PURPLE_TYPE_SUBTYPE
,
8043 PURPLE_SUBTYPE_ACCOUNT
),
8044 purple_value_new(PURPLE_TYPE_STRING
),
8045 purple_value_new(PURPLE_TYPE_STRING
),
8046 purple_value_new(PURPLE_TYPE_SUBTYPE
,
8047 PURPLE_SUBTYPE_CONVERSATION
),
8048 purple_value_new(PURPLE_TYPE_INT
));
8050 purple_signal_register(handle
, "conversation-switched",
8051 purple_marshal_VOID__POINTER
, NULL
, 1,
8052 purple_value_new(PURPLE_TYPE_SUBTYPE
,
8053 PURPLE_SUBTYPE_CONVERSATION
));
8055 purple_signal_register(handle
, "conversation-hiding",
8056 purple_marshal_VOID__POINTER
, NULL
, 1,
8057 purple_value_new(PURPLE_TYPE_BOXED
,
8058 "PidginConversation *"));
8060 purple_signal_register(handle
, "conversation-displayed",
8061 purple_marshal_VOID__POINTER
, NULL
, 1,
8062 purple_value_new(PURPLE_TYPE_BOXED
,
8063 "PidginConversation *"));
8065 purple_signal_register(handle
, "chat-nick-autocomplete",
8066 purple_marshal_BOOLEAN__POINTER_BOOLEAN
,
8067 purple_value_new(PURPLE_TYPE_BOOLEAN
), 1,
8068 purple_value_new(PURPLE_TYPE_SUBTYPE
,
8069 PURPLE_SUBTYPE_CONVERSATION
));
8071 purple_signal_register(handle
, "chat-nick-clicked",
8072 purple_marshal_BOOLEAN__POINTER_POINTER_UINT
,
8073 purple_value_new(PURPLE_TYPE_BOOLEAN
), 3,
8074 purple_value_new(PURPLE_TYPE_SUBTYPE
,
8075 PURPLE_SUBTYPE_CONVERSATION
),
8076 purple_value_new(PURPLE_TYPE_STRING
),
8077 purple_value_new(PURPLE_TYPE_UINT
));
8080 /**********************************************************************
8082 **********************************************************************/
8083 purple_cmd_register("say", "S", PURPLE_CMD_P_DEFAULT
,
8084 PURPLE_CMD_FLAG_CHAT
| PURPLE_CMD_FLAG_IM
, NULL
,
8085 say_command_cb
, _("say <message>: Send a message normally as if you weren't using a command."), NULL
);
8086 purple_cmd_register("me", "S", PURPLE_CMD_P_DEFAULT
,
8087 PURPLE_CMD_FLAG_CHAT
| PURPLE_CMD_FLAG_IM
, NULL
,
8088 me_command_cb
, _("me <action>: Send an IRC style action to a buddy or chat."), NULL
);
8089 purple_cmd_register("debug", "w", PURPLE_CMD_P_DEFAULT
,
8090 PURPLE_CMD_FLAG_CHAT
| PURPLE_CMD_FLAG_IM
, NULL
,
8091 debug_command_cb
, _("debug <option>: Send various debug information to the current conversation."), NULL
);
8092 purple_cmd_register("clear", "", PURPLE_CMD_P_DEFAULT
,
8093 PURPLE_CMD_FLAG_CHAT
| PURPLE_CMD_FLAG_IM
, NULL
,
8094 clear_command_cb
, _("clear: Clears the conversation scrollback."), NULL
);
8095 purple_cmd_register("clearall", "", PURPLE_CMD_P_DEFAULT
,
8096 PURPLE_CMD_FLAG_CHAT
| PURPLE_CMD_FLAG_IM
, NULL
,
8097 clearall_command_cb
, _("clear: Clears all conversation scrollbacks."), NULL
);
8098 purple_cmd_register("help", "w", PURPLE_CMD_P_DEFAULT
,
8099 PURPLE_CMD_FLAG_CHAT
| PURPLE_CMD_FLAG_IM
| PURPLE_CMD_FLAG_ALLOW_WRONG_ARGS
, NULL
,
8100 help_command_cb
, _("help <command>: Help on a specific command."), NULL
);
8102 /**********************************************************************
8104 **********************************************************************/
8106 purple_signal_connect(purple_connections_get_handle(), "signed-on", handle
,
8107 G_CALLBACK(account_signed_off_cb
),
8108 GINT_TO_POINTER(PURPLE_CONV_ACCOUNT_ONLINE
));
8109 purple_signal_connect(purple_connections_get_handle(), "signed-off", handle
,
8110 G_CALLBACK(account_signed_off_cb
),
8111 GINT_TO_POINTER(PURPLE_CONV_ACCOUNT_OFFLINE
));
8112 purple_signal_connect(purple_connections_get_handle(), "signing-off", handle
,
8113 G_CALLBACK(account_signing_off
), NULL
);
8115 purple_signal_connect(purple_conversations_get_handle(), "received-im-msg",
8116 handle
, G_CALLBACK(received_im_msg_cb
), NULL
);
8117 purple_signal_connect(purple_conversations_get_handle(), "cleared-message-history",
8118 handle
, G_CALLBACK(clear_conversation_scrollback_cb
), NULL
);
8120 purple_signal_connect(purple_conversations_get_handle(), "deleting-chat-buddy",
8121 handle
, G_CALLBACK(deleting_chat_buddy_cb
), NULL
);
8123 purple_conversations_set_ui_ops(&conversation_ui_ops
);
8125 hidden_convwin
= pidgin_conv_window_new();
8126 window_list
= g_list_remove(window_list
, hidden_convwin
);
8128 purple_signal_connect(purple_accounts_get_handle(), "account-status-changed",
8129 handle
, PURPLE_CALLBACK(account_status_changed_cb
), NULL
);
8131 /* Callbacks to update a conversation */
8132 purple_signal_connect(blist_handle
, "blist-node-added", handle
,
8133 G_CALLBACK(buddy_update_cb
), NULL
);
8134 purple_signal_connect(blist_handle
, "blist-node-removed", handle
,
8135 G_CALLBACK(buddy_update_cb
), NULL
);
8136 purple_signal_connect(blist_handle
, "buddy-signed-on",
8137 handle
, PURPLE_CALLBACK(update_buddy_sign
), "on");
8138 purple_signal_connect(blist_handle
, "buddy-signed-off",
8139 handle
, PURPLE_CALLBACK(update_buddy_sign
), "off");
8140 purple_signal_connect(blist_handle
, "buddy-status-changed",
8141 handle
, PURPLE_CALLBACK(update_buddy_status_changed
), NULL
);
8142 purple_signal_connect(blist_handle
, "buddy-privacy-changed",
8143 handle
, PURPLE_CALLBACK(update_buddy_privacy_changed
), NULL
);
8144 purple_signal_connect(blist_handle
, "buddy-idle-changed",
8145 handle
, PURPLE_CALLBACK(update_buddy_idle_changed
), NULL
);
8146 purple_signal_connect(blist_handle
, "buddy-icon-changed",
8147 handle
, PURPLE_CALLBACK(update_buddy_icon
), NULL
);
8148 purple_signal_connect(purple_conversations_get_handle(), "buddy-typing",
8149 handle
, PURPLE_CALLBACK(update_buddy_typing
), NULL
);
8150 purple_signal_connect(purple_conversations_get_handle(), "buddy-typing-stopped",
8151 handle
, PURPLE_CALLBACK(update_buddy_typing
), NULL
);
8152 purple_signal_connect(pidgin_conversations_get_handle(), "conversation-switched",
8153 handle
, PURPLE_CALLBACK(update_conversation_switched
), NULL
);
8154 purple_signal_connect(purple_conversations_get_handle(), "chat-left", handle
,
8155 PURPLE_CALLBACK(update_chat
), NULL
);
8156 purple_signal_connect(purple_conversations_get_handle(), "chat-joined", handle
,
8157 PURPLE_CALLBACK(update_chat
), NULL
);
8158 purple_signal_connect(purple_conversations_get_handle(), "chat-topic-changed", handle
,
8159 PURPLE_CALLBACK(update_chat_topic
), NULL
);
8160 purple_signal_connect_priority(purple_conversations_get_handle(), "conversation-updated", handle
,
8161 PURPLE_CALLBACK(pidgin_conv_updated
), NULL
,
8162 PURPLE_SIGNAL_PRIORITY_LOWEST
);
8163 purple_signal_connect(purple_conversations_get_handle(), "wrote-im-msg", handle
,
8164 PURPLE_CALLBACK(wrote_msg_update_unseen_cb
), NULL
);
8165 purple_signal_connect(purple_conversations_get_handle(), "wrote-chat-msg", handle
,
8166 PURPLE_CALLBACK(wrote_msg_update_unseen_cb
), NULL
);
8169 /* Set default tab colors */
8170 GString
*str
= g_string_new(NULL
);
8171 GtkSettings
*settings
= gtk_settings_get_default();
8172 GtkStyle
*parent
= gtk_rc_get_style_by_paths(settings
, "tab-container.tab-label*", NULL
, G_TYPE_NONE
), *now
;
8174 const char *stylename
;
8175 const char *labelname
;
8178 {"pidgin_tab_label_typing_default", "tab-label-typing", "#4e9a06"},
8179 {"pidgin_tab_label_typed_default", "tab-label-typed", "#c4a000"},
8180 {"pidgin_tab_label_attention_default", "tab-label-attention", "#006aff"},
8181 {"pidgin_tab_label_unreadchat_default", "tab-label-unreadchat", "#cc0000"},
8182 {"pidgin_tab_label_event_default", "tab-label-event", "#888a85"},
8186 for (iter
= 0; styles
[iter
].stylename
; iter
++) {
8187 now
= gtk_rc_get_style_by_paths(settings
, styles
[iter
].labelname
, NULL
, G_TYPE_NONE
);
8188 if (parent
== now
||
8189 (parent
&& now
&& parent
->rc_style
== now
->rc_style
)) {
8190 g_string_append_printf(str
, "style \"%s\" {\n"
8191 "fg[ACTIVE] = \"%s\"\n"
8193 "widget \"*%s\" style \"%s\"\n",
8194 styles
[iter
].stylename
,
8196 styles
[iter
].labelname
, styles
[iter
].stylename
);
8199 gtk_rc_parse_string(str
->str
);
8200 g_string_free(str
, TRUE
);
8201 gtk_rc_reset_styles(settings
);
8206 pidgin_conversations_uninit(void)
8208 purple_prefs_disconnect_by_handle(pidgin_conversations_get_handle());
8209 purple_signals_disconnect_by_handle(pidgin_conversations_get_handle());
8210 purple_signals_unregister_by_instance(pidgin_conversations_get_handle());
8228 /* down here is where gtkconvwin.c ought to start. except they share like every freaking function,
8229 * and touch each others' private members all day long */
8232 * @file gtkconvwin.c GTK+ Conversation Window API
8237 * Pidgin is the legal property of its developers, whose names are too numerous
8238 * to list here. Please refer to the COPYRIGHT file distributed with this
8239 * source distribution.
8241 * This program is free software; you can redistribute it and/or modify
8242 * it under the terms of the GNU General Public License as published by
8243 * the Free Software Foundation; either version 2 of the License, or
8244 * (at your option) any later version.
8246 * This program is distributed in the hope that it will be useful,
8247 * but WITHOUT ANY WARRANTY; without even the implied warranty of
8248 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
8249 * GNU General Public License for more details.
8251 * You should have received a copy of the GNU General Public License
8252 * along with this program; if not, write to the Free Software
8253 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA
8256 #include "internal.h"
8260 #include <gdk/gdkkeysyms.h>
8262 #include "account.h"
8265 #include "imgstore.h"
8269 #include "request.h"
8272 #include "gtkdnd-hints.h"
8273 #include "gtkblist.h"
8274 #include "gtkconv.h"
8275 #include "gtkdialogs.h"
8276 #include "gtkmenutray.h"
8277 #include "gtkpounce.h"
8278 #include "gtkprefs.h"
8279 #include "gtkprivacy.h"
8280 #include "gtkutils.h"
8281 #include "pidginstock.h"
8282 #include "gtkimhtml.h"
8283 #include "gtkimhtmltoolbar.h"
8286 do_close(GtkWidget
*w
, int resp
, PidginWindow
*win
)
8288 gtk_widget_destroy(warn_close_dialog
);
8289 warn_close_dialog
= NULL
;
8291 if (resp
== GTK_RESPONSE_OK
)
8292 pidgin_conv_window_destroy(win
);
8296 build_warn_close_dialog(PidginWindow
*gtkwin
)
8298 GtkWidget
*label
, *vbox
, *hbox
, *img
;
8300 g_return_if_fail(warn_close_dialog
== NULL
);
8302 warn_close_dialog
= gtk_dialog_new_with_buttons(_("Confirm close"),
8303 GTK_WINDOW(gtkwin
->window
), GTK_DIALOG_MODAL
,
8304 GTK_STOCK_CANCEL
, GTK_RESPONSE_CANCEL
,
8305 GTK_STOCK_CLOSE
, GTK_RESPONSE_OK
, NULL
);
8307 gtk_dialog_set_default_response(GTK_DIALOG(warn_close_dialog
),
8310 gtk_container_set_border_width(GTK_CONTAINER(warn_close_dialog
),
8312 gtk_window_set_resizable(GTK_WINDOW(warn_close_dialog
), FALSE
);
8313 gtk_dialog_set_has_separator(GTK_DIALOG(warn_close_dialog
),
8316 /* Setup the outside spacing. */
8317 vbox
= GTK_DIALOG(warn_close_dialog
)->vbox
;
8319 gtk_box_set_spacing(GTK_BOX(vbox
), 12);
8320 gtk_container_set_border_width(GTK_CONTAINER(vbox
), 6);
8322 img
= gtk_image_new_from_stock(PIDGIN_STOCK_DIALOG_WARNING
,
8323 gtk_icon_size_from_name(PIDGIN_ICON_SIZE_TANGO_HUGE
));
8324 /* Setup the inner hbox and put the dialog's icon in it. */
8325 hbox
= gtk_hbox_new(FALSE
, 12);
8326 gtk_container_add(GTK_CONTAINER(vbox
), hbox
);
8327 gtk_box_pack_start(GTK_BOX(hbox
), img
, FALSE
, FALSE
, 0);
8328 gtk_misc_set_alignment(GTK_MISC(img
), 0, 0);
8330 /* Setup the right vbox. */
8331 vbox
= gtk_vbox_new(FALSE
, 12);
8332 gtk_container_add(GTK_CONTAINER(hbox
), vbox
);
8334 label
= gtk_label_new(_("You have unread messages. Are you sure you want to close the window?"));
8335 gtk_widget_set_size_request(label
, 350, -1);
8336 gtk_label_set_line_wrap(GTK_LABEL(label
), TRUE
);
8337 gtk_misc_set_alignment(GTK_MISC(label
), 0, 0);
8338 gtk_box_pack_start(GTK_BOX(vbox
), label
, FALSE
, FALSE
, 0);
8340 /* Connect the signals. */
8341 g_signal_connect(G_OBJECT(warn_close_dialog
), "response",
8342 G_CALLBACK(do_close
), gtkwin
);
8346 /**************************************************************************
8348 **************************************************************************/
8351 close_win_cb(GtkWidget
*w
, GdkEventAny
*e
, gpointer d
)
8353 PidginWindow
*win
= d
;
8356 /* If there are unread messages then show a warning dialog */
8357 for (l
= pidgin_conv_window_get_gtkconvs(win
);
8358 l
!= NULL
; l
= l
->next
)
8360 PidginConversation
*gtkconv
= l
->data
;
8361 if (purple_conversation_get_type(gtkconv
->active_conv
) == PURPLE_CONV_TYPE_IM
&&
8362 gtkconv
->unseen_state
>= PIDGIN_UNSEEN_TEXT
)
8364 build_warn_close_dialog(win
);
8365 gtk_widget_show_all(warn_close_dialog
);
8371 pidgin_conv_window_destroy(win
);
8377 conv_set_unseen(PurpleConversation
*conv
, PidginUnseenState state
)
8379 int unseen_count
= 0;
8380 PidginUnseenState unseen_state
= PIDGIN_UNSEEN_NONE
;
8382 if(purple_conversation_get_data(conv
, "unseen-count"))
8383 unseen_count
= GPOINTER_TO_INT(purple_conversation_get_data(conv
, "unseen-count"));
8385 if(purple_conversation_get_data(conv
, "unseen-state"))
8386 unseen_state
= GPOINTER_TO_INT(purple_conversation_get_data(conv
, "unseen-state"));
8388 if (state
== PIDGIN_UNSEEN_NONE
)
8391 unseen_state
= PIDGIN_UNSEEN_NONE
;
8395 if (state
>= PIDGIN_UNSEEN_TEXT
)
8398 if (state
> unseen_state
)
8399 unseen_state
= state
;
8402 purple_conversation_set_data(conv
, "unseen-count", GINT_TO_POINTER(unseen_count
));
8403 purple_conversation_set_data(conv
, "unseen-state", GINT_TO_POINTER(unseen_state
));
8405 purple_conversation_update(conv
, PURPLE_CONV_UPDATE_UNSEEN
);
8409 gtkconv_set_unseen(PidginConversation
*gtkconv
, PidginUnseenState state
)
8411 if (state
== PIDGIN_UNSEEN_NONE
)
8413 gtkconv
->unseen_count
= 0;
8414 gtkconv
->unseen_state
= PIDGIN_UNSEEN_NONE
;
8418 if (state
>= PIDGIN_UNSEEN_TEXT
)
8419 gtkconv
->unseen_count
++;
8421 if (state
> gtkconv
->unseen_state
)
8422 gtkconv
->unseen_state
= state
;
8425 purple_conversation_set_data(gtkconv
->active_conv
, "unseen-count", GINT_TO_POINTER(gtkconv
->unseen_count
));
8426 purple_conversation_set_data(gtkconv
->active_conv
, "unseen-state", GINT_TO_POINTER(gtkconv
->unseen_state
));
8428 purple_conversation_update(gtkconv
->active_conv
, PURPLE_CONV_UPDATE_UNSEEN
);
8432 * When a conversation window is focused, we know the user
8433 * has looked at it so we know there are no longer unseen
8437 focus_win_cb(GtkWidget
*w
, GdkEventFocus
*e
, gpointer d
)
8439 PidginWindow
*win
= d
;
8440 PidginConversation
*gtkconv
= pidgin_conv_window_get_active_gtkconv(win
);
8443 gtkconv_set_unseen(gtkconv
, PIDGIN_UNSEEN_NONE
);
8449 notebook_init_grab(PidginWindow
*gtkwin
, GtkWidget
*widget
)
8451 static GdkCursor
*cursor
= NULL
;
8453 gtkwin
->in_drag
= TRUE
;
8455 if (gtkwin
->drag_leave_signal
) {
8456 g_signal_handler_disconnect(G_OBJECT(widget
),
8457 gtkwin
->drag_leave_signal
);
8458 gtkwin
->drag_leave_signal
= 0;
8462 cursor
= gdk_cursor_new(GDK_FLEUR
);
8464 /* Grab the pointer */
8465 gtk_grab_add(gtkwin
->notebook
);
8467 /* Currently for win32 GTK+ (as of 2.2.1), gdk_pointer_is_grabbed will
8468 always be true after a button press. */
8469 if (!gdk_pointer_is_grabbed())
8471 gdk_pointer_grab(gtkwin
->notebook
->window
, FALSE
,
8472 GDK_BUTTON1_MOTION_MASK
| GDK_BUTTON_RELEASE_MASK
,
8473 NULL
, cursor
, GDK_CURRENT_TIME
);
8477 notebook_motion_cb(GtkWidget
*widget
, GdkEventButton
*e
, PidginWindow
*win
)
8481 * Make sure the user moved the mouse far enough for the
8482 * drag to be initiated.
8484 if (win
->in_predrag
) {
8485 if (e
->x_root
< win
->drag_min_x
||
8486 e
->x_root
>= win
->drag_max_x
||
8487 e
->y_root
< win
->drag_min_y
||
8488 e
->y_root
>= win
->drag_max_y
) {
8490 win
->in_predrag
= FALSE
;
8491 notebook_init_grab(win
, widget
);
8494 else { /* Otherwise, draw the arrows. */
8495 PidginWindow
*dest_win
;
8496 GtkNotebook
*dest_notebook
;
8499 gboolean horiz_tabs
= FALSE
;
8500 gboolean to_right
= FALSE
;
8502 /* Get the window that the cursor is over. */
8503 dest_win
= pidgin_conv_window_get_at_xy(e
->x_root
, e
->y_root
);
8505 if (dest_win
== NULL
) {
8506 dnd_hints_hide_all();
8511 dest_notebook
= GTK_NOTEBOOK(dest_win
->notebook
);
8513 if (gtk_notebook_get_show_tabs(dest_notebook
)) {
8514 page_num
= pidgin_conv_get_tab_at_xy(dest_win
,
8515 e
->x_root
, e
->y_root
, &to_right
);
8516 to_right
= to_right
&& (win
!= dest_win
);
8517 tab
= pidgin_conv_window_get_gtkconv_at_index(dest_win
, page_num
)->tabby
;
8520 to_right
= pidgin_conv_xy_to_right_infopane(dest_win
, e
->x_root
, e
->y_root
);
8521 tab
= pidgin_conv_window_get_gtkconv_at_index(dest_win
, page_num
)->infopane_hbox
;
8524 if (gtk_notebook_get_tab_pos(dest_notebook
) == GTK_POS_TOP
||
8525 gtk_notebook_get_tab_pos(dest_notebook
) == GTK_POS_BOTTOM
) {
8529 if (gtk_notebook_get_show_tabs(dest_notebook
) == FALSE
&& win
== dest_win
)
8531 /* dragging a tab from a single-tabbed window over its own window */
8532 dnd_hints_hide_all();
8534 } else if (horiz_tabs
) {
8535 if (((gpointer
)win
== (gpointer
)dest_win
&& win
->drag_tab
< page_num
) || to_right
) {
8536 dnd_hints_show_relative(HINT_ARROW_DOWN
, tab
, HINT_POSITION_RIGHT
, HINT_POSITION_TOP
);
8537 dnd_hints_show_relative(HINT_ARROW_UP
, tab
, HINT_POSITION_RIGHT
, HINT_POSITION_BOTTOM
);
8539 dnd_hints_show_relative(HINT_ARROW_DOWN
, tab
, HINT_POSITION_LEFT
, HINT_POSITION_TOP
);
8540 dnd_hints_show_relative(HINT_ARROW_UP
, tab
, HINT_POSITION_LEFT
, HINT_POSITION_BOTTOM
);
8543 if (((gpointer
)win
== (gpointer
)dest_win
&& win
->drag_tab
< page_num
) || to_right
) {
8544 dnd_hints_show_relative(HINT_ARROW_RIGHT
, tab
, HINT_POSITION_LEFT
, HINT_POSITION_BOTTOM
);
8545 dnd_hints_show_relative(HINT_ARROW_LEFT
, tab
, HINT_POSITION_RIGHT
, HINT_POSITION_BOTTOM
);
8547 dnd_hints_show_relative(HINT_ARROW_RIGHT
, tab
, HINT_POSITION_LEFT
, HINT_POSITION_TOP
);
8548 dnd_hints_show_relative(HINT_ARROW_LEFT
, tab
, HINT_POSITION_RIGHT
, HINT_POSITION_TOP
);
8557 notebook_leave_cb(GtkWidget
*widget
, GdkEventCrossing
*e
, PidginWindow
*win
)
8562 if (e
->x_root
< win
->drag_min_x
||
8563 e
->x_root
>= win
->drag_max_x
||
8564 e
->y_root
< win
->drag_min_y
||
8565 e
->y_root
>= win
->drag_max_y
) {
8567 win
->in_predrag
= FALSE
;
8568 notebook_init_grab(win
, widget
);
8579 infopane_press_cb(GtkWidget
*widget
, GdkEventButton
*e
, PidginConversation
*gtkconv
)
8581 if (e
->type
== GDK_2BUTTON_PRESS
&& e
->button
== 1) {
8582 if (infopane_entry_activate(gtkconv
))
8586 if (e
->type
!= GDK_BUTTON_PRESS
)
8589 if (e
->button
== 1) {
8592 if (gtkconv
->win
->in_drag
)
8595 gtkconv
->win
->in_predrag
= TRUE
;
8596 gtkconv
->win
->drag_tab
= gtk_notebook_page_num(GTK_NOTEBOOK(gtkconv
->win
->notebook
), gtkconv
->tab_cont
);
8598 gdk_window_get_origin(gtkconv
->infopane_hbox
->window
, &nb_x
, &nb_y
);
8600 gtkconv
->win
->drag_min_x
= gtkconv
->infopane_hbox
->allocation
.x
+ nb_x
;
8601 gtkconv
->win
->drag_min_y
= gtkconv
->infopane_hbox
->allocation
.y
+ nb_y
;
8602 gtkconv
->win
->drag_max_x
= gtkconv
->infopane_hbox
->allocation
.width
+ gtkconv
->win
->drag_min_x
;
8603 gtkconv
->win
->drag_max_y
= gtkconv
->infopane_hbox
->allocation
.height
+ gtkconv
->win
->drag_min_y
;
8605 gtkconv
->win
->drag_motion_signal
= g_signal_connect(G_OBJECT(gtkconv
->win
->notebook
), "motion_notify_event",
8606 G_CALLBACK(notebook_motion_cb
), gtkconv
->win
);
8607 gtkconv
->win
->drag_leave_signal
= g_signal_connect(G_OBJECT(gtkconv
->win
->notebook
), "leave_notify_event",
8608 G_CALLBACK(notebook_leave_cb
), gtkconv
->win
);
8612 if (e
->button
== 3) {
8613 /* Right click was pressed. Popup the context menu. */
8614 GtkWidget
*menu
= gtk_menu_new(), *sub
;
8615 gboolean populated
= populate_menu_with_options(menu
, gtkconv
, TRUE
);
8616 sub
= gtk_menu_item_get_submenu(GTK_MENU_ITEM(gtkconv
->win
->menu
.send_to
));
8618 if (sub
&& GTK_WIDGET_IS_SENSITIVE(gtkconv
->win
->menu
.send_to
)) {
8619 GtkWidget
*item
= gtk_menu_item_new_with_mnemonic(_("S_end To"));
8621 pidgin_separator(menu
);
8622 gtk_menu_shell_append(GTK_MENU_SHELL(menu
), item
);
8623 gtk_menu_item_set_submenu(GTK_MENU_ITEM(item
), sub
);
8624 gtk_widget_show(item
);
8625 gtk_widget_show_all(sub
);
8626 } else if (!populated
) {
8627 gtk_widget_destroy(menu
);
8631 gtk_widget_show_all(menu
);
8632 gtk_menu_popup(GTK_MENU(menu
), NULL
, NULL
, NULL
, NULL
, e
->button
, e
->time
);
8639 notebook_press_cb(GtkWidget
*widget
, GdkEventButton
*e
, PidginWindow
*win
)
8646 if (e
->button
== 2 && e
->type
== GDK_BUTTON_PRESS
) {
8647 PidginConversation
*gtkconv
;
8648 tab_clicked
= pidgin_conv_get_tab_at_xy(win
, e
->x_root
, e
->y_root
, NULL
);
8650 if (tab_clicked
== -1)
8653 gtkconv
= pidgin_conv_window_get_gtkconv_at_index(win
, tab_clicked
);
8654 close_conv_cb(NULL
, gtkconv
);
8659 if (e
->button
!= 1 || e
->type
!= GDK_BUTTON_PRESS
)
8664 purple_debug(PURPLE_DEBUG_WARNING
, "gtkconv",
8665 "Already in the middle of a window drag at tab_press_cb\n");
8670 * Make sure a tab was actually clicked. The arrow buttons
8673 tab_clicked
= pidgin_conv_get_tab_at_xy(win
, e
->x_root
, e
->y_root
, NULL
);
8675 if (tab_clicked
== -1)
8679 * Get the relative position of the press event, with regards to
8680 * the position of the notebook.
8682 gdk_window_get_origin(win
->notebook
->window
, &nb_x
, &nb_y
);
8684 /* Reset the min/max x/y */
8685 win
->drag_min_x
= 0;
8686 win
->drag_min_y
= 0;
8687 win
->drag_max_x
= 0;
8688 win
->drag_max_y
= 0;
8690 /* Find out which tab was dragged. */
8691 page
= gtk_notebook_get_nth_page(GTK_NOTEBOOK(win
->notebook
), tab_clicked
);
8692 tab
= gtk_notebook_get_tab_label(GTK_NOTEBOOK(win
->notebook
), page
);
8694 win
->drag_min_x
= tab
->allocation
.x
+ nb_x
;
8695 win
->drag_min_y
= tab
->allocation
.y
+ nb_y
;
8696 win
->drag_max_x
= tab
->allocation
.width
+ win
->drag_min_x
;
8697 win
->drag_max_y
= tab
->allocation
.height
+ win
->drag_min_y
;
8699 /* Make sure the click occurred in the tab. */
8700 if (e
->x_root
< win
->drag_min_x
||
8701 e
->x_root
>= win
->drag_max_x
||
8702 e
->y_root
< win
->drag_min_y
||
8703 e
->y_root
>= win
->drag_max_y
) {
8708 win
->in_predrag
= TRUE
;
8709 win
->drag_tab
= tab_clicked
;
8711 /* Connect the new motion signals. */
8712 win
->drag_motion_signal
=
8713 g_signal_connect(G_OBJECT(widget
), "motion_notify_event",
8714 G_CALLBACK(notebook_motion_cb
), win
);
8716 win
->drag_leave_signal
=
8717 g_signal_connect(G_OBJECT(widget
), "leave_notify_event",
8718 G_CALLBACK(notebook_leave_cb
), win
);
8724 notebook_release_cb(GtkWidget
*widget
, GdkEventButton
*e
, PidginWindow
*win
)
8726 PidginWindow
*dest_win
;
8727 GtkNotebook
*dest_notebook
;
8728 PurpleConversation
*conv
;
8729 PidginConversation
*gtkconv
;
8730 gint dest_page_num
= 0;
8731 gboolean new_window
= FALSE
;
8732 gboolean to_right
= FALSE
;
8735 * Don't check to make sure that the event's window matches the
8736 * widget's, because we may be getting an event passed on from the
8739 if (e
->button
!= 1 && e
->type
!= GDK_BUTTON_RELEASE
)
8742 if (gdk_pointer_is_grabbed()) {
8743 gdk_pointer_ungrab(GDK_CURRENT_TIME
);
8744 gtk_grab_remove(widget
);
8747 if (!win
->in_predrag
&& !win
->in_drag
)
8750 /* Disconnect the motion signal. */
8751 if (win
->drag_motion_signal
) {
8752 g_signal_handler_disconnect(G_OBJECT(widget
),
8753 win
->drag_motion_signal
);
8755 win
->drag_motion_signal
= 0;
8759 * If we're in a pre-drag, we'll also need to disconnect the leave
8762 if (win
->in_predrag
) {
8763 win
->in_predrag
= FALSE
;
8765 if (win
->drag_leave_signal
) {
8766 g_signal_handler_disconnect(G_OBJECT(widget
),
8767 win
->drag_leave_signal
);
8769 win
->drag_leave_signal
= 0;
8773 /* If we're not in drag... */
8774 /* We're perfectly normal people! */
8778 win
->in_drag
= FALSE
;
8780 dnd_hints_hide_all();
8782 dest_win
= pidgin_conv_window_get_at_xy(e
->x_root
, e
->y_root
);
8784 conv
= pidgin_conv_window_get_active_conversation(win
);
8786 if (dest_win
== NULL
) {
8787 /* If the current window doesn't have any other conversations,
8788 * there isn't much point transferring the conv to a new window. */
8789 if (pidgin_conv_window_get_gtkconv_count(win
) > 1) {
8790 /* Make a new window to stick this to. */
8791 dest_win
= pidgin_conv_window_new();
8796 if (dest_win
== NULL
)
8799 purple_signal_emit(pidgin_conversations_get_handle(),
8800 "conversation-dragging", win
, dest_win
);
8802 /* Get the destination page number. */
8804 dest_notebook
= GTK_NOTEBOOK(dest_win
->notebook
);
8805 if (gtk_notebook_get_show_tabs(dest_notebook
)) {
8806 dest_page_num
= pidgin_conv_get_tab_at_xy(dest_win
,
8807 e
->x_root
, e
->y_root
, &to_right
);
8810 to_right
= pidgin_conv_xy_to_right_infopane(dest_win
, e
->x_root
, e
->y_root
);
8814 gtkconv
= pidgin_conv_window_get_gtkconv_at_index(win
, win
->drag_tab
);
8816 if (win
== dest_win
) {
8817 gtk_notebook_reorder_child(GTK_NOTEBOOK(win
->notebook
), gtkconv
->tab_cont
, dest_page_num
);
8819 pidgin_conv_window_remove_gtkconv(win
, gtkconv
);
8820 pidgin_conv_window_add_gtkconv(dest_win
, gtkconv
);
8821 gtk_notebook_reorder_child(GTK_NOTEBOOK(dest_win
->notebook
), gtkconv
->tab_cont
, dest_page_num
+ to_right
);
8822 pidgin_conv_window_switch_gtkconv(dest_win
, gtkconv
);
8824 gint win_width
, win_height
;
8826 gtk_window_get_size(GTK_WINDOW(dest_win
->window
),
8827 &win_width
, &win_height
);
8828 #ifdef _WIN32 /* only override window manager placement on Windows */
8829 gtk_window_move(GTK_WINDOW(dest_win
->window
),
8830 e
->x_root
- (win_width
/ 2),
8831 e
->y_root
- (win_height
/ 2));
8834 pidgin_conv_window_show(dest_win
);
8838 gtk_widget_grab_focus(PIDGIN_CONVERSATION(conv
)->entry
);
8845 before_switch_conv_cb(GtkNotebook
*notebook
, GtkWidget
*page
, gint page_num
,
8849 PurpleConversation
*conv
;
8850 PidginConversation
*gtkconv
;
8853 conv
= pidgin_conv_window_get_active_conversation(win
);
8855 g_return_if_fail(conv
!= NULL
);
8857 if (purple_conversation_get_type(conv
) != PURPLE_CONV_TYPE_IM
)
8860 gtkconv
= PIDGIN_CONVERSATION(conv
);
8862 if (gtkconv
->u
.im
->typing_timer
!= 0) {
8863 g_source_remove(gtkconv
->u
.im
->typing_timer
);
8864 gtkconv
->u
.im
->typing_timer
= 0;
8867 stop_anim(NULL
, gtkconv
);
8870 close_window(GtkWidget
*w
, PidginWindow
*win
)
8872 close_win_cb(w
, NULL
, win
);
8876 detach_tab_cb(GtkWidget
*w
, GObject
*menu
)
8878 PidginWindow
*win
, *new_window
;
8879 PidginConversation
*gtkconv
;
8881 gtkconv
= g_object_get_data(menu
, "clicked_tab");
8886 win
= pidgin_conv_get_window(gtkconv
);
8887 /* Nothing to do if there's only one tab in the window */
8888 if (pidgin_conv_window_get_gtkconv_count(win
) == 1)
8891 pidgin_conv_window_remove_gtkconv(win
, gtkconv
);
8893 new_window
= pidgin_conv_window_new();
8894 pidgin_conv_window_add_gtkconv(new_window
, gtkconv
);
8895 pidgin_conv_window_show(new_window
);
8899 close_others_cb(GtkWidget
*w
, GObject
*menu
)
8902 PidginConversation
*gtkconv
;
8905 gtkconv
= g_object_get_data(menu
, "clicked_tab");
8910 win
= pidgin_conv_get_window(gtkconv
);
8912 for (iter
= pidgin_conv_window_get_gtkconvs(win
); iter
; )
8914 PidginConversation
*gconv
= iter
->data
;
8917 if (gconv
!= gtkconv
)
8919 close_conv_cb(NULL
, gconv
);
8924 static void close_tab_cb(GtkWidget
*w
, GObject
*menu
)
8926 PidginConversation
*gtkconv
;
8928 gtkconv
= g_object_get_data(menu
, "clicked_tab");
8931 close_conv_cb(NULL
, gtkconv
);
8935 right_click_menu_cb(GtkNotebook
*notebook
, GdkEventButton
*event
, PidginWindow
*win
)
8937 GtkWidget
*item
, *menu
;
8938 PidginConversation
*gtkconv
;
8940 if (event
->type
!= GDK_BUTTON_PRESS
|| event
->button
!= 3)
8943 gtkconv
= pidgin_conv_window_get_gtkconv_at_index(win
,
8944 pidgin_conv_get_tab_at_xy(win
, event
->x_root
, event
->y_root
, NULL
));
8946 if (g_object_get_data(G_OBJECT(notebook
->menu
), "clicked_tab"))
8948 g_object_set_data(G_OBJECT(notebook
->menu
), "clicked_tab", gtkconv
);
8952 g_object_set_data(G_OBJECT(notebook
->menu
), "clicked_tab", gtkconv
);
8954 menu
= notebook
->menu
;
8955 pidgin_separator(GTK_WIDGET(menu
));
8957 item
= gtk_menu_item_new_with_label(_("Close other tabs"));
8958 gtk_widget_show(item
);
8959 gtk_menu_shell_append(GTK_MENU_SHELL(menu
), item
);
8960 g_signal_connect(G_OBJECT(item
), "activate",
8961 G_CALLBACK(close_others_cb
), menu
);
8963 item
= gtk_menu_item_new_with_label(_("Close all tabs"));
8964 gtk_widget_show(item
);
8965 gtk_menu_shell_append(GTK_MENU_SHELL(menu
), item
);
8966 g_signal_connect(G_OBJECT(item
), "activate",
8967 G_CALLBACK(close_window
), win
);
8969 pidgin_separator(menu
);
8971 item
= gtk_menu_item_new_with_label(_("Detach this tab"));
8972 gtk_widget_show(item
);
8973 gtk_menu_shell_append(GTK_MENU_SHELL(menu
), item
);
8974 g_signal_connect(G_OBJECT(item
), "activate",
8975 G_CALLBACK(detach_tab_cb
), menu
);
8977 item
= gtk_menu_item_new_with_label(_("Close this tab"));
8978 gtk_widget_show(item
);
8979 gtk_menu_shell_append(GTK_MENU_SHELL(menu
), item
);
8980 g_signal_connect(G_OBJECT(item
), "activate",
8981 G_CALLBACK(close_tab_cb
), menu
);
8987 remove_edit_entry(PidginConversation
*gtkconv
, GtkWidget
*entry
)
8989 g_signal_handlers_disconnect_matched(G_OBJECT(entry
), G_SIGNAL_MATCH_DATA
,
8990 0, 0, NULL
, NULL
, gtkconv
);
8991 gtk_widget_show(gtkconv
->infopane
);
8992 gtk_widget_grab_focus(gtkconv
->entry
);
8993 gtk_widget_destroy(entry
);
8997 alias_focus_cb(GtkWidget
*widget
, GdkEventFocus
*event
, gpointer user_data
)
8999 remove_edit_entry(user_data
, widget
);
9004 alias_key_press_cb(GtkWidget
*widget
, GdkEventKey
*event
, gpointer user_data
)
9006 if (event
->keyval
== GDK_Escape
) {
9007 remove_edit_entry(user_data
, widget
);
9014 alias_cb(GtkEntry
*entry
, gpointer user_data
)
9016 PidginConversation
*gtkconv
;
9017 PurpleConversation
*conv
;
9018 PurpleAccount
*account
;
9021 gtkconv
= (PidginConversation
*)user_data
;
9022 if (gtkconv
== NULL
) {
9025 conv
= gtkconv
->active_conv
;
9026 account
= purple_conversation_get_account(conv
);
9027 name
= purple_conversation_get_name(conv
);
9029 if (purple_conversation_get_type(conv
) == PURPLE_CONV_TYPE_IM
) {
9031 buddy
= purple_find_buddy(account
, name
);
9032 if (buddy
!= NULL
) {
9033 purple_blist_alias_buddy(buddy
,
9034 gtk_entry_get_text(entry
));
9036 serv_alias_buddy(buddy
);
9037 } else if (purple_conversation_get_type(conv
) == PURPLE_CONV_TYPE_CHAT
) {
9038 gtk_entry_set_text(GTK_ENTRY(gtkconv
->u
.chat
->topic_text
), gtk_entry_get_text(entry
));
9039 topic_callback(NULL
, gtkconv
);
9041 remove_edit_entry(user_data
, GTK_WIDGET(entry
));
9045 infopane_entry_activate(PidginConversation
*gtkconv
)
9047 GtkWidget
*entry
= NULL
;
9048 PurpleConversation
*conv
= gtkconv
->active_conv
;
9049 const char *text
= NULL
;
9051 if (!GTK_WIDGET_VISIBLE(gtkconv
->infopane
)) {
9052 /* There's already an entry for alias. Let's not create another one. */
9056 if (!purple_account_is_connected(gtkconv
->active_conv
->account
)) {
9057 /* Do not allow aliasing someone on a disconnected account. */
9061 if (purple_conversation_get_type(conv
) == PURPLE_CONV_TYPE_IM
) {
9062 PurpleBuddy
*buddy
= purple_find_buddy(gtkconv
->active_conv
->account
, gtkconv
->active_conv
->name
);
9064 /* This buddy isn't in your buddy list, so we can't alias him */
9067 text
= purple_buddy_get_contact_alias(buddy
);
9068 } else if (purple_conversation_get_type(conv
) == PURPLE_CONV_TYPE_CHAT
) {
9069 PurpleConnection
*gc
;
9070 PurplePluginProtocolInfo
*prpl_info
= NULL
;
9072 gc
= purple_conversation_get_gc(conv
);
9074 prpl_info
= PURPLE_PLUGIN_PROTOCOL_INFO(gc
->prpl
);
9075 if (prpl_info
&& prpl_info
->set_chat_topic
== NULL
)
9076 /* This protocol doesn't support setting the chat room topic */
9079 text
= purple_conv_chat_get_topic(PURPLE_CONV_CHAT(conv
));
9083 entry
= gtk_entry_new();
9084 gtk_entry_set_has_frame(GTK_ENTRY(entry
), FALSE
);
9085 gtk_entry_set_width_chars(GTK_ENTRY(entry
), 10);
9086 gtk_entry_set_alignment(GTK_ENTRY(entry
), 0.5);
9088 gtk_box_pack_start(GTK_BOX(gtkconv
->infopane_hbox
), entry
, TRUE
, TRUE
, 0);
9089 /* after the tab label */
9090 gtk_box_reorder_child(GTK_BOX(gtkconv
->infopane_hbox
), entry
, 0);
9092 g_signal_connect(G_OBJECT(entry
), "activate", G_CALLBACK(alias_cb
), gtkconv
);
9093 g_signal_connect(G_OBJECT(entry
), "focus-out-event", G_CALLBACK(alias_focus_cb
), gtkconv
);
9094 g_signal_connect(G_OBJECT(entry
), "key-press-event", G_CALLBACK(alias_key_press_cb
), gtkconv
);
9097 gtk_entry_set_text(GTK_ENTRY(entry
), text
);
9098 gtk_widget_show(entry
);
9099 gtk_widget_hide(gtkconv
->infopane
);
9100 gtk_widget_grab_focus(entry
);
9106 window_keypress_cb(GtkWidget
*widget
, GdkEventKey
*event
, PidginWindow
*win
)
9108 PidginConversation
*gtkconv
= pidgin_conv_window_get_active_gtkconv(win
);
9110 return conv_keypress_common(gtkconv
, event
);
9114 switch_conv_cb(GtkNotebook
*notebook
, GtkWidget
*page
, gint page_num
,
9118 PurpleConversation
*conv
;
9119 PidginConversation
*gtkconv
;
9120 const char *sound_method
;
9123 gtkconv
= pidgin_conv_window_get_gtkconv_at_index(win
, page_num
);
9124 conv
= gtkconv
->active_conv
;
9126 g_return_if_fail(conv
!= NULL
);
9128 /* clear unseen flag if conversation is not hidden */
9129 if(!pidgin_conv_is_hidden(gtkconv
)) {
9130 gtkconv_set_unseen(gtkconv
, PIDGIN_UNSEEN_NONE
);
9133 /* Update the menubar */
9135 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(gtkconv
->win
->menu
.logging
),
9136 purple_conversation_is_logging(conv
));
9138 generate_send_to_items(win
);
9139 regenerate_options_items(win
);
9140 regenerate_plugins_items(win
);
9142 pidgin_conv_switch_active_conversation(conv
);
9144 sound_method
= purple_prefs_get_string(PIDGIN_PREFS_ROOT
"/sound/method");
9145 if (strcmp(sound_method
, "none") != 0)
9146 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(win
->menu
.sounds
),
9147 gtkconv
->make_sound
);
9149 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(win
->menu
.show_formatting_toolbar
),
9150 purple_prefs_get_bool(PIDGIN_PREFS_ROOT
"/conversations/show_formatting_toolbar"));
9152 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(win
->menu
.show_timestamps
),
9153 purple_prefs_get_bool(PIDGIN_PREFS_ROOT
"/conversations/show_timestamps"));
9156 * We pause icons when they are not visible. If this icon should
9157 * be animated then start it back up again.
9159 if ((purple_conversation_get_type(conv
) == PURPLE_CONV_TYPE_IM
) &&
9160 (gtkconv
->u
.im
->animate
))
9161 start_anim(NULL
, gtkconv
);
9163 purple_signal_emit(pidgin_conversations_get_handle(), "conversation-switched", conv
);
9166 /**************************************************************************
9168 **************************************************************************/
9171 pidgin_conv_windows_get_list()
9177 make_status_icon_list(const char *stock
, GtkWidget
*w
)
9180 l
= g_list_append(l
, gtk_widget_render_icon (w
, stock
,
9181 gtk_icon_size_from_name(PIDGIN_ICON_SIZE_TANGO_EXTRA_SMALL
), "GtkWindow"));
9182 l
= g_list_append(l
, gtk_widget_render_icon (w
, stock
,
9183 gtk_icon_size_from_name(PIDGIN_ICON_SIZE_TANGO_SMALL
), "GtkWindow"));
9184 l
= g_list_append(l
, gtk_widget_render_icon (w
, stock
,
9185 gtk_icon_size_from_name(PIDGIN_ICON_SIZE_TANGO_MEDIUM
), "GtkWindow"));
9186 l
= g_list_append(l
, gtk_widget_render_icon (w
, stock
,
9187 gtk_icon_size_from_name(PIDGIN_ICON_SIZE_TANGO_LARGE
), "GtkWindow"));
9192 create_icon_lists(GtkWidget
*w
)
9194 available_list
= make_status_icon_list(PIDGIN_STOCK_STATUS_AVAILABLE
, w
);
9195 busy_list
= make_status_icon_list(PIDGIN_STOCK_STATUS_BUSY
, w
);
9196 xa_list
= make_status_icon_list(PIDGIN_STOCK_STATUS_XA
, w
);
9197 offline_list
= make_status_icon_list(PIDGIN_STOCK_STATUS_OFFLINE
, w
);
9198 away_list
= make_status_icon_list(PIDGIN_STOCK_STATUS_AWAY
, w
);
9199 prpl_lists
= g_hash_table_new(g_str_hash
, g_str_equal
);
9203 plugin_changed_cb(PurplePlugin
*p
, gpointer data
)
9205 regenerate_plugins_items(data
);
9208 static gboolean
gtk_conv_configure_cb(GtkWidget
*w
, GdkEventConfigure
*event
, gpointer data
) {
9211 if (GTK_WIDGET_VISIBLE(w
))
9212 gtk_window_get_position(GTK_WINDOW(w
), &x
, &y
);
9214 return FALSE
; /* carry on normally */
9216 /* Workaround for GTK+ bug # 169811 - "configure_event" is fired
9217 * when the window is being maximized */
9218 if (gdk_window_get_state(w
->window
) & GDK_WINDOW_STATE_MAXIMIZED
)
9221 /* don't save off-screen positioning */
9222 if (x
+ event
->width
< 0 ||
9223 y
+ event
->height
< 0 ||
9224 x
> gdk_screen_width() ||
9225 y
> gdk_screen_height())
9226 return FALSE
; /* carry on normally */
9228 /* store the position */
9229 purple_prefs_set_int(PIDGIN_PREFS_ROOT
"/conversations/im/x", x
);
9230 purple_prefs_set_int(PIDGIN_PREFS_ROOT
"/conversations/im/y", y
);
9231 purple_prefs_set_int(PIDGIN_PREFS_ROOT
"/conversations/im/width", event
->width
);
9232 purple_prefs_set_int(PIDGIN_PREFS_ROOT
"/conversations/im/height", event
->height
);
9234 /* continue to handle event normally */
9240 pidgin_conv_set_position_size(PidginWindow
*win
, int conv_x
, int conv_y
,
9241 int conv_width
, int conv_height
)
9243 /* if the window exists, is hidden, we're saving positions, and the
9244 * position is sane... */
9245 if (win
&& win
->window
&&
9246 !GTK_WIDGET_VISIBLE(win
->window
) && conv_width
!= 0) {
9248 #ifdef _WIN32 /* only override window manager placement on Windows */
9249 /* ...check position is on screen... */
9250 if (conv_x
>= gdk_screen_width())
9251 conv_x
= gdk_screen_width() - 100;
9252 else if (conv_x
+ conv_width
< 0)
9255 if (conv_y
>= gdk_screen_height())
9256 conv_y
= gdk_screen_height() - 100;
9257 else if (conv_y
+ conv_height
< 0)
9260 /* ...and move it back. */
9261 gtk_window_move(GTK_WINDOW(win
->window
), conv_x
, conv_y
);
9263 gtk_window_resize(GTK_WINDOW(win
->window
), conv_width
, conv_height
);
9268 pidgin_conv_restore_position(PidginWindow
*win
) {
9269 pidgin_conv_set_position_size(win
,
9270 purple_prefs_get_int(PIDGIN_PREFS_ROOT
"/conversations/im/x"),
9271 purple_prefs_get_int(PIDGIN_PREFS_ROOT
"/conversations/im/y"),
9272 purple_prefs_get_int(PIDGIN_PREFS_ROOT
"/conversations/im/width"),
9273 purple_prefs_get_int(PIDGIN_PREFS_ROOT
"/conversations/im/height"));
9277 pidgin_conv_window_new()
9280 GtkPositionType pos
;
9281 GtkWidget
*testidea
;
9283 GdkModifierType state
;
9285 win
= g_malloc0(sizeof(PidginWindow
));
9287 window_list
= g_list_append(window_list
, win
);
9289 /* Create the window. */
9290 win
->window
= pidgin_create_window(NULL
, 0, "conversation", TRUE
);
9291 if (!gtk_get_current_event_state(&state
))
9292 gtk_window_set_focus_on_map(GTK_WINDOW(win
->window
), FALSE
);
9294 /* Etan: I really think this entire function call should happen only
9295 * when we are on Windows but I was informed that back before we used
9296 * to save the window position we stored the window size, so I'm
9297 * leaving it for now. */
9298 #if TRUE || defined(_WIN32)
9299 pidgin_conv_restore_position(win
);
9302 if (available_list
== NULL
) {
9303 create_icon_lists(win
->window
);
9306 g_signal_connect(G_OBJECT(win
->window
), "delete_event",
9307 G_CALLBACK(close_win_cb
), win
);
9308 g_signal_connect(G_OBJECT(win
->window
), "focus_in_event",
9309 G_CALLBACK(focus_win_cb
), win
);
9311 /* Intercept keystrokes from the menu items */
9312 g_signal_connect(G_OBJECT(win
->window
), "key_press_event",
9313 G_CALLBACK(window_keypress_cb
), win
);
9316 /* Create the notebook. */
9317 win
->notebook
= gtk_notebook_new();
9319 pos
= purple_prefs_get_int(PIDGIN_PREFS_ROOT
"/conversations/tab_side");
9322 gtk_notebook_set_tab_hborder(GTK_NOTEBOOK(win
->notebook
), 0);
9323 gtk_notebook_set_tab_vborder(GTK_NOTEBOOK(win
->notebook
), 0);
9325 gtk_notebook_set_tab_pos(GTK_NOTEBOOK(win
->notebook
), pos
);
9326 gtk_notebook_set_scrollable(GTK_NOTEBOOK(win
->notebook
), TRUE
);
9327 gtk_notebook_popup_enable(GTK_NOTEBOOK(win
->notebook
));
9328 gtk_notebook_set_show_tabs(GTK_NOTEBOOK(win
->notebook
), FALSE
);
9329 gtk_notebook_set_show_border(GTK_NOTEBOOK(win
->notebook
), TRUE
);
9331 g_signal_connect(G_OBJECT(win
->notebook
), "button-press-event",
9332 G_CALLBACK(right_click_menu_cb
), win
);
9334 gtk_widget_show(win
->notebook
);
9336 g_signal_connect(G_OBJECT(win
->notebook
), "switch_page",
9337 G_CALLBACK(before_switch_conv_cb
), win
);
9338 g_signal_connect_after(G_OBJECT(win
->notebook
), "switch_page",
9339 G_CALLBACK(switch_conv_cb
), win
);
9341 /* Setup the tab drag and drop signals. */
9342 gtk_widget_add_events(win
->notebook
,
9343 GDK_BUTTON1_MOTION_MASK
| GDK_LEAVE_NOTIFY_MASK
);
9344 g_signal_connect(G_OBJECT(win
->notebook
), "button_press_event",
9345 G_CALLBACK(notebook_press_cb
), win
);
9346 g_signal_connect(G_OBJECT(win
->notebook
), "button_release_event",
9347 G_CALLBACK(notebook_release_cb
), win
);
9349 testidea
= gtk_vbox_new(FALSE
, 0);
9351 /* Setup the menubar. */
9352 menubar
= setup_menubar(win
);
9353 gtk_box_pack_start(GTK_BOX(testidea
), menubar
, FALSE
, TRUE
, 0);
9355 gtk_box_pack_start(GTK_BOX(testidea
), win
->notebook
, TRUE
, TRUE
, 0);
9357 gtk_container_add(GTK_CONTAINER(win
->window
), testidea
);
9359 gtk_widget_show(testidea
);
9361 /* Update the plugin actions when plugins are (un)loaded */
9362 purple_signal_connect(purple_plugins_get_handle(), "plugin-load",
9363 win
, PURPLE_CALLBACK(plugin_changed_cb
), win
);
9364 purple_signal_connect(purple_plugins_get_handle(), "plugin-unload",
9365 win
, PURPLE_CALLBACK(plugin_changed_cb
), win
);
9369 g_signal_connect(G_OBJECT(win
->window
), "show",
9370 G_CALLBACK(winpidgin_ensure_onscreen
), win
->window
);
9372 if (purple_prefs_get_bool(PIDGIN_PREFS_ROOT
"/win32/minimize_new_convs")
9373 && !gtk_get_current_event_state(&state
))
9374 gtk_window_iconify(GTK_WINDOW(win
->window
));
9381 pidgin_conv_window_destroy(PidginWindow
*win
)
9383 purple_prefs_disconnect_by_handle(win
);
9384 window_list
= g_list_remove(window_list
, win
);
9386 /* Close the "Find" dialog if it's open */
9387 if (win
->dialogs
.search
)
9388 gtk_widget_destroy(win
->dialogs
.search
);
9390 if (win
->gtkconvs
) {
9391 while (win
->gtkconvs
) {
9392 gboolean last
= (win
->gtkconvs
->next
== NULL
);
9393 close_conv_cb(NULL
, win
->gtkconvs
->data
);
9399 gtk_widget_destroy(win
->window
);
9401 g_object_unref(G_OBJECT(win
->menu
.item_factory
));
9403 purple_notify_close_with_handle(win
);
9404 purple_signals_disconnect_by_handle(win
);
9410 pidgin_conv_window_show(PidginWindow
*win
)
9412 gtk_widget_show(win
->window
);
9416 pidgin_conv_window_hide(PidginWindow
*win
)
9418 gtk_widget_hide(win
->window
);
9422 pidgin_conv_window_raise(PidginWindow
*win
)
9424 gdk_window_raise(GDK_WINDOW(win
->window
->window
));
9428 pidgin_conv_window_switch_gtkconv(PidginWindow
*win
, PidginConversation
*gtkconv
)
9430 gtk_notebook_set_current_page(GTK_NOTEBOOK(win
->notebook
),
9431 gtk_notebook_page_num(GTK_NOTEBOOK(win
->notebook
),
9432 gtkconv
->tab_cont
));
9436 gtkconv_tab_set_tip(GtkWidget
*widget
, GdkEventCrossing
*event
, PidginConversation
*gtkconv
)
9438 #if GTK_CHECK_VERSION(2, 12, 0)
9439 #define gtk_tooltips_set_tip(tips, w, l, p) gtk_widget_set_tooltip_text(w, l)
9441 /* PANGO_VERSION_CHECK macro was introduced in 1.15. So we need this double check. */
9442 #ifndef PANGO_VERSION_CHECK
9443 #define pango_layout_is_ellipsized(l) TRUE
9444 #elif !PANGO_VERSION_CHECK(1,16,0)
9445 #define pango_layout_is_ellipsized(l) TRUE
9447 PangoLayout
*layout
;
9449 layout
= gtk_label_get_layout(GTK_LABEL(gtkconv
->tab_label
));
9450 gtk_tooltips_set_tip(gtkconv
->tooltips
, widget
,
9451 pango_layout_is_ellipsized(layout
) ? gtk_label_get_text(GTK_LABEL(gtkconv
->tab_label
)) : NULL
,
9454 #if GTK_CHECK_VERSION(2, 12, 0)
9455 #undef gtk_tooltips_set_tip
9460 pidgin_conv_window_add_gtkconv(PidginWindow
*win
, PidginConversation
*gtkconv
)
9462 PurpleConversation
*conv
= gtkconv
->active_conv
;
9463 PidginConversation
*focus_gtkconv
;
9464 GtkWidget
*tab_cont
= gtkconv
->tab_cont
;
9465 PurpleConversationType conv_type
;
9466 const gchar
*tmp_lab
;
9468 conv_type
= purple_conversation_get_type(conv
);
9470 win
->gtkconvs
= g_list_append(win
->gtkconvs
, gtkconv
);
9473 if (win
->gtkconvs
&& win
->gtkconvs
->next
&& win
->gtkconvs
->next
->next
== NULL
)
9474 pidgin_conv_tab_pack(win
, ((PidginConversation
*)win
->gtkconvs
->data
));
9478 gtkconv
->close
= pidgin_create_small_button(gtk_label_new("×"));
9479 gtk_tooltips_set_tip(gtkconv
->tooltips
, gtkconv
->close
,
9480 _("Close conversation"), NULL
);
9482 g_signal_connect(gtkconv
->close
, "clicked", G_CALLBACK (close_conv_cb
), gtkconv
);
9485 gtkconv
->icon
= gtk_image_new();
9486 gtkconv
->menu_icon
= gtk_image_new();
9487 g_object_set(G_OBJECT(gtkconv
->icon
),
9488 "icon-size", gtk_icon_size_from_name(PIDGIN_ICON_SIZE_TANGO_MICROSCOPIC
),
9490 g_object_set(G_OBJECT(gtkconv
->menu_icon
),
9491 "icon-size", gtk_icon_size_from_name(PIDGIN_ICON_SIZE_TANGO_MICROSCOPIC
),
9493 gtk_widget_show(gtkconv
->icon
);
9494 update_tab_icon(conv
);
9497 gtkconv
->tab_label
= gtk_label_new(tmp_lab
= purple_conversation_get_title(conv
));
9498 gtk_widget_set_name(gtkconv
->tab_label
, "tab-label");
9500 gtkconv
->menu_tabby
= gtk_hbox_new(FALSE
, PIDGIN_HIG_BOX_SPACE
);
9501 gtkconv
->menu_label
= gtk_label_new(tmp_lab
);
9502 gtk_box_pack_start(GTK_BOX(gtkconv
->menu_tabby
), gtkconv
->menu_icon
, FALSE
, FALSE
, 0);
9504 gtk_widget_show_all(gtkconv
->menu_icon
);
9506 gtk_box_pack_start(GTK_BOX(gtkconv
->menu_tabby
), gtkconv
->menu_label
, TRUE
, TRUE
, 0);
9507 gtk_widget_show(gtkconv
->menu_label
);
9508 gtk_misc_set_alignment(GTK_MISC(gtkconv
->menu_label
), 0, 0);
9510 gtk_widget_show(gtkconv
->menu_tabby
);
9512 if (conv_type
== PURPLE_CONV_TYPE_IM
)
9513 pidgin_conv_update_buddy_icon(conv
);
9515 /* Build and set conversations tab */
9516 pidgin_conv_tab_pack(win
, gtkconv
);
9518 gtk_notebook_set_menu_label(GTK_NOTEBOOK(win
->notebook
), tab_cont
, gtkconv
->menu_tabby
);
9520 gtk_widget_show(tab_cont
);
9522 if (pidgin_conv_window_get_gtkconv_count(win
) == 1) {
9523 /* Er, bug in notebooks? Switch to the page manually. */
9524 gtk_notebook_set_current_page(GTK_NOTEBOOK(win
->notebook
), 0);
9526 gtk_notebook_set_show_tabs(GTK_NOTEBOOK(win
->notebook
), TRUE
);
9529 focus_gtkconv
= g_list_nth_data(pidgin_conv_window_get_gtkconvs(win
),
9530 gtk_notebook_get_current_page(GTK_NOTEBOOK(win
->notebook
)));
9531 gtk_widget_grab_focus(focus_gtkconv
->entry
);
9533 if (pidgin_conv_window_get_gtkconv_count(win
) == 1)
9534 update_send_to_selection(win
);
9538 pidgin_conv_tab_pack(PidginWindow
*win
, PidginConversation
*gtkconv
)
9540 gboolean tabs_side
= FALSE
;
9542 GtkWidget
*first
, *third
, *ebox
;
9544 if (purple_prefs_get_int(PIDGIN_PREFS_ROOT
"/conversations/tab_side") == GTK_POS_LEFT
||
9545 purple_prefs_get_int(PIDGIN_PREFS_ROOT
"/conversations/tab_side") == GTK_POS_RIGHT
)
9547 else if (purple_prefs_get_int(PIDGIN_PREFS_ROOT
"/conversations/tab_side") == (GTK_POS_LEFT
|8))
9549 else if (purple_prefs_get_int(PIDGIN_PREFS_ROOT
"/conversations/tab_side") == (GTK_POS_RIGHT
|8))
9553 g_object_set(G_OBJECT(gtkconv
->tab_label
), "ellipsize", PANGO_ELLIPSIZE_END
, NULL
);
9554 gtk_label_set_width_chars(GTK_LABEL(gtkconv
->tab_label
), 4);
9556 g_object_set(G_OBJECT(gtkconv
->tab_label
), "ellipsize", PANGO_ELLIPSIZE_NONE
, NULL
);
9557 gtk_label_set_width_chars(GTK_LABEL(gtkconv
->tab_label
), -1);
9561 gtk_label_set_width_chars(
9562 GTK_LABEL(gtkconv
->tab_label
),
9563 MIN(g_utf8_strlen(gtk_label_get_text(GTK_LABEL(gtkconv
->tab_label
)), -1), 12)
9567 gtk_label_set_angle(GTK_LABEL(gtkconv
->tab_label
), angle
);
9570 gtk_misc_set_alignment(GTK_MISC(gtkconv
->tab_label
), 0.00, 0.5);
9571 gtk_misc_set_padding(GTK_MISC(gtkconv
->tab_label
), 4, 0);
9575 gtkconv
->tabby
= gtk_vbox_new(FALSE
, PIDGIN_HIG_BOX_SPACE
);
9577 gtkconv
->tabby
= gtk_hbox_new(FALSE
, PIDGIN_HIG_BOX_SPACE
);
9578 gtk_widget_set_name(gtkconv
->tabby
, "tab-container");
9580 /* select the correct ordering for verticle tabs */
9582 first
= gtkconv
->close
;
9583 third
= gtkconv
->icon
;
9585 first
= gtkconv
->icon
;
9586 third
= gtkconv
->close
;
9589 ebox
= gtk_event_box_new();
9590 gtk_event_box_set_visible_window(GTK_EVENT_BOX(ebox
), FALSE
);
9591 gtk_container_add(GTK_CONTAINER(ebox
), gtkconv
->tabby
);
9592 g_signal_connect(G_OBJECT(ebox
), "enter-notify-event",
9593 G_CALLBACK(gtkconv_tab_set_tip
), gtkconv
);
9595 if (gtkconv
->tab_label
->parent
== NULL
) {
9596 /* Pack if it's a new widget */
9597 gtk_box_pack_start(GTK_BOX(gtkconv
->tabby
), first
, FALSE
, FALSE
, 0);
9598 gtk_box_pack_start(GTK_BOX(gtkconv
->tabby
), gtkconv
->tab_label
, TRUE
, TRUE
, 0);
9599 gtk_box_pack_start(GTK_BOX(gtkconv
->tabby
), third
, FALSE
, FALSE
, 0);
9601 /* Add this pane to the conversation's notebook. */
9602 gtk_notebook_append_page(GTK_NOTEBOOK(win
->notebook
), gtkconv
->tab_cont
, ebox
);
9604 /* reparent old widgets on preference changes */
9605 gtk_widget_reparent(first
, gtkconv
->tabby
);
9606 gtk_widget_reparent(gtkconv
->tab_label
, gtkconv
->tabby
);
9607 gtk_widget_reparent(third
, gtkconv
->tabby
);
9608 gtk_box_set_child_packing(GTK_BOX(gtkconv
->tabby
), first
, FALSE
, FALSE
, 0, GTK_PACK_START
);
9609 gtk_box_set_child_packing(GTK_BOX(gtkconv
->tabby
), gtkconv
->tab_label
, TRUE
, TRUE
, 0, GTK_PACK_START
);
9610 gtk_box_set_child_packing(GTK_BOX(gtkconv
->tabby
), third
, FALSE
, FALSE
, 0, GTK_PACK_START
);
9612 /* Reset the tabs label to the new version */
9613 gtk_notebook_set_tab_label(GTK_NOTEBOOK(win
->notebook
), gtkconv
->tab_cont
, ebox
);
9616 gtk_notebook_set_tab_label_packing(GTK_NOTEBOOK(win
->notebook
), gtkconv
->tab_cont
,
9617 !tabs_side
&& !angle
,
9618 TRUE
, GTK_PACK_START
);
9620 if (pidgin_conv_window_get_gtkconv_count(win
) == 1)
9621 gtk_notebook_set_show_tabs(GTK_NOTEBOOK(win
->notebook
),
9622 purple_prefs_get_bool(PIDGIN_PREFS_ROOT
"/conversations/tabs") &&
9623 (!purple_prefs_get_bool(PIDGIN_PREFS_ROOT
"/conversations/im/show_buddy_icons") ||
9624 purple_prefs_get_int(PIDGIN_PREFS_ROOT
"/conversations/tab_side") != GTK_POS_TOP
));
9626 /* show the widgets */
9627 /* gtk_widget_show(gtkconv->icon); */
9628 gtk_widget_show(gtkconv
->tab_label
);
9629 if (purple_prefs_get_bool(PIDGIN_PREFS_ROOT
"/conversations/close_on_tabs"))
9630 gtk_widget_show(gtkconv
->close
);
9631 gtk_widget_show(gtkconv
->tabby
);
9632 gtk_widget_show(ebox
);
9636 pidgin_conv_window_remove_gtkconv(PidginWindow
*win
, PidginConversation
*gtkconv
)
9640 index
= gtk_notebook_page_num(GTK_NOTEBOOK(win
->notebook
), gtkconv
->tab_cont
);
9642 g_object_ref(gtkconv
->tab_cont
);
9643 gtk_object_sink(GTK_OBJECT(gtkconv
->tab_cont
));
9645 gtk_notebook_remove_page(GTK_NOTEBOOK(win
->notebook
), index
);
9647 win
->gtkconvs
= g_list_remove(win
->gtkconvs
, gtkconv
);
9649 g_signal_handlers_disconnect_matched(win
->window
, G_SIGNAL_MATCH_DATA
,
9650 0, 0, NULL
, NULL
, gtkconv
);
9652 if (win
->gtkconvs
&& win
->gtkconvs
->next
== NULL
)
9653 pidgin_conv_tab_pack(win
, win
->gtkconvs
->data
);
9655 if (!win
->gtkconvs
&& win
!= hidden_convwin
)
9656 pidgin_conv_window_destroy(win
);
9659 PidginConversation
*
9660 pidgin_conv_window_get_gtkconv_at_index(const PidginWindow
*win
, int index
)
9662 GtkWidget
*tab_cont
;
9666 tab_cont
= gtk_notebook_get_nth_page(GTK_NOTEBOOK(win
->notebook
), index
);
9667 return tab_cont
? g_object_get_data(G_OBJECT(tab_cont
), "PidginConversation") : NULL
;
9670 PidginConversation
*
9671 pidgin_conv_window_get_active_gtkconv(const PidginWindow
*win
)
9674 GtkWidget
*tab_cont
;
9676 index
= gtk_notebook_get_current_page(GTK_NOTEBOOK(win
->notebook
));
9679 tab_cont
= gtk_notebook_get_nth_page(GTK_NOTEBOOK(win
->notebook
), index
);
9682 return g_object_get_data(G_OBJECT(tab_cont
), "PidginConversation");
9686 PurpleConversation
*
9687 pidgin_conv_window_get_active_conversation(const PidginWindow
*win
)
9689 PidginConversation
*gtkconv
;
9691 gtkconv
= pidgin_conv_window_get_active_gtkconv(win
);
9692 return gtkconv
? gtkconv
->active_conv
: NULL
;
9696 pidgin_conv_window_is_active_conversation(const PurpleConversation
*conv
)
9698 return conv
== pidgin_conv_window_get_active_conversation(PIDGIN_CONVERSATION(conv
)->win
);
9702 pidgin_conv_window_has_focus(PidginWindow
*win
)
9704 gboolean has_focus
= FALSE
;
9706 g_object_get(G_OBJECT(win
->window
), "has-toplevel-focus", &has_focus
, NULL
);
9712 pidgin_conv_window_get_at_xy(int x
, int y
)
9718 gdkwin
= gdk_window_at_pointer(&x
, &y
);
9721 gdkwin
= gdk_window_get_toplevel(gdkwin
);
9723 for (l
= pidgin_conv_windows_get_list(); l
!= NULL
; l
= l
->next
) {
9726 if (gdkwin
== win
->window
->window
)
9734 pidgin_conv_window_get_gtkconvs(PidginWindow
*win
)
9736 return win
->gtkconvs
;
9740 pidgin_conv_window_get_gtkconv_count(PidginWindow
*win
)
9742 return g_list_length(win
->gtkconvs
);
9746 pidgin_conv_window_first_with_type(PurpleConversationType type
)
9748 GList
*wins
, *convs
;
9750 PidginConversation
*conv
;
9752 if (type
== PURPLE_CONV_TYPE_UNKNOWN
)
9755 for (wins
= pidgin_conv_windows_get_list(); wins
!= NULL
; wins
= wins
->next
) {
9758 for (convs
= win
->gtkconvs
;
9760 convs
= convs
->next
) {
9764 if (purple_conversation_get_type(conv
->active_conv
) == type
)
9773 pidgin_conv_window_last_with_type(PurpleConversationType type
)
9775 GList
*wins
, *convs
;
9777 PidginConversation
*conv
;
9779 if (type
== PURPLE_CONV_TYPE_UNKNOWN
)
9782 for (wins
= g_list_last(pidgin_conv_windows_get_list());
9784 wins
= wins
->prev
) {
9788 for (convs
= win
->gtkconvs
;
9790 convs
= convs
->next
) {
9794 if (purple_conversation_get_type(conv
->active_conv
) == type
)
9803 /**************************************************************************
9804 * Conversation placement functions
9805 **************************************************************************/
9810 PidginConvPlacementFunc fnc
;
9812 } ConvPlacementData
;
9814 static GList
*conv_placement_fncs
= NULL
;
9815 static PidginConvPlacementFunc place_conv
= NULL
;
9817 /* This one places conversations in the last made window. */
9819 conv_placement_last_created_win(PidginConversation
*conv
)
9823 GList
*l
= g_list_last(pidgin_conv_windows_get_list());
9824 win
= l
? l
->data
: NULL
;;
9827 win
= pidgin_conv_window_new();
9829 g_signal_connect(G_OBJECT(win
->window
), "configure_event",
9830 G_CALLBACK(gtk_conv_configure_cb
), NULL
);
9832 pidgin_conv_window_add_gtkconv(win
, conv
);
9833 pidgin_conv_window_show(win
);
9835 pidgin_conv_window_add_gtkconv(win
, conv
);
9839 /* This one places conversations in the last made window of the same type. */
9841 conv_placement_last_created_win_type_configured_cb(GtkWidget
*w
,
9842 GdkEventConfigure
*event
, PidginConversation
*conv
)
9845 PurpleConversationType type
= purple_conversation_get_type(conv
->active_conv
);
9848 if (GTK_WIDGET_VISIBLE(w
))
9849 gtk_window_get_position(GTK_WINDOW(w
), &x
, &y
);
9851 return FALSE
; /* carry on normally */
9853 /* Workaround for GTK+ bug # 169811 - "configure_event" is fired
9854 * when the window is being maximized */
9855 if (gdk_window_get_state(w
->window
) & GDK_WINDOW_STATE_MAXIMIZED
)
9858 /* don't save off-screen positioning */
9859 if (x
+ event
->width
< 0 ||
9860 y
+ event
->height
< 0 ||
9861 x
> gdk_screen_width() ||
9862 y
> gdk_screen_height())
9863 return FALSE
; /* carry on normally */
9865 for (all
= conv
->convs
; all
!= NULL
; all
= all
->next
) {
9866 if (type
!= purple_conversation_get_type(all
->data
)) {
9867 /* this window has different types of conversation, don't save */
9872 if (type
== PURPLE_CONV_TYPE_IM
) {
9873 purple_prefs_set_int(PIDGIN_PREFS_ROOT
"/conversations/im/x", x
);
9874 purple_prefs_set_int(PIDGIN_PREFS_ROOT
"/conversations/im/y", y
);
9875 purple_prefs_set_int(PIDGIN_PREFS_ROOT
"/conversations/im/width", event
->width
);
9876 purple_prefs_set_int(PIDGIN_PREFS_ROOT
"/conversations/im/height", event
->height
);
9877 } else if (type
== PURPLE_CONV_TYPE_CHAT
) {
9878 purple_prefs_set_int(PIDGIN_PREFS_ROOT
"/conversations/chat/x", x
);
9879 purple_prefs_set_int(PIDGIN_PREFS_ROOT
"/conversations/chat/y", y
);
9880 purple_prefs_set_int(PIDGIN_PREFS_ROOT
"/conversations/chat/width", event
->width
);
9881 purple_prefs_set_int(PIDGIN_PREFS_ROOT
"/conversations/chat/height", event
->height
);
9888 conv_placement_last_created_win_type(PidginConversation
*conv
)
9892 win
= pidgin_conv_window_last_with_type(purple_conversation_get_type(conv
->active_conv
));
9895 win
= pidgin_conv_window_new();
9897 if (PURPLE_CONV_TYPE_IM
== purple_conversation_get_type(conv
->active_conv
) ||
9898 purple_prefs_get_int(PIDGIN_PREFS_ROOT
"/conversations/chat/width") == 0) {
9899 pidgin_conv_set_position_size(win
,
9900 purple_prefs_get_int(PIDGIN_PREFS_ROOT
"/conversations/im/x"),
9901 purple_prefs_get_int(PIDGIN_PREFS_ROOT
"/conversations/im/y"),
9902 purple_prefs_get_int(PIDGIN_PREFS_ROOT
"/conversations/im/width"),
9903 purple_prefs_get_int(PIDGIN_PREFS_ROOT
"/conversations/im/height"));
9904 } else if (PURPLE_CONV_TYPE_CHAT
== purple_conversation_get_type(conv
->active_conv
)) {
9905 pidgin_conv_set_position_size(win
,
9906 purple_prefs_get_int(PIDGIN_PREFS_ROOT
"/conversations/chat/x"),
9907 purple_prefs_get_int(PIDGIN_PREFS_ROOT
"/conversations/chat/y"),
9908 purple_prefs_get_int(PIDGIN_PREFS_ROOT
"/conversations/chat/width"),
9909 purple_prefs_get_int(PIDGIN_PREFS_ROOT
"/conversations/chat/height"));
9912 pidgin_conv_window_add_gtkconv(win
, conv
);
9913 pidgin_conv_window_show(win
);
9915 g_signal_connect(G_OBJECT(win
->window
), "configure_event",
9916 G_CALLBACK(conv_placement_last_created_win_type_configured_cb
), conv
);
9918 pidgin_conv_window_add_gtkconv(win
, conv
);
9921 /* This one places each conversation in its own window. */
9923 conv_placement_new_window(PidginConversation
*conv
)
9927 win
= pidgin_conv_window_new();
9929 g_signal_connect(G_OBJECT(win
->window
), "configure_event",
9930 G_CALLBACK(gtk_conv_configure_cb
), NULL
);
9932 pidgin_conv_window_add_gtkconv(win
, conv
);
9934 pidgin_conv_window_show(win
);
9937 static PurpleGroup
*
9938 conv_get_group(PidginConversation
*conv
)
9940 PurpleGroup
*group
= NULL
;
9942 if (purple_conversation_get_type(conv
->active_conv
) == PURPLE_CONV_TYPE_IM
) {
9945 buddy
= purple_find_buddy(purple_conversation_get_account(conv
->active_conv
),
9946 purple_conversation_get_name(conv
->active_conv
));
9949 group
= purple_buddy_get_group(buddy
);
9951 } else if (purple_conversation_get_type(conv
->active_conv
) == PURPLE_CONV_TYPE_CHAT
) {
9954 chat
= purple_blist_find_chat(purple_conversation_get_account(conv
->active_conv
),
9955 purple_conversation_get_name(conv
->active_conv
));
9958 group
= purple_chat_get_group(chat
);
9965 * This groups things by, well, group. Buddies from groups will always be
9966 * grouped together, and a buddy from a group not belonging to any currently
9967 * open windows will get a new window.
9970 conv_placement_by_group(PidginConversation
*conv
)
9972 PurpleGroup
*group
= NULL
;
9975 group
= conv_get_group(conv
);
9977 /* Go through the list of IMs and find one with this group. */
9978 for (wl
= pidgin_conv_windows_get_list(); wl
!= NULL
; wl
= wl
->next
) {
9980 PidginConversation
*conv2
;
9981 PurpleGroup
*group2
= NULL
;
9985 for (cl
= win2
->gtkconvs
;
9990 group2
= conv_get_group(conv2
);
9992 if (group
== group2
) {
9993 pidgin_conv_window_add_gtkconv(win2
, conv
);
10000 /* Make a new window. */
10001 conv_placement_new_window(conv
);
10004 /* This groups things by account. Otherwise, the same semantics as above */
10006 conv_placement_by_account(PidginConversation
*conv
)
10008 GList
*wins
, *convs
;
10009 PurpleAccount
*account
;
10011 account
= purple_conversation_get_account(conv
->active_conv
);
10013 /* Go through the list of IMs and find one with this group. */
10014 for (wins
= pidgin_conv_windows_get_list(); wins
!= NULL
; wins
= wins
->next
) {
10015 PidginWindow
*win2
;
10016 PidginConversation
*conv2
;
10020 for (convs
= win2
->gtkconvs
;
10022 convs
= convs
->next
) {
10023 conv2
= convs
->data
;
10025 if (account
== purple_conversation_get_account(conv2
->active_conv
)) {
10026 pidgin_conv_window_add_gtkconv(win2
, conv
);
10032 /* Make a new window. */
10033 conv_placement_new_window(conv
);
10036 static ConvPlacementData
*
10037 get_conv_placement_data(const char *id
)
10039 ConvPlacementData
*data
= NULL
;
10042 for (n
= conv_placement_fncs
; n
; n
= n
->next
) {
10044 if (!strcmp(data
->id
, id
))
10052 add_conv_placement_fnc(const char *id
, const char *name
,
10053 PidginConvPlacementFunc fnc
)
10055 ConvPlacementData
*data
;
10057 data
= g_new(ConvPlacementData
, 1);
10059 data
->id
= g_strdup(id
);
10060 data
->name
= g_strdup(name
);
10063 conv_placement_fncs
= g_list_append(conv_placement_fncs
, data
);
10067 ensure_default_funcs(void)
10069 if (conv_placement_fncs
== NULL
) {
10070 add_conv_placement_fnc("last", _("Last created window"),
10071 conv_placement_last_created_win
);
10072 add_conv_placement_fnc("im_chat", _("Separate IM and Chat windows"),
10073 conv_placement_last_created_win_type
);
10074 add_conv_placement_fnc("new", _("New window"),
10075 conv_placement_new_window
);
10076 add_conv_placement_fnc("group", _("By group"),
10077 conv_placement_by_group
);
10078 add_conv_placement_fnc("account", _("By account"),
10079 conv_placement_by_account
);
10084 pidgin_conv_placement_get_options(void)
10086 GList
*n
, *list
= NULL
;
10087 ConvPlacementData
*data
;
10089 ensure_default_funcs();
10091 for (n
= conv_placement_fncs
; n
; n
= n
->next
) {
10093 list
= g_list_append(list
, data
->name
);
10094 list
= g_list_append(list
, data
->id
);
10102 pidgin_conv_placement_add_fnc(const char *id
, const char *name
,
10103 PidginConvPlacementFunc fnc
)
10105 g_return_if_fail(id
!= NULL
);
10106 g_return_if_fail(name
!= NULL
);
10107 g_return_if_fail(fnc
!= NULL
);
10109 ensure_default_funcs();
10111 add_conv_placement_fnc(id
, name
, fnc
);
10115 pidgin_conv_placement_remove_fnc(const char *id
)
10117 ConvPlacementData
*data
= get_conv_placement_data(id
);
10122 conv_placement_fncs
= g_list_remove(conv_placement_fncs
, data
);
10125 g_free(data
->name
);
10130 pidgin_conv_placement_get_name(const char *id
)
10132 ConvPlacementData
*data
;
10134 ensure_default_funcs();
10136 data
= get_conv_placement_data(id
);
10144 PidginConvPlacementFunc
10145 pidgin_conv_placement_get_fnc(const char *id
)
10147 ConvPlacementData
*data
;
10149 ensure_default_funcs();
10151 data
= get_conv_placement_data(id
);
10160 pidgin_conv_placement_set_current_func(PidginConvPlacementFunc func
)
10162 g_return_if_fail(func
!= NULL
);
10164 /* If tabs are enabled, set the function, otherwise, NULL it out. */
10165 if (purple_prefs_get_bool(PIDGIN_PREFS_ROOT
"/conversations/tabs"))
10171 PidginConvPlacementFunc
10172 pidgin_conv_placement_get_current_func(void)
10178 pidgin_conv_placement_place(PidginConversation
*gtkconv
)
10181 place_conv(gtkconv
);
10183 conv_placement_new_window(gtkconv
);
10187 pidgin_conv_is_hidden(PidginConversation
*gtkconv
)
10189 g_return_val_if_fail(gtkconv
!= NULL
, FALSE
);
10191 return (gtkconv
->win
== hidden_convwin
);
10195 /* Algorithm from http://www.w3.org/TR/AERT#color-contrast */
10197 color_is_visible(GdkColor foreground
, GdkColor background
, int color_contrast
, int brightness_contrast
)
10199 gulong fg_brightness
;
10200 gulong bg_brightness
;
10203 int fred
, fgreen
, fblue
, bred
, bgreen
, bblue
;
10205 /* this algorithm expects colors between 0 and 255 for each of red green and blue.
10206 * GTK on the other hand has values between 0 and 65535
10207 * Err suggested I >> 8, which grabbed the high bits.
10210 fred
= foreground
.red
>> 8 ;
10211 fgreen
= foreground
.green
>> 8 ;
10212 fblue
= foreground
.blue
>> 8 ;
10215 bred
= background
.red
>> 8 ;
10216 bgreen
= background
.green
>> 8 ;
10217 bblue
= background
.blue
>> 8 ;
10219 fg_brightness
= (fred
* 299 + fgreen
* 587 + fblue
* 114) / 1000;
10220 bg_brightness
= (bred
* 299 + bgreen
* 587 + bblue
* 114) / 1000;
10221 br_diff
= abs(fg_brightness
- bg_brightness
);
10223 col_diff
= abs(fred
- bred
) + abs(fgreen
- bgreen
) + abs(fblue
- bblue
);
10225 return ((col_diff
> color_contrast
) && (br_diff
> brightness_contrast
));
10230 generate_nick_colors(guint
*color_count
, GdkColor background
)
10232 guint numcolors
= *color_count
;
10233 guint i
= 0, j
= 0;
10234 GdkColor
*colors
= g_new(GdkColor
, numcolors
);
10235 GdkColor nick_highlight
;
10236 GdkColor send_color
;
10237 time_t breakout_time
;
10239 gdk_color_parse(DEFAULT_HIGHLIGHT_COLOR
, &nick_highlight
);
10240 gdk_color_parse(DEFAULT_SEND_COLOR
, &send_color
);
10242 srand(background
.red
+ background
.green
+ background
.blue
+ 1);
10244 breakout_time
= time(NULL
) + 3;
10246 /* first we look through the list of "good" colors: colors that differ from every other color in the
10247 * list. only some of them will differ from the background color though. lets see if we can find
10248 * numcolors of them that do
10250 while (i
< numcolors
&& j
< NUM_NICK_SEED_COLORS
&& time(NULL
) < breakout_time
)
10252 GdkColor color
= nick_seed_colors
[j
];
10254 if (color_is_visible(color
, background
, MIN_COLOR_CONTRAST
, MIN_BRIGHTNESS_CONTRAST
) &&
10255 color_is_visible(color
, nick_highlight
, MIN_COLOR_CONTRAST
/ 2, 0) &&
10256 color_is_visible(color
, send_color
, MIN_COLOR_CONTRAST
/ 4, 0))
10264 /* we might not have found numcolors in the last loop. if we did, we'll never enter this one.
10265 * if we did not, lets just find some colors that don't conflict with the background. its
10266 * expensive to find colors that not only don't conflict with the background, but also do not
10267 * conflict with each other.
10269 while(i
< numcolors
&& time(NULL
) < breakout_time
)
10271 GdkColor color
= { 0, rand() % 65536, rand() % 65536, rand() % 65536 };
10273 if (color_is_visible(color
, background
, MIN_COLOR_CONTRAST
, MIN_BRIGHTNESS_CONTRAST
) &&
10274 color_is_visible(color
, nick_highlight
, MIN_COLOR_CONTRAST
/ 2, 0) &&
10275 color_is_visible(color
, send_color
, MIN_COLOR_CONTRAST
/ 4, 0))
10282 if (i
< numcolors
) {
10283 GdkColor
*c
= colors
;
10284 purple_debug_warning("gtkconv", "Unable to generate enough random colors before timeout. %u colors found.\n", i
);
10285 colors
= g_memdup(c
, i
* sizeof(GdkColor
));