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 <gtkspell/gtkspell.h>
39 #include <gdk/gdkkeysyms.h>
54 #include "gtkdnd-hints.h"
57 #include "gtkconvwin.h"
58 #include "gtkdialogs.h"
59 #include "gtkimhtml.h"
60 #include "gtkimhtmltoolbar.h"
62 #include "gtkmenutray.h"
63 #include "gtkpounce.h"
65 #include "gtkprivacy.h"
66 #include "gtkthemes.h"
68 #include "pidginstock.h"
69 #include "pidgintooltip.h"
71 #include "gtknickcolors.h"
73 #define CLOSE_CONV_TIMEOUT_SECS (10 * 60)
75 #define AUTO_RESPONSE "<AUTO-REPLY> : "
79 PIDGIN_CONV_SET_TITLE
= 1 << 0,
80 PIDGIN_CONV_BUDDY_ICON
= 1 << 1,
81 PIDGIN_CONV_MENU
= 1 << 2,
82 PIDGIN_CONV_TAB_ICON
= 1 << 3,
83 PIDGIN_CONV_TOPIC
= 1 << 4,
84 PIDGIN_CONV_SMILEY_THEME
= 1 << 5,
85 PIDGIN_CONV_COLORIZE_TITLE
= 1 << 6
92 CONV_PROTOCOL_ICON_COLUMN
,
94 } PidginInfopaneColumns
;
96 #define PIDGIN_CONV_ALL ((1 << 7) - 1)
98 /* XXX: These color defines shouldn't really be here. But the nick-color
99 * generation algorithm uses them, so keeping these around until we fix that. */
100 #define DEFAULT_SEND_COLOR "#204a87"
101 #define DEFAULT_HIGHLIGHT_COLOR "#AF7F00"
103 #define BUDDYICON_SIZE_MIN 32
104 #define BUDDYICON_SIZE_MAX 96
106 /* Undef this to turn off "custom-smiley" debug messages */
107 #define DEBUG_CUSTOM_SMILEY
109 #define LUMINANCE(c) (float)((0.3*(c.red))+(0.59*(c.green))+(0.11*(c.blue)))
111 /* From http://www.w3.org/TR/AERT#color-contrast */
112 #define MIN_BRIGHTNESS_CONTRAST 75
113 #define MIN_COLOR_CONTRAST 200
115 #define NUM_NICK_COLORS 220
116 static GdkColor
*nick_colors
= NULL
;
117 static guint nbr_nick_colors
;
125 PurpleConversation
*conv
;
129 static GtkWidget
*invite_dialog
= NULL
;
130 static GtkWidget
*warn_close_dialog
= NULL
;
132 static PidginWindow
*hidden_convwin
= NULL
;
133 static GList
*window_list
= NULL
;
135 /* Lists of status icons at all available sizes for use as window icons */
136 static GList
*available_list
= NULL
;
137 static GList
*away_list
= NULL
;
138 static GList
*busy_list
= NULL
;
139 static GList
*xa_list
= NULL
;
140 static GList
*offline_list
= NULL
;
141 static GHashTable
*prpl_lists
= NULL
;
143 static gboolean
update_send_to_selection(PidginWindow
*win
);
144 static void generate_send_to_items(PidginWindow
*win
);
146 /* Prototypes. <-- because Paco-Paco hates this comment. */
147 static gboolean
infopane_entry_activate(PidginConversation
*gtkconv
);
148 static void got_typing_keypress(PidginConversation
*gtkconv
, gboolean first
);
149 static void gray_stuff_out(PidginConversation
*gtkconv
);
150 static void add_chat_buddy_common(PurpleConversation
*conv
, PurpleConvChatBuddy
*cb
, const char *old_name
);
151 static gboolean
tab_complete(PurpleConversation
*conv
);
152 static void pidgin_conv_updated(PurpleConversation
*conv
, PurpleConvUpdateType type
);
153 static void conv_set_unseen(PurpleConversation
*gtkconv
, PidginUnseenState state
);
154 static void gtkconv_set_unseen(PidginConversation
*gtkconv
, PidginUnseenState state
);
155 static void update_typing_icon(PidginConversation
*gtkconv
);
156 static void update_typing_message(PidginConversation
*gtkconv
, const char *message
);
157 static const char *item_factory_translate_func (const char *path
, gpointer func_data
);
158 gboolean
pidgin_conv_has_focus(PurpleConversation
*conv
);
159 static GdkColor
* generate_nick_colors(guint
*numcolors
, GdkColor background
);
160 static gboolean
color_is_visible(GdkColor foreground
, GdkColor background
, guint color_contrast
, guint brightness_contrast
);
161 static GtkTextTag
*get_buddy_tag(PurpleConversation
*conv
, const char *who
, PurpleMessageFlags flag
, gboolean create
);
162 static void pidgin_conv_update_fields(PurpleConversation
*conv
, PidginConvFields fields
);
163 static void focus_out_from_menubar(GtkWidget
*wid
, PidginWindow
*win
);
164 static void pidgin_conv_tab_pack(PidginWindow
*win
, PidginConversation
*gtkconv
);
165 static gboolean
infopane_press_cb(GtkWidget
*widget
, GdkEventButton
*e
, PidginConversation
*conv
);
166 static void hide_conv(PidginConversation
*gtkconv
, gboolean closetimer
);
168 static void pidgin_conv_set_position_size(PidginWindow
*win
, int x
, int y
,
169 int width
, int height
);
170 static gboolean
pidgin_conv_xy_to_right_infopane(PidginWindow
*win
, int x
, int y
);
172 static const GdkColor
*get_nick_color(PidginConversation
*gtkconv
, const char *name
)
175 GtkStyle
*style
= gtk_widget_get_style(gtkconv
->imhtml
);
178 col
= nick_colors
[g_str_hash(name
) % nbr_nick_colors
];
179 scale
= ((1-(LUMINANCE(style
->base
[GTK_STATE_NORMAL
]) / LUMINANCE(style
->white
))) *
180 (LUMINANCE(style
->white
)/MAX(MAX(col
.red
, col
.blue
), col
.green
)));
182 /* The colors are chosen to look fine on white; we should never have to darken */
192 static PurpleBlistNode
*
193 get_conversation_blist_node(PurpleConversation
*conv
)
195 PurpleBlistNode
*node
= NULL
;
197 switch (purple_conversation_get_type(conv
)) {
198 case PURPLE_CONV_TYPE_IM
:
199 node
= PURPLE_BLIST_NODE(purple_find_buddy(conv
->account
, conv
->name
));
200 node
= node
? node
->parent
: NULL
;
202 case PURPLE_CONV_TYPE_CHAT
:
203 node
= PURPLE_BLIST_NODE(purple_blist_find_chat(conv
->account
, conv
->name
));
211 /**************************************************************************
213 **************************************************************************/
216 close_this_sucker(gpointer data
)
218 PidginConversation
*gtkconv
= data
;
219 GList
*list
= g_list_copy(gtkconv
->convs
);
220 g_list_foreach(list
, (GFunc
)purple_conversation_destroy
, NULL
);
226 close_conv_cb(GtkButton
*button
, PidginConversation
*gtkconv
)
228 /* We are going to destroy the conversations immediately only if the 'close immediately'
229 * preference is selected. Otherwise, close the conversation after a reasonable timeout
230 * (I am going to consider 10 minutes as a 'reasonable timeout' here.
231 * For chats, close immediately if the chat is not in the buddylist, or if the chat is
232 * not marked 'Persistent' */
233 PurpleConversation
*conv
= gtkconv
->active_conv
;
234 PurpleAccount
*account
= purple_conversation_get_account(conv
);
235 const char *name
= purple_conversation_get_name(conv
);
237 switch (purple_conversation_get_type(conv
)) {
238 case PURPLE_CONV_TYPE_IM
:
240 if (purple_prefs_get_bool(PIDGIN_PREFS_ROOT
"/conversations/im/close_immediately"))
241 close_this_sucker(gtkconv
);
243 hide_conv(gtkconv
, TRUE
);
246 case PURPLE_CONV_TYPE_CHAT
:
248 PurpleChat
*chat
= purple_blist_find_chat(account
, name
);
250 !purple_blist_node_get_bool(&chat
->node
, "gtk-persistent"))
251 close_this_sucker(gtkconv
);
253 hide_conv(gtkconv
, FALSE
);
264 lbox_size_allocate_cb(GtkWidget
*w
, GtkAllocation
*allocation
, gpointer data
)
266 purple_prefs_set_int(PIDGIN_PREFS_ROOT
"/conversations/chat/userlist_width", allocation
->width
== 1 ? 0 : allocation
->width
);
272 default_formatize(PidginConversation
*c
)
274 PurpleConversation
*conv
= c
->active_conv
;
275 gtk_imhtml_setup_entry(GTK_IMHTML(c
->entry
), conv
->features
);
279 conversation_entry_clear(PidginConversation
*gtkconv
)
281 GtkIMHtml
*imhtml
= GTK_IMHTML(gtkconv
->entry
);
282 gtk_source_undo_manager_begin_not_undoable_action(imhtml
->undo_manager
);
283 gtk_imhtml_clear(imhtml
);
284 gtk_source_undo_manager_end_not_undoable_action(imhtml
->undo_manager
);
288 clear_formatting_cb(GtkIMHtml
*imhtml
, PidginConversation
*gtkconv
)
290 default_formatize(gtkconv
);
294 pidgin_get_cmd_prefix(void)
300 say_command_cb(PurpleConversation
*conv
,
301 const char *cmd
, char **args
, char **error
, void *data
)
303 if (purple_conversation_get_type(conv
) == PURPLE_CONV_TYPE_IM
)
304 purple_conv_im_send(PURPLE_CONV_IM(conv
), args
[0]);
305 else if (purple_conversation_get_type(conv
) == PURPLE_CONV_TYPE_CHAT
)
306 purple_conv_chat_send(PURPLE_CONV_CHAT(conv
), args
[0]);
308 return PURPLE_CMD_RET_OK
;
312 me_command_cb(PurpleConversation
*conv
,
313 const char *cmd
, char **args
, char **error
, void *data
)
317 tmp
= g_strdup_printf("/me %s", args
[0]);
319 if (purple_conversation_get_type(conv
) == PURPLE_CONV_TYPE_IM
)
320 purple_conv_im_send(PURPLE_CONV_IM(conv
), tmp
);
321 else if (purple_conversation_get_type(conv
) == PURPLE_CONV_TYPE_CHAT
)
322 purple_conv_chat_send(PURPLE_CONV_CHAT(conv
), tmp
);
325 return PURPLE_CMD_RET_OK
;
329 debug_command_cb(PurpleConversation
*conv
,
330 const char *cmd
, char **args
, char **error
, void *data
)
334 if (!g_ascii_strcasecmp(args
[0], "version")) {
335 tmp
= g_strdup_printf("Using Pidgin v%s with libpurple v%s.",
336 DISPLAY_VERSION
, purple_core_get_version());
337 } else if (!g_ascii_strcasecmp(args
[0], "plugins")) {
338 /* Show all the loaded plugins, including the protocol plugins and plugin loaders.
339 * This is intentional, since third party prpls are often sources of bugs, and some
340 * plugin loaders (e.g. mono) can also be buggy.
342 GString
*str
= g_string_new("Loaded Plugins: ");
343 const GList
*plugins
= purple_plugins_get_loaded();
345 for (; plugins
; plugins
= plugins
->next
) {
346 str
= g_string_append(str
, purple_plugin_get_name(plugins
->data
));
348 str
= g_string_append(str
, ", ");
351 str
= g_string_append(str
, "(none)");
354 tmp
= g_string_free(str
, FALSE
);
356 purple_conversation_write(conv
, NULL
, _("Supported debug options are: plugins, version"),
357 PURPLE_MESSAGE_NO_LOG
|PURPLE_MESSAGE_ERROR
, time(NULL
));
358 return PURPLE_CMD_RET_OK
;
361 markup
= g_markup_escape_text(tmp
, -1);
362 if (purple_conversation_get_type(conv
) == PURPLE_CONV_TYPE_IM
)
363 purple_conv_im_send(PURPLE_CONV_IM(conv
), markup
);
364 else if (purple_conversation_get_type(conv
) == PURPLE_CONV_TYPE_CHAT
)
365 purple_conv_chat_send(PURPLE_CONV_CHAT(conv
), markup
);
369 return PURPLE_CMD_RET_OK
;
372 static void clear_conversation_scrollback_cb(PurpleConversation
*conv
,
375 PidginConversation
*gtkconv
= NULL
;
377 gtkconv
= PIDGIN_CONVERSATION(conv
);
379 gtk_imhtml_clear(GTK_IMHTML(gtkconv
->imhtml
));
383 clear_command_cb(PurpleConversation
*conv
,
384 const char *cmd
, char **args
, char **error
, void *data
)
386 purple_conversation_clear_message_history(conv
);
387 return PURPLE_CMD_RET_OK
;
391 clearall_command_cb(PurpleConversation
*conv
,
392 const char *cmd
, char **args
, char **error
, void *data
)
394 purple_conversation_foreach(purple_conversation_clear_message_history
);
395 return PURPLE_CMD_RET_OK
;
399 help_command_cb(PurpleConversation
*conv
,
400 const char *cmd
, char **args
, char **error
, void *data
)
405 if (args
[0] != NULL
) {
406 s
= g_string_new("");
407 text
= purple_cmd_help(conv
, args
[0]);
410 for (l
= text
; l
; l
= l
->next
)
412 g_string_append_printf(s
, "%s\n", (char *)l
->data
);
414 g_string_append_printf(s
, "%s", (char *)l
->data
);
416 g_string_append(s
, _("No such command (in this context)."));
419 s
= g_string_new(_("Use \"/help <command>\" for help on a specific command.\n"
420 "The following commands are available in this context:\n"));
422 text
= purple_cmd_list(conv
);
423 for (l
= text
; l
; l
= l
->next
)
425 g_string_append_printf(s
, "%s, ", (char *)l
->data
);
427 g_string_append_printf(s
, "%s.", (char *)l
->data
);
431 purple_conversation_write(conv
, NULL
, s
->str
, PURPLE_MESSAGE_NO_LOG
, time(NULL
));
432 g_string_free(s
, TRUE
);
434 return PURPLE_CMD_RET_OK
;
438 send_history_add(PidginConversation
*gtkconv
, const char *message
)
442 first
= g_list_first(gtkconv
->send_history
);
444 first
->data
= g_strdup(message
);
445 gtkconv
->send_history
= g_list_prepend(first
, NULL
);
449 check_for_and_do_command(PurpleConversation
*conv
)
451 PidginConversation
*gtkconv
;
455 gboolean retval
= FALSE
;
457 gtkconv
= PIDGIN_CONVERSATION(conv
);
458 prefix
= pidgin_get_cmd_prefix();
460 cmd
= gtk_imhtml_get_text(GTK_IMHTML(gtkconv
->entry
), NULL
, NULL
);
461 gtk_text_buffer_get_start_iter(GTK_IMHTML(gtkconv
->entry
)->text_buffer
, &start
);
463 if (cmd
&& (strncmp(cmd
, prefix
, strlen(prefix
)) == 0)
464 && !gtk_text_iter_get_child_anchor(&start
)) {
465 PurpleCmdStatus status
;
466 char *error
, *cmdline
, *markup
, *send_history
;
469 send_history
= gtk_imhtml_get_markup(GTK_IMHTML(gtkconv
->entry
));
470 send_history_add(gtkconv
, send_history
);
471 g_free(send_history
);
473 cmdline
= cmd
+ strlen(prefix
);
475 if (purple_strequal(cmdline
, "xyzzy")) {
476 purple_conversation_write(conv
, "", "Nothing happens",
477 PURPLE_MESSAGE_NO_LOG
, time(NULL
));
482 gtk_text_iter_forward_chars(&start
, g_utf8_strlen(prefix
, -1));
483 gtk_text_buffer_get_end_iter(GTK_IMHTML(gtkconv
->entry
)->text_buffer
, &end
);
484 markup
= gtk_imhtml_get_markup_range(GTK_IMHTML(gtkconv
->entry
), &start
, &end
);
485 status
= purple_cmd_do_command(conv
, cmdline
, markup
, &error
);
489 case PURPLE_CMD_STATUS_OK
:
492 case PURPLE_CMD_STATUS_NOT_FOUND
:
494 PurplePluginProtocolInfo
*prpl_info
= NULL
;
495 PurpleConnection
*gc
;
497 if ((gc
= purple_conversation_get_gc(conv
)))
498 prpl_info
= PURPLE_PLUGIN_PROTOCOL_INFO(gc
->prpl
);
500 if ((prpl_info
!= NULL
) && (prpl_info
->options
& OPT_PROTO_SLASH_COMMANDS_NATIVE
)) {
503 /* If the first word in the entered text has a '/' in it, then the user
504 * probably didn't mean it as a command. So send the text as message. */
505 spaceslash
= cmdline
;
506 while (*spaceslash
&& *spaceslash
!= ' ' && *spaceslash
!= '/')
509 if (*spaceslash
!= '/') {
510 purple_conversation_write(conv
, "", _("Unknown command."), PURPLE_MESSAGE_NO_LOG
, time(NULL
));
516 case PURPLE_CMD_STATUS_WRONG_ARGS
:
517 purple_conversation_write(conv
, "", _("Syntax Error: You typed the wrong number of arguments "
519 PURPLE_MESSAGE_NO_LOG
, time(NULL
));
522 case PURPLE_CMD_STATUS_FAILED
:
523 purple_conversation_write(conv
, "", error
? error
: _("Your command failed for an unknown reason."),
524 PURPLE_MESSAGE_NO_LOG
, time(NULL
));
528 case PURPLE_CMD_STATUS_WRONG_TYPE
:
529 if(purple_conversation_get_type(conv
) == PURPLE_CONV_TYPE_IM
)
530 purple_conversation_write(conv
, "", _("That command only works in chats, not IMs."),
531 PURPLE_MESSAGE_NO_LOG
, time(NULL
));
533 purple_conversation_write(conv
, "", _("That command only works in IMs, not chats."),
534 PURPLE_MESSAGE_NO_LOG
, time(NULL
));
537 case PURPLE_CMD_STATUS_WRONG_PRPL
:
538 purple_conversation_write(conv
, "", _("That command doesn't work on this protocol."),
539 PURPLE_MESSAGE_NO_LOG
, time(NULL
));
550 send_cb(GtkWidget
*widget
, PidginConversation
*gtkconv
)
552 PurpleConversation
*conv
= gtkconv
->active_conv
;
553 PurpleAccount
*account
;
554 PurpleConnection
*gc
;
555 PurpleMessageFlags flags
= 0;
558 account
= purple_conversation_get_account(conv
);
560 if (check_for_and_do_command(conv
)) {
561 conversation_entry_clear(gtkconv
);
565 if ((purple_conversation_get_type(conv
) == PURPLE_CONV_TYPE_CHAT
) &&
566 purple_conv_chat_has_left(PURPLE_CONV_CHAT(conv
)))
569 if (!purple_account_is_connected(account
))
572 buf
= gtk_imhtml_get_markup(GTK_IMHTML(gtkconv
->entry
));
573 clean
= gtk_imhtml_get_text(GTK_IMHTML(gtkconv
->entry
), NULL
, NULL
);
575 gtk_widget_grab_focus(gtkconv
->entry
);
577 if (strlen(clean
) == 0) {
585 /* XXX: is there a better way to tell if the message has images? */
586 if (GTK_IMHTML(gtkconv
->entry
)->im_images
!= NULL
)
587 flags
|= PURPLE_MESSAGE_IMAGES
;
589 gc
= purple_account_get_connection(account
);
590 if (gc
&& (conv
->features
& PURPLE_CONNECTION_NO_NEWLINES
)) {
594 bufs
= gtk_imhtml_get_markup_lines(GTK_IMHTML(gtkconv
->entry
));
595 for (i
= 0; bufs
[i
]; i
++) {
596 send_history_add(gtkconv
, bufs
[i
]);
597 if (purple_conversation_get_type(conv
) == PURPLE_CONV_TYPE_IM
)
598 purple_conv_im_send_with_flags(PURPLE_CONV_IM(conv
), bufs
[i
], flags
);
599 else if (purple_conversation_get_type(conv
) == PURPLE_CONV_TYPE_CHAT
)
600 purple_conv_chat_send_with_flags(PURPLE_CONV_CHAT(conv
), bufs
[i
], flags
);
606 send_history_add(gtkconv
, buf
);
607 if (purple_conversation_get_type(conv
) == PURPLE_CONV_TYPE_IM
)
608 purple_conv_im_send_with_flags(PURPLE_CONV_IM(conv
), buf
, flags
);
609 else if (purple_conversation_get_type(conv
) == PURPLE_CONV_TYPE_CHAT
)
610 purple_conv_chat_send_with_flags(PURPLE_CONV_CHAT(conv
), buf
, flags
);
616 conversation_entry_clear(gtkconv
);
617 gtkconv_set_unseen(gtkconv
, PIDGIN_UNSEEN_NONE
);
621 add_remove_cb(GtkWidget
*widget
, PidginConversation
*gtkconv
)
623 PurpleAccount
*account
;
625 PurpleConversation
*conv
= gtkconv
->active_conv
;
627 account
= purple_conversation_get_account(conv
);
628 name
= purple_conversation_get_name(conv
);
630 if (purple_conversation_get_type(conv
) == PURPLE_CONV_TYPE_IM
) {
633 b
= purple_find_buddy(account
, name
);
635 pidgin_dialogs_remove_buddy(b
);
636 else if (account
!= NULL
&& purple_account_is_connected(account
))
637 purple_blist_request_add_buddy(account
, (char *)name
, NULL
, NULL
);
638 } else if (purple_conversation_get_type(conv
) == PURPLE_CONV_TYPE_CHAT
) {
641 c
= purple_blist_find_chat(account
, name
);
643 pidgin_dialogs_remove_chat(c
);
644 else if (account
!= NULL
&& purple_account_is_connected(account
))
645 purple_blist_request_add_chat(account
, NULL
, NULL
, name
);
648 gtk_widget_grab_focus(PIDGIN_CONVERSATION(conv
)->entry
);
651 static void chat_do_info(PidginConversation
*gtkconv
, const char *who
)
653 PurpleConversation
*conv
= gtkconv
->active_conv
;
654 PurpleConnection
*gc
;
656 if ((gc
= purple_conversation_get_gc(conv
))) {
657 pidgin_retrieve_user_info_in_chat(gc
, who
, purple_conv_chat_get_id(PURPLE_CONV_CHAT(conv
)));
663 info_cb(GtkWidget
*widget
, PidginConversation
*gtkconv
)
665 PurpleConversation
*conv
= gtkconv
->active_conv
;
667 if (purple_conversation_get_type(conv
) == PURPLE_CONV_TYPE_IM
) {
668 pidgin_retrieve_user_info(purple_conversation_get_gc(conv
),
669 purple_conversation_get_name(conv
));
670 gtk_widget_grab_focus(gtkconv
->entry
);
671 } else if (purple_conversation_get_type(conv
) == PURPLE_CONV_TYPE_CHAT
) {
672 /* Get info of the person currently selected in the GtkTreeView */
673 PidginChatPane
*gtkchat
;
676 GtkTreeSelection
*sel
;
679 gtkchat
= gtkconv
->u
.chat
;
681 model
= gtk_tree_view_get_model(GTK_TREE_VIEW(gtkchat
->list
));
682 sel
= gtk_tree_view_get_selection(GTK_TREE_VIEW(gtkchat
->list
));
684 if (gtk_tree_selection_get_selected(sel
, NULL
, &iter
))
685 gtk_tree_model_get(GTK_TREE_MODEL(model
), &iter
, CHAT_USERS_NAME_COLUMN
, &name
, -1);
689 chat_do_info(gtkconv
, name
);
695 block_cb(GtkWidget
*widget
, PidginConversation
*gtkconv
)
697 PurpleConversation
*conv
= gtkconv
->active_conv
;
698 PurpleAccount
*account
;
700 account
= purple_conversation_get_account(conv
);
702 if (account
!= NULL
&& purple_account_is_connected(account
))
703 pidgin_request_add_block(account
, purple_conversation_get_name(conv
));
705 gtk_widget_grab_focus(PIDGIN_CONVERSATION(conv
)->entry
);
709 unblock_cb(GtkWidget
*widget
, PidginConversation
*gtkconv
)
711 PurpleConversation
*conv
= gtkconv
->active_conv
;
712 PurpleAccount
*account
;
714 account
= purple_conversation_get_account(conv
);
716 if (account
!= NULL
&& purple_account_is_connected(account
))
717 pidgin_request_add_permit(account
, purple_conversation_get_name(conv
));
719 gtk_widget_grab_focus(PIDGIN_CONVERSATION(conv
)->entry
);
723 chat_invite_filter(const PidginBuddyCompletionEntry
*entry
, gpointer data
)
725 PurpleAccount
*filter_account
= data
;
726 PurpleAccount
*account
= NULL
;
728 if (entry
->is_buddy
) {
729 if (PURPLE_BUDDY_IS_ONLINE(entry
->entry
.buddy
))
730 account
= purple_buddy_get_account(entry
->entry
.buddy
);
734 account
= entry
->entry
.logged_buddy
->account
;
736 if (account
== filter_account
)
742 do_invite(GtkWidget
*w
, int resp
, InviteBuddyInfo
*info
)
744 const char *buddy
, *message
;
745 PurpleConversation
*conv
;
749 if (resp
== GTK_RESPONSE_OK
) {
750 buddy
= gtk_entry_get_text(GTK_ENTRY(info
->entry
));
751 message
= gtk_entry_get_text(GTK_ENTRY(info
->message
));
753 if (!g_ascii_strcasecmp(buddy
, ""))
756 serv_chat_invite(purple_conversation_get_gc(conv
),
757 purple_conv_chat_get_id(PURPLE_CONV_CHAT(conv
)),
761 gtk_widget_destroy(invite_dialog
);
762 invite_dialog
= NULL
;
768 invite_dnd_recv(GtkWidget
*widget
, GdkDragContext
*dc
, gint x
, gint y
,
769 GtkSelectionData
*sd
, guint inf
, guint t
, gpointer data
)
771 InviteBuddyInfo
*info
= (InviteBuddyInfo
*)data
;
772 const char *convprotocol
;
773 gboolean success
= TRUE
;
775 convprotocol
= purple_account_get_protocol_id(purple_conversation_get_account(info
->conv
));
777 if (sd
->target
== gdk_atom_intern("PURPLE_BLIST_NODE", FALSE
))
779 PurpleBlistNode
*node
= NULL
;
782 memcpy(&node
, sd
->data
, sizeof(node
));
784 if (PURPLE_BLIST_NODE_IS_CONTACT(node
))
785 buddy
= purple_contact_get_priority_buddy((PurpleContact
*)node
);
786 else if (PURPLE_BLIST_NODE_IS_BUDDY(node
))
787 buddy
= (PurpleBuddy
*)node
;
791 if (!purple_strequal(convprotocol
, purple_account_get_protocol_id(buddy
->account
)))
793 purple_notify_error(PIDGIN_CONVERSATION(info
->conv
), NULL
,
794 _("That buddy is not on the same protocol as this "
799 gtk_entry_set_text(GTK_ENTRY(info
->entry
), purple_buddy_get_name(buddy
));
801 gtk_drag_finish(dc
, success
, (dc
->action
== GDK_ACTION_MOVE
), t
);
803 else if (sd
->target
== gdk_atom_intern("application/x-im-contact", FALSE
))
805 char *protocol
= NULL
;
806 char *username
= NULL
;
807 PurpleAccount
*account
;
809 if (pidgin_parse_x_im_contact((const char *)sd
->data
, FALSE
, &account
,
810 &protocol
, &username
, NULL
))
814 purple_notify_error(PIDGIN_CONVERSATION(info
->conv
), NULL
,
815 _("You are not currently signed on with an account that "
816 "can invite that buddy."), NULL
);
818 else if (!purple_strequal(convprotocol
, purple_account_get_protocol_id(account
)))
820 purple_notify_error(PIDGIN_CONVERSATION(info
->conv
), NULL
,
821 _("That buddy is not on the same protocol as this "
827 gtk_entry_set_text(GTK_ENTRY(info
->entry
), username
);
834 gtk_drag_finish(dc
, success
, (dc
->action
== GDK_ACTION_MOVE
), t
);
838 static const GtkTargetEntry dnd_targets
[] =
840 {"PURPLE_BLIST_NODE", GTK_TARGET_SAME_APP
, 0},
841 {"application/x-im-contact", 0, 1}
845 invite_cb(GtkWidget
*widget
, PidginConversation
*gtkconv
)
847 PurpleConversation
*conv
= gtkconv
->active_conv
;
848 InviteBuddyInfo
*info
= NULL
;
850 if (invite_dialog
== NULL
) {
851 PidginWindow
*gtkwin
;
853 GtkWidget
*vbox
, *hbox
;
857 img
= gtk_image_new_from_stock(PIDGIN_STOCK_DIALOG_QUESTION
,
858 gtk_icon_size_from_name(PIDGIN_ICON_SIZE_TANGO_HUGE
));
860 info
= g_new0(InviteBuddyInfo
, 1);
863 gtkwin
= pidgin_conv_get_window(gtkconv
);
865 /* Create the new dialog. */
866 invite_dialog
= gtk_dialog_new_with_buttons(
867 _("Invite Buddy Into Chat Room"),
868 GTK_WINDOW(gtkwin
->window
), 0,
869 GTK_STOCK_CANCEL
, GTK_RESPONSE_CANCEL
,
870 PIDGIN_STOCK_INVITE
, GTK_RESPONSE_OK
, NULL
);
872 gtk_dialog_set_default_response(GTK_DIALOG(invite_dialog
),
874 gtk_container_set_border_width(GTK_CONTAINER(invite_dialog
), PIDGIN_HIG_BOX_SPACE
);
875 gtk_window_set_resizable(GTK_WINDOW(invite_dialog
), FALSE
);
876 gtk_dialog_set_has_separator(GTK_DIALOG(invite_dialog
), FALSE
);
878 info
->window
= GTK_WIDGET(invite_dialog
);
880 /* Setup the outside spacing. */
881 vbox
= GTK_DIALOG(invite_dialog
)->vbox
;
883 gtk_box_set_spacing(GTK_BOX(vbox
), PIDGIN_HIG_BORDER
);
884 gtk_container_set_border_width(GTK_CONTAINER(vbox
), PIDGIN_HIG_BOX_SPACE
);
886 /* Setup the inner hbox and put the dialog's icon in it. */
887 hbox
= gtk_hbox_new(FALSE
, PIDGIN_HIG_BORDER
);
888 gtk_container_add(GTK_CONTAINER(vbox
), hbox
);
889 gtk_box_pack_start(GTK_BOX(hbox
), img
, FALSE
, FALSE
, 0);
890 gtk_misc_set_alignment(GTK_MISC(img
), 0, 0);
892 /* Setup the right vbox. */
893 vbox
= gtk_vbox_new(FALSE
, 0);
894 gtk_container_add(GTK_CONTAINER(hbox
), vbox
);
896 /* Put our happy label in it. */
897 label
= gtk_label_new(_("Please enter the name of the user you wish "
898 "to invite, along with an optional invite "
900 gtk_widget_set_size_request(label
, 350, -1);
901 gtk_label_set_line_wrap(GTK_LABEL(label
), TRUE
);
902 gtk_misc_set_alignment(GTK_MISC(label
), 0, 0);
903 gtk_box_pack_start(GTK_BOX(vbox
), label
, FALSE
, FALSE
, 0);
905 /* hbox for the table, and to give it some spacing on the left. */
906 hbox
= gtk_hbox_new(FALSE
, PIDGIN_HIG_BOX_SPACE
);
907 gtk_container_add(GTK_CONTAINER(vbox
), hbox
);
909 /* Setup the table we're going to use to lay stuff out. */
910 table
= gtk_table_new(2, 2, FALSE
);
911 gtk_table_set_row_spacings(GTK_TABLE(table
), PIDGIN_HIG_BOX_SPACE
);
912 gtk_table_set_col_spacings(GTK_TABLE(table
), PIDGIN_HIG_BOX_SPACE
);
913 gtk_container_set_border_width(GTK_CONTAINER(table
), PIDGIN_HIG_BORDER
);
914 gtk_box_pack_start(GTK_BOX(vbox
), table
, FALSE
, FALSE
, 0);
916 /* Now the Buddy label */
917 label
= gtk_label_new(NULL
);
918 gtk_label_set_markup_with_mnemonic(GTK_LABEL(label
), _("_Buddy:"));
919 gtk_misc_set_alignment(GTK_MISC(label
), 0, 0);
920 gtk_table_attach_defaults(GTK_TABLE(table
), label
, 0, 1, 0, 1);
922 /* Now the Buddy drop-down entry field. */
923 info
->entry
= gtk_entry_new();
924 pidgin_setup_screenname_autocomplete_with_filter(info
->entry
, NULL
, chat_invite_filter
,
925 purple_conversation_get_account(conv
));
926 gtk_table_attach_defaults(GTK_TABLE(table
), info
->entry
, 1, 2, 0, 1);
927 gtk_label_set_mnemonic_widget(GTK_LABEL(label
), info
->entry
);
929 /* Now the label for "Message" */
930 label
= gtk_label_new(NULL
);
931 gtk_label_set_markup_with_mnemonic(GTK_LABEL(label
), _("_Message:"));
932 gtk_misc_set_alignment(GTK_MISC(label
), 0, 0);
933 gtk_table_attach_defaults(GTK_TABLE(table
), label
, 0, 1, 1, 2);
936 /* And finally, the Message entry field. */
937 info
->message
= gtk_entry_new();
938 gtk_entry_set_activates_default(GTK_ENTRY(info
->message
), TRUE
);
940 gtk_table_attach_defaults(GTK_TABLE(table
), info
->message
, 1, 2, 1, 2);
941 gtk_label_set_mnemonic_widget(GTK_LABEL(label
), info
->message
);
943 /* Connect the signals. */
944 g_signal_connect(G_OBJECT(invite_dialog
), "response",
945 G_CALLBACK(do_invite
), info
);
946 /* Setup drag-and-drop */
947 gtk_drag_dest_set(info
->window
,
948 GTK_DEST_DEFAULT_MOTION
|
949 GTK_DEST_DEFAULT_DROP
,
951 sizeof(dnd_targets
) / sizeof(GtkTargetEntry
),
953 gtk_drag_dest_set(info
->entry
,
954 GTK_DEST_DEFAULT_MOTION
|
955 GTK_DEST_DEFAULT_DROP
,
957 sizeof(dnd_targets
) / sizeof(GtkTargetEntry
),
960 g_signal_connect(G_OBJECT(info
->window
), "drag_data_received",
961 G_CALLBACK(invite_dnd_recv
), info
);
962 g_signal_connect(G_OBJECT(info
->entry
), "drag_data_received",
963 G_CALLBACK(invite_dnd_recv
), info
);
966 gtk_widget_show_all(invite_dialog
);
969 gtk_widget_grab_focus(info
->entry
);
973 menu_new_conv_cb(gpointer data
, guint action
, GtkWidget
*widget
)
979 menu_join_chat_cb(gpointer data
, guint action
, GtkWidget
*widget
)
981 pidgin_blist_joinchat_show();
985 savelog_writefile_cb(void *user_data
, const char *filename
)
987 PurpleConversation
*conv
= (PurpleConversation
*)user_data
;
993 if ((fp
= g_fopen(filename
, "w+")) == NULL
) {
994 purple_notify_error(PIDGIN_CONVERSATION(conv
), NULL
, _("Unable to open file."), NULL
);
998 name
= purple_conversation_get_name(conv
);
999 fprintf(fp
, "<html>\n<head>\n");
1000 fprintf(fp
, "<meta http-equiv=\"content-type\" content=\"text/html; charset=UTF-8\">\n");
1001 fprintf(fp
, "<title>%s</title>\n</head>\n<body>\n", name
);
1002 fprintf(fp
, _("<h1>Conversation with %s</h1>\n"), name
);
1004 lines
= gtk_imhtml_get_markup_lines(
1005 GTK_IMHTML(PIDGIN_CONVERSATION(conv
)->imhtml
));
1006 text
= g_strjoinv("<br>\n", lines
);
1007 fprintf(fp
, "%s", text
);
1011 fprintf(fp
, "\n</body>\n</html>\n");
1016 * It would be kinda cool if this gave the option of saving a
1017 * plaintext v. HTML file.
1020 menu_save_as_cb(gpointer data
, guint action
, GtkWidget
*widget
)
1022 PidginWindow
*win
= data
;
1023 PurpleConversation
*conv
= pidgin_conv_window_get_active_conversation(win
);
1024 PurpleBuddy
*buddy
= purple_find_buddy(conv
->account
, conv
->name
);
1030 name
= purple_buddy_get_contact_alias(buddy
);
1032 name
= purple_normalize(conv
->account
, conv
->name
);
1034 buf
= g_strdup_printf("%s.html", name
);
1035 for (c
= buf
; *c
; c
++)
1037 if (*c
== '/' || *c
== '\\')
1040 purple_request_file(PIDGIN_CONVERSATION(conv
), _("Save Conversation"),
1042 TRUE
, G_CALLBACK(savelog_writefile_cb
), NULL
,
1050 menu_view_log_cb(gpointer data
, guint action
, GtkWidget
*widget
)
1052 PidginWindow
*win
= data
;
1053 PurpleConversation
*conv
;
1055 PidginBuddyList
*gtkblist
;
1058 PurpleAccount
*account
;
1062 conv
= pidgin_conv_window_get_active_conversation(win
);
1064 if (purple_conversation_get_type(conv
) == PURPLE_CONV_TYPE_IM
)
1065 type
= PURPLE_LOG_IM
;
1066 else if (purple_conversation_get_type(conv
) == PURPLE_CONV_TYPE_CHAT
)
1067 type
= PURPLE_LOG_CHAT
;
1071 gtkblist
= pidgin_blist_get_default_gtk_blist();
1073 cursor
= gdk_cursor_new(GDK_WATCH
);
1074 gdk_window_set_cursor(gtkblist
->window
->window
, cursor
);
1075 gdk_window_set_cursor(win
->window
->window
, cursor
);
1076 gdk_cursor_unref(cursor
);
1077 gdk_display_flush(gdk_drawable_get_display(GDK_DRAWABLE(widget
->window
)));
1079 name
= purple_conversation_get_name(conv
);
1080 account
= purple_conversation_get_account(conv
);
1082 buddies
= purple_find_buddies(account
, name
);
1083 for (cur
= buddies
; cur
!= NULL
; cur
= cur
->next
)
1085 PurpleBlistNode
*node
= cur
->data
;
1086 if ((node
!= NULL
) && ((node
->prev
!= NULL
) || (node
->next
!= NULL
)))
1088 pidgin_log_show_contact((PurpleContact
*)node
->parent
);
1089 g_slist_free(buddies
);
1090 gdk_window_set_cursor(gtkblist
->window
->window
, NULL
);
1091 gdk_window_set_cursor(win
->window
->window
, NULL
);
1095 g_slist_free(buddies
);
1097 pidgin_log_show(type
, name
, account
);
1099 gdk_window_set_cursor(gtkblist
->window
->window
, NULL
);
1100 gdk_window_set_cursor(win
->window
->window
, NULL
);
1104 menu_clear_cb(gpointer data
, guint action
, GtkWidget
*widget
)
1106 PidginWindow
*win
= data
;
1107 PurpleConversation
*conv
;
1109 conv
= pidgin_conv_window_get_active_conversation(win
);
1110 purple_conversation_clear_message_history(conv
);
1114 menu_find_cb(gpointer data
, guint action
, GtkWidget
*widget
)
1116 PidginWindow
*gtkwin
= data
;
1117 PidginConversation
*gtkconv
= pidgin_conv_window_get_active_gtkconv(gtkwin
);
1118 gtk_widget_show_all(gtkconv
->quickfind
.container
);
1119 gtk_widget_grab_focus(gtkconv
->quickfind
.entry
);
1124 menu_initiate_media_call_cb(gpointer data
, guint action
, GtkWidget
*widget
)
1126 PidginWindow
*win
= (PidginWindow
*)data
;
1127 PurpleConversation
*conv
= pidgin_conv_window_get_active_conversation(win
);
1128 PurpleAccount
*account
= purple_conversation_get_account(conv
);
1130 purple_prpl_initiate_media(account
,
1131 purple_conversation_get_name(conv
),
1132 action
== 0 ? PURPLE_MEDIA_AUDIO
:
1133 action
== 1 ? PURPLE_MEDIA_VIDEO
:
1134 action
== 2 ? PURPLE_MEDIA_AUDIO
|
1135 PURPLE_MEDIA_VIDEO
: PURPLE_MEDIA_NONE
);
1140 menu_send_file_cb(gpointer data
, guint action
, GtkWidget
*widget
)
1142 PidginWindow
*win
= data
;
1143 PurpleConversation
*conv
= pidgin_conv_window_get_active_conversation(win
);
1145 if (purple_conversation_get_type(conv
) == PURPLE_CONV_TYPE_IM
) {
1146 serv_send_file(purple_conversation_get_gc(conv
), purple_conversation_get_name(conv
), NULL
);
1152 menu_get_attention_cb(gpointer data
, guint action
, GtkWidget
*widget
)
1154 PidginWindow
*win
= data
;
1155 PurpleConversation
*conv
= pidgin_conv_window_get_active_conversation(win
);
1157 if (purple_conversation_get_type(conv
) == PURPLE_CONV_TYPE_IM
) {
1158 purple_prpl_send_attention(purple_conversation_get_gc(conv
),
1159 purple_conversation_get_name(conv
), 0);
1164 menu_add_pounce_cb(gpointer data
, guint action
, GtkWidget
*widget
)
1166 PidginWindow
*win
= data
;
1167 PurpleConversation
*conv
;
1169 conv
= pidgin_conv_window_get_active_gtkconv(win
)->active_conv
;
1171 pidgin_pounce_editor_show(purple_conversation_get_account(conv
),
1172 purple_conversation_get_name(conv
), NULL
);
1176 menu_insert_link_cb(gpointer data
, guint action
, GtkWidget
*widget
)
1178 PidginWindow
*win
= data
;
1179 PidginConversation
*gtkconv
;
1180 GtkIMHtmlToolbar
*toolbar
;
1182 gtkconv
= pidgin_conv_window_get_active_gtkconv(win
);
1183 toolbar
= GTK_IMHTMLTOOLBAR(gtkconv
->toolbar
);
1185 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(toolbar
->link
),
1186 !gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(toolbar
->link
)));
1190 menu_insert_image_cb(gpointer data
, guint action
, GtkWidget
*widget
)
1192 PidginWindow
*win
= data
;
1193 PidginConversation
*gtkconv
;
1194 GtkIMHtmlToolbar
*toolbar
;
1196 gtkconv
= pidgin_conv_window_get_active_gtkconv(win
);
1197 toolbar
= GTK_IMHTMLTOOLBAR(gtkconv
->toolbar
);
1199 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(toolbar
->image
),
1200 !gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(toolbar
->image
)));
1205 menu_alias_cb(gpointer data
, guint action
, GtkWidget
*widget
)
1207 PidginWindow
*win
= data
;
1208 PurpleConversation
*conv
;
1209 PurpleAccount
*account
;
1212 conv
= pidgin_conv_window_get_active_conversation(win
);
1213 account
= purple_conversation_get_account(conv
);
1214 name
= purple_conversation_get_name(conv
);
1216 if (purple_conversation_get_type(conv
) == PURPLE_CONV_TYPE_IM
) {
1219 b
= purple_find_buddy(account
, name
);
1221 pidgin_dialogs_alias_buddy(b
);
1222 } else if (purple_conversation_get_type(conv
) == PURPLE_CONV_TYPE_CHAT
) {
1225 c
= purple_blist_find_chat(account
, name
);
1227 pidgin_dialogs_alias_chat(c
);
1232 menu_get_info_cb(gpointer data
, guint action
, GtkWidget
*widget
)
1234 PidginWindow
*win
= data
;
1235 PurpleConversation
*conv
;
1237 conv
= pidgin_conv_window_get_active_conversation(win
);
1239 info_cb(NULL
, PIDGIN_CONVERSATION(conv
));
1243 menu_invite_cb(gpointer data
, guint action
, GtkWidget
*widget
)
1245 PidginWindow
*win
= data
;
1246 PurpleConversation
*conv
;
1248 conv
= pidgin_conv_window_get_active_conversation(win
);
1250 invite_cb(NULL
, PIDGIN_CONVERSATION(conv
));
1254 menu_block_cb(gpointer data
, guint action
, GtkWidget
*widget
)
1256 PidginWindow
*win
= data
;
1257 PurpleConversation
*conv
;
1259 conv
= pidgin_conv_window_get_active_conversation(win
);
1261 block_cb(NULL
, PIDGIN_CONVERSATION(conv
));
1265 menu_unblock_cb(gpointer data
, guint action
, GtkWidget
*widget
)
1267 PidginWindow
*win
= data
;
1268 PurpleConversation
*conv
;
1270 conv
= pidgin_conv_window_get_active_conversation(win
);
1272 unblock_cb(NULL
, PIDGIN_CONVERSATION(conv
));
1276 menu_add_remove_cb(gpointer data
, guint action
, GtkWidget
*widget
)
1278 PidginWindow
*win
= data
;
1279 PurpleConversation
*conv
;
1281 conv
= pidgin_conv_window_get_active_conversation(win
);
1283 add_remove_cb(NULL
, PIDGIN_CONVERSATION(conv
));
1287 close_already(gpointer data
)
1289 purple_conversation_destroy(data
);
1294 hide_conv(PidginConversation
*gtkconv
, gboolean closetimer
)
1298 purple_signal_emit(pidgin_conversations_get_handle(),
1299 "conversation-hiding", gtkconv
);
1301 for (list
= g_list_copy(gtkconv
->convs
); list
; list
= g_list_delete_link(list
, list
)) {
1302 PurpleConversation
*conv
= list
->data
;
1304 guint timer
= GPOINTER_TO_INT(purple_conversation_get_data(conv
, "close-timer"));
1306 purple_timeout_remove(timer
);
1307 timer
= purple_timeout_add_seconds(CLOSE_CONV_TIMEOUT_SECS
, close_already
, conv
);
1308 purple_conversation_set_data(conv
, "close-timer", GINT_TO_POINTER(timer
));
1311 /* I will miss you */
1312 purple_conversation_set_ui_ops(conv
, NULL
);
1314 pidgin_conv_window_remove_gtkconv(gtkconv
->win
, gtkconv
);
1315 pidgin_conv_window_add_gtkconv(hidden_convwin
, gtkconv
);
1321 menu_close_conv_cb(gpointer data
, guint action
, GtkWidget
*widget
)
1323 PidginWindow
*win
= data
;
1325 close_conv_cb(NULL
, PIDGIN_CONVERSATION(pidgin_conv_window_get_active_conversation(win
)));
1329 menu_logging_cb(gpointer data
, guint action
, GtkWidget
*widget
)
1331 PidginWindow
*win
= data
;
1332 PurpleConversation
*conv
;
1334 PurpleBlistNode
*node
;
1336 conv
= pidgin_conv_window_get_active_conversation(win
);
1341 logging
= gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(widget
));
1343 if (logging
== purple_conversation_is_logging(conv
))
1346 node
= get_conversation_blist_node(conv
);
1350 /* Enable logging first so the message below can be logged. */
1351 purple_conversation_set_logging(conv
, TRUE
);
1353 purple_conversation_write(conv
, NULL
,
1354 _("Logging started. Future messages in this conversation will be logged."),
1355 conv
->logs
? (PURPLE_MESSAGE_SYSTEM
) :
1356 (PURPLE_MESSAGE_SYSTEM
| PURPLE_MESSAGE_NO_LOG
),
1361 purple_conversation_write(conv
, NULL
,
1362 _("Logging stopped. Future messages in this conversation will not be logged."),
1363 conv
->logs
? (PURPLE_MESSAGE_SYSTEM
) :
1364 (PURPLE_MESSAGE_SYSTEM
| PURPLE_MESSAGE_NO_LOG
),
1367 /* Disable the logging second, so that the above message can be logged. */
1368 purple_conversation_set_logging(conv
, FALSE
);
1371 /* Save the setting IFF it's different than the pref. */
1374 case PURPLE_CONV_TYPE_IM
:
1375 if (logging
== purple_prefs_get_bool("/purple/logging/log_ims"))
1376 purple_blist_node_remove_setting(node
, "enable-logging");
1378 purple_blist_node_set_bool(node
, "enable-logging", logging
);
1381 case PURPLE_CONV_TYPE_CHAT
:
1382 if (logging
== purple_prefs_get_bool("/purple/logging/log_chats"))
1383 purple_blist_node_remove_setting(node
, "enable-logging");
1385 purple_blist_node_set_bool(node
, "enable-logging", logging
);
1394 menu_toolbar_cb(gpointer data
, guint action
, GtkWidget
*widget
)
1396 purple_prefs_set_bool(PIDGIN_PREFS_ROOT
"/conversations/show_formatting_toolbar",
1397 gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(widget
)));
1401 menu_sounds_cb(gpointer data
, guint action
, GtkWidget
*widget
)
1403 PidginWindow
*win
= data
;
1404 PurpleConversation
*conv
;
1405 PidginConversation
*gtkconv
;
1406 PurpleBlistNode
*node
;
1408 conv
= pidgin_conv_window_get_active_conversation(win
);
1413 gtkconv
= PIDGIN_CONVERSATION(conv
);
1415 gtkconv
->make_sound
=
1416 gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(widget
));
1417 node
= get_conversation_blist_node(conv
);
1419 purple_blist_node_set_bool(node
, "gtk-mute-sound", !gtkconv
->make_sound
);
1423 menu_timestamps_cb(gpointer data
, guint action
, GtkWidget
*widget
)
1425 purple_prefs_set_bool(PIDGIN_PREFS_ROOT
"/conversations/show_timestamps",
1426 gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(widget
)));
1430 chat_do_im(PidginConversation
*gtkconv
, const char *who
)
1432 PurpleConversation
*conv
= gtkconv
->active_conv
;
1433 PurpleAccount
*account
;
1434 PurpleConnection
*gc
;
1435 PurplePluginProtocolInfo
*prpl_info
= NULL
;
1436 gchar
*real_who
= NULL
;
1438 account
= purple_conversation_get_account(conv
);
1439 g_return_if_fail(account
!= NULL
);
1441 gc
= purple_account_get_connection(account
);
1442 g_return_if_fail(gc
!= NULL
);
1444 prpl_info
= PURPLE_PLUGIN_PROTOCOL_INFO(gc
->prpl
);
1446 if (prpl_info
&& prpl_info
->get_cb_real_name
)
1447 real_who
= prpl_info
->get_cb_real_name(gc
,
1448 purple_conv_chat_get_id(PURPLE_CONV_CHAT(conv
)), who
);
1450 if(!who
&& !real_who
)
1453 pidgin_dialogs_im_with_user(account
, real_who
? real_who
: who
);
1458 static void pidgin_conv_chat_update_user(PurpleConversation
*conv
, const char *user
);
1461 ignore_cb(GtkWidget
*w
, PidginConversation
*gtkconv
)
1463 PurpleConversation
*conv
= gtkconv
->active_conv
;
1464 PurpleConvChat
*chat
;
1467 chat
= PURPLE_CONV_CHAT(conv
);
1468 name
= g_object_get_data(G_OBJECT(w
), "user_data");
1473 if (purple_conv_chat_is_user_ignored(chat
, name
))
1474 purple_conv_chat_unignore(chat
, name
);
1476 purple_conv_chat_ignore(chat
, name
);
1478 pidgin_conv_chat_update_user(conv
, name
);
1482 menu_chat_im_cb(GtkWidget
*w
, PidginConversation
*gtkconv
)
1484 const char *who
= g_object_get_data(G_OBJECT(w
), "user_data");
1486 chat_do_im(gtkconv
, who
);
1490 menu_chat_send_file_cb(GtkWidget
*w
, PidginConversation
*gtkconv
)
1492 PurplePluginProtocolInfo
*prpl_info
;
1493 PurpleConversation
*conv
= gtkconv
->active_conv
;
1494 const char *who
= g_object_get_data(G_OBJECT(w
), "user_data");
1495 PurpleConnection
*gc
= purple_conversation_get_gc(conv
);
1496 gchar
*real_who
= NULL
;
1498 g_return_if_fail(gc
!= NULL
);
1500 prpl_info
= PURPLE_PLUGIN_PROTOCOL_INFO(gc
->prpl
);
1502 if (prpl_info
&& prpl_info
->get_cb_real_name
)
1503 real_who
= prpl_info
->get_cb_real_name(gc
,
1504 purple_conv_chat_get_id(PURPLE_CONV_CHAT(conv
)), who
);
1506 serv_send_file(gc
, real_who
? real_who
: who
, NULL
);
1511 menu_chat_info_cb(GtkWidget
*w
, PidginConversation
*gtkconv
)
1515 who
= g_object_get_data(G_OBJECT(w
), "user_data");
1517 chat_do_info(gtkconv
, who
);
1521 menu_chat_get_away_cb(GtkWidget
*w
, PidginConversation
*gtkconv
)
1523 PurpleConversation
*conv
= gtkconv
->active_conv
;
1524 PurplePluginProtocolInfo
*prpl_info
= NULL
;
1525 PurpleConnection
*gc
;
1528 gc
= purple_conversation_get_gc(conv
);
1529 who
= g_object_get_data(G_OBJECT(w
), "user_data");
1532 prpl_info
= PURPLE_PLUGIN_PROTOCOL_INFO(gc
->prpl
);
1535 * May want to expand this to work similarly to menu_info_cb?
1538 if (prpl_info
->get_cb_away
!= NULL
)
1540 prpl_info
->get_cb_away(gc
,
1541 purple_conv_chat_get_id(PURPLE_CONV_CHAT(conv
)), who
);
1547 menu_chat_add_remove_cb(GtkWidget
*w
, PidginConversation
*gtkconv
)
1549 PurpleConversation
*conv
= gtkconv
->active_conv
;
1550 PurpleAccount
*account
;
1554 account
= purple_conversation_get_account(conv
);
1555 name
= g_object_get_data(G_OBJECT(w
), "user_data");
1556 b
= purple_find_buddy(account
, name
);
1559 pidgin_dialogs_remove_buddy(b
);
1560 else if (account
!= NULL
&& purple_account_is_connected(account
))
1561 purple_blist_request_add_buddy(account
, name
, NULL
, NULL
);
1563 gtk_widget_grab_focus(PIDGIN_CONVERSATION(conv
)->entry
);
1566 static GtkTextMark
*
1567 get_mark_for_user(PidginConversation
*gtkconv
, const char *who
)
1569 GtkTextBuffer
*buf
= gtk_text_view_get_buffer(GTK_TEXT_VIEW(gtkconv
->imhtml
));
1570 char *tmp
= g_strconcat("user:", who
, NULL
);
1571 GtkTextMark
*mark
= gtk_text_buffer_get_mark(buf
, tmp
);
1578 menu_last_said_cb(GtkWidget
*w
, PidginConversation
*gtkconv
)
1583 who
= g_object_get_data(G_OBJECT(w
), "user_data");
1584 mark
= get_mark_for_user(gtkconv
, who
);
1587 gtk_text_view_scroll_to_mark(GTK_TEXT_VIEW(gtkconv
->imhtml
), mark
, 0.1, FALSE
, 0, 0);
1589 g_return_if_reached();
1593 create_chat_menu(PurpleConversation
*conv
, const char *who
, PurpleConnection
*gc
)
1595 static GtkWidget
*menu
= NULL
;
1596 PurplePluginProtocolInfo
*prpl_info
= NULL
;
1597 PurpleConvChat
*chat
= PURPLE_CONV_CHAT(conv
);
1598 gboolean is_me
= FALSE
;
1600 PurpleBuddy
*buddy
= NULL
;
1603 prpl_info
= PURPLE_PLUGIN_PROTOCOL_INFO(gc
->prpl
);
1606 * If a menu already exists, destroy it before creating a new one,
1607 * thus freeing-up the memory it occupied.
1610 gtk_widget_destroy(menu
);
1612 if (purple_strequal(chat
->nick
, purple_normalize(conv
->account
, who
)))
1615 menu
= gtk_menu_new();
1618 button
= pidgin_new_item_from_stock(menu
, _("IM"), PIDGIN_STOCK_TOOLBAR_MESSAGE_NEW
,
1619 G_CALLBACK(menu_chat_im_cb
), PIDGIN_CONVERSATION(conv
), 0, 0, NULL
);
1622 gtk_widget_set_sensitive(button
, FALSE
);
1624 g_object_set_data_full(G_OBJECT(button
), "user_data", g_strdup(who
), g_free
);
1627 if (prpl_info
&& prpl_info
->send_file
)
1629 gboolean can_receive_file
= TRUE
;
1631 button
= pidgin_new_item_from_stock(menu
, _("Send File"),
1632 PIDGIN_STOCK_TOOLBAR_SEND_FILE
, G_CALLBACK(menu_chat_send_file_cb
),
1633 PIDGIN_CONVERSATION(conv
), 0, 0, NULL
);
1635 if (gc
== NULL
|| prpl_info
== NULL
)
1636 can_receive_file
= FALSE
;
1638 gchar
*real_who
= NULL
;
1639 if (prpl_info
->get_cb_real_name
)
1640 real_who
= prpl_info
->get_cb_real_name(gc
,
1641 purple_conv_chat_get_id(PURPLE_CONV_CHAT(conv
)), who
);
1642 if (!(!prpl_info
->can_receive_file
|| prpl_info
->can_receive_file(gc
, real_who
? real_who
: who
)))
1643 can_receive_file
= FALSE
;
1647 if (!can_receive_file
)
1648 gtk_widget_set_sensitive(button
, FALSE
);
1650 g_object_set_data_full(G_OBJECT(button
), "user_data", g_strdup(who
), g_free
);
1654 if (purple_conv_chat_is_user_ignored(PURPLE_CONV_CHAT(conv
), who
))
1655 button
= pidgin_new_item_from_stock(menu
, _("Un-Ignore"), PIDGIN_STOCK_IGNORE
,
1656 G_CALLBACK(ignore_cb
), PIDGIN_CONVERSATION(conv
), 0, 0, NULL
);
1658 button
= pidgin_new_item_from_stock(menu
, _("Ignore"), PIDGIN_STOCK_IGNORE
,
1659 G_CALLBACK(ignore_cb
), PIDGIN_CONVERSATION(conv
), 0, 0, NULL
);
1662 gtk_widget_set_sensitive(button
, FALSE
);
1664 g_object_set_data_full(G_OBJECT(button
), "user_data", g_strdup(who
), g_free
);
1667 if (prpl_info
&& (prpl_info
->get_info
|| prpl_info
->get_cb_info
)) {
1668 button
= pidgin_new_item_from_stock(menu
, _("Info"), PIDGIN_STOCK_TOOLBAR_USER_INFO
,
1669 G_CALLBACK(menu_chat_info_cb
), PIDGIN_CONVERSATION(conv
), 0, 0, NULL
);
1672 gtk_widget_set_sensitive(button
, FALSE
);
1674 g_object_set_data_full(G_OBJECT(button
), "user_data", g_strdup(who
), g_free
);
1677 if (prpl_info
&& prpl_info
->get_cb_away
) {
1678 button
= pidgin_new_item_from_stock(menu
, _("Get Away Message"), PIDGIN_STOCK_AWAY
,
1679 G_CALLBACK(menu_chat_get_away_cb
), PIDGIN_CONVERSATION(conv
), 0, 0, NULL
);
1682 gtk_widget_set_sensitive(button
, FALSE
);
1684 g_object_set_data_full(G_OBJECT(button
), "user_data", g_strdup(who
), g_free
);
1687 if (!is_me
&& prpl_info
&& !(prpl_info
->options
& OPT_PROTO_UNIQUE_CHATNAME
)) {
1688 if ((buddy
= purple_find_buddy(conv
->account
, who
)) != NULL
)
1689 button
= pidgin_new_item_from_stock(menu
, _("Remove"), GTK_STOCK_REMOVE
,
1690 G_CALLBACK(menu_chat_add_remove_cb
), PIDGIN_CONVERSATION(conv
), 0, 0, NULL
);
1692 button
= pidgin_new_item_from_stock(menu
, _("Add"), GTK_STOCK_ADD
,
1693 G_CALLBACK(menu_chat_add_remove_cb
), PIDGIN_CONVERSATION(conv
), 0, 0, NULL
);
1696 gtk_widget_set_sensitive(button
, FALSE
);
1698 g_object_set_data_full(G_OBJECT(button
), "user_data", g_strdup(who
), g_free
);
1701 button
= pidgin_new_item_from_stock(menu
, _("Last Said"), GTK_STOCK_INDEX
,
1702 G_CALLBACK(menu_last_said_cb
), PIDGIN_CONVERSATION(conv
), 0, 0, NULL
);
1703 g_object_set_data_full(G_OBJECT(button
), "user_data", g_strdup(who
), g_free
);
1704 if (!get_mark_for_user(PIDGIN_CONVERSATION(conv
), who
))
1705 gtk_widget_set_sensitive(button
, FALSE
);
1709 if (purple_account_is_connected(conv
->account
))
1710 pidgin_append_blist_node_proto_menu(menu
, conv
->account
->gc
,
1711 (PurpleBlistNode
*)buddy
);
1712 pidgin_append_blist_node_extended_menu(menu
, (PurpleBlistNode
*)buddy
);
1713 gtk_widget_show_all(menu
);
1721 gtkconv_chat_popup_menu_cb(GtkWidget
*widget
, PidginConversation
*gtkconv
)
1723 PurpleConversation
*conv
= gtkconv
->active_conv
;
1724 PidginChatPane
*gtkchat
;
1725 PurpleConnection
*gc
;
1726 PurpleAccount
*account
;
1727 GtkTreeSelection
*sel
;
1729 GtkTreeModel
*model
;
1733 gtkconv
= PIDGIN_CONVERSATION(conv
);
1734 gtkchat
= gtkconv
->u
.chat
;
1735 account
= purple_conversation_get_account(conv
);
1738 model
= gtk_tree_view_get_model(GTK_TREE_VIEW(gtkchat
->list
));
1740 sel
= gtk_tree_view_get_selection(GTK_TREE_VIEW(gtkchat
->list
));
1741 if(!gtk_tree_selection_get_selected(sel
, NULL
, &iter
))
1744 gtk_tree_model_get(GTK_TREE_MODEL(model
), &iter
, CHAT_USERS_NAME_COLUMN
, &who
, -1);
1745 menu
= create_chat_menu (conv
, who
, gc
);
1746 gtk_menu_popup(GTK_MENU(menu
), NULL
, NULL
,
1747 pidgin_treeview_popup_menu_position_func
, widget
,
1748 0, GDK_CURRENT_TIME
);
1756 right_click_chat_cb(GtkWidget
*widget
, GdkEventButton
*event
,
1757 PidginConversation
*gtkconv
)
1759 PurpleConversation
*conv
= gtkconv
->active_conv
;
1760 PidginChatPane
*gtkchat
;
1761 PurpleConnection
*gc
;
1762 PurpleAccount
*account
;
1765 GtkTreeModel
*model
;
1766 GtkTreeViewColumn
*column
;
1770 gtkchat
= gtkconv
->u
.chat
;
1771 account
= purple_conversation_get_account(conv
);
1774 model
= gtk_tree_view_get_model(GTK_TREE_VIEW(gtkchat
->list
));
1776 gtk_tree_view_get_path_at_pos(GTK_TREE_VIEW(gtkchat
->list
),
1777 event
->x
, event
->y
, &path
, &column
, &x
, &y
);
1782 gtk_tree_selection_select_path(GTK_TREE_SELECTION(
1783 gtk_tree_view_get_selection(GTK_TREE_VIEW(gtkchat
->list
))), path
);
1784 gtk_tree_view_set_cursor(GTK_TREE_VIEW(gtkchat
->list
),
1786 gtk_widget_grab_focus(GTK_WIDGET(gtkchat
->list
));
1788 gtk_tree_model_get_iter(GTK_TREE_MODEL(model
), &iter
, path
);
1789 gtk_tree_model_get(GTK_TREE_MODEL(model
), &iter
, CHAT_USERS_NAME_COLUMN
, &who
, -1);
1791 /* emit chat-nick-clicked signal */
1792 if (event
->type
== GDK_BUTTON_PRESS
) {
1793 gint plugin_return
= GPOINTER_TO_INT(purple_signal_emit_return_1(
1794 pidgin_conversations_get_handle(), "chat-nick-clicked",
1795 conv
, who
, event
->button
));
1800 if (event
->button
== 1 && event
->type
== GDK_2BUTTON_PRESS
) {
1801 chat_do_im(gtkconv
, who
);
1802 } else if (event
->button
== 2 && event
->type
== GDK_BUTTON_PRESS
) {
1803 /* Move to user's anchor */
1804 GtkTextMark
*mark
= get_mark_for_user(gtkconv
, who
);
1807 gtk_text_view_scroll_to_mark(GTK_TEXT_VIEW(gtkconv
->imhtml
), mark
, 0.1, FALSE
, 0, 0);
1808 } else if (event
->button
== 3 && event
->type
== GDK_BUTTON_PRESS
) {
1809 GtkWidget
*menu
= create_chat_menu (conv
, who
, gc
);
1810 gtk_menu_popup(GTK_MENU(menu
), NULL
, NULL
, NULL
, NULL
,
1811 event
->button
, event
->time
);
1816 gtk_tree_path_free(path
);
1822 activate_list_cb(GtkTreeView
*list
, GtkTreePath
*path
, GtkTreeViewColumn
*column
, PidginConversation
*gtkconv
)
1825 GtkTreeModel
*model
;
1828 model
= gtk_tree_view_get_model(GTK_TREE_VIEW(list
));
1830 gtk_tree_model_get_iter(GTK_TREE_MODEL(model
), &iter
, path
);
1831 gtk_tree_model_get(GTK_TREE_MODEL(model
), &iter
, CHAT_USERS_NAME_COLUMN
, &who
, -1);
1832 chat_do_im(gtkconv
, who
);
1838 move_to_next_unread_tab(PidginConversation
*gtkconv
, gboolean forward
)
1840 PidginConversation
*next_gtkconv
= NULL
, *most_active
= NULL
;
1841 PidginUnseenState unseen_state
= PIDGIN_UNSEEN_NONE
;
1843 int initial
, i
, total
, diff
;
1846 initial
= gtk_notebook_page_num(GTK_NOTEBOOK(win
->notebook
),
1848 total
= pidgin_conv_window_get_gtkconv_count(win
);
1849 /* By adding total here, the moduli calculated later will always have two
1850 * positive arguments. x % y where x < 0 is not guaranteed to return a
1853 diff
= (forward
? 1 : -1) + total
;
1855 for (i
= (initial
+ diff
) % total
; i
!= initial
; i
= (i
+ diff
) % total
) {
1856 next_gtkconv
= pidgin_conv_window_get_gtkconv_at_index(win
, i
);
1857 if (next_gtkconv
->unseen_state
> unseen_state
) {
1858 most_active
= next_gtkconv
;
1859 unseen_state
= most_active
->unseen_state
;
1860 if(PIDGIN_UNSEEN_NICK
== unseen_state
) /* highest possible state */
1865 if (most_active
== NULL
) { /* no new messages */
1866 i
= (i
+ diff
) % total
;
1867 most_active
= pidgin_conv_window_get_gtkconv_at_index(win
, i
);
1870 if (most_active
!= NULL
&& most_active
!= gtkconv
)
1871 pidgin_conv_window_switch_gtkconv(win
, most_active
);
1875 gtkconv_cycle_focus(PidginConversation
*gtkconv
, GtkDirectionType dir
)
1877 PurpleConversation
*conv
= gtkconv
->active_conv
;
1878 gboolean chat
= purple_conversation_get_type(conv
) == PURPLE_CONV_TYPE_CHAT
;
1879 GtkWidget
*next
= NULL
;
1884 {gtkconv
->entry
, gtkconv
->imhtml
},
1885 {gtkconv
->imhtml
, chat
? gtkconv
->u
.chat
->list
: gtkconv
->entry
},
1886 {chat
? gtkconv
->u
.chat
->list
: NULL
, gtkconv
->entry
},
1890 for (ptr
= transitions
; !next
&& ptr
->from
; ptr
++) {
1891 GtkWidget
*from
, *to
;
1892 if (dir
== GTK_DIR_TAB_FORWARD
) {
1899 if (gtk_widget_is_focus(from
))
1904 gtk_widget_grab_focus(next
);
1909 conv_keypress_common(PidginConversation
*gtkconv
, GdkEventKey
*event
)
1915 curconv
= gtk_notebook_get_current_page(GTK_NOTEBOOK(win
->notebook
));
1917 /* clear any tooltips */
1918 pidgin_tooltip_destroy();
1920 /* If CTRL was held down... */
1921 if (event
->state
& GDK_CONTROL_MASK
) {
1922 switch (event
->keyval
) {
1924 case GDK_KP_Page_Down
:
1926 if (!pidgin_conv_window_get_gtkconv_at_index(win
, curconv
+ 1))
1927 gtk_notebook_set_current_page(GTK_NOTEBOOK(win
->notebook
), 0);
1929 gtk_notebook_set_current_page(GTK_NOTEBOOK(win
->notebook
), curconv
+ 1);
1934 case GDK_KP_Page_Up
:
1936 if (!pidgin_conv_window_get_gtkconv_at_index(win
, curconv
- 1))
1937 gtk_notebook_set_current_page(GTK_NOTEBOOK(win
->notebook
), -1);
1939 gtk_notebook_set_current_page(GTK_NOTEBOOK(win
->notebook
), curconv
- 1);
1945 case GDK_ISO_Left_Tab
:
1946 if (event
->state
& GDK_SHIFT_MASK
) {
1947 move_to_next_unread_tab(gtkconv
, FALSE
);
1949 move_to_next_unread_tab(gtkconv
, TRUE
);
1956 gtk_notebook_reorder_child(GTK_NOTEBOOK(win
->notebook
),
1957 gtk_notebook_get_nth_page(GTK_NOTEBOOK(win
->notebook
), curconv
),
1963 gtk_notebook_reorder_child(GTK_NOTEBOOK(win
->notebook
),
1964 gtk_notebook_get_nth_page(GTK_NOTEBOOK(win
->notebook
), curconv
),
1965 (curconv
+ 1) % gtk_notebook_get_n_pages(GTK_NOTEBOOK(win
->notebook
)));
1969 if (gtkconv_cycle_focus(gtkconv
, event
->state
& GDK_SHIFT_MASK
? GTK_DIR_TAB_BACKWARD
: GTK_DIR_TAB_FORWARD
))
1972 } /* End of switch */
1975 /* If ALT (or whatever) was held down... */
1976 else if (event
->state
& GDK_MOD1_MASK
)
1978 if (event
->keyval
> '0' && event
->keyval
<= '9')
1980 guint switchto
= event
->keyval
- '1';
1981 if (switchto
< pidgin_conv_window_get_gtkconv_count(win
))
1982 gtk_notebook_set_current_page(GTK_NOTEBOOK(win
->notebook
), switchto
);
1988 /* If neither CTRL nor ALT were held down... */
1991 switch (event
->keyval
) {
1993 if (gtk_widget_is_focus(GTK_WIDGET(win
->notebook
))) {
1994 infopane_entry_activate(gtkconv
);
1999 if (gtkconv_cycle_focus(gtkconv
, event
->state
& GDK_SHIFT_MASK
? GTK_DIR_TAB_BACKWARD
: GTK_DIR_TAB_FORWARD
))
2008 entry_key_press_cb(GtkWidget
*entry
, GdkEventKey
*event
, gpointer data
)
2010 PurpleConversation
*conv
;
2011 PidginConversation
*gtkconv
;
2013 gtkconv
= (PidginConversation
*)data
;
2014 conv
= gtkconv
->active_conv
;
2016 if (conv_keypress_common(gtkconv
, event
))
2019 /* If CTRL was held down... */
2020 if (event
->state
& GDK_CONTROL_MASK
) {
2021 switch (event
->keyval
) {
2023 if (!gtkconv
->send_history
)
2026 if (gtkconv
->entry
!= entry
)
2029 if (!gtkconv
->send_history
->prev
) {
2030 GtkTextIter start
, end
;
2032 g_free(gtkconv
->send_history
->data
);
2034 gtk_text_buffer_get_start_iter(gtkconv
->entry_buffer
,
2036 gtk_text_buffer_get_end_iter(gtkconv
->entry_buffer
, &end
);
2038 gtkconv
->send_history
->data
=
2039 gtk_imhtml_get_markup(GTK_IMHTML(gtkconv
->entry
));
2042 if (gtkconv
->send_history
->next
&& gtkconv
->send_history
->next
->data
) {
2045 GtkTextBuffer
*buffer
= gtk_text_view_get_buffer(GTK_TEXT_VIEW(gtkconv
->entry
));
2047 gtkconv
->send_history
= gtkconv
->send_history
->next
;
2049 /* Block the signal to prevent application of default formatting. */
2050 object
= g_object_ref(G_OBJECT(gtkconv
->entry
));
2051 g_signal_handlers_block_matched(object
, G_SIGNAL_MATCH_DATA
, 0, 0, NULL
,
2053 /* Clear the formatting. */
2054 gtk_imhtml_clear_formatting(GTK_IMHTML(gtkconv
->entry
));
2055 /* Unblock the signal. */
2056 g_signal_handlers_unblock_matched(object
, G_SIGNAL_MATCH_DATA
, 0, 0, NULL
,
2058 g_object_unref(object
);
2060 gtk_imhtml_clear(GTK_IMHTML(gtkconv
->entry
));
2061 gtk_imhtml_append_text_with_images(
2062 GTK_IMHTML(gtkconv
->entry
), gtkconv
->send_history
->data
,
2064 /* this is mainly just a hack so the formatting at the
2065 * cursor gets picked up. */
2066 gtk_text_buffer_get_end_iter(buffer
, &iter
);
2067 gtk_text_buffer_move_mark_by_name(buffer
, "insert", &iter
);
2074 if (!gtkconv
->send_history
)
2077 if (gtkconv
->entry
!= entry
)
2080 if (gtkconv
->send_history
->prev
&& gtkconv
->send_history
->prev
->data
) {
2083 GtkTextBuffer
*buffer
= gtk_text_view_get_buffer(GTK_TEXT_VIEW(gtkconv
->entry
));
2085 gtkconv
->send_history
= gtkconv
->send_history
->prev
;
2087 /* Block the signal to prevent application of default formatting. */
2088 object
= g_object_ref(G_OBJECT(gtkconv
->entry
));
2089 g_signal_handlers_block_matched(object
, G_SIGNAL_MATCH_DATA
, 0, 0, NULL
,
2091 /* Clear the formatting. */
2092 gtk_imhtml_clear_formatting(GTK_IMHTML(gtkconv
->entry
));
2093 /* Unblock the signal. */
2094 g_signal_handlers_unblock_matched(object
, G_SIGNAL_MATCH_DATA
, 0, 0, NULL
,
2096 g_object_unref(object
);
2098 gtk_imhtml_clear(GTK_IMHTML(gtkconv
->entry
));
2099 gtk_imhtml_append_text_with_images(
2100 GTK_IMHTML(gtkconv
->entry
), gtkconv
->send_history
->data
,
2102 /* this is mainly just a hack so the formatting at the
2103 * cursor gets picked up. */
2104 if (*(char *)gtkconv
->send_history
->data
) {
2105 gtk_text_buffer_get_end_iter(buffer
, &iter
);
2106 gtk_text_buffer_move_mark_by_name(buffer
, "insert", &iter
);
2108 /* Restore the default formatting */
2109 default_formatize(gtkconv
);
2115 } /* End of switch */
2118 /* If ALT (or whatever) was held down... */
2119 else if (event
->state
& GDK_MOD1_MASK
) {
2123 /* If neither CTRL nor ALT were held down... */
2125 switch (event
->keyval
) {
2128 case GDK_ISO_Left_Tab
:
2129 if (gtkconv
->entry
!= entry
)
2133 plugin_return
= GPOINTER_TO_INT(purple_signal_emit_return_1(
2134 pidgin_conversations_get_handle(), "chat-nick-autocomplete",
2135 conv
, event
->state
& GDK_SHIFT_MASK
));
2136 return plugin_return
? TRUE
: tab_complete(conv
);
2141 case GDK_KP_Page_Up
:
2142 gtk_imhtml_page_up(GTK_IMHTML(gtkconv
->imhtml
));
2147 case GDK_KP_Page_Down
:
2148 gtk_imhtml_page_down(GTK_IMHTML(gtkconv
->imhtml
));
2159 * This guy just kills a single right click from being propagated any
2160 * further. I have no idea *why* we need this, but we do ... It
2161 * prevents right clicks on the GtkTextView in a convo dialog from
2162 * going all the way down to the notebook. I suspect a bug in
2163 * GtkTextView, but I'm not ready to point any fingers yet.
2166 entry_stop_rclick_cb(GtkWidget
*widget
, GdkEventButton
*event
, gpointer data
)
2168 if (event
->button
== 3 && event
->type
== GDK_BUTTON_PRESS
) {
2169 /* Right single click */
2170 g_signal_stop_emission_by_name(G_OBJECT(widget
), "button_press_event");
2179 * If someone tries to type into the conversation backlog of a
2180 * conversation window then we yank focus from the conversation backlog
2181 * and give it to the text entry box so that people can type
2182 * all the live long day and it will get entered into the entry box.
2185 refocus_entry_cb(GtkWidget
*widget
, GdkEventKey
*event
, gpointer data
)
2187 PidginConversation
*gtkconv
= data
;
2189 /* If we have a valid key for the conversation display, then exit */
2190 if ((event
->state
& GDK_CONTROL_MASK
) ||
2191 (event
->keyval
== GDK_F6
) ||
2192 (event
->keyval
== GDK_F10
) ||
2193 (event
->keyval
== GDK_Shift_L
) ||
2194 (event
->keyval
== GDK_Shift_R
) ||
2195 (event
->keyval
== GDK_Control_L
) ||
2196 (event
->keyval
== GDK_Control_R
) ||
2197 (event
->keyval
== GDK_Escape
) ||
2198 (event
->keyval
== GDK_Up
) ||
2199 (event
->keyval
== GDK_Down
) ||
2200 (event
->keyval
== GDK_Left
) ||
2201 (event
->keyval
== GDK_Right
) ||
2202 (event
->keyval
== GDK_Page_Up
) ||
2203 (event
->keyval
== GDK_KP_Page_Up
) ||
2204 (event
->keyval
== GDK_Page_Down
) ||
2205 (event
->keyval
== GDK_KP_Page_Down
) ||
2206 (event
->keyval
== GDK_Home
) ||
2207 (event
->keyval
== GDK_End
) ||
2208 (event
->keyval
== GDK_Tab
) ||
2209 (event
->keyval
== GDK_KP_Tab
) ||
2210 (event
->keyval
== GDK_ISO_Left_Tab
))
2212 if (event
->type
== GDK_KEY_PRESS
)
2213 return conv_keypress_common(gtkconv
, event
);
2217 if (event
->type
== GDK_KEY_RELEASE
)
2218 gtk_widget_grab_focus(gtkconv
->entry
);
2220 gtk_widget_event(gtkconv
->entry
, (GdkEvent
*)event
);
2226 regenerate_options_items(PidginWindow
*win
);
2229 pidgin_conv_switch_active_conversation(PurpleConversation
*conv
)
2231 PidginConversation
*gtkconv
;
2232 PurpleConversation
*old_conv
;
2234 const char *protocol_name
;
2236 g_return_if_fail(conv
!= NULL
);
2238 gtkconv
= PIDGIN_CONVERSATION(conv
);
2239 old_conv
= gtkconv
->active_conv
;
2241 purple_debug_info("gtkconv", "setting active conversation on toolbar %p\n",
2243 gtk_imhtmltoolbar_switch_active_conversation(GTK_IMHTMLTOOLBAR(gtkconv
->toolbar
),
2246 if (old_conv
== conv
)
2249 purple_conversation_close_logs(old_conv
);
2250 gtkconv
->active_conv
= conv
;
2252 purple_conversation_set_logging(conv
,
2253 gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(gtkconv
->win
->menu
.logging
)));
2255 entry
= GTK_IMHTML(gtkconv
->entry
);
2256 protocol_name
= purple_account_get_protocol_name(conv
->account
);
2257 gtk_imhtml_set_protocol_name(entry
, protocol_name
);
2258 gtk_imhtml_set_protocol_name(GTK_IMHTML(gtkconv
->imhtml
), protocol_name
);
2260 if (!(conv
->features
& PURPLE_CONNECTION_HTML
))
2261 gtk_imhtml_clear_formatting(GTK_IMHTML(gtkconv
->entry
));
2262 else if (conv
->features
& PURPLE_CONNECTION_FORMATTING_WBFO
&&
2263 !(old_conv
->features
& PURPLE_CONNECTION_FORMATTING_WBFO
))
2265 /* The old conversation allowed formatting on parts of the
2266 * buffer, but the new one only allows it on the whole
2267 * buffer. This code saves the formatting from the current
2268 * position of the cursor, clears the formatting, then
2269 * applies the saved formatting to the entire buffer. */
2274 char *fontface
= gtk_imhtml_get_current_fontface(entry
);
2275 char *forecolor
= gtk_imhtml_get_current_forecolor(entry
);
2276 char *backcolor
= gtk_imhtml_get_current_backcolor(entry
);
2277 char *background
= gtk_imhtml_get_current_background(entry
);
2278 gint fontsize
= gtk_imhtml_get_current_fontsize(entry
);
2281 gboolean underline2
;
2283 gtk_imhtml_get_current_format(entry
, &bold
, &italic
, &underline
);
2285 /* Clear existing formatting */
2286 gtk_imhtml_clear_formatting(entry
);
2288 /* Apply saved formatting to the whole buffer. */
2290 gtk_imhtml_get_current_format(entry
, &bold2
, &italic2
, &underline2
);
2293 gtk_imhtml_toggle_bold(entry
);
2295 if (italic
!= italic2
)
2296 gtk_imhtml_toggle_italic(entry
);
2298 if (underline
!= underline2
)
2299 gtk_imhtml_toggle_underline(entry
);
2301 gtk_imhtml_toggle_fontface(entry
, fontface
);
2303 if (!(conv
->features
& PURPLE_CONNECTION_NO_FONTSIZE
))
2304 gtk_imhtml_font_set_size(entry
, fontsize
);
2306 gtk_imhtml_toggle_forecolor(entry
, forecolor
);
2308 if (!(conv
->features
& PURPLE_CONNECTION_NO_BGCOLOR
))
2310 gtk_imhtml_toggle_backcolor(entry
, backcolor
);
2311 gtk_imhtml_toggle_background(entry
, background
);
2321 /* This is done in default_formatize, which is called from clear_formatting_cb,
2322 * which is (obviously) a clear_formatting signal handler. However, if we're
2323 * here, we didn't call gtk_imhtml_clear_formatting() (because we want to
2324 * preserve the formatting exactly as it is), so we have to do this now. */
2325 gtk_imhtml_set_whole_buffer_formatting_only(entry
,
2326 (conv
->features
& PURPLE_CONNECTION_FORMATTING_WBFO
));
2329 purple_signal_emit(pidgin_conversations_get_handle(), "conversation-switched", conv
);
2331 gray_stuff_out(gtkconv
);
2332 update_typing_icon(gtkconv
);
2333 g_object_set_data(G_OBJECT(entry
), "transient_buddy", NULL
);
2334 regenerate_options_items(gtkconv
->win
);
2336 gtk_window_set_title(GTK_WINDOW(gtkconv
->win
->window
),
2337 gtk_label_get_text(GTK_LABEL(gtkconv
->tab_label
)));
2341 menu_conv_sel_send_cb(GObject
*m
, gpointer data
)
2343 PurpleAccount
*account
= g_object_get_data(m
, "purple_account");
2344 gchar
*name
= g_object_get_data(m
, "purple_buddy_name");
2345 PurpleConversation
*conv
;
2347 if (gtk_check_menu_item_get_active((GtkCheckMenuItem
*) m
) == FALSE
)
2350 conv
= purple_conversation_new(PURPLE_CONV_TYPE_IM
, account
, name
);
2351 pidgin_conv_switch_active_conversation(conv
);
2355 insert_text_cb(GtkTextBuffer
*textbuffer
, GtkTextIter
*position
,
2356 gchar
*new_text
, gint new_text_length
, gpointer user_data
)
2358 PidginConversation
*gtkconv
= (PidginConversation
*)user_data
;
2360 g_return_if_fail(gtkconv
!= NULL
);
2362 if (!purple_prefs_get_bool("/purple/conversations/im/send_typing"))
2365 got_typing_keypress(gtkconv
, (gtk_text_iter_is_start(position
) &&
2366 gtk_text_iter_is_end(position
)));
2370 delete_text_cb(GtkTextBuffer
*textbuffer
, GtkTextIter
*start_pos
,
2371 GtkTextIter
*end_pos
, gpointer user_data
)
2373 PidginConversation
*gtkconv
= (PidginConversation
*)user_data
;
2374 PurpleConversation
*conv
;
2377 g_return_if_fail(gtkconv
!= NULL
);
2379 conv
= gtkconv
->active_conv
;
2381 if (!purple_prefs_get_bool("/purple/conversations/im/send_typing"))
2384 im
= PURPLE_CONV_IM(conv
);
2386 if (gtk_text_iter_is_start(start_pos
) && gtk_text_iter_is_end(end_pos
)) {
2388 /* We deleted all the text, so turn off typing. */
2389 purple_conv_im_stop_send_typed_timeout(im
);
2391 serv_send_typing(purple_conversation_get_gc(conv
),
2392 purple_conversation_get_name(conv
),
2396 /* We're deleting, but not all of it, so it counts as typing. */
2397 got_typing_keypress(gtkconv
, FALSE
);
2401 /**************************************************************************
2402 * A bunch of buddy icon functions
2403 **************************************************************************/
2405 static GList
*get_prpl_icon_list(PurpleAccount
*account
)
2408 PurplePlugin
*prpl
= purple_find_prpl(purple_account_get_protocol_id(account
));
2409 PurplePluginProtocolInfo
*prpl_info
= PURPLE_PLUGIN_PROTOCOL_INFO(prpl
);
2410 const char *prplname
= prpl_info
->list_icon(account
, NULL
);
2411 l
= g_hash_table_lookup(prpl_lists
, prplname
);
2415 l
= g_list_prepend(l
, pidgin_create_prpl_icon(account
, PIDGIN_PRPL_ICON_LARGE
));
2416 l
= g_list_prepend(l
, pidgin_create_prpl_icon(account
, PIDGIN_PRPL_ICON_MEDIUM
));
2417 l
= g_list_prepend(l
, pidgin_create_prpl_icon(account
, PIDGIN_PRPL_ICON_SMALL
));
2419 g_hash_table_insert(prpl_lists
, g_strdup(prplname
), l
);
2424 pidgin_conv_get_tab_icons(PurpleConversation
*conv
)
2426 PurpleAccount
*account
= NULL
;
2427 const char *name
= NULL
;
2429 g_return_val_if_fail(conv
!= NULL
, NULL
);
2431 account
= purple_conversation_get_account(conv
);
2432 name
= purple_conversation_get_name(conv
);
2434 g_return_val_if_fail(account
!= NULL
, NULL
);
2435 g_return_val_if_fail(name
!= NULL
, NULL
);
2437 /* Use the buddy icon, if possible */
2438 if (purple_conversation_get_type(conv
) == PURPLE_CONV_TYPE_IM
) {
2439 PurpleBuddy
*b
= purple_find_buddy(account
, name
);
2442 p
= purple_buddy_get_presence(b
);
2443 if (purple_presence_is_status_primitive_active(p
, PURPLE_STATUS_AWAY
))
2445 if (purple_presence_is_status_primitive_active(p
, PURPLE_STATUS_UNAVAILABLE
))
2447 if (purple_presence_is_status_primitive_active(p
, PURPLE_STATUS_EXTENDED_AWAY
))
2449 if (purple_presence_is_status_primitive_active(p
, PURPLE_STATUS_OFFLINE
))
2450 return offline_list
;
2452 return available_list
;
2456 return get_prpl_icon_list(account
);
2460 pidgin_conv_get_icon_stock(PurpleConversation
*conv
)
2462 PurpleAccount
*account
= NULL
;
2463 const char *stock
= NULL
;
2465 g_return_val_if_fail(conv
!= NULL
, NULL
);
2467 account
= purple_conversation_get_account(conv
);
2468 g_return_val_if_fail(account
!= NULL
, NULL
);
2470 /* Use the buddy icon, if possible */
2471 if (purple_conversation_get_type(conv
) == PURPLE_CONV_TYPE_IM
) {
2472 const char *name
= NULL
;
2474 name
= purple_conversation_get_name(conv
);
2475 b
= purple_find_buddy(account
, name
);
2477 PurplePresence
*p
= purple_buddy_get_presence(b
);
2478 PurpleStatus
*active
= purple_presence_get_active_status(p
);
2479 PurpleStatusType
*type
= purple_status_get_type(active
);
2480 PurpleStatusPrimitive prim
= purple_status_type_get_primitive(type
);
2481 stock
= pidgin_stock_id_from_status_primitive(prim
);
2483 stock
= PIDGIN_STOCK_STATUS_PERSON
;
2486 stock
= PIDGIN_STOCK_STATUS_CHAT
;
2493 pidgin_conv_get_icon(PurpleConversation
*conv
, GtkWidget
*parent
, const char *icon_size
)
2495 PurpleAccount
*account
= NULL
;
2496 const char *name
= NULL
;
2497 const char *stock
= NULL
;
2498 GdkPixbuf
*status
= NULL
;
2499 PurpleBlistUiOps
*ops
= purple_blist_get_ui_ops();
2502 g_return_val_if_fail(conv
!= NULL
, NULL
);
2504 account
= purple_conversation_get_account(conv
);
2505 name
= purple_conversation_get_name(conv
);
2507 g_return_val_if_fail(account
!= NULL
, NULL
);
2508 g_return_val_if_fail(name
!= NULL
, NULL
);
2510 /* Use the buddy icon, if possible */
2511 if (purple_conversation_get_type(conv
) == PURPLE_CONV_TYPE_IM
) {
2512 PurpleBuddy
*b
= purple_find_buddy(account
, name
);
2514 /* I hate this hack. It fixes a bug where the pending message icon
2515 * displays in the conv tab even though it shouldn't.
2516 * A better solution would be great. */
2517 if (ops
&& ops
->update
)
2518 ops
->update(NULL
, (PurpleBlistNode
*)b
);
2522 stock
= pidgin_conv_get_icon_stock(conv
);
2523 size
= gtk_icon_size_from_name(icon_size
);
2524 status
= gtk_widget_render_icon (parent
, stock
, size
, "GtkWidget");
2529 pidgin_conv_get_tab_icon(PurpleConversation
*conv
, gboolean small_icon
)
2531 const char *icon_size
= small_icon
? PIDGIN_ICON_SIZE_TANGO_MICROSCOPIC
: PIDGIN_ICON_SIZE_TANGO_EXTRA_SMALL
;
2532 return pidgin_conv_get_icon(conv
, PIDGIN_CONVERSATION(conv
)->icon
, icon_size
);
2537 update_tab_icon(PurpleConversation
*conv
)
2539 PidginConversation
*gtkconv
;
2542 GdkPixbuf
*emblem
= NULL
;
2543 const char *status
= NULL
;
2544 const char *infopane_status
= NULL
;
2546 g_return_if_fail(conv
!= NULL
);
2548 gtkconv
= PIDGIN_CONVERSATION(conv
);
2550 if (conv
!= gtkconv
->active_conv
)
2553 status
= infopane_status
= pidgin_conv_get_icon_stock(conv
);
2555 if (purple_conversation_get_type(conv
) == PURPLE_CONV_TYPE_IM
) {
2556 PurpleBuddy
*b
= purple_find_buddy(conv
->account
, conv
->name
);
2558 emblem
= pidgin_blist_get_emblem((PurpleBlistNode
*)b
);
2561 g_return_if_fail(status
!= NULL
);
2563 g_object_set(G_OBJECT(gtkconv
->icon
), "stock", status
, NULL
);
2564 g_object_set(G_OBJECT(gtkconv
->menu_icon
), "stock", status
, NULL
);
2566 gtk_list_store_set(GTK_LIST_STORE(gtkconv
->infopane_model
),
2567 &(gtkconv
->infopane_iter
),
2568 CONV_ICON_COLUMN
, infopane_status
, -1);
2570 gtk_list_store_set(GTK_LIST_STORE(gtkconv
->infopane_model
),
2571 &(gtkconv
->infopane_iter
),
2572 CONV_EMBLEM_COLUMN
, emblem
, -1);
2574 g_object_unref(emblem
);
2576 if (purple_prefs_get_bool(PIDGIN_PREFS_ROOT
"/blist/show_protocol_icons")) {
2577 emblem
= pidgin_create_prpl_icon(gtkconv
->active_conv
->account
, PIDGIN_PRPL_ICON_SMALL
);
2582 gtk_list_store_set(GTK_LIST_STORE(gtkconv
->infopane_model
),
2583 &(gtkconv
->infopane_iter
),
2584 CONV_PROTOCOL_ICON_COLUMN
, emblem
, -1);
2586 g_object_unref(emblem
);
2588 /* XXX seanegan Why do I have to do this? */
2589 gtk_widget_queue_resize(gtkconv
->infopane
);
2590 gtk_widget_queue_draw(gtkconv
->infopane
);
2592 if (pidgin_conv_window_is_active_conversation(conv
) &&
2593 (purple_conversation_get_type(conv
) != PURPLE_CONV_TYPE_IM
||
2594 gtkconv
->u
.im
->anim
== NULL
))
2596 l
= pidgin_conv_get_tab_icons(conv
);
2598 gtk_window_set_icon_list(GTK_WINDOW(win
->window
), l
);
2603 /* This gets added as an idle handler when doing something that
2604 * redraws the icon. It sets the auto_resize gboolean to TRUE.
2605 * This way, when the size_allocate callback gets triggered, it notices
2606 * that this is an autoresize, and after the main loop iterates, it
2607 * gets set back to FALSE
2609 static gboolean
reset_auto_resize_cb(gpointer data
)
2611 PidginConversation
*gtkconv
= (PidginConversation
*)data
;
2612 gtkconv
->auto_resize
= FALSE
;
2618 redraw_icon(gpointer data
)
2620 PidginConversation
*gtkconv
= (PidginConversation
*)data
;
2621 PurpleConversation
*conv
= gtkconv
->active_conv
;
2622 PurpleAccount
*account
;
2627 int scale_width
, scale_height
;
2630 gtkconv
= PIDGIN_CONVERSATION(conv
);
2631 account
= purple_conversation_get_account(conv
);
2633 if (!(account
&& account
->gc
)) {
2634 gtkconv
->u
.im
->icon_timer
= 0;
2638 gdk_pixbuf_animation_iter_advance(gtkconv
->u
.im
->iter
, NULL
);
2639 buf
= gdk_pixbuf_animation_iter_get_pixbuf(gtkconv
->u
.im
->iter
);
2641 scale_width
= gdk_pixbuf_get_width(buf
);
2642 scale_height
= gdk_pixbuf_get_height(buf
);
2644 gtk_widget_get_size_request(gtkconv
->u
.im
->icon_container
, NULL
, &size
);
2645 size
= MIN(size
, MIN(scale_width
, scale_height
));
2646 size
= CLAMP(size
, BUDDYICON_SIZE_MIN
, BUDDYICON_SIZE_MAX
);
2648 if (scale_width
== scale_height
) {
2649 scale_width
= scale_height
= size
;
2650 } else if (scale_height
> scale_width
) {
2651 scale_width
= size
* scale_width
/ scale_height
;
2652 scale_height
= size
;
2654 scale_height
= size
* scale_height
/ scale_width
;
2658 scale
= gdk_pixbuf_scale_simple(buf
, scale_width
, scale_height
,
2659 GDK_INTERP_BILINEAR
);
2660 if (pidgin_gdk_pixbuf_is_opaque(scale
))
2661 pidgin_gdk_pixbuf_make_round(scale
);
2663 gtk_image_set_from_pixbuf(GTK_IMAGE(gtkconv
->u
.im
->icon
), scale
);
2664 g_object_unref(G_OBJECT(scale
));
2665 gtk_widget_queue_draw(gtkconv
->u
.im
->icon
);
2667 delay
= gdk_pixbuf_animation_iter_get_delay_time(gtkconv
->u
.im
->iter
);
2672 gtkconv
->u
.im
->icon_timer
= g_timeout_add(delay
, redraw_icon
, gtkconv
);
2678 start_anim(GtkObject
*obj
, PidginConversation
*gtkconv
)
2682 if (gtkconv
->u
.im
->anim
== NULL
)
2685 if (gtkconv
->u
.im
->icon_timer
!= 0)
2688 if (gdk_pixbuf_animation_is_static_image(gtkconv
->u
.im
->anim
))
2691 delay
= gdk_pixbuf_animation_iter_get_delay_time(gtkconv
->u
.im
->iter
);
2696 gtkconv
->u
.im
->icon_timer
= g_timeout_add(delay
, redraw_icon
, gtkconv
);
2700 remove_icon(GtkWidget
*widget
, PidginConversation
*gtkconv
)
2704 PurpleConversation
*conv
= gtkconv
->active_conv
;
2706 g_return_if_fail(conv
!= NULL
);
2708 gtk_widget_set_size_request(gtkconv
->u
.im
->icon_container
, -1, BUDDYICON_SIZE_MIN
);
2709 children
= gtk_container_get_children(GTK_CONTAINER(gtkconv
->u
.im
->icon_container
));
2711 /* We know there's only one child here. It'd be nice to shortcut to the
2712 event box, but we can't change the PidginConversation until 3.0 */
2713 event
= (GtkWidget
*)children
->data
;
2714 gtk_container_remove(GTK_CONTAINER(gtkconv
->u
.im
->icon_container
), event
);
2715 g_list_free(children
);
2718 if (gtkconv
->u
.im
->anim
!= NULL
)
2719 g_object_unref(G_OBJECT(gtkconv
->u
.im
->anim
));
2721 if (gtkconv
->u
.im
->icon_timer
!= 0)
2722 g_source_remove(gtkconv
->u
.im
->icon_timer
);
2724 if (gtkconv
->u
.im
->iter
!= NULL
)
2725 g_object_unref(G_OBJECT(gtkconv
->u
.im
->iter
));
2727 gtkconv
->u
.im
->icon_timer
= 0;
2728 gtkconv
->u
.im
->icon
= NULL
;
2729 gtkconv
->u
.im
->anim
= NULL
;
2730 gtkconv
->u
.im
->iter
= NULL
;
2731 gtkconv
->u
.im
->show_icon
= FALSE
;
2735 saveicon_writefile_cb(void *user_data
, const char *filename
)
2737 PidginConversation
*gtkconv
= (PidginConversation
*)user_data
;
2738 PurpleConversation
*conv
= gtkconv
->active_conv
;
2739 PurpleBuddyIcon
*icon
;
2743 icon
= purple_conv_im_get_icon(PURPLE_CONV_IM(conv
));
2744 data
= purple_buddy_icon_get_data(icon
, &len
);
2746 if ((len
<= 0) || (data
== NULL
) || !purple_util_write_data_to_file_absolute(filename
, data
, len
)) {
2747 purple_notify_error(gtkconv
, NULL
, _("Unable to save icon file to disk."), NULL
);
2752 custom_icon_sel_cb(const char *filename
, gpointer data
)
2757 PurpleContact
*contact
;
2758 PidginConversation
*gtkconv
= data
;
2759 PurpleConversation
*conv
= gtkconv
->active_conv
;
2760 PurpleAccount
*account
= purple_conversation_get_account(conv
);
2762 name
= purple_conversation_get_name(conv
);
2763 buddy
= purple_find_buddy(account
, name
);
2765 purple_debug_info("custom-icon", "You can only set custom icons for people on your buddylist.\n");
2768 contact
= purple_buddy_get_contact(buddy
);
2770 purple_buddy_icons_node_set_custom_icon_from_file((PurpleBlistNode
*)contact
, filename
);
2775 set_custom_icon_cb(GtkWidget
*widget
, PidginConversation
*gtkconv
)
2777 GtkWidget
*win
= pidgin_buddy_icon_chooser_new(GTK_WINDOW(gtkconv
->win
->window
),
2778 custom_icon_sel_cb
, gtkconv
);
2779 gtk_widget_show_all(win
);
2783 change_size_cb(GtkWidget
*widget
, PidginConversation
*gtkconv
)
2786 PurpleConversation
*conv
= gtkconv
->active_conv
;
2789 gtk_widget_get_size_request(gtkconv
->u
.im
->icon_container
, NULL
, &size
);
2791 if (size
== BUDDYICON_SIZE_MAX
) {
2792 size
= BUDDYICON_SIZE_MIN
;
2794 size
= BUDDYICON_SIZE_MAX
;
2797 gtk_widget_set_size_request(gtkconv
->u
.im
->icon_container
, -1, size
);
2798 pidgin_conv_update_buddy_icon(conv
);
2800 buddies
= purple_find_buddies(purple_conversation_get_account(conv
),
2801 purple_conversation_get_name(conv
));
2802 for (; buddies
; buddies
= g_slist_delete_link(buddies
, buddies
)) {
2803 PurpleBuddy
*buddy
= buddies
->data
;
2804 PurpleContact
*contact
= purple_buddy_get_contact(buddy
);
2805 purple_blist_node_set_int((PurpleBlistNode
*)contact
, "pidgin-infopane-iconsize", size
);
2810 remove_custom_icon_cb(GtkWidget
*widget
, PidginConversation
*gtkconv
)
2814 PurpleAccount
*account
;
2815 PurpleContact
*contact
;
2816 PurpleConversation
*conv
= gtkconv
->active_conv
;
2818 account
= purple_conversation_get_account(conv
);
2819 name
= purple_conversation_get_name(conv
);
2820 buddy
= purple_find_buddy(account
, name
);
2824 contact
= purple_buddy_get_contact(buddy
);
2826 purple_buddy_icons_node_set_custom_icon_from_file((PurpleBlistNode
*)contact
, NULL
);
2830 icon_menu_save_cb(GtkWidget
*widget
, PidginConversation
*gtkconv
)
2832 PurpleConversation
*conv
= gtkconv
->active_conv
;
2836 g_return_if_fail(conv
!= NULL
);
2838 ext
= purple_buddy_icon_get_extension(purple_conv_im_get_icon(PURPLE_CONV_IM(conv
)));
2840 buf
= g_strdup_printf("%s.%s", purple_normalize(conv
->account
, conv
->name
), ext
);
2842 purple_request_file(gtkconv
, _("Save Icon"), buf
, TRUE
,
2843 G_CALLBACK(saveicon_writefile_cb
), NULL
,
2844 conv
->account
, NULL
, conv
,
2851 stop_anim(GtkObject
*obj
, PidginConversation
*gtkconv
)
2853 if (gtkconv
->u
.im
->icon_timer
!= 0)
2854 g_source_remove(gtkconv
->u
.im
->icon_timer
);
2856 gtkconv
->u
.im
->icon_timer
= 0;
2861 toggle_icon_animate_cb(GtkWidget
*w
, PidginConversation
*gtkconv
)
2863 gtkconv
->u
.im
->animate
=
2864 gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(w
));
2866 if (gtkconv
->u
.im
->animate
)
2867 start_anim(NULL
, gtkconv
);
2869 stop_anim(NULL
, gtkconv
);
2873 icon_menu(GtkObject
*obj
, GdkEventButton
*e
, PidginConversation
*gtkconv
)
2875 static GtkWidget
*menu
= NULL
;
2876 PurpleConversation
*conv
;
2879 if (e
->button
== 1 && e
->type
== GDK_BUTTON_PRESS
) {
2880 change_size_cb(NULL
, gtkconv
);
2884 if (e
->button
!= 3 || e
->type
!= GDK_BUTTON_PRESS
) {
2889 * If a menu already exists, destroy it before creating a new one,
2890 * thus freeing-up the memory it occupied.
2893 gtk_widget_destroy(menu
);
2895 menu
= gtk_menu_new();
2897 if (gtkconv
->u
.im
->anim
&&
2898 !(gdk_pixbuf_animation_is_static_image(gtkconv
->u
.im
->anim
)))
2900 pidgin_new_check_item(menu
, _("Animate"),
2901 G_CALLBACK(toggle_icon_animate_cb
), gtkconv
,
2902 gtkconv
->u
.im
->icon_timer
);
2905 pidgin_new_item_from_stock(menu
, _("Hide Icon"), NULL
, G_CALLBACK(remove_icon
),
2906 gtkconv
, 0, 0, NULL
);
2908 pidgin_new_item_from_stock(menu
, _("Save Icon As..."), GTK_STOCK_SAVE_AS
,
2909 G_CALLBACK(icon_menu_save_cb
), gtkconv
,
2912 pidgin_new_item_from_stock(menu
, _("Set Custom Icon..."), NULL
,
2913 G_CALLBACK(set_custom_icon_cb
), gtkconv
,
2916 pidgin_new_item_from_stock(menu
, _("Change Size"), NULL
,
2917 G_CALLBACK(change_size_cb
), gtkconv
,
2920 /* Is there a custom icon for this person? */
2921 conv
= gtkconv
->active_conv
;
2922 buddy
= purple_find_buddy(purple_conversation_get_account(conv
),
2923 purple_conversation_get_name(conv
));
2926 PurpleContact
*contact
= purple_buddy_get_contact(buddy
);
2927 if (contact
&& purple_buddy_icons_node_has_custom_icon((PurpleBlistNode
*)contact
))
2929 pidgin_new_item_from_stock(menu
, _("Remove Custom Icon"), NULL
,
2930 G_CALLBACK(remove_custom_icon_cb
), gtkconv
,
2935 gtk_menu_popup(GTK_MENU(menu
), NULL
, NULL
, NULL
, NULL
, e
->button
, e
->time
);
2940 /**************************************************************************
2941 * End of the bunch of buddy icon functions
2942 **************************************************************************/
2944 pidgin_conv_present_conversation(PurpleConversation
*conv
)
2946 PidginConversation
*gtkconv
;
2947 GdkModifierType state
;
2949 pidgin_conv_attach_to_conversation(conv
);
2950 gtkconv
= PIDGIN_CONVERSATION(conv
);
2952 pidgin_conv_switch_active_conversation(conv
);
2953 /* Switch the tab only if the user initiated the event by pressing
2954 * a button or hitting a key. */
2955 if (gtk_get_current_event_state(&state
))
2956 pidgin_conv_window_switch_gtkconv(gtkconv
->win
, gtkconv
);
2957 gtk_window_present(GTK_WINDOW(gtkconv
->win
->window
));
2961 pidgin_conversations_find_unseen_list(PurpleConversationType type
,
2962 PidginUnseenState min_state
,
2963 gboolean hidden_only
,
2970 if (type
== PURPLE_CONV_TYPE_IM
) {
2971 l
= purple_get_ims();
2972 } else if (type
== PURPLE_CONV_TYPE_CHAT
) {
2973 l
= purple_get_chats();
2975 l
= purple_get_conversations();
2978 for (; l
!= NULL
&& (max_count
== 0 || c
< max_count
); l
= l
->next
) {
2979 PurpleConversation
*conv
= (PurpleConversation
*)l
->data
;
2980 PidginConversation
*gtkconv
= PIDGIN_CONVERSATION(conv
);
2982 if(gtkconv
== NULL
|| gtkconv
->active_conv
!= conv
)
2985 if (gtkconv
->unseen_state
>= min_state
2987 (hidden_only
&& gtkconv
->win
== hidden_convwin
))) {
2989 r
= g_list_prepend(r
, conv
);
2998 unseen_conv_menu_cb(GtkMenuItem
*item
, PurpleConversation
*conv
)
3000 g_return_if_fail(conv
!= NULL
);
3001 pidgin_conv_present_conversation(conv
);
3005 unseen_all_conv_menu_cb(GtkMenuItem
*item
, GList
*list
)
3007 g_return_if_fail(list
!= NULL
);
3008 /* Do not free the list from here. It will be freed from the
3009 * 'destroy' callback on the menuitem. */
3011 pidgin_conv_present_conversation(list
->data
);
3017 pidgin_conversations_fill_menu(GtkWidget
*menu
, GList
*convs
)
3022 g_return_val_if_fail(menu
!= NULL
, 0);
3023 g_return_val_if_fail(convs
!= NULL
, 0);
3025 for (l
= convs
; l
!= NULL
; l
= l
->next
) {
3026 PurpleConversation
*conv
= (PurpleConversation
*)l
->data
;
3027 PidginConversation
*gtkconv
= PIDGIN_CONVERSATION(conv
);
3029 GtkWidget
*icon
= gtk_image_new_from_stock(pidgin_conv_get_icon_stock(conv
),
3030 gtk_icon_size_from_name(PIDGIN_ICON_SIZE_TANGO_MICROSCOPIC
));
3032 gchar
*text
= g_strdup_printf("%s (%d)",
3033 gtk_label_get_text(GTK_LABEL(gtkconv
->tab_label
)),
3034 gtkconv
->unseen_count
);
3036 item
= gtk_image_menu_item_new_with_label(text
);
3037 gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(item
), icon
);
3038 g_signal_connect(G_OBJECT(item
), "activate", G_CALLBACK(unseen_conv_menu_cb
), conv
);
3039 gtk_menu_shell_append(GTK_MENU_SHELL(menu
), item
);
3045 /* There are more than one conversation. Add an option to show all conversations. */
3047 GList
*list
= g_list_copy(convs
);
3049 pidgin_separator(menu
);
3051 item
= gtk_menu_item_new_with_label(_("Show All"));
3052 g_signal_connect(G_OBJECT(item
), "activate", G_CALLBACK(unseen_all_conv_menu_cb
), list
);
3053 g_signal_connect_swapped(G_OBJECT(item
), "destroy", G_CALLBACK(g_list_free
), list
);
3054 gtk_menu_shell_append(GTK_MENU_SHELL(menu
), item
);
3061 pidgin_conv_get_window(PidginConversation
*gtkconv
)
3063 g_return_val_if_fail(gtkconv
!= NULL
, NULL
);
3064 return gtkconv
->win
;
3067 static GtkItemFactoryEntry menu_items
[] =
3069 /* Conversation menu */
3070 { N_("/_Conversation"), NULL
, NULL
, 0, "<Branch>", NULL
},
3072 { N_("/Conversation/New Instant _Message..."), "<CTL>M", menu_new_conv_cb
,
3073 0, "<StockItem>", PIDGIN_STOCK_TOOLBAR_MESSAGE_NEW
},
3074 { N_("/Conversation/Join a _Chat..."), NULL
, menu_join_chat_cb
,
3075 0, "<StockItem>", PIDGIN_STOCK_CHAT
},
3077 { "/Conversation/sep0", NULL
, NULL
, 0, "<Separator>", NULL
},
3079 { N_("/Conversation/_Find..."), NULL
, menu_find_cb
, 0,
3080 "<StockItem>", GTK_STOCK_FIND
},
3081 { N_("/Conversation/View _Log"), NULL
, menu_view_log_cb
, 0, "<Item>", NULL
},
3082 { N_("/Conversation/_Save As..."), NULL
, menu_save_as_cb
, 0,
3083 "<StockItem>", GTK_STOCK_SAVE_AS
},
3084 { N_("/Conversation/Clea_r Scrollback"), "<CTL>L", menu_clear_cb
, 0, "<StockItem>", GTK_STOCK_CLEAR
},
3086 { "/Conversation/sep1", NULL
, NULL
, 0, "<Separator>", NULL
},
3089 { N_("/Conversation/M_edia"), NULL
, NULL
, 0, "<Branch>", NULL
},
3091 { N_("/Conversation/Media/_Audio Call"), NULL
, menu_initiate_media_call_cb
, 0,
3092 "<StockItem>", PIDGIN_STOCK_TOOLBAR_AUDIO_CALL
},
3093 { N_("/Conversation/Media/_Video Call"), NULL
, menu_initiate_media_call_cb
, 1,
3094 "<StockItem>", PIDGIN_STOCK_TOOLBAR_VIDEO_CALL
},
3095 { N_("/Conversation/Media/Audio\\/Video _Call"), NULL
, menu_initiate_media_call_cb
, 2,
3096 "<StockItem>", PIDGIN_STOCK_TOOLBAR_VIDEO_CALL
},
3099 { N_("/Conversation/Se_nd File..."), NULL
, menu_send_file_cb
, 0, "<StockItem>", PIDGIN_STOCK_TOOLBAR_SEND_FILE
},
3100 { N_("/Conversation/Get _Attention"), NULL
, menu_get_attention_cb
, 0, "<StockItem>", PIDGIN_STOCK_TOOLBAR_SEND_ATTENTION
},
3101 { N_("/Conversation/Add Buddy _Pounce..."), NULL
, menu_add_pounce_cb
,
3102 0, "<Item>", NULL
},
3103 { N_("/Conversation/_Get Info"), "<CTL>O", menu_get_info_cb
, 0,
3104 "<StockItem>", PIDGIN_STOCK_TOOLBAR_USER_INFO
},
3105 { N_("/Conversation/In_vite..."), NULL
, menu_invite_cb
, 0,
3107 { N_("/Conversation/M_ore"), NULL
, NULL
, 0, "<Branch>", NULL
},
3109 { "/Conversation/sep2", NULL
, NULL
, 0, "<Separator>", NULL
},
3111 { N_("/Conversation/Al_ias..."), NULL
, menu_alias_cb
, 0,
3113 { N_("/Conversation/_Block..."), NULL
, menu_block_cb
, 0,
3114 "<StockItem>", PIDGIN_STOCK_TOOLBAR_BLOCK
},
3115 { N_("/Conversation/_Unblock..."), NULL
, menu_unblock_cb
, 0,
3116 "<StockItem>", PIDGIN_STOCK_TOOLBAR_UNBLOCK
},
3117 { N_("/Conversation/_Add..."), NULL
, menu_add_remove_cb
, 0,
3118 "<StockItem>", GTK_STOCK_ADD
},
3119 { N_("/Conversation/_Remove..."), NULL
, menu_add_remove_cb
, 0,
3120 "<StockItem>", GTK_STOCK_REMOVE
},
3122 { "/Conversation/sep3", NULL
, NULL
, 0, "<Separator>", NULL
},
3124 { N_("/Conversation/Insert Lin_k..."), NULL
, menu_insert_link_cb
, 0,
3125 "<StockItem>", PIDGIN_STOCK_TOOLBAR_INSERT_LINK
},
3126 { N_("/Conversation/Insert Imag_e..."), NULL
, menu_insert_image_cb
, 0,
3127 "<StockItem>", PIDGIN_STOCK_TOOLBAR_INSERT_IMAGE
},
3129 { "/Conversation/sep4", NULL
, NULL
, 0, "<Separator>", NULL
},
3132 { N_("/Conversation/_Close"), NULL
, menu_close_conv_cb
, 0,
3133 "<StockItem>", GTK_STOCK_CLOSE
},
3136 { N_("/_Options"), NULL
, NULL
, 0, "<Branch>", NULL
},
3137 { N_("/Options/Enable _Logging"), NULL
, menu_logging_cb
, 0, "<CheckItem>", NULL
},
3138 { N_("/Options/Enable _Sounds"), NULL
, menu_sounds_cb
, 0, "<CheckItem>", NULL
},
3139 { "/Options/sep0", NULL
, NULL
, 0, "<Separator>", NULL
},
3140 { N_("/Options/Show Formatting _Toolbars"), NULL
, menu_toolbar_cb
, 0, "<CheckItem>", NULL
},
3141 { N_("/Options/Show Ti_mestamps"), NULL
, menu_timestamps_cb
, 0, "<CheckItem>", NULL
},
3144 static const int menu_item_count
=
3145 sizeof(menu_items
) / sizeof(*menu_items
);
3148 item_factory_translate_func (const char *path
, gpointer func_data
)
3154 sound_method_pref_changed_cb(const char *name
, PurplePrefType type
,
3155 gconstpointer value
, gpointer data
)
3157 PidginWindow
*win
= data
;
3158 const char *method
= value
;
3160 if (purple_strequal(method
, "none"))
3162 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(win
->menu
.sounds
),
3164 gtk_widget_set_sensitive(win
->menu
.sounds
, FALSE
);
3168 PidginConversation
*gtkconv
= pidgin_conv_window_get_active_gtkconv(win
);
3170 if (gtkconv
!= NULL
)
3171 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(win
->menu
.sounds
),
3172 gtkconv
->make_sound
);
3173 gtk_widget_set_sensitive(win
->menu
.sounds
, TRUE
);
3178 /* Returns TRUE if some items were added to the menu, FALSE otherwise */
3180 populate_menu_with_options(GtkWidget
*menu
, PidginConversation
*gtkconv
, gboolean all
)
3183 PurpleConversation
*conv
;
3184 PurpleBlistNode
*node
= NULL
;
3185 PurpleChat
*chat
= NULL
;
3186 PurpleBuddy
*buddy
= NULL
;
3189 conv
= gtkconv
->active_conv
;
3191 if (purple_conversation_get_type(conv
) == PURPLE_CONV_TYPE_CHAT
) {
3192 chat
= purple_blist_find_chat(conv
->account
, conv
->name
);
3194 if ((chat
== NULL
) && (gtkconv
->imhtml
!= NULL
)) {
3195 chat
= g_object_get_data(G_OBJECT(gtkconv
->imhtml
), "transient_chat");
3198 if ((chat
== NULL
) && (gtkconv
->imhtml
!= NULL
)) {
3199 GHashTable
*components
;
3200 PurpleAccount
*account
= purple_conversation_get_account(conv
);
3201 PurplePlugin
*prpl
= purple_find_prpl(purple_account_get_protocol_id(account
));
3202 PurplePluginProtocolInfo
*prpl_info
= PURPLE_PLUGIN_PROTOCOL_INFO(prpl
);
3203 if (purple_account_get_connection(account
) != NULL
&&
3204 PURPLE_PROTOCOL_PLUGIN_HAS_FUNC(prpl_info
, chat_info_defaults
)) {
3205 components
= prpl_info
->chat_info_defaults(purple_account_get_connection(account
),
3206 purple_conversation_get_name(conv
));
3208 components
= g_hash_table_new_full(g_str_hash
, g_str_equal
,
3210 g_hash_table_replace(components
, g_strdup("channel"),
3211 g_strdup(purple_conversation_get_name(conv
)));
3213 chat
= purple_chat_new(conv
->account
, NULL
, components
);
3214 purple_blist_node_set_flags((PurpleBlistNode
*)chat
,
3215 PURPLE_BLIST_NODE_FLAG_NO_SAVE
);
3216 g_object_set_data_full(G_OBJECT(gtkconv
->imhtml
), "transient_chat",
3217 chat
, (GDestroyNotify
)purple_blist_remove_chat
);
3220 if (!purple_account_is_connected(conv
->account
))
3223 buddy
= purple_find_buddy(conv
->account
, conv
->name
);
3225 /* gotta remain bug-compatible :( libpurple < 2.0.2 didn't handle
3226 * removing "isolated" buddy nodes well */
3227 if (purple_version_check(2, 0, 2) == NULL
) {
3228 if ((buddy
== NULL
) && (gtkconv
->imhtml
!= NULL
)) {
3229 buddy
= g_object_get_data(G_OBJECT(gtkconv
->imhtml
), "transient_buddy");
3232 if ((buddy
== NULL
) && (gtkconv
->imhtml
!= NULL
)) {
3233 buddy
= purple_buddy_new(conv
->account
, conv
->name
, NULL
);
3234 purple_blist_node_set_flags((PurpleBlistNode
*)buddy
,
3235 PURPLE_BLIST_NODE_FLAG_NO_SAVE
);
3236 g_object_set_data_full(G_OBJECT(gtkconv
->imhtml
), "transient_buddy",
3237 buddy
, (GDestroyNotify
)purple_buddy_destroy
);
3243 node
= (PurpleBlistNode
*)chat
;
3245 node
= (PurpleBlistNode
*)buddy
;
3247 /* Now add the stuff */
3250 pidgin_blist_make_buddy_menu(menu
, buddy
, TRUE
);
3255 if (purple_account_is_connected(conv
->account
))
3256 pidgin_append_blist_node_proto_menu(menu
, conv
->account
->gc
, node
);
3257 pidgin_append_blist_node_extended_menu(menu
, node
);
3260 if ((list
= gtk_container_get_children(GTK_CONTAINER(menu
))) == NULL
) {
3270 regenerate_media_items(PidginWindow
*win
)
3273 PurpleAccount
*account
;
3274 PurpleConversation
*conv
;
3276 conv
= pidgin_conv_window_get_active_conversation(win
);
3279 purple_debug_error("gtkconv", "couldn't get active conversation"
3280 " when regenerating media items\n");
3284 account
= purple_conversation_get_account(conv
);
3286 if (account
== NULL
) {
3287 purple_debug_error("gtkconv", "couldn't get account when"
3288 " regenerating media items\n");
3293 * Check if account support voice and/or calls, and
3294 * if the current buddy supports it.
3296 if (account
!= NULL
&& purple_conversation_get_type(conv
)
3297 == PURPLE_CONV_TYPE_IM
) {
3298 PurpleMediaCaps caps
=
3299 purple_prpl_get_media_caps(account
,
3300 purple_conversation_get_name(conv
));
3302 gtk_widget_set_sensitive(win
->audio_call
,
3303 caps
& PURPLE_MEDIA_CAPS_AUDIO
3305 gtk_widget_set_sensitive(win
->video_call
,
3306 caps
& PURPLE_MEDIA_CAPS_VIDEO
3308 gtk_widget_set_sensitive(win
->audio_video_call
,
3309 caps
& PURPLE_MEDIA_CAPS_AUDIO_VIDEO
3311 } else if (purple_conversation_get_type(conv
)
3312 == PURPLE_CONV_TYPE_CHAT
) {
3313 /* for now, don't care about chats... */
3314 gtk_widget_set_sensitive(win
->audio_call
, FALSE
);
3315 gtk_widget_set_sensitive(win
->video_call
, FALSE
);
3316 gtk_widget_set_sensitive(win
->audio_video_call
, FALSE
);
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
);
3326 regenerate_options_items(PidginWindow
*win
)
3329 PidginConversation
*gtkconv
;
3332 gtkconv
= pidgin_conv_window_get_active_gtkconv(win
);
3333 menu
= gtk_item_factory_get_widget(win
->menu
.item_factory
, N_("/Conversation/More"));
3335 /* Remove the previous entries */
3336 for (list
= gtk_container_get_children(GTK_CONTAINER(menu
)); list
; )
3338 GtkWidget
*w
= list
->data
;
3339 list
= g_list_delete_link(list
, list
);
3340 gtk_widget_destroy(w
);
3343 if (!populate_menu_with_options(menu
, gtkconv
, FALSE
))
3345 GtkWidget
*item
= gtk_menu_item_new_with_label(_("No actions available"));
3346 gtk_menu_shell_append(GTK_MENU_SHELL(menu
), item
);
3347 gtk_widget_set_sensitive(item
, FALSE
);
3350 gtk_widget_show_all(menu
);
3354 remove_from_list(GtkWidget
*widget
, PidginWindow
*win
)
3356 GList
*list
= g_object_get_data(G_OBJECT(win
->window
), "plugin-actions");
3357 list
= g_list_remove(list
, widget
);
3358 g_object_set_data(G_OBJECT(win
->window
), "plugin-actions", list
);
3362 regenerate_plugins_items(PidginWindow
*win
)
3364 GList
*action_items
;
3367 PidginConversation
*gtkconv
;
3368 PurpleConversation
*conv
;
3371 if (win
->window
== NULL
|| win
== hidden_convwin
)
3374 gtkconv
= pidgin_conv_window_get_active_gtkconv(win
);
3375 if (gtkconv
== NULL
)
3378 conv
= gtkconv
->active_conv
;
3379 action_items
= g_object_get_data(G_OBJECT(win
->window
), "plugin-actions");
3381 /* Remove the old menuitems */
3382 while (action_items
) {
3383 g_signal_handlers_disconnect_by_func(G_OBJECT(action_items
->data
),
3384 G_CALLBACK(remove_from_list
), win
);
3385 gtk_widget_destroy(action_items
->data
);
3386 action_items
= g_list_delete_link(action_items
, action_items
);
3389 menu
= gtk_item_factory_get_widget(win
->menu
.item_factory
, N_("/Options"));
3391 list
= purple_conversation_get_extended_menu(conv
);
3393 action_items
= g_list_prepend(NULL
, (item
= pidgin_separator(menu
)));
3394 g_signal_connect(G_OBJECT(item
), "destroy", G_CALLBACK(remove_from_list
), win
);
3397 for(; list
; list
= g_list_delete_link(list
, list
)) {
3398 PurpleMenuAction
*act
= (PurpleMenuAction
*) list
->data
;
3399 item
= pidgin_append_menu_action(menu
, act
, conv
);
3400 action_items
= g_list_prepend(action_items
, item
);
3401 gtk_widget_show_all(item
);
3402 g_signal_connect(G_OBJECT(item
), "destroy", G_CALLBACK(remove_from_list
), win
);
3404 g_object_set_data(G_OBJECT(win
->window
), "plugin-actions", action_items
);
3407 static void menubar_activated(GtkWidget
*item
, gpointer data
)
3409 PidginWindow
*win
= data
;
3410 regenerate_media_items(win
);
3411 regenerate_options_items(win
);
3412 regenerate_plugins_items(win
);
3414 /* The following are to make sure the 'More' submenu is not regenerated every time
3415 * the focus shifts from 'Conversations' to some other menu and back. */
3416 g_signal_handlers_block_by_func(G_OBJECT(item
), G_CALLBACK(menubar_activated
), data
);
3417 g_signal_connect(G_OBJECT(win
->menu
.menubar
), "deactivate", G_CALLBACK(focus_out_from_menubar
), data
);
3421 focus_out_from_menubar(GtkWidget
*wid
, PidginWindow
*win
)
3423 /* The menubar has been deactivated. Make sure the 'More' submenu is regenerated next time
3424 * the 'Conversation' menu pops up. */
3425 GtkWidget
*menuitem
= gtk_item_factory_get_item(win
->menu
.item_factory
, N_("/Conversation"));
3426 g_signal_handlers_unblock_by_func(G_OBJECT(menuitem
), G_CALLBACK(menubar_activated
), win
);
3427 g_signal_handlers_disconnect_by_func(G_OBJECT(win
->menu
.menubar
),
3428 G_CALLBACK(focus_out_from_menubar
), win
);
3432 setup_menubar(PidginWindow
*win
)
3434 GtkAccelGroup
*accel_group
;
3436 GtkWidget
*menuitem
;
3438 accel_group
= gtk_accel_group_new ();
3439 gtk_window_add_accel_group(GTK_WINDOW(win
->window
), accel_group
);
3440 g_object_unref(accel_group
);
3442 win
->menu
.item_factory
=
3443 gtk_item_factory_new(GTK_TYPE_MENU_BAR
, "<main>", accel_group
);
3445 gtk_item_factory_set_translate_func(win
->menu
.item_factory
,
3446 (GtkTranslateFunc
)item_factory_translate_func
,
3449 gtk_item_factory_create_items(win
->menu
.item_factory
, menu_item_count
,
3451 g_signal_connect(G_OBJECT(accel_group
), "accel-changed",
3452 G_CALLBACK(pidgin_save_accels_cb
), NULL
);
3454 /* Make sure the 'Conversation -> More' menuitems are regenerated whenever
3455 * the 'Conversation' menu pops up because the entries can change after the
3456 * conversation is created. */
3457 menuitem
= gtk_item_factory_get_item(win
->menu
.item_factory
, N_("/Conversation"));
3458 g_signal_connect(G_OBJECT(menuitem
), "activate", G_CALLBACK(menubar_activated
), win
);
3461 gtk_item_factory_get_widget(win
->menu
.item_factory
, "<main>");
3463 win
->menu
.view_log
=
3464 gtk_item_factory_get_widget(win
->menu
.item_factory
,
3465 N_("/Conversation/View Log"));
3469 gtk_item_factory_get_widget(win
->menu
.item_factory
,
3470 N_("/Conversation/Media/Audio Call"));
3472 gtk_item_factory_get_widget(win
->menu
.item_factory
,
3473 N_("/Conversation/Media/Video Call"));
3474 win
->audio_video_call
=
3475 gtk_item_factory_get_widget(win
->menu
.item_factory
,
3476 N_("/Conversation/Media/Audio\\/Video Call"));
3481 win
->menu
.send_file
=
3482 gtk_item_factory_get_widget(win
->menu
.item_factory
,
3483 N_("/Conversation/Send File..."));
3485 g_object_set_data(G_OBJECT(win
->window
), "get_attention",
3486 gtk_item_factory_get_widget(win
->menu
.item_factory
,
3487 N_("/Conversation/Get Attention")));
3488 win
->menu
.add_pounce
=
3489 gtk_item_factory_get_widget(win
->menu
.item_factory
,
3490 N_("/Conversation/Add Buddy Pounce..."));
3494 win
->menu
.get_info
=
3495 gtk_item_factory_get_widget(win
->menu
.item_factory
,
3496 N_("/Conversation/Get Info"));
3499 gtk_item_factory_get_widget(win
->menu
.item_factory
,
3500 N_("/Conversation/Invite..."));
3505 gtk_item_factory_get_widget(win
->menu
.item_factory
,
3506 N_("/Conversation/Alias..."));
3509 gtk_item_factory_get_widget(win
->menu
.item_factory
,
3510 N_("/Conversation/Block..."));
3513 gtk_item_factory_get_widget(win
->menu
.item_factory
,
3514 N_("/Conversation/Unblock..."));
3517 gtk_item_factory_get_widget(win
->menu
.item_factory
,
3518 N_("/Conversation/Add..."));
3521 gtk_item_factory_get_widget(win
->menu
.item_factory
,
3522 N_("/Conversation/Remove..."));
3526 win
->menu
.insert_link
=
3527 gtk_item_factory_get_widget(win
->menu
.item_factory
,
3528 N_("/Conversation/Insert Link..."));
3530 win
->menu
.insert_image
=
3531 gtk_item_factory_get_widget(win
->menu
.item_factory
,
3532 N_("/Conversation/Insert Image..."));
3537 gtk_item_factory_get_widget(win
->menu
.item_factory
,
3538 N_("/Options/Enable Logging"));
3540 gtk_item_factory_get_widget(win
->menu
.item_factory
,
3541 N_("/Options/Enable Sounds"));
3542 method
= purple_prefs_get_string(PIDGIN_PREFS_ROOT
"/sound/method");
3543 if (purple_strequal(method
, "none"))
3545 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(win
->menu
.sounds
),
3547 gtk_widget_set_sensitive(win
->menu
.sounds
, FALSE
);
3549 purple_prefs_connect_callback(win
, PIDGIN_PREFS_ROOT
"/sound/method",
3550 sound_method_pref_changed_cb
, win
);
3552 win
->menu
.show_formatting_toolbar
=
3553 gtk_item_factory_get_widget(win
->menu
.item_factory
,
3554 N_("/Options/Show Formatting Toolbars"));
3555 win
->menu
.show_timestamps
=
3556 gtk_item_factory_get_widget(win
->menu
.item_factory
,
3557 N_("/Options/Show Timestamps"));
3558 win
->menu
.show_icon
= NULL
;
3560 win
->menu
.tray
= pidgin_menu_tray_new();
3561 gtk_menu_shell_append(GTK_MENU_SHELL(win
->menu
.menubar
),
3563 gtk_widget_show(win
->menu
.tray
);
3565 gtk_widget_show(win
->menu
.menubar
);
3567 return win
->menu
.menubar
;
3571 /**************************************************************************
3573 **************************************************************************/
3576 got_typing_keypress(PidginConversation
*gtkconv
, gboolean first
)
3578 PurpleConversation
*conv
= gtkconv
->active_conv
;
3582 * We know we got something, so we at least have to make sure we don't
3583 * send PURPLE_TYPED any time soon.
3586 im
= PURPLE_CONV_IM(conv
);
3588 purple_conv_im_stop_send_typed_timeout(im
);
3589 purple_conv_im_start_send_typed_timeout(im
);
3591 /* Check if we need to send another PURPLE_TYPING message */
3592 if (first
|| (purple_conv_im_get_type_again(im
) != 0 &&
3593 time(NULL
) > purple_conv_im_get_type_again(im
)))
3595 unsigned int timeout
;
3596 timeout
= serv_send_typing(purple_conversation_get_gc(conv
),
3597 purple_conversation_get_name(conv
),
3599 purple_conv_im_set_type_again(im
, timeout
);
3605 typing_animation(gpointer data
) {
3606 PidginConversation
*gtkconv
= data
;
3607 PidginWindow
*gtkwin
= gtkconv
->win
;
3608 const char *stock_id
= NULL
;
3610 if(gtkconv
!= pidgin_conv_window_get_active_gtkconv(gtkwin
)) {
3614 switch (rand() % 5) {
3616 stock_id
= PIDGIN_STOCK_ANIMATION_TYPING0
;
3619 stock_id
= PIDGIN_STOCK_ANIMATION_TYPING1
;
3622 stock_id
= PIDGIN_STOCK_ANIMATION_TYPING2
;
3625 stock_id
= PIDGIN_STOCK_ANIMATION_TYPING3
;
3628 stock_id
= PIDGIN_STOCK_ANIMATION_TYPING4
;
3631 if (gtkwin
->menu
.typing_icon
== NULL
) {
3632 gtkwin
->menu
.typing_icon
= gtk_image_new_from_stock(stock_id
, GTK_ICON_SIZE_MENU
);
3633 pidgin_menu_tray_append(PIDGIN_MENU_TRAY(gtkwin
->menu
.tray
),
3634 gtkwin
->menu
.typing_icon
,
3635 _("User is typing..."));
3637 gtk_image_set_from_stock(GTK_IMAGE(gtkwin
->menu
.typing_icon
), stock_id
, GTK_ICON_SIZE_MENU
);
3639 gtk_widget_show(gtkwin
->menu
.typing_icon
);
3645 update_typing_message(PidginConversation
*gtkconv
, const char *message
)
3647 GtkTextBuffer
*buffer
;
3648 GtkTextMark
*stmark
, *enmark
;
3650 if (g_object_get_data(G_OBJECT(gtkconv
->imhtml
), "disable-typing-notification"))
3653 buffer
= gtk_text_view_get_buffer(GTK_TEXT_VIEW(gtkconv
->imhtml
));
3654 stmark
= gtk_text_buffer_get_mark(buffer
, "typing-notification-start");
3655 enmark
= gtk_text_buffer_get_mark(buffer
, "typing-notification-end");
3656 if (stmark
&& enmark
) {
3657 GtkTextIter start
, end
;
3658 gtk_text_buffer_get_iter_at_mark(buffer
, &start
, stmark
);
3659 gtk_text_buffer_get_iter_at_mark(buffer
, &end
, enmark
);
3660 gtk_text_buffer_delete_mark(buffer
, stmark
);
3661 gtk_text_buffer_delete_mark(buffer
, enmark
);
3662 gtk_text_buffer_delete(buffer
, &start
, &end
);
3663 } else if (message
&& *message
== '\n' && message
[1] == ' ' && message
[2] == '\0')
3668 message
= "\n "; /* The blank space is required to avoid a GTK+/Pango bug */
3673 gtk_text_buffer_get_end_iter(buffer
, &iter
);
3674 gtk_text_buffer_create_mark(buffer
, "typing-notification-start", &iter
, TRUE
);
3675 gtk_text_buffer_insert_with_tags_by_name(buffer
, &iter
, message
, -1, "TYPING-NOTIFICATION", NULL
);
3676 gtk_text_buffer_get_end_iter(buffer
, &iter
);
3677 gtk_text_buffer_create_mark(buffer
, "typing-notification-end", &iter
, TRUE
);
3682 update_typing_icon(PidginConversation
*gtkconv
)
3684 PurpleConvIm
*im
= NULL
;
3685 PurpleConversation
*conv
= gtkconv
->active_conv
;
3686 char *message
= NULL
;
3688 if (purple_conversation_get_type(conv
) == PURPLE_CONV_TYPE_IM
)
3689 im
= PURPLE_CONV_IM(conv
);
3694 if (purple_conv_im_get_typing_state(im
) == PURPLE_NOT_TYPING
) {
3696 update_typing_message(gtkconv
, NULL
);
3698 update_typing_message(gtkconv
, "\n ");
3703 if (purple_conv_im_get_typing_state(im
) == PURPLE_TYPING
) {
3704 message
= g_strdup_printf(_("\n%s is typing..."), purple_conversation_get_title(conv
));
3706 message
= g_strdup_printf(_("\n%s has stopped typing"), purple_conversation_get_title(conv
));
3709 update_typing_message(gtkconv
, message
);
3714 update_send_to_selection(PidginWindow
*win
)
3716 PurpleAccount
*account
;
3717 PurpleConversation
*conv
;
3722 conv
= pidgin_conv_window_get_active_conversation(win
);
3727 account
= purple_conversation_get_account(conv
);
3729 if (account
== NULL
)
3732 if (win
->menu
.send_to
== NULL
)
3735 if (!(b
= purple_find_buddy(account
, conv
->name
)))
3739 gtk_widget_show(win
->menu
.send_to
);
3741 menu
= gtk_menu_item_get_submenu(GTK_MENU_ITEM(win
->menu
.send_to
));
3743 for (child
= gtk_container_get_children(GTK_CONTAINER(menu
));
3745 child
= g_list_delete_link(child
, child
)) {
3747 GtkWidget
*item
= child
->data
;
3748 PurpleBuddy
*item_buddy
;
3749 PurpleAccount
*item_account
= g_object_get_data(G_OBJECT(item
), "purple_account");
3750 gchar
*buddy_name
= g_object_get_data(G_OBJECT(item
),
3751 "purple_buddy_name");
3752 item_buddy
= purple_find_buddy(item_account
, buddy_name
);
3754 if (b
== item_buddy
) {
3755 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(item
), TRUE
);
3765 send_to_item_enter_notify_cb(GtkWidget
*menuitem
, GdkEventCrossing
*event
, GtkWidget
*label
)
3767 gtk_widget_set_sensitive(GTK_WIDGET(label
), TRUE
);
3772 send_to_item_leave_notify_cb(GtkWidget
*menuitem
, GdkEventCrossing
*event
, GtkWidget
*label
)
3774 gtk_widget_set_sensitive(GTK_WIDGET(label
), FALSE
);
3779 create_sendto_item(GtkWidget
*menu
, GtkSizeGroup
*sg
, GSList
**group
, PurpleBuddy
*buddy
, PurpleAccount
*account
, const char *name
)
3784 GtkWidget
*menuitem
;
3788 /* Create a pixmap for the protocol icon. */
3789 pixbuf
= pidgin_create_prpl_icon(account
, PIDGIN_PRPL_ICON_SMALL
);
3791 /* Now convert it to GtkImage */
3793 image
= gtk_image_new();
3796 image
= gtk_image_new_from_pixbuf(pixbuf
);
3797 g_object_unref(G_OBJECT(pixbuf
));
3800 gtk_size_group_add_widget(sg
, image
);
3802 /* Make our menu item */
3803 text
= g_strdup_printf("%s (%s)", name
, purple_account_get_name_for_display(account
));
3804 menuitem
= gtk_radio_menu_item_new_with_label(*group
, text
);
3806 *group
= gtk_radio_menu_item_get_group(GTK_RADIO_MENU_ITEM(menuitem
));
3808 /* Do some evil, see some evil, speak some evil. */
3809 box
= gtk_hbox_new(FALSE
, 0);
3811 label
= gtk_bin_get_child(GTK_BIN(menuitem
));
3812 g_object_ref(label
);
3813 gtk_container_remove(GTK_CONTAINER(menuitem
), label
);
3815 gtk_box_pack_start(GTK_BOX(box
), image
, FALSE
, FALSE
, 0);
3816 gtk_box_pack_start(GTK_BOX(box
), label
, TRUE
, TRUE
, 4);
3818 if (buddy
!= NULL
&&
3819 !purple_presence_is_online(purple_buddy_get_presence(buddy
)))
3821 gtk_widget_set_sensitive(label
, FALSE
);
3823 /* Set the label sensitive when the menuitem is highlighted and
3824 * insensitive again when the mouse leaves it. This way, it
3825 * doesn't appear weird from the highlighting of the embossed
3826 * (insensitive style) text.*/
3827 g_signal_connect(menuitem
, "enter-notify-event",
3828 G_CALLBACK(send_to_item_enter_notify_cb
), label
);
3829 g_signal_connect(menuitem
, "leave-notify-event",
3830 G_CALLBACK(send_to_item_leave_notify_cb
), label
);
3833 g_object_unref(label
);
3835 gtk_container_add(GTK_CONTAINER(menuitem
), box
);
3837 gtk_widget_show(label
);
3838 gtk_widget_show(image
);
3839 gtk_widget_show(box
);
3841 /* Set our data and callbacks. */
3842 g_object_set_data(G_OBJECT(menuitem
), "purple_account", account
);
3843 g_object_set_data_full(G_OBJECT(menuitem
), "purple_buddy_name", g_strdup(name
), g_free
);
3845 g_signal_connect(G_OBJECT(menuitem
), "activate",
3846 G_CALLBACK(menu_conv_sel_send_cb
), NULL
);
3848 gtk_widget_show(menuitem
);
3849 gtk_menu_shell_append(GTK_MENU_SHELL(menu
), menuitem
);
3853 compare_buddy_presence(PurplePresence
*p1
, PurplePresence
*p2
)
3855 /* This is necessary because multiple PurpleBuddy's don't share the same
3856 * PurplePresence anymore.
3858 PurpleBuddy
*b1
= purple_presence_get_buddy(p1
);
3859 PurpleBuddy
*b2
= purple_presence_get_buddy(p2
);
3860 if (purple_buddy_get_account(b1
) == purple_buddy_get_account(b2
) &&
3861 purple_strequal(purple_buddy_get_name(b1
), purple_buddy_get_name(b2
)))
3867 generate_send_to_items(PidginWindow
*win
)
3870 GSList
*group
= NULL
;
3871 GtkSizeGroup
*sg
= gtk_size_group_new(GTK_SIZE_GROUP_HORIZONTAL
);
3872 PidginConversation
*gtkconv
;
3875 g_return_if_fail(win
!= NULL
);
3877 gtkconv
= pidgin_conv_window_get_active_gtkconv(win
);
3879 g_return_if_fail(gtkconv
!= NULL
);
3881 if (win
->menu
.send_to
!= NULL
)
3882 gtk_widget_destroy(win
->menu
.send_to
);
3884 /* Build the Send To menu */
3885 win
->menu
.send_to
= gtk_menu_item_new_with_mnemonic(_("S_end To"));
3886 gtk_widget_show(win
->menu
.send_to
);
3888 menu
= gtk_menu_new();
3889 gtk_menu_shell_insert(GTK_MENU_SHELL(win
->menu
.menubar
),
3890 win
->menu
.send_to
, 2);
3891 gtk_menu_item_set_submenu(GTK_MENU_ITEM(win
->menu
.send_to
), menu
);
3893 gtk_widget_show(menu
);
3895 if (gtkconv
->active_conv
->type
== PURPLE_CONV_TYPE_IM
) {
3896 buds
= purple_find_buddies(gtkconv
->active_conv
->account
, gtkconv
->active_conv
->name
);
3900 /* The user isn't on the buddy list. So we don't create any sendto menu. */
3904 GList
*list
= NULL
, *iter
;
3905 for (l
= buds
; l
!= NULL
; l
= l
->next
)
3907 PurpleBlistNode
*node
;
3909 node
= PURPLE_BLIST_NODE(purple_buddy_get_contact(PURPLE_BUDDY(l
->data
)));
3911 for (node
= node
->child
; node
!= NULL
; node
= node
->next
)
3913 PurpleBuddy
*buddy
= (PurpleBuddy
*)node
;
3914 PurpleAccount
*account
;
3916 if (!PURPLE_BLIST_NODE_IS_BUDDY(node
))
3919 account
= purple_buddy_get_account(buddy
);
3920 if (purple_account_is_connected(account
) || account
== gtkconv
->active_conv
->account
)
3922 /* Use the PurplePresence to get unique buddies. */
3923 PurplePresence
*presence
= purple_buddy_get_presence(buddy
);
3924 if (!g_list_find_custom(list
, presence
, (GCompareFunc
)compare_buddy_presence
))
3925 list
= g_list_prepend(list
, presence
);
3930 /* Create the sendto menu only if it has more than one item to show */
3931 if (list
&& list
->next
) {
3932 /* Loop over the list backwards so we get the items in the right order,
3933 * since we did a g_list_prepend() earlier. */
3934 for (iter
= g_list_last(list
); iter
!= NULL
; iter
= iter
->prev
) {
3935 PurplePresence
*pre
= iter
->data
;
3936 PurpleBuddy
*buddy
= purple_presence_get_buddy(pre
);
3937 create_sendto_item(menu
, sg
, &group
, buddy
,
3938 purple_buddy_get_account(buddy
), purple_buddy_get_name(buddy
));
3948 gtk_widget_show(win
->menu
.send_to
);
3949 /* TODO: This should never be insensitive. Possibly hidden or not. */
3951 gtk_widget_set_sensitive(win
->menu
.send_to
, FALSE
);
3952 update_send_to_selection(win
);
3956 get_chat_buddy_status_icon(PurpleConvChat
*chat
, const char *name
, PurpleConvChatBuddyFlags flags
)
3958 const char *image
= NULL
;
3960 if (flags
& PURPLE_CBFLAGS_FOUNDER
) {
3961 image
= PIDGIN_STOCK_STATUS_FOUNDER
;
3962 } else if (flags
& PURPLE_CBFLAGS_OP
) {
3963 image
= PIDGIN_STOCK_STATUS_OPERATOR
;
3964 } else if (flags
& PURPLE_CBFLAGS_HALFOP
) {
3965 image
= PIDGIN_STOCK_STATUS_HALFOP
;
3966 } else if (flags
& PURPLE_CBFLAGS_VOICE
) {
3967 image
= PIDGIN_STOCK_STATUS_VOICE
;
3968 } else if ((!flags
) && purple_conv_chat_is_user_ignored(chat
, name
)) {
3969 image
= PIDGIN_STOCK_STATUS_IGNORED
;
3977 deleting_chat_buddy_cb(PurpleConvChatBuddy
*cb
)
3980 GtkTreeRowReference
*ref
= cb
->ui_data
;
3981 gtk_tree_row_reference_free(ref
);
3987 add_chat_buddy_common(PurpleConversation
*conv
, PurpleConvChatBuddy
*cb
, const char *old_name
)
3989 PidginConversation
*gtkconv
;
3990 PidginChatPane
*gtkchat
;
3991 PurpleConvChat
*chat
;
3992 PurpleConnection
*gc
;
3993 PurplePluginProtocolInfo
*prpl_info
;
3996 GtkTreePath
*newpath
;
3999 gboolean is_me
= FALSE
;
4001 gchar
*tmp
, *alias_key
, *name
, *alias
;
4002 PurpleConvChatBuddyFlags flags
;
4003 GdkColor
*color
= NULL
;
4009 chat
= PURPLE_CONV_CHAT(conv
);
4010 gtkconv
= PIDGIN_CONVERSATION(conv
);
4011 gtkchat
= gtkconv
->u
.chat
;
4012 gc
= purple_conversation_get_gc(conv
);
4014 if (!gc
|| !(prpl_info
= PURPLE_PLUGIN_PROTOCOL_INFO(gc
->prpl
)))
4017 tm
= gtk_tree_view_get_model(GTK_TREE_VIEW(gtkchat
->list
));
4018 ls
= GTK_LIST_STORE(tm
);
4020 stock
= get_chat_buddy_status_icon(chat
, name
, flags
);
4022 if (purple_strequal(chat
->nick
, purple_normalize(conv
->account
, old_name
!= NULL
? old_name
: name
)))
4025 is_buddy
= cb
->buddy
;
4027 tmp
= g_utf8_casefold(alias
, -1);
4028 alias_key
= g_utf8_collate_key(tmp
, -1);
4032 GtkTextTag
*tag
= gtk_text_tag_table_lookup(
4033 gtk_text_buffer_get_tag_table(GTK_IMHTML(gtkconv
->imhtml
)->text_buffer
),
4035 g_object_get(tag
, "foreground-gdk", &color
, NULL
);
4038 if ((tag
= get_buddy_tag(conv
, name
, 0, FALSE
)))
4039 g_object_set(G_OBJECT(tag
), "style", PANGO_STYLE_NORMAL
, NULL
);
4040 if ((tag
= get_buddy_tag(conv
, name
, PURPLE_MESSAGE_NICK
, FALSE
)))
4041 g_object_set(G_OBJECT(tag
), "style", PANGO_STYLE_NORMAL
, NULL
);
4042 color
= (GdkColor
*)get_nick_color(gtkconv
, name
);
4045 gtk_list_store_insert_with_values(ls
, &iter
,
4047 * The GTK docs are mute about the effects of the "row" value for performance.
4048 * X-Chat hardcodes their value to 0 (prepend) and -1 (append), so we will too.
4049 * It *might* be faster to search the gtk_list_store and set row accurately,
4050 * but no one in #gtk+ seems to know anything about it either.
4051 * Inserting in the "wrong" location has no visible ill effects. - F.P.
4054 CHAT_USERS_ICON_STOCK_COLUMN
, stock
,
4055 CHAT_USERS_ALIAS_COLUMN
, alias
,
4056 CHAT_USERS_ALIAS_KEY_COLUMN
, alias_key
,
4057 CHAT_USERS_NAME_COLUMN
, name
,
4058 CHAT_USERS_FLAGS_COLUMN
, flags
,
4059 CHAT_USERS_COLOR_COLUMN
, color
,
4060 CHAT_USERS_WEIGHT_COLUMN
, is_buddy
? PANGO_WEIGHT_BOLD
: PANGO_WEIGHT_NORMAL
,
4064 GtkTreeRowReference
*ref
= cb
->ui_data
;
4065 gtk_tree_row_reference_free(ref
);
4068 newpath
= gtk_tree_model_get_path(tm
, &iter
);
4069 cb
->ui_data
= gtk_tree_row_reference_new(tm
, newpath
);
4070 gtk_tree_path_free(newpath
);
4073 gdk_color_free(color
);
4078 * @param most_matched Used internally by this function.
4079 * @param entered The partial string that the user types before hitting the
4081 * @param entered_bytes The length of entered.
4082 * @param partial This is a return variable. This will be set to a string
4083 * containing the largest common string between all matches. This will
4084 * be inserted into the input box at the start of the word that the
4085 * user is tab completing. For example, if a chat room contains
4086 * "AlfFan" and "AlfHater" and the user types "a<TAB>" then this will
4088 * @param nick_partial Used internally by this function. Shoudl be a
4089 * temporary buffer that is entered_bytes+1 bytes long.
4090 * @param matches This is a return variable. If the given name is a potential
4091 * match for the entered string, then add a copy of the name to this
4092 * list. The caller is responsible for g_free'ing the data in this
4094 * @param name The buddy name or alias or slash command name that we're
4095 * checking for a match.
4098 tab_complete_process_item(int *most_matched
, const char *entered
, gsize entered_bytes
, char **partial
, char *nick_partial
,
4099 GList
**matches
, char *name
)
4101 memcpy(nick_partial
, name
, entered_bytes
);
4102 if (purple_utf8_strcasecmp(nick_partial
, entered
))
4105 /* if we're here, it's a possible completion */
4107 if (*most_matched
== -1) {
4109 * this will only get called once, since from now
4110 * on *most_matched is >= 0
4112 *most_matched
= strlen(name
);
4113 *partial
= g_strdup(name
);
4115 else if (*most_matched
) {
4116 char *tmp
= g_strdup(name
);
4118 while (purple_utf8_strcasecmp(tmp
, *partial
)) {
4119 (*partial
)[*most_matched
] = '\0';
4120 if (*most_matched
< (goffset
)strlen(tmp
))
4121 tmp
[*most_matched
] = '\0';
4129 *matches
= g_list_insert_sorted(*matches
, g_strdup(name
),
4130 (GCompareFunc
)purple_utf8_strcasecmp
);
4134 tab_complete(PurpleConversation
*conv
)
4136 PidginConversation
*gtkconv
;
4137 GtkTextIter cursor
, word_start
, start_buffer
;
4139 int most_matched
= -1;
4140 char *entered
, *partial
= NULL
;
4144 GList
*matches
= NULL
;
4145 gboolean command
= FALSE
;
4146 gsize entered_bytes
= 0;
4148 gtkconv
= PIDGIN_CONVERSATION(conv
);
4150 gtk_text_buffer_get_start_iter(gtkconv
->entry_buffer
, &start_buffer
);
4151 gtk_text_buffer_get_iter_at_mark(gtkconv
->entry_buffer
, &cursor
,
4152 gtk_text_buffer_get_insert(gtkconv
->entry_buffer
));
4154 word_start
= cursor
;
4156 /* if there's nothing there just return */
4157 if (!gtk_text_iter_compare(&cursor
, &start_buffer
))
4158 return (purple_conversation_get_type(conv
) == PURPLE_CONV_TYPE_CHAT
) ? TRUE
: FALSE
;
4160 text
= gtk_text_buffer_get_text(gtkconv
->entry_buffer
, &start_buffer
,
4163 /* if we're at the end of ": " we need to move back 2 spaces */
4164 start
= strlen(text
) - 1;
4166 if (start
>= 1 && !strncmp(&text
[start
-1], ": ", 2)) {
4167 gtk_text_iter_backward_chars(&word_start
, 2);
4170 /* find the start of the word that we're tabbing.
4171 * Using gtk_text_iter_backward_word_start won't work, because a nick can contain
4172 * characters (e.g. '.', '/' etc.) that Pango may think are word separators. */
4173 while (gtk_text_iter_backward_char(&word_start
)) {
4174 if (gtk_text_iter_get_char(&word_start
) == ' ') {
4175 /* Reached the whitespace before the start of the word. Move forward once */
4176 gtk_text_iter_forward_char(&word_start
);
4181 prefix
= pidgin_get_cmd_prefix();
4182 if (gtk_text_iter_get_offset(&word_start
) == 0 &&
4183 (strlen(text
) >= strlen(prefix
)) && !strncmp(text
, prefix
, strlen(prefix
))) {
4185 gtk_text_iter_forward_chars(&word_start
, strlen(prefix
));
4190 entered
= gtk_text_buffer_get_text(gtkconv
->entry_buffer
, &word_start
,
4192 entered_bytes
= strlen(entered
);
4194 if (!g_utf8_strlen(entered
, -1)) {
4196 return (purple_conversation_get_type(conv
) == PURPLE_CONV_TYPE_CHAT
) ? TRUE
: FALSE
;
4199 nick_partial
= g_malloc0(entered_bytes
+ 1);
4202 GList
*list
= purple_cmd_list(conv
);
4206 for (l
= list
; l
!= NULL
; l
= l
->next
) {
4207 tab_complete_process_item(&most_matched
, entered
, entered_bytes
, &partial
, nick_partial
,
4211 } else if (purple_conversation_get_type(conv
) == PURPLE_CONV_TYPE_CHAT
) {
4212 PurpleConvChat
*chat
= PURPLE_CONV_CHAT(conv
);
4213 GList
*l
= purple_conv_chat_get_users(chat
);
4214 GtkTreeModel
*model
= gtk_tree_view_get_model(GTK_TREE_VIEW(PIDGIN_CONVERSATION(conv
)->u
.chat
->list
));
4219 for (; l
!= NULL
; l
= l
->next
) {
4220 tab_complete_process_item(&most_matched
, entered
, entered_bytes
, &partial
, nick_partial
,
4221 &matches
, ((PurpleConvChatBuddy
*)l
->data
)->name
);
4226 if (gtk_tree_model_get_iter_first(GTK_TREE_MODEL(model
), &iter
))
4232 gtk_tree_model_get(model
, &iter
,
4233 CHAT_USERS_NAME_COLUMN
, &name
,
4234 CHAT_USERS_ALIAS_COLUMN
, &alias
,
4237 if (name
&& alias
&& !purple_strequal(name
, alias
))
4238 tab_complete_process_item(&most_matched
, entered
, entered_bytes
, &partial
, nick_partial
,
4243 f
= gtk_tree_model_iter_next(model
, &iter
);
4247 g_free(nick_partial
);
4252 g_free(nick_partial
);
4254 /* we're only here if we're doing new style */
4256 /* if there weren't any matches, return */
4258 /* if matches isn't set partials won't be either */
4260 return (purple_conversation_get_type(conv
) == PURPLE_CONV_TYPE_CHAT
) ? TRUE
: FALSE
;
4263 gtk_text_buffer_delete(gtkconv
->entry_buffer
, &word_start
, &cursor
);
4265 if (!matches
->next
) {
4266 /* there was only one match. fill it in. */
4267 gtk_text_buffer_get_start_iter(gtkconv
->entry_buffer
, &start_buffer
);
4268 gtk_text_buffer_get_iter_at_mark(gtkconv
->entry_buffer
, &cursor
,
4269 gtk_text_buffer_get_insert(gtkconv
->entry_buffer
));
4271 if (!gtk_text_iter_compare(&cursor
, &start_buffer
)) {
4272 char *tmp
= g_strdup_printf("%s: ", (char *)matches
->data
);
4273 gtk_text_buffer_insert_at_cursor(gtkconv
->entry_buffer
, tmp
, -1);
4277 gtk_text_buffer_insert_at_cursor(gtkconv
->entry_buffer
,
4280 g_free(matches
->data
);
4281 g_list_free(matches
);
4285 * there were lots of matches, fill in as much as possible
4286 * and display all of them
4288 char *addthis
= g_malloc0(1);
4291 char *tmp
= addthis
;
4292 addthis
= g_strconcat(tmp
, matches
->data
, " ", NULL
);
4294 g_free(matches
->data
);
4295 matches
= g_list_remove(matches
, matches
->data
);
4298 purple_conversation_write(conv
, NULL
, addthis
, PURPLE_MESSAGE_NO_LOG
,
4300 gtk_text_buffer_insert_at_cursor(gtkconv
->entry_buffer
, partial
, -1);
4310 static void topic_callback(GtkWidget
*w
, PidginConversation
*gtkconv
)
4312 PurplePluginProtocolInfo
*prpl_info
= NULL
;
4313 PurpleConnection
*gc
;
4314 PurpleConversation
*conv
= gtkconv
->active_conv
;
4315 PidginChatPane
*gtkchat
;
4317 const char *current_topic
;
4319 gc
= purple_conversation_get_gc(conv
);
4321 if(!gc
|| !(prpl_info
= PURPLE_PLUGIN_PROTOCOL_INFO(gc
->prpl
)))
4324 if(prpl_info
->set_chat_topic
== NULL
)
4327 gtkconv
= PIDGIN_CONVERSATION(conv
);
4328 gtkchat
= gtkconv
->u
.chat
;
4329 new_topic
= g_strdup(gtk_entry_get_text(GTK_ENTRY(gtkchat
->topic_text
)));
4330 current_topic
= purple_conv_chat_get_topic(PURPLE_CONV_CHAT(conv
));
4332 if(current_topic
&& !g_utf8_collate(new_topic
, current_topic
)){
4338 gtk_entry_set_text(GTK_ENTRY(gtkchat
->topic_text
), current_topic
);
4340 gtk_entry_set_text(GTK_ENTRY(gtkchat
->topic_text
), "");
4342 prpl_info
->set_chat_topic(gc
, purple_conv_chat_get_id(PURPLE_CONV_CHAT(conv
)),
4349 sort_chat_users(GtkTreeModel
*model
, GtkTreeIter
*a
, GtkTreeIter
*b
, gpointer userdata
)
4351 PurpleConvChatBuddyFlags f1
= 0, f2
= 0;
4352 char *user1
= NULL
, *user2
= NULL
;
4353 gboolean buddy1
= FALSE
, buddy2
= FALSE
;
4356 gtk_tree_model_get(model
, a
,
4357 CHAT_USERS_ALIAS_KEY_COLUMN
, &user1
,
4358 CHAT_USERS_FLAGS_COLUMN
, &f1
,
4359 CHAT_USERS_WEIGHT_COLUMN
, &buddy1
,
4361 gtk_tree_model_get(model
, b
,
4362 CHAT_USERS_ALIAS_KEY_COLUMN
, &user2
,
4363 CHAT_USERS_FLAGS_COLUMN
, &f2
,
4364 CHAT_USERS_WEIGHT_COLUMN
, &buddy2
,
4367 /* Only sort by membership levels */
4368 f1
&= PURPLE_CBFLAGS_VOICE
| PURPLE_CBFLAGS_HALFOP
| PURPLE_CBFLAGS_OP
|
4369 PURPLE_CBFLAGS_FOUNDER
;
4370 f2
&= PURPLE_CBFLAGS_VOICE
| PURPLE_CBFLAGS_HALFOP
| PURPLE_CBFLAGS_OP
|
4371 PURPLE_CBFLAGS_FOUNDER
;
4373 ret
= g_strcmp0(user1
, user2
);
4375 if (user1
!= NULL
&& user2
!= NULL
) {
4377 /* sort more important users first */
4378 ret
= (f1
> f2
) ? -1 : 1;
4379 } else if (buddy1
!= buddy2
) {
4380 ret
= (buddy1
> buddy2
) ? -1 : 1;
4391 update_chat_alias(PurpleBuddy
*buddy
, PurpleConversation
*conv
, PurpleConnection
*gc
, PurplePluginProtocolInfo
*prpl_info
)
4393 PidginConversation
*gtkconv
= PIDGIN_CONVERSATION(conv
);
4394 PurpleConvChat
*chat
= PURPLE_CONV_CHAT(conv
);
4395 GtkTreeModel
*model
;
4396 char *normalized_name
;
4400 g_return_if_fail(buddy
!= NULL
);
4401 g_return_if_fail(conv
!= NULL
);
4403 /* This is safe because this callback is only used in chats, not IMs. */
4404 model
= gtk_tree_view_get_model(GTK_TREE_VIEW(gtkconv
->u
.chat
->list
));
4406 if (!gtk_tree_model_get_iter_first(GTK_TREE_MODEL(model
), &iter
))
4409 normalized_name
= g_strdup(purple_normalize(conv
->account
, buddy
->name
));
4414 gtk_tree_model_get(model
, &iter
, CHAT_USERS_NAME_COLUMN
, &name
, -1);
4416 if (purple_strequal(normalized_name
, purple_normalize(conv
->account
, name
))) {
4417 const char *alias
= name
;
4419 char *alias_key
= NULL
;
4420 PurpleBuddy
*buddy2
;
4422 if (!purple_strequal(chat
->nick
, purple_normalize(conv
->account
, name
))) {
4423 /* This user is not me, so look into updating the alias. */
4425 if ((buddy2
= purple_find_buddy(conv
->account
, name
)) != NULL
) {
4426 alias
= purple_buddy_get_contact_alias(buddy2
);
4429 tmp
= g_utf8_casefold(alias
, -1);
4430 alias_key
= g_utf8_collate_key(tmp
, -1);
4433 gtk_list_store_set(GTK_LIST_STORE(model
), &iter
,
4434 CHAT_USERS_ALIAS_COLUMN
, alias
,
4435 CHAT_USERS_ALIAS_KEY_COLUMN
, alias_key
,
4443 f
= gtk_tree_model_iter_next(model
, &iter
);
4448 g_free(normalized_name
);
4452 blist_node_aliased_cb(PurpleBlistNode
*node
, const char *old_alias
, PurpleConversation
*conv
)
4454 PurpleConnection
*gc
;
4455 PurplePluginProtocolInfo
*prpl_info
;
4457 g_return_if_fail(node
!= NULL
);
4458 g_return_if_fail(conv
!= NULL
);
4460 gc
= purple_conversation_get_gc(conv
);
4461 g_return_if_fail(gc
!= NULL
);
4462 g_return_if_fail(gc
->prpl
!= NULL
);
4463 prpl_info
= PURPLE_PLUGIN_PROTOCOL_INFO(gc
->prpl
);
4465 if (prpl_info
->options
& OPT_PROTO_UNIQUE_CHATNAME
)
4468 if (PURPLE_BLIST_NODE_IS_CONTACT(node
))
4470 PurpleBlistNode
*bnode
;
4472 for(bnode
= node
->child
; bnode
; bnode
= bnode
->next
) {
4474 if(!PURPLE_BLIST_NODE_IS_BUDDY(bnode
))
4477 update_chat_alias((PurpleBuddy
*)bnode
, conv
, gc
, prpl_info
);
4480 else if (PURPLE_BLIST_NODE_IS_BUDDY(node
))
4481 update_chat_alias((PurpleBuddy
*)node
, conv
, gc
, prpl_info
);
4482 else if (PURPLE_BLIST_NODE_IS_CHAT(node
) &&
4483 purple_conversation_get_account(conv
) == ((PurpleChat
*)node
)->account
)
4485 if (old_alias
== NULL
|| g_utf8_collate(old_alias
, purple_conversation_get_title(conv
)) == 0)
4486 pidgin_conv_update_fields(conv
, PIDGIN_CONV_SET_TITLE
);
4491 buddy_cb_common(PurpleBuddy
*buddy
, PurpleConversation
*conv
, gboolean is_buddy
)
4493 GtkTreeModel
*model
;
4494 char *normalized_name
;
4496 GtkTextTag
*texttag
;
4499 g_return_if_fail(buddy
!= NULL
);
4500 g_return_if_fail(conv
!= NULL
);
4502 /* Do nothing if the buddy does not belong to the conv's account */
4503 if (purple_buddy_get_account(buddy
) != purple_conversation_get_account(conv
))
4506 /* This is safe because this callback is only used in chats, not IMs. */
4507 model
= gtk_tree_view_get_model(GTK_TREE_VIEW(PIDGIN_CONVERSATION(conv
)->u
.chat
->list
));
4509 if (!gtk_tree_model_get_iter_first(GTK_TREE_MODEL(model
), &iter
))
4512 normalized_name
= g_strdup(purple_normalize(conv
->account
, buddy
->name
));
4517 gtk_tree_model_get(model
, &iter
, CHAT_USERS_NAME_COLUMN
, &name
, -1);
4519 if (purple_strequal(normalized_name
, purple_normalize(conv
->account
, name
))) {
4520 gtk_list_store_set(GTK_LIST_STORE(model
), &iter
,
4521 CHAT_USERS_WEIGHT_COLUMN
, is_buddy
? PANGO_WEIGHT_BOLD
: PANGO_WEIGHT_NORMAL
, -1);
4526 f
= gtk_tree_model_iter_next(model
, &iter
);
4531 g_free(normalized_name
);
4533 blist_node_aliased_cb((PurpleBlistNode
*)buddy
, NULL
, conv
);
4535 texttag
= get_buddy_tag(conv
, purple_buddy_get_name(buddy
), 0, FALSE
); /* XXX: do we want the normalized name? */
4537 g_object_set(texttag
, "weight", is_buddy
? PANGO_WEIGHT_BOLD
: PANGO_WEIGHT_NORMAL
, NULL
);
4542 buddy_added_cb(PurpleBlistNode
*node
, PurpleConversation
*conv
)
4544 if (!PURPLE_BLIST_NODE_IS_BUDDY(node
))
4547 buddy_cb_common(PURPLE_BUDDY(node
), conv
, TRUE
);
4551 buddy_removed_cb(PurpleBlistNode
*node
, PurpleConversation
*conv
)
4553 if (!PURPLE_BLIST_NODE_IS_BUDDY(node
))
4556 /* If there's another buddy for the same "dude" on the list, do nothing. */
4557 if (purple_find_buddy(purple_buddy_get_account(PURPLE_BUDDY(node
)),
4558 purple_buddy_get_name(PURPLE_BUDDY(node
))) != NULL
)
4561 buddy_cb_common(PURPLE_BUDDY(node
), conv
, FALSE
);
4564 static void send_menu_cb(GtkWidget
*widget
, PidginConversation
*gtkconv
)
4566 g_signal_emit_by_name(gtkconv
->entry
, "message_send");
4570 entry_popup_menu_cb(GtkIMHtml
*imhtml
, GtkMenu
*menu
, gpointer data
)
4572 GtkWidget
*menuitem
;
4573 PidginConversation
*gtkconv
= data
;
4575 g_return_if_fail(menu
!= NULL
);
4576 g_return_if_fail(gtkconv
!= NULL
);
4578 menuitem
= pidgin_new_item_from_stock(NULL
, _("_Send"), NULL
,
4579 G_CALLBACK(send_menu_cb
), gtkconv
,
4581 if (gtk_text_buffer_get_char_count(imhtml
->text_buffer
) == 0)
4582 gtk_widget_set_sensitive(menuitem
, FALSE
);
4583 gtk_menu_shell_insert(GTK_MENU_SHELL(menu
), menuitem
, 0);
4585 menuitem
= gtk_separator_menu_item_new();
4586 gtk_widget_show(menuitem
);
4587 gtk_menu_shell_insert(GTK_MENU_SHELL(menu
), menuitem
, 1);
4590 static gboolean
resize_imhtml_cb(PidginConversation
*gtkconv
)
4592 GtkTextBuffer
*buffer
;
4595 GdkRectangle oneline
;
4597 int pad_top
, pad_inside
, pad_bottom
;
4598 int total_height
= (gtkconv
->imhtml
->allocation
.height
+ gtkconv
->entry
->allocation
.height
);
4599 int max_height
= total_height
/ 2;
4600 int min_lines
= purple_prefs_get_int(PIDGIN_PREFS_ROOT
"/conversations/minimum_entry_lines");
4602 gboolean interior_focus
;
4605 pad_top
= gtk_text_view_get_pixels_above_lines(GTK_TEXT_VIEW(gtkconv
->entry
));
4606 pad_bottom
= gtk_text_view_get_pixels_below_lines(GTK_TEXT_VIEW(gtkconv
->entry
));
4607 pad_inside
= gtk_text_view_get_pixels_inside_wrap(GTK_TEXT_VIEW(gtkconv
->entry
));
4609 buffer
= gtk_text_view_get_buffer(GTK_TEXT_VIEW(gtkconv
->entry
));
4610 gtk_text_buffer_get_start_iter(buffer
, &iter
);
4611 gtk_text_view_get_iter_location(GTK_TEXT_VIEW(gtkconv
->entry
), &iter
, &oneline
);
4613 lines
= gtk_text_buffer_get_line_count(buffer
);
4618 gtk_text_view_get_line_yrange(GTK_TEXT_VIEW(gtkconv
->entry
), &iter
, NULL
, &lineheight
);
4619 height
+= lineheight
;
4621 } while (gtk_text_iter_forward_line(&iter
));
4622 height
+= lines
* (oneline
.height
+ pad_top
+ pad_bottom
);
4624 /* Make sure there's enough room for at least min_lines. Allocate enough space to
4625 * prevent scrolling when the second line is a continuation of the first line, or
4626 * is the beginning of a new paragraph. */
4627 min_height
= min_lines
* (oneline
.height
+ MAX(pad_inside
, pad_top
+ pad_bottom
));
4628 height
= CLAMP(height
, MIN(min_height
, max_height
), max_height
);
4630 gtk_widget_style_get(gtkconv
->entry
,
4631 "interior-focus", &interior_focus
,
4632 "focus-line-width", &focus_width
,
4634 if (!interior_focus
)
4635 height
+= 2 * focus_width
;
4637 diff
= height
- gtkconv
->entry
->allocation
.height
;
4638 if (ABS(diff
) < oneline
.height
/ 2)
4641 gtk_widget_set_size_request(gtkconv
->lower_hbox
, -1,
4642 diff
+ gtkconv
->lower_hbox
->allocation
.height
);
4648 minimum_entry_lines_pref_cb(const char *name
,
4649 PurplePrefType type
,
4650 gconstpointer value
,
4653 GList
*l
= purple_get_conversations();
4654 PurpleConversation
*conv
;
4657 conv
= (PurpleConversation
*)l
->data
;
4659 if (PIDGIN_IS_PIDGIN_CONVERSATION(conv
))
4660 resize_imhtml_cb(PIDGIN_CONVERSATION(conv
));
4667 setup_chat_topic(PidginConversation
*gtkconv
, GtkWidget
*vbox
)
4669 PurpleConversation
*conv
= gtkconv
->active_conv
;
4670 PurpleConnection
*gc
= purple_conversation_get_gc(conv
);
4671 PurplePluginProtocolInfo
*prpl_info
= PURPLE_PLUGIN_PROTOCOL_INFO(gc
->prpl
);
4672 if (prpl_info
->options
& OPT_PROTO_CHAT_TOPIC
)
4674 GtkWidget
*hbox
, *label
;
4675 PidginChatPane
*gtkchat
= gtkconv
->u
.chat
;
4677 hbox
= gtk_hbox_new(FALSE
, PIDGIN_HIG_BOX_SPACE
);
4678 gtk_box_pack_start(GTK_BOX(vbox
), hbox
, FALSE
, FALSE
, 0);
4680 label
= gtk_label_new(_("Topic:"));
4681 gtk_box_pack_start(GTK_BOX(hbox
), label
, FALSE
, FALSE
, 0);
4683 gtkchat
->topic_text
= gtk_entry_new();
4684 gtk_widget_set_size_request(gtkchat
->topic_text
, -1, BUDDYICON_SIZE_MIN
);
4686 if(prpl_info
->set_chat_topic
== NULL
) {
4687 gtk_editable_set_editable(GTK_EDITABLE(gtkchat
->topic_text
), FALSE
);
4689 g_signal_connect(GTK_OBJECT(gtkchat
->topic_text
), "activate",
4690 G_CALLBACK(topic_callback
), gtkconv
);
4693 gtk_box_pack_start(GTK_BOX(hbox
), gtkchat
->topic_text
, TRUE
, TRUE
, 0);
4694 g_signal_connect(G_OBJECT(gtkchat
->topic_text
), "key_press_event",
4695 G_CALLBACK(entry_key_press_cb
), gtkconv
);
4700 pidgin_conv_userlist_create_tooltip(GtkWidget
*tipwindow
, GtkTreePath
*path
,
4701 gpointer userdata
, int *w
, int *h
)
4703 PidginConversation
*gtkconv
= userdata
;
4705 GtkTreeModel
*model
= gtk_tree_view_get_model(GTK_TREE_VIEW(gtkconv
->u
.chat
->list
));
4706 PurpleConversation
*conv
= gtkconv
->active_conv
;
4707 PurpleBlistNode
*node
;
4708 PurplePluginProtocolInfo
*prpl_info
;
4709 PurpleAccount
*account
= purple_conversation_get_account(conv
);
4712 if (account
->gc
== NULL
)
4715 if (!gtk_tree_model_get_iter(GTK_TREE_MODEL(model
), &iter
, path
))
4718 gtk_tree_model_get(GTK_TREE_MODEL(model
), &iter
, CHAT_USERS_NAME_COLUMN
, &who
, -1);
4720 prpl_info
= PURPLE_PLUGIN_PROTOCOL_INFO(account
->gc
->prpl
);
4721 node
= (PurpleBlistNode
*)(purple_find_buddy(conv
->account
, who
));
4722 if (node
&& prpl_info
&& (prpl_info
->options
& OPT_PROTO_UNIQUE_CHATNAME
))
4723 pidgin_blist_draw_tooltip(node
, gtkconv
->infopane
);
4730 setup_chat_userlist(PidginConversation
*gtkconv
, GtkWidget
*hpaned
)
4732 PidginChatPane
*gtkchat
= gtkconv
->u
.chat
;
4733 GtkWidget
*lbox
, *list
;
4735 GtkCellRenderer
*rend
;
4736 GtkTreeViewColumn
*col
;
4738 void *blist_handle
= purple_blist_get_handle();
4739 PurpleConversation
*conv
= gtkconv
->active_conv
;
4741 /* Build the right pane. */
4742 lbox
= gtk_vbox_new(FALSE
, PIDGIN_HIG_BOX_SPACE
);
4743 gtk_paned_pack2(GTK_PANED(hpaned
), lbox
, FALSE
, TRUE
);
4744 gtk_widget_show(lbox
);
4746 /* Setup the label telling how many people are in the room. */
4747 gtkchat
->count
= gtk_label_new(_("0 people in room"));
4748 gtk_label_set_ellipsize(GTK_LABEL(gtkchat
->count
), PANGO_ELLIPSIZE_END
);
4749 gtk_box_pack_start(GTK_BOX(lbox
), gtkchat
->count
, FALSE
, FALSE
, 0);
4750 gtk_widget_show(gtkchat
->count
);
4752 /* Setup the list of users. */
4754 ls
= gtk_list_store_new(CHAT_USERS_COLUMNS
, GDK_TYPE_PIXBUF
, G_TYPE_STRING
,
4755 G_TYPE_STRING
, G_TYPE_STRING
, G_TYPE_INT
,
4756 GDK_TYPE_COLOR
, G_TYPE_INT
, G_TYPE_STRING
);
4757 gtk_tree_sortable_set_sort_func(GTK_TREE_SORTABLE(ls
), CHAT_USERS_ALIAS_KEY_COLUMN
,
4758 sort_chat_users
, NULL
, NULL
);
4760 list
= gtk_tree_view_new_with_model(GTK_TREE_MODEL(ls
));
4762 /* Allow a user to specify gtkrc settings for the chat userlist only */
4763 gtk_widget_set_name(list
, "pidgin_conv_userlist");
4765 rend
= gtk_cell_renderer_pixbuf_new();
4766 g_object_set(G_OBJECT(rend
),
4767 "stock-size", gtk_icon_size_from_name(PIDGIN_ICON_SIZE_TANGO_EXTRA_SMALL
),
4769 col
= gtk_tree_view_column_new_with_attributes(NULL
, rend
,
4770 "stock-id", CHAT_USERS_ICON_STOCK_COLUMN
, NULL
);
4771 gtk_tree_view_column_set_sizing(col
, GTK_TREE_VIEW_COLUMN_AUTOSIZE
);
4772 gtk_tree_view_append_column(GTK_TREE_VIEW(list
), col
);
4773 ul_width
= purple_prefs_get_int(PIDGIN_PREFS_ROOT
"/conversations/chat/userlist_width");
4774 gtk_widget_set_size_request(lbox
, ul_width
, -1);
4776 /* Hack to prevent completely collapsed userlist coming back with a 1 pixel width.
4777 * I would have liked to use the GtkPaned "max-position", but for some reason that didn't work */
4779 gtk_paned_set_position(GTK_PANED(hpaned
), 999999);
4781 g_signal_connect(G_OBJECT(list
), "button_press_event",
4782 G_CALLBACK(right_click_chat_cb
), gtkconv
);
4783 g_signal_connect(G_OBJECT(list
), "row-activated",
4784 G_CALLBACK(activate_list_cb
), gtkconv
);
4785 g_signal_connect(G_OBJECT(list
), "popup-menu",
4786 G_CALLBACK(gtkconv_chat_popup_menu_cb
), gtkconv
);
4787 g_signal_connect(G_OBJECT(lbox
), "size-allocate", G_CALLBACK(lbox_size_allocate_cb
), gtkconv
);
4789 pidgin_tooltip_setup_for_treeview(list
, gtkconv
,
4790 pidgin_conv_userlist_create_tooltip
, NULL
);
4792 rend
= gtk_cell_renderer_text_new();
4794 "foreground-set", TRUE
,
4797 g_object_set(G_OBJECT(rend
), "editable", TRUE
, NULL
);
4799 col
= gtk_tree_view_column_new_with_attributes(NULL
, rend
,
4800 "text", CHAT_USERS_ALIAS_COLUMN
,
4801 "foreground-gdk", CHAT_USERS_COLOR_COLUMN
,
4802 "weight", CHAT_USERS_WEIGHT_COLUMN
,
4805 purple_signal_connect(blist_handle
, "blist-node-added",
4806 gtkchat
, PURPLE_CALLBACK(buddy_added_cb
), conv
);
4807 purple_signal_connect(blist_handle
, "blist-node-removed",
4808 gtkchat
, PURPLE_CALLBACK(buddy_removed_cb
), conv
);
4809 purple_signal_connect(blist_handle
, "blist-node-aliased",
4810 gtkchat
, PURPLE_CALLBACK(blist_node_aliased_cb
), conv
);
4812 gtk_tree_view_column_set_expand(col
, TRUE
);
4813 g_object_set(rend
, "ellipsize", PANGO_ELLIPSIZE_END
, NULL
);
4815 gtk_tree_view_append_column(GTK_TREE_VIEW(list
), col
);
4817 gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(list
), FALSE
);
4818 gtk_widget_show(list
);
4820 gtkchat
->list
= list
;
4822 gtk_box_pack_start(GTK_BOX(lbox
),
4823 pidgin_make_scrollable(list
, GTK_POLICY_AUTOMATIC
, GTK_POLICY_AUTOMATIC
, GTK_SHADOW_IN
, -1, -1),
4828 pidgin_conv_create_tooltip(GtkWidget
*tipwindow
, gpointer userdata
, int *w
, int *h
)
4830 PurpleBlistNode
*node
= NULL
;
4831 PurpleConversation
*conv
;
4832 PidginConversation
*gtkconv
= userdata
;
4834 conv
= gtkconv
->active_conv
;
4835 if (purple_conversation_get_type(conv
) == PURPLE_CONV_TYPE_CHAT
) {
4836 node
= (PurpleBlistNode
*)(purple_blist_find_chat(conv
->account
, conv
->name
));
4838 node
= g_object_get_data(G_OBJECT(gtkconv
->imhtml
), "transient_chat");
4840 node
= (PurpleBlistNode
*)(purple_find_buddy(conv
->account
, conv
->name
));
4842 /* Using the transient blist nodes to show the tooltip doesn't quite work yet. */
4844 node
= g_object_get_data(G_OBJECT(gtkconv
->imhtml
), "transient_buddy");
4849 pidgin_blist_draw_tooltip(node
, gtkconv
->infopane
);
4853 /* Quick Find {{{ */
4855 pidgin_conv_end_quickfind(PidginConversation
*gtkconv
)
4857 gtk_widget_modify_base(gtkconv
->quickfind
.entry
, GTK_STATE_NORMAL
, NULL
);
4859 gtk_imhtml_search_clear(GTK_IMHTML(gtkconv
->imhtml
));
4860 gtk_widget_hide_all(gtkconv
->quickfind
.container
);
4862 gtk_widget_grab_focus(gtkconv
->entry
);
4867 quickfind_process_input(GtkWidget
*entry
, GdkEventKey
*event
, PidginConversation
*gtkconv
)
4869 switch (event
->keyval
) {
4872 if (gtk_imhtml_search_find(GTK_IMHTML(gtkconv
->imhtml
), gtk_entry_get_text(GTK_ENTRY(entry
)))) {
4873 gtk_widget_modify_base(gtkconv
->quickfind
.entry
, GTK_STATE_NORMAL
, NULL
);
4879 gtk_widget_modify_base(gtkconv
->quickfind
.entry
, GTK_STATE_NORMAL
, &col
);
4883 pidgin_conv_end_quickfind(gtkconv
);
4892 pidgin_conv_setup_quickfind(PidginConversation
*gtkconv
, GtkWidget
*container
)
4894 GtkWidget
*widget
= gtk_hbox_new(FALSE
, 0);
4895 GtkWidget
*label
, *entry
, *close
;
4897 gtk_box_pack_start(GTK_BOX(container
), widget
, FALSE
, FALSE
, 0);
4899 close
= pidgin_create_small_button(gtk_label_new("×"));
4900 gtk_box_pack_start(GTK_BOX(widget
), close
, FALSE
, FALSE
, 0);
4901 gtk_tooltips_set_tip(gtkconv
->tooltips
, close
,
4902 _("Close Find bar"), NULL
);
4904 label
= gtk_label_new(_("Find:"));
4905 gtk_box_pack_start(GTK_BOX(widget
), label
, FALSE
, FALSE
, 10);
4907 entry
= gtk_entry_new();
4908 gtk_box_pack_start(GTK_BOX(widget
), entry
, TRUE
, TRUE
, 0);
4910 gtkconv
->quickfind
.entry
= entry
;
4911 gtkconv
->quickfind
.container
= widget
;
4913 /* Hook to signals and stuff */
4914 g_signal_connect(G_OBJECT(entry
), "key_press_event",
4915 G_CALLBACK(quickfind_process_input
), gtkconv
);
4916 g_signal_connect_swapped(G_OBJECT(close
), "button-press-event",
4917 G_CALLBACK(pidgin_conv_end_quickfind
), gtkconv
);
4923 setup_common_pane(PidginConversation
*gtkconv
)
4925 GtkWidget
*vbox
, *frame
, *imhtml_sw
, *event_box
;
4926 GtkCellRenderer
*rend
;
4928 PurpleConversation
*conv
= gtkconv
->active_conv
;
4930 gboolean chat
= (conv
->type
== PURPLE_CONV_TYPE_CHAT
);
4931 int buddyicon_size
= 0;
4933 /* Setup the top part of the pane */
4934 vbox
= gtk_vbox_new(FALSE
, PIDGIN_HIG_BOX_SPACE
);
4935 gtk_widget_show(vbox
);
4937 /* Setup the info pane */
4938 event_box
= gtk_event_box_new();
4939 gtk_event_box_set_visible_window(GTK_EVENT_BOX(event_box
), FALSE
);
4940 gtk_widget_show(event_box
);
4941 gtkconv
->infopane_hbox
= gtk_hbox_new(FALSE
, 0);
4942 gtk_box_pack_start(GTK_BOX(vbox
), event_box
, FALSE
, FALSE
, 0);
4943 gtk_container_add(GTK_CONTAINER(event_box
), gtkconv
->infopane_hbox
);
4944 gtk_widget_show(gtkconv
->infopane_hbox
);
4945 gtk_widget_add_events(event_box
,
4946 GDK_POINTER_MOTION_MASK
| GDK_LEAVE_NOTIFY_MASK
);
4947 g_signal_connect(G_OBJECT(event_box
), "button-press-event",
4948 G_CALLBACK(infopane_press_cb
), gtkconv
);
4950 pidgin_tooltip_setup_for_widget(event_box
, gtkconv
,
4951 pidgin_conv_create_tooltip
, NULL
);
4953 gtkconv
->infopane
= gtk_cell_view_new();
4954 gtkconv
->infopane_model
= gtk_list_store_new(CONV_NUM_COLUMNS
, G_TYPE_STRING
, G_TYPE_STRING
, GDK_TYPE_PIXBUF
, GDK_TYPE_PIXBUF
);
4955 gtk_cell_view_set_model(GTK_CELL_VIEW(gtkconv
->infopane
),
4956 GTK_TREE_MODEL(gtkconv
->infopane_model
));
4957 g_object_unref(gtkconv
->infopane_model
);
4958 gtk_list_store_append(gtkconv
->infopane_model
, &(gtkconv
->infopane_iter
));
4959 gtk_box_pack_start(GTK_BOX(gtkconv
->infopane_hbox
), gtkconv
->infopane
, TRUE
, TRUE
, 0);
4960 path
= gtk_tree_path_new_from_string("0");
4961 gtk_cell_view_set_displayed_row(GTK_CELL_VIEW(gtkconv
->infopane
), path
);
4962 gtk_tree_path_free(path
);
4965 /* This empty widget is used to ensure that the infopane is consistently
4966 sized for chat windows. The correct fix is to put an icon in the chat
4967 window as well, because that would make "Set Custom Icon" consistent
4968 for both the buddy list and the chat window, but PidginConversation
4969 is pretty much stuck until 3.0. */
4970 GtkWidget
*sizing_vbox
;
4971 sizing_vbox
= gtk_vbox_new(FALSE
, 0);
4972 gtk_widget_set_size_request(sizing_vbox
, -1, BUDDYICON_SIZE_MIN
);
4973 gtk_box_pack_start(GTK_BOX(gtkconv
->infopane_hbox
), sizing_vbox
, FALSE
, FALSE
, 0);
4974 gtk_widget_show(sizing_vbox
);
4977 gtkconv
->u
.im
->icon_container
= gtk_vbox_new(FALSE
, 0);
4979 if ((buddy
= purple_find_buddy(purple_conversation_get_account(conv
),
4980 purple_conversation_get_name(conv
))) != NULL
) {
4981 PurpleContact
*contact
= purple_buddy_get_contact(buddy
);
4983 buddyicon_size
= purple_blist_node_get_int((PurpleBlistNode
*)contact
, "pidgin-infopane-iconsize");
4986 buddyicon_size
= CLAMP(buddyicon_size
, BUDDYICON_SIZE_MIN
, BUDDYICON_SIZE_MAX
);
4987 gtk_widget_set_size_request(gtkconv
->u
.im
->icon_container
, -1, buddyicon_size
);
4989 gtk_box_pack_start(GTK_BOX(gtkconv
->infopane_hbox
),
4990 gtkconv
->u
.im
->icon_container
, FALSE
, FALSE
, 0);
4992 gtk_widget_show(gtkconv
->u
.im
->icon_container
);
4995 gtk_widget_show(gtkconv
->infopane
);
4997 rend
= gtk_cell_renderer_pixbuf_new();
4998 gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(gtkconv
->infopane
), rend
, FALSE
);
4999 gtk_cell_layout_set_attributes(GTK_CELL_LAYOUT(gtkconv
->infopane
), rend
, "stock-id", CONV_ICON_COLUMN
, NULL
);
5000 g_object_set(rend
, "xalign", 0.0, "xpad", 6, "ypad", 0,
5001 "stock-size", gtk_icon_size_from_name(PIDGIN_ICON_SIZE_TANGO_EXTRA_SMALL
),
5004 rend
= gtk_cell_renderer_text_new();
5005 gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(gtkconv
->infopane
), rend
, TRUE
);
5006 gtk_cell_layout_set_attributes(GTK_CELL_LAYOUT(gtkconv
->infopane
), rend
, "markup", CONV_TEXT_COLUMN
, NULL
);
5007 g_object_set(rend
, "ypad", 0, "yalign", 0.5, NULL
);
5009 g_object_set(rend
, "ellipsize", PANGO_ELLIPSIZE_END
, NULL
);
5011 rend
= gtk_cell_renderer_pixbuf_new();
5012 gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(gtkconv
->infopane
), rend
, FALSE
);
5013 gtk_cell_layout_set_attributes(GTK_CELL_LAYOUT(gtkconv
->infopane
), rend
, "pixbuf", CONV_PROTOCOL_ICON_COLUMN
, NULL
);
5014 g_object_set(rend
, "xalign", 0.0, "xpad", 3, "ypad", 0, 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_EMBLEM_COLUMN
, NULL
);
5019 g_object_set(rend
, "xalign", 0.0, "xpad", 6, "ypad", 0, NULL
);
5021 /* Setup the gtkimhtml widget */
5022 frame
= pidgin_create_imhtml(FALSE
, >kconv
->imhtml
, NULL
, &imhtml_sw
);
5023 gtk_widget_set_size_request(gtkconv
->imhtml
, -1, 0);
5028 setup_chat_topic(gtkconv
, vbox
);
5030 /* Add the gtkimhtml frame */
5031 hpaned
= gtk_hpaned_new();
5032 gtk_box_pack_start(GTK_BOX(vbox
), hpaned
, TRUE
, TRUE
, 0);
5033 gtk_widget_show(hpaned
);
5034 gtk_paned_pack1(GTK_PANED(hpaned
), frame
, TRUE
, TRUE
);
5036 /* Now add the userlist */
5037 setup_chat_userlist(gtkconv
, hpaned
);
5039 gtk_box_pack_start(GTK_BOX(vbox
), frame
, TRUE
, TRUE
, 0);
5041 gtk_widget_show(frame
);
5043 gtk_widget_set_name(gtkconv
->imhtml
, "pidgin_conv_imhtml");
5044 gtk_imhtml_show_comments(GTK_IMHTML(gtkconv
->imhtml
),TRUE
);
5045 g_object_set_data(G_OBJECT(gtkconv
->imhtml
), "gtkconv", gtkconv
);
5047 g_object_set(G_OBJECT(imhtml_sw
), "vscrollbar-policy", GTK_POLICY_ALWAYS
, NULL
);
5049 g_signal_connect_after(G_OBJECT(gtkconv
->imhtml
), "button_press_event",
5050 G_CALLBACK(entry_stop_rclick_cb
), NULL
);
5051 g_signal_connect(G_OBJECT(gtkconv
->imhtml
), "key_press_event",
5052 G_CALLBACK(refocus_entry_cb
), gtkconv
);
5053 g_signal_connect(G_OBJECT(gtkconv
->imhtml
), "key_release_event",
5054 G_CALLBACK(refocus_entry_cb
), gtkconv
);
5056 pidgin_conv_setup_quickfind(gtkconv
, vbox
);
5058 gtkconv
->lower_hbox
= gtk_hbox_new(FALSE
, PIDGIN_HIG_BOX_SPACE
);
5059 gtk_box_pack_start(GTK_BOX(vbox
), gtkconv
->lower_hbox
, FALSE
, FALSE
, 0);
5060 gtk_widget_show(gtkconv
->lower_hbox
);
5062 /* Setup the toolbar, entry widget and all signals */
5063 frame
= pidgin_create_imhtml(TRUE
, >kconv
->entry
, >kconv
->toolbar
, NULL
);
5064 gtk_box_pack_start(GTK_BOX(gtkconv
->lower_hbox
), frame
, TRUE
, TRUE
, 0);
5065 gtk_widget_show(frame
);
5067 gtk_widget_set_name(gtkconv
->entry
, "pidgin_conv_entry");
5068 gtk_imhtml_set_protocol_name(GTK_IMHTML(gtkconv
->entry
),
5069 purple_account_get_protocol_name(conv
->account
));
5071 g_signal_connect(G_OBJECT(gtkconv
->entry
), "populate-popup",
5072 G_CALLBACK(entry_popup_menu_cb
), gtkconv
);
5073 g_signal_connect(G_OBJECT(gtkconv
->entry
), "key_press_event",
5074 G_CALLBACK(entry_key_press_cb
), gtkconv
);
5075 g_signal_connect_after(G_OBJECT(gtkconv
->entry
), "message_send",
5076 G_CALLBACK(send_cb
), gtkconv
);
5077 g_signal_connect_after(G_OBJECT(gtkconv
->entry
), "button_press_event",
5078 G_CALLBACK(entry_stop_rclick_cb
), NULL
);
5080 gtkconv
->entry_buffer
=
5081 gtk_text_view_get_buffer(GTK_TEXT_VIEW(gtkconv
->entry
));
5082 g_object_set_data(G_OBJECT(gtkconv
->entry_buffer
), "user_data", gtkconv
);
5085 /* For sending typing notifications for IMs */
5086 g_signal_connect(G_OBJECT(gtkconv
->entry_buffer
), "insert_text",
5087 G_CALLBACK(insert_text_cb
), gtkconv
);
5088 g_signal_connect(G_OBJECT(gtkconv
->entry_buffer
), "delete_range",
5089 G_CALLBACK(delete_text_cb
), gtkconv
);
5090 gtkconv
->u
.im
->typing_timer
= 0;
5091 gtkconv
->u
.im
->animate
= purple_prefs_get_bool(PIDGIN_PREFS_ROOT
"/conversations/im/animate_buddy_icons");
5092 gtkconv
->u
.im
->show_icon
= TRUE
;
5095 g_signal_connect_swapped(G_OBJECT(gtkconv
->entry_buffer
), "changed",
5096 G_CALLBACK(resize_imhtml_cb
), gtkconv
);
5097 g_signal_connect_swapped(G_OBJECT(gtkconv
->entry
), "size-allocate",
5098 G_CALLBACK(resize_imhtml_cb
), gtkconv
);
5100 default_formatize(gtkconv
);
5101 g_signal_connect_after(G_OBJECT(gtkconv
->entry
), "format_function_clear",
5102 G_CALLBACK(clear_formatting_cb
), gtkconv
);
5107 conv_dnd_recv(GtkWidget
*widget
, GdkDragContext
*dc
, guint x
, guint y
,
5108 GtkSelectionData
*sd
, guint info
, guint t
,
5109 PidginConversation
*gtkconv
)
5111 PurpleConversation
*conv
= gtkconv
->active_conv
;
5112 PidginWindow
*win
= gtkconv
->win
;
5113 PurpleConversation
*c
;
5114 PurpleAccount
*convaccount
= purple_conversation_get_account(conv
);
5115 PurpleConnection
*gc
= purple_account_get_connection(convaccount
);
5116 PurplePluginProtocolInfo
*prpl_info
= gc
? PURPLE_PLUGIN_PROTOCOL_INFO(gc
->prpl
) : NULL
;
5118 if (sd
->target
== gdk_atom_intern("PURPLE_BLIST_NODE", FALSE
))
5120 PurpleBlistNode
*n
= NULL
;
5122 PidginConversation
*gtkconv
= NULL
;
5123 PurpleAccount
*buddyaccount
;
5124 const char *buddyname
;
5126 n
= *(PurpleBlistNode
**)sd
->data
;
5128 if (PURPLE_BLIST_NODE_IS_CONTACT(n
))
5129 b
= purple_contact_get_priority_buddy((PurpleContact
*)n
);
5130 else if (PURPLE_BLIST_NODE_IS_BUDDY(n
))
5131 b
= (PurpleBuddy
*)n
;
5135 buddyaccount
= purple_buddy_get_account(b
);
5136 buddyname
= purple_buddy_get_name(b
);
5138 * If a buddy is dragged to a chat window of the same protocol,
5139 * invite him to the chat.
5141 if (purple_conversation_get_type(conv
) == PURPLE_CONV_TYPE_CHAT
&&
5142 prpl_info
&& PURPLE_PROTOCOL_PLUGIN_HAS_FUNC(prpl_info
, chat_invite
) &&
5143 purple_strequal(purple_account_get_protocol_id(convaccount
),
5144 purple_account_get_protocol_id(buddyaccount
))) {
5145 purple_conv_chat_invite_user(PURPLE_CONV_CHAT(conv
), buddyname
, NULL
, TRUE
);
5148 * If we already have an open conversation with this buddy, then
5149 * just move the conv to this window. Otherwise, create a new
5150 * conv and add it to this window.
5152 c
= purple_find_conversation_with_account(PURPLE_CONV_TYPE_IM
, buddyname
, buddyaccount
);
5154 PidginWindow
*oldwin
;
5155 gtkconv
= PIDGIN_CONVERSATION(c
);
5156 oldwin
= gtkconv
->win
;
5157 if (oldwin
!= win
) {
5158 pidgin_conv_window_remove_gtkconv(oldwin
, gtkconv
);
5159 pidgin_conv_window_add_gtkconv(win
, gtkconv
);
5162 c
= purple_conversation_new(PURPLE_CONV_TYPE_IM
, buddyaccount
, buddyname
);
5163 gtkconv
= PIDGIN_CONVERSATION(c
);
5164 if (gtkconv
->win
!= win
) {
5165 pidgin_conv_window_remove_gtkconv(gtkconv
->win
, gtkconv
);
5166 pidgin_conv_window_add_gtkconv(win
, gtkconv
);
5170 /* Make this conversation the active conversation */
5171 pidgin_conv_window_switch_gtkconv(win
, gtkconv
);
5174 gtk_drag_finish(dc
, TRUE
, (dc
->action
== GDK_ACTION_MOVE
), t
);
5176 else if (sd
->target
== gdk_atom_intern("application/x-im-contact", FALSE
))
5178 char *protocol
= NULL
;
5179 char *username
= NULL
;
5180 PurpleAccount
*account
;
5181 PidginConversation
*gtkconv
;
5183 if (pidgin_parse_x_im_contact((const char *)sd
->data
, FALSE
, &account
,
5184 &protocol
, &username
, NULL
))
5186 if (account
== NULL
)
5188 purple_notify_error(win
, NULL
,
5189 _("You are not currently signed on with an account that "
5190 "can add that buddy."), NULL
);
5193 * If a buddy is dragged to a chat window of the same protocol,
5194 * invite him to the chat.
5196 if (purple_conversation_get_type(conv
) == PURPLE_CONV_TYPE_CHAT
&&
5197 prpl_info
&& PURPLE_PROTOCOL_PLUGIN_HAS_FUNC(prpl_info
, chat_invite
) &&
5198 purple_strequal(purple_account_get_protocol_id(convaccount
), protocol
)) {
5199 purple_conv_chat_invite_user(PURPLE_CONV_CHAT(conv
), username
, NULL
, TRUE
);
5201 c
= purple_conversation_new(PURPLE_CONV_TYPE_IM
, account
, username
);
5202 gtkconv
= PIDGIN_CONVERSATION(c
);
5203 if (gtkconv
->win
!= win
) {
5204 pidgin_conv_window_remove_gtkconv(gtkconv
->win
, gtkconv
);
5205 pidgin_conv_window_add_gtkconv(win
, gtkconv
);
5214 gtk_drag_finish(dc
, TRUE
, (dc
->action
== GDK_ACTION_MOVE
), t
);
5216 else if (sd
->target
== gdk_atom_intern("text/uri-list", FALSE
)) {
5217 if (purple_conversation_get_type(conv
) == PURPLE_CONV_TYPE_IM
)
5218 pidgin_dnd_file_manage(sd
, convaccount
, purple_conversation_get_name(conv
));
5219 gtk_drag_finish(dc
, TRUE
, (dc
->action
== GDK_ACTION_MOVE
), t
);
5222 gtk_drag_finish(dc
, FALSE
, FALSE
, t
);
5226 static const GtkTargetEntry te
[] =
5228 GTK_IMHTML_DND_TARGETS
,
5229 {"PURPLE_BLIST_NODE", GTK_TARGET_SAME_APP
, GTK_IMHTML_DRAG_NUM
},
5230 {"application/x-im-contact", 0, GTK_IMHTML_DRAG_NUM
+ 1}
5233 static PidginConversation
*
5234 pidgin_conv_find_gtkconv(PurpleConversation
* conv
)
5236 PurpleBuddy
*bud
= purple_find_buddy(conv
->account
, conv
->name
);
5238 PurpleBlistNode
*cn
, *bn
;
5243 if (!(c
= purple_buddy_get_contact(bud
)))
5246 cn
= PURPLE_BLIST_NODE(c
);
5247 for (bn
= purple_blist_node_get_first_child(cn
); bn
; bn
= purple_blist_node_get_sibling_next(bn
)) {
5248 PurpleBuddy
*b
= PURPLE_BUDDY(bn
);
5249 PurpleConversation
*conv
;
5250 if ((conv
= purple_find_conversation_with_account(PURPLE_CONV_TYPE_IM
, b
->name
, b
->account
))) {
5252 return conv
->ui_data
;
5260 buddy_update_cb(PurpleBlistNode
*bnode
, gpointer null
)
5264 g_return_if_fail(bnode
);
5265 if (!PURPLE_BLIST_NODE_IS_BUDDY(bnode
))
5268 for (list
= pidgin_conv_windows_get_list(); list
; list
= list
->next
)
5270 PidginWindow
*win
= list
->data
;
5271 PurpleConversation
*conv
= pidgin_conv_window_get_active_conversation(win
);
5273 if (purple_conversation_get_type(conv
) != PURPLE_CONV_TYPE_IM
)
5276 pidgin_conv_update_fields(conv
, PIDGIN_CONV_MENU
);
5281 ignore_middle_click(GtkWidget
*widget
, GdkEventButton
*e
, gpointer null
)
5283 /* A click on the pane is propagated to the notebook containing the pane.
5284 * So if Stu accidentally aims high and middle clicks on the pane-handle,
5285 * it causes a conversation tab to close. Let's stop that from happening.
5287 if (e
->button
== 2 && e
->type
== GDK_BUTTON_PRESS
)
5292 static void set_typing_font(GtkWidget
*widget
, GtkStyle
*style
, PidginConversation
*gtkconv
)
5294 static PangoFontDescription
*font_desc
= NULL
;
5295 static GdkColor
*color
= NULL
;
5296 static gboolean enable
= TRUE
;
5298 if (font_desc
== NULL
) {
5299 char *string
= NULL
;
5300 gtk_widget_style_get(widget
,
5301 "typing-notification-font", &string
,
5302 "typing-notification-color", &color
,
5303 "typing-notification-enable", &enable
,
5305 font_desc
= pango_font_description_from_string(string
);
5307 if (color
== NULL
) {
5308 GdkColor def
= {0, 0x8888, 0x8888, 0x8888};
5309 color
= gdk_color_copy(&def
);
5313 gtk_text_buffer_create_tag(GTK_IMHTML(widget
)->text_buffer
, "TYPING-NOTIFICATION",
5314 "foreground-gdk", color
,
5315 "font-desc", font_desc
,
5319 g_object_set_data(G_OBJECT(widget
), "disable-typing-notification", GINT_TO_POINTER(TRUE
));
5320 /* or may be 'gtkconv->disable_typing = TRUE;' instead? */
5323 g_signal_handlers_disconnect_by_func(G_OBJECT(widget
), set_typing_font
, gtkconv
);
5326 /**************************************************************************
5327 * Conversation UI operations
5328 **************************************************************************/
5330 private_gtkconv_new(PurpleConversation
*conv
, gboolean hidden
)
5332 PidginConversation
*gtkconv
;
5333 PurpleConversationType conv_type
= purple_conversation_get_type(conv
);
5334 GtkWidget
*pane
= NULL
;
5335 GtkWidget
*tab_cont
;
5336 PurpleBlistNode
*convnode
;
5339 if (conv_type
== PURPLE_CONV_TYPE_IM
&& (gtkconv
= pidgin_conv_find_gtkconv(conv
))) {
5340 conv
->ui_data
= gtkconv
;
5341 if (!g_list_find(gtkconv
->convs
, conv
))
5342 gtkconv
->convs
= g_list_prepend(gtkconv
->convs
, conv
);
5343 pidgin_conv_switch_active_conversation(conv
);
5347 gtkconv
= g_new0(PidginConversation
, 1);
5348 conv
->ui_data
= gtkconv
;
5349 gtkconv
->active_conv
= conv
;
5350 gtkconv
->convs
= g_list_prepend(gtkconv
->convs
, conv
);
5351 gtkconv
->send_history
= g_list_append(NULL
, NULL
);
5353 /* Setup some initial variables. */
5354 gtkconv
->tooltips
= gtk_tooltips_new();
5355 gtkconv
->unseen_state
= PIDGIN_UNSEEN_NONE
;
5356 gtkconv
->unseen_count
= 0;
5358 if (conv_type
== PURPLE_CONV_TYPE_IM
) {
5359 gtkconv
->u
.im
= g_malloc0(sizeof(PidginImPane
));
5360 } else if (conv_type
== PURPLE_CONV_TYPE_CHAT
) {
5361 gtkconv
->u
.chat
= g_malloc0(sizeof(PidginChatPane
));
5363 pane
= setup_common_pane(gtkconv
);
5365 gtk_imhtml_set_format_functions(GTK_IMHTML(gtkconv
->imhtml
),
5366 gtk_imhtml_get_format_functions(GTK_IMHTML(gtkconv
->imhtml
)) | GTK_IMHTML_IMAGE
);
5369 if (conv_type
== PURPLE_CONV_TYPE_CHAT
)
5370 g_free(gtkconv
->u
.chat
);
5371 else if (conv_type
== PURPLE_CONV_TYPE_IM
)
5372 g_free(gtkconv
->u
.im
);
5375 conv
->ui_data
= NULL
;
5379 /* Setup drag-and-drop */
5380 gtk_drag_dest_set(pane
,
5381 GTK_DEST_DEFAULT_MOTION
|
5382 GTK_DEST_DEFAULT_DROP
,
5383 te
, sizeof(te
) / sizeof(GtkTargetEntry
),
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(gtkconv
->imhtml
, 0,
5391 te
, sizeof(te
) / sizeof(GtkTargetEntry
),
5394 gtk_drag_dest_set(gtkconv
->entry
, 0,
5395 te
, sizeof(te
) / sizeof(GtkTargetEntry
),
5398 g_signal_connect(G_OBJECT(pane
), "button_press_event",
5399 G_CALLBACK(ignore_middle_click
), NULL
);
5400 g_signal_connect(G_OBJECT(pane
), "drag_data_received",
5401 G_CALLBACK(conv_dnd_recv
), gtkconv
);
5402 g_signal_connect(G_OBJECT(gtkconv
->imhtml
), "drag_data_received",
5403 G_CALLBACK(conv_dnd_recv
), gtkconv
);
5404 g_signal_connect(G_OBJECT(gtkconv
->entry
), "drag_data_received",
5405 G_CALLBACK(conv_dnd_recv
), gtkconv
);
5407 g_signal_connect(gtkconv
->imhtml
, "style-set", G_CALLBACK(set_typing_font
), gtkconv
);
5409 /* Setup the container for the tab. */
5410 gtkconv
->tab_cont
= tab_cont
= gtk_vbox_new(FALSE
, PIDGIN_HIG_BOX_SPACE
);
5411 g_object_set_data(G_OBJECT(tab_cont
), "PidginConversation", gtkconv
);
5412 gtk_container_set_border_width(GTK_CONTAINER(tab_cont
), PIDGIN_HIG_BOX_SPACE
);
5413 gtk_container_add(GTK_CONTAINER(tab_cont
), pane
);
5414 gtk_widget_show(pane
);
5416 convnode
= get_conversation_blist_node(conv
);
5417 if (convnode
== NULL
|| !purple_blist_node_get_bool(convnode
, "gtk-mute-sound"))
5418 gtkconv
->make_sound
= TRUE
;
5420 if (convnode
!= NULL
&&
5421 (value
= g_hash_table_lookup(convnode
->settings
, "enable-logging")) &&
5422 purple_value_get_type(value
) == PURPLE_TYPE_BOOLEAN
)
5424 purple_conversation_set_logging(conv
, purple_value_get_boolean(value
));
5427 if (purple_prefs_get_bool(PIDGIN_PREFS_ROOT
"/conversations/show_formatting_toolbar"))
5428 gtk_widget_show(gtkconv
->toolbar
);
5430 gtk_widget_hide(gtkconv
->toolbar
);
5432 if (purple_prefs_get_bool(PIDGIN_PREFS_ROOT
"/conversations/im/show_buddy_icons"))
5433 gtk_widget_show(gtkconv
->infopane_hbox
);
5435 gtk_widget_hide(gtkconv
->infopane_hbox
);
5437 gtk_imhtml_show_comments(GTK_IMHTML(gtkconv
->imhtml
),
5438 purple_prefs_get_bool(PIDGIN_PREFS_ROOT
"/conversations/show_timestamps"));
5439 gtk_imhtml_set_protocol_name(GTK_IMHTML(gtkconv
->imhtml
),
5440 purple_account_get_protocol_name(conv
->account
));
5442 g_signal_connect_swapped(G_OBJECT(pane
), "focus",
5443 G_CALLBACK(gtk_widget_grab_focus
),
5447 pidgin_conv_window_add_gtkconv(hidden_convwin
, gtkconv
);
5449 pidgin_conv_placement_place(gtkconv
);
5451 if (nick_colors
== NULL
) {
5452 nbr_nick_colors
= NUM_NICK_COLORS
;
5453 nick_colors
= generate_nick_colors(&nbr_nick_colors
, gtk_widget_get_style(gtkconv
->imhtml
)->base
[GTK_STATE_NORMAL
]);
5456 if (conv
->features
& PURPLE_CONNECTION_ALLOW_CUSTOM_SMILEY
)
5457 pidgin_themes_smiley_themeize_custom(gtkconv
->entry
);
5461 pidgin_conv_new_hidden(PurpleConversation
*conv
)
5463 private_gtkconv_new(conv
, TRUE
);
5467 pidgin_conv_new(PurpleConversation
*conv
)
5469 private_gtkconv_new(conv
, FALSE
);
5470 if (PIDGIN_IS_PIDGIN_CONVERSATION(conv
))
5471 purple_signal_emit(pidgin_conversations_get_handle(),
5472 "conversation-displayed", PIDGIN_CONVERSATION(conv
));
5476 received_im_msg_cb(PurpleAccount
*account
, char *sender
, char *message
,
5477 PurpleConversation
*conv
, PurpleMessageFlags flags
)
5479 PurpleConversationUiOps
*ui_ops
= pidgin_conversations_get_conv_ui_ops();
5480 gboolean hide
= FALSE
;
5483 /* create hidden conv if hide_new pref is always */
5484 if (purple_strequal(purple_prefs_get_string(PIDGIN_PREFS_ROOT
"/conversations/im/hide_new"), "always"))
5487 /* create hidden conv if hide_new pref is away and account is away */
5488 if (purple_strequal(purple_prefs_get_string(PIDGIN_PREFS_ROOT
"/conversations/im/hide_new"), "away") &&
5489 !purple_status_is_available(purple_account_get_active_status(account
)))
5492 if (conv
&& PIDGIN_IS_PIDGIN_CONVERSATION(conv
) && !hide
) {
5493 PidginConversation
*gtkconv
= PIDGIN_CONVERSATION(conv
);
5494 if (gtkconv
->win
== hidden_convwin
) {
5495 pidgin_conv_attach_to_conversation(gtkconv
->active_conv
);
5501 ui_ops
->create_conversation
= pidgin_conv_new_hidden
;
5502 purple_conversation_new(PURPLE_CONV_TYPE_IM
, account
, sender
);
5503 ui_ops
->create_conversation
= pidgin_conv_new
;
5506 /* Somebody wants to keep this conversation around, so don't time it out */
5508 timer
= GPOINTER_TO_INT(purple_conversation_get_data(conv
, "close-timer"));
5510 purple_timeout_remove(timer
);
5511 purple_conversation_set_data(conv
, "close-timer", GINT_TO_POINTER(0));
5517 pidgin_conv_destroy(PurpleConversation
*conv
)
5519 PidginConversation
*gtkconv
= PIDGIN_CONVERSATION(conv
);
5521 gtkconv
->convs
= g_list_remove(gtkconv
->convs
, conv
);
5522 /* Don't destroy ourselves until all our convos are gone */
5523 if (gtkconv
->convs
) {
5524 /* Make sure the destroyed conversation is not the active one */
5525 if (gtkconv
->active_conv
== conv
) {
5526 gtkconv
->active_conv
= gtkconv
->convs
->data
;
5527 purple_conversation_update(gtkconv
->active_conv
, PURPLE_CONV_UPDATE_FEATURES
);
5532 pidgin_conv_window_remove_gtkconv(gtkconv
->win
, gtkconv
);
5534 /* If the "Save Conversation" or "Save Icon" dialogs are open then close them */
5535 purple_request_close_with_handle(gtkconv
);
5536 purple_notify_close_with_handle(gtkconv
);
5538 gtk_widget_destroy(gtkconv
->tab_cont
);
5539 g_object_unref(gtkconv
->tab_cont
);
5541 if (purple_conversation_get_type(conv
) == PURPLE_CONV_TYPE_IM
) {
5542 if (gtkconv
->u
.im
->icon_timer
!= 0)
5543 g_source_remove(gtkconv
->u
.im
->icon_timer
);
5545 if (gtkconv
->u
.im
->anim
!= NULL
)
5546 g_object_unref(G_OBJECT(gtkconv
->u
.im
->anim
));
5548 if (gtkconv
->u
.im
->typing_timer
!= 0)
5549 g_source_remove(gtkconv
->u
.im
->typing_timer
);
5551 g_free(gtkconv
->u
.im
);
5552 } else if (purple_conversation_get_type(conv
) == PURPLE_CONV_TYPE_CHAT
) {
5553 purple_signals_disconnect_by_handle(gtkconv
->u
.chat
);
5554 g_free(gtkconv
->u
.chat
);
5557 gtk_object_sink(GTK_OBJECT(gtkconv
->tooltips
));
5559 gtkconv
->send_history
= g_list_first(gtkconv
->send_history
);
5560 g_list_foreach(gtkconv
->send_history
, (GFunc
)g_free
, NULL
);
5561 g_list_free(gtkconv
->send_history
);
5563 if (gtkconv
->attach
.timer
) {
5564 g_source_remove(gtkconv
->attach
.timer
);
5572 pidgin_conv_write_im(PurpleConversation
*conv
, const char *who
,
5573 const char *message
, PurpleMessageFlags flags
,
5576 PidginConversation
*gtkconv
;
5578 gtkconv
= PIDGIN_CONVERSATION(conv
);
5580 if (conv
!= gtkconv
->active_conv
&&
5581 flags
& PURPLE_MESSAGE_ACTIVE_ONLY
)
5583 /* Plugins that want these messages suppressed should be
5584 * calling purple_conv_im_write(), so they get suppressed here,
5585 * before being written to the log. */
5586 purple_debug_info("gtkconv",
5587 "Suppressing message for an inactive conversation in pidgin_conv_write_im()\n");
5591 purple_conversation_write(conv
, who
, message
, flags
, mtime
);
5595 get_text_tag_color(GtkTextTag
*tag
)
5597 GdkColor
*color
= NULL
;
5598 gboolean set
= FALSE
;
5599 static char colcode
[] = "#XXXXXX";
5601 g_object_get(G_OBJECT(tag
), "foreground-set", &set
, "foreground-gdk", &color
, NULL
);
5603 g_snprintf(colcode
, sizeof(colcode
), "#%02x%02x%02x",
5604 color
->red
>> 8, color
->green
>> 8, color
->blue
>> 8);
5608 gdk_color_free(color
);
5612 /* The callback for an event on a link tag. */
5613 static gboolean
buddytag_event(GtkTextTag
*tag
, GObject
*imhtml
,
5614 GdkEvent
*event
, GtkTextIter
*arg2
, gpointer data
)
5616 if (event
->type
== GDK_BUTTON_PRESS
5617 || event
->type
== GDK_2BUTTON_PRESS
) {
5618 GdkEventButton
*btn_event
= (GdkEventButton
*) event
;
5619 PurpleConversation
*conv
= data
;
5622 /* strlen("BUDDY " or "HILIT ") == 6 */
5623 g_return_val_if_fail((tag
->name
!= NULL
)
5624 && (strlen(tag
->name
) > 6), FALSE
);
5626 buddyname
= (tag
->name
) + 6;
5628 /* emit chat-nick-clicked signal */
5629 if (event
->type
== GDK_BUTTON_PRESS
) {
5630 gint plugin_return
= GPOINTER_TO_INT(purple_signal_emit_return_1(
5631 pidgin_conversations_get_handle(), "chat-nick-clicked",
5632 data
, buddyname
, btn_event
->button
));
5637 if (btn_event
->button
== 1 &&
5638 event
->type
== GDK_2BUTTON_PRESS
) {
5639 chat_do_im(PIDGIN_CONVERSATION(conv
), buddyname
);
5641 } else if (btn_event
->button
== 2
5642 && event
->type
== GDK_2BUTTON_PRESS
) {
5643 chat_do_info(PIDGIN_CONVERSATION(conv
), buddyname
);
5646 } else if (btn_event
->button
== 3
5647 && event
->type
== GDK_BUTTON_PRESS
) {
5648 GtkTextIter start
, end
;
5650 /* we shouldn't display the popup
5651 * if the user has selected something: */
5652 if (!gtk_text_buffer_get_selection_bounds(
5653 gtk_text_iter_get_buffer(arg2
),
5655 GtkWidget
*menu
= NULL
;
5656 PurpleConnection
*gc
=
5657 purple_conversation_get_gc(conv
);
5660 menu
= create_chat_menu(conv
, buddyname
, gc
);
5661 gtk_menu_popup(GTK_MENU(menu
), NULL
, NULL
,
5662 NULL
, GTK_WIDGET(imhtml
),
5666 /* Don't propagate the event any further */
5675 static GtkTextTag
*get_buddy_tag(PurpleConversation
*conv
, const char *who
, PurpleMessageFlags flag
,
5678 PidginConversation
*gtkconv
= PIDGIN_CONVERSATION(conv
);
5679 GtkTextTag
*buddytag
;
5681 gboolean highlight
= (flag
& PURPLE_MESSAGE_NICK
);
5682 GtkTextBuffer
*buffer
= GTK_IMHTML(gtkconv
->imhtml
)->text_buffer
;
5684 str
= g_strdup_printf(highlight
? "HILIT %s" : "BUDDY %s", who
);
5686 buddytag
= gtk_text_tag_table_lookup(
5687 gtk_text_buffer_get_tag_table(buffer
), str
);
5689 if (buddytag
== NULL
&& create
) {
5691 buddytag
= gtk_text_buffer_create_tag(buffer
, str
,
5692 "foreground", get_text_tag_color(gtk_text_tag_table_lookup(
5693 gtk_text_buffer_get_tag_table(buffer
), "highlight-name")),
5694 "weight", PANGO_WEIGHT_BOLD
,
5697 buddytag
= gtk_text_buffer_create_tag(
5699 "foreground-gdk", get_nick_color(gtkconv
, who
),
5700 "weight", purple_find_buddy(purple_conversation_get_account(conv
), who
) ? PANGO_WEIGHT_BOLD
: PANGO_WEIGHT_NORMAL
,
5703 g_object_set_data(G_OBJECT(buddytag
), "cursor", "");
5704 g_signal_connect(G_OBJECT(buddytag
), "event",
5705 G_CALLBACK(buddytag_event
), conv
);
5713 static void pidgin_conv_calculate_newday(PidginConversation
*gtkconv
, time_t mtime
)
5715 struct tm
*tm
= localtime(&mtime
);
5717 tm
->tm_hour
= tm
->tm_min
= tm
->tm_sec
= 0;
5720 gtkconv
->newday
= mktime(tm
);
5723 /* Detect string direction and encapsulate the string in RLE/LRE/PDF unicode characters
5724 str - pointer to string (string is re-allocated and the pointer updated) */
5726 str_embed_direction_chars(char **str
)
5733 if (PANGO_DIRECTION_RTL
== pango_find_base_dir(*str
, -1))
5735 sprintf(pre_str
, "%c%c%c",
5736 0xE2, 0x80, 0xAB); /* RLE */
5737 sprintf(post_str
, "%c%c%c%c%c%c%c%c%c",
5738 0xE2, 0x80, 0xAC, /* PDF */
5739 0xE2, 0x80, 0x8E, /* LRM */
5740 0xE2, 0x80, 0xAC); /* PDF */
5744 sprintf(pre_str
, "%c%c%c",
5745 0xE2, 0x80, 0xAA); /* LRE */
5746 sprintf(post_str
, "%c%c%c%c%c%c%c%c%c",
5747 0xE2, 0x80, 0xAC, /* PDF */
5748 0xE2, 0x80, 0x8F, /* RLM */
5749 0xE2, 0x80, 0xAC); /* PDF */
5752 ret
= g_strconcat(pre_str
, *str
, post_str
, NULL
);
5760 pidgin_conv_write_conv(PurpleConversation
*conv
, const char *name
, const char *alias
,
5761 const char *message
, PurpleMessageFlags flags
,
5764 PidginConversation
*gtkconv
;
5765 PurpleConnection
*gc
;
5766 PurpleAccount
*account
;
5767 int gtk_font_options
= 0;
5768 int gtk_font_options_all
= 0;
5769 int max_scrollback_lines
;
5771 char buf2
[BUF_LONG
];
5775 char *with_font_tag
;
5776 char *sml_attrib
= NULL
;
5778 PurpleConversationType type
;
5780 gboolean plugin_return
;
5783 gboolean is_rtl_message
= FALSE
;
5785 g_return_if_fail(conv
!= NULL
);
5786 gtkconv
= PIDGIN_CONVERSATION(conv
);
5787 g_return_if_fail(gtkconv
!= NULL
);
5789 if (gtkconv
->attach
.timer
) {
5790 /* We are currently in the process of filling up the buffer with the message
5791 * history of the conversation. So we do not need to add the message here.
5792 * Instead, this message will be added to the message-list, which in turn will
5793 * be processed and displayed by the attach-callback.
5798 if (conv
!= gtkconv
->active_conv
)
5800 if (flags
& PURPLE_MESSAGE_ACTIVE_ONLY
)
5802 /* Unless this had PURPLE_MESSAGE_NO_LOG, this message
5803 * was logged. Plugin writers: if this isn't what
5804 * you wanted, call purple_conv_im_write() instead of
5805 * purple_conversation_write(). */
5806 purple_debug_info("gtkconv",
5807 "Suppressing message for an inactive conversation in pidgin_conv_write_conv()\n");
5811 /* Set the active conversation to the one that just messaged us. */
5812 /* TODO: consider not doing this if the account is offline or something */
5813 if (flags
& (PURPLE_MESSAGE_SEND
| PURPLE_MESSAGE_RECV
))
5814 pidgin_conv_switch_active_conversation(conv
);
5817 type
= purple_conversation_get_type(conv
);
5818 account
= purple_conversation_get_account(conv
);
5819 g_return_if_fail(account
!= NULL
);
5820 gc
= purple_account_get_connection(account
);
5821 g_return_if_fail(gc
!= NULL
|| !(flags
& (PURPLE_MESSAGE_SEND
| PURPLE_MESSAGE_RECV
)));
5823 /* Make sure URLs are clickable */
5824 if(flags
& PURPLE_MESSAGE_NO_LINKIFY
)
5825 displaying
= g_strdup(message
);
5827 displaying
= purple_markup_linkify(message
);
5829 plugin_return
= GPOINTER_TO_INT(purple_signal_emit_return_1(
5830 pidgin_conversations_get_handle(), (type
== PURPLE_CONV_TYPE_IM
?
5831 "displaying-im-msg" : "displaying-chat-msg"),
5832 account
, name
, &displaying
, conv
, flags
));
5838 length
= strlen(displaying
) + 1;
5840 /* Awful hack to work around GtkIMHtml's inefficient rendering of messages with lots of formatting changes.
5841 * If a message has over 100 '<' characters, strip formatting before appending it. Hopefully nobody actually
5842 * needs that much formatting, anyway.
5844 for (bracket
= strchr(displaying
, '<'); bracket
&& *(bracket
+ 1); bracket
= strchr(bracket
+ 1, '<'))
5847 if (tag_count
> 100) {
5848 char *tmp
= displaying
;
5849 displaying
= purple_markup_strip_html(tmp
);
5853 line_count
= gtk_text_buffer_get_line_count(
5854 gtk_text_view_get_buffer(GTK_TEXT_VIEW(
5857 max_scrollback_lines
= purple_prefs_get_int(
5858 PIDGIN_PREFS_ROOT
"/conversations/scrollback_lines");
5859 /* If we're sitting at more than 100 lines more than the
5860 max scrollback, trim down to max scrollback */
5861 if (max_scrollback_lines
> 0
5862 && line_count
> (max_scrollback_lines
+ 100)) {
5863 GtkTextBuffer
*text_buffer
= gtk_text_view_get_buffer(
5864 GTK_TEXT_VIEW(gtkconv
->imhtml
));
5865 GtkTextIter start
, end
;
5867 gtk_text_buffer_get_start_iter(text_buffer
, &start
);
5868 gtk_text_buffer_get_iter_at_line(text_buffer
, &end
,
5869 (line_count
- max_scrollback_lines
));
5870 gtk_imhtml_delete(GTK_IMHTML(gtkconv
->imhtml
), &start
, &end
);
5873 if (type
== PURPLE_CONV_TYPE_CHAT
)
5875 /* Create anchor for user */
5877 char *tmp
= g_strconcat("user:", name
, NULL
);
5879 gtk_text_buffer_get_end_iter(gtk_text_view_get_buffer(GTK_TEXT_VIEW(gtkconv
->imhtml
)), &iter
);
5880 gtk_text_buffer_create_mark(gtk_text_view_get_buffer(GTK_TEXT_VIEW(gtkconv
->imhtml
)),
5885 if (purple_prefs_get_bool(PIDGIN_PREFS_ROOT
"/conversations/use_smooth_scrolling"))
5886 gtk_font_options_all
|= GTK_IMHTML_USE_SMOOTHSCROLLING
;
5888 if (gtk_text_buffer_get_char_count(gtk_text_view_get_buffer(GTK_TEXT_VIEW(gtkconv
->imhtml
))))
5889 gtk_imhtml_append_text(GTK_IMHTML(gtkconv
->imhtml
), "<BR>", gtk_font_options_all
| GTK_IMHTML_NO_SCROLL
);
5891 /* First message in a conversation. */
5892 if (gtkconv
->newday
== 0)
5893 pidgin_conv_calculate_newday(gtkconv
, mtime
);
5895 /* Show the date on the first message in a new day, or if the message is
5896 * older than 20 minutes. */
5897 show_date
= (mtime
>= gtkconv
->newday
) || (time(NULL
) > mtime
+ 20*60);
5899 mdate
= purple_signal_emit_return_1(pidgin_conversations_get_handle(),
5900 "conversation-timestamp",
5901 conv
, mtime
, show_date
);
5905 struct tm
*tm
= localtime(&mtime
);
5908 tmp
= purple_date_format_long(tm
);
5910 tmp
= purple_time_format(tm
);
5911 mdate
= g_strdup_printf("(%s)", tmp
);
5914 /* Bi-Directional support - set timestamp direction using unicode characters */
5915 is_rtl_message
= purple_markup_is_rtl(message
);
5917 /* Handle plaintext messages with RTL text but no direction in the markup */
5918 if (!is_rtl_message
&& pango_find_base_dir(message
, -1) == PANGO_DIRECTION_RTL
)
5920 char *wrapped
= g_strdup_printf("<SPAN style=\"direction:rtl;text-align:right;\">%s</SPAN>", displaying
);
5923 displaying
= wrapped
;
5925 length
= strlen(displaying
) + 1;
5926 is_rtl_message
= TRUE
;
5929 /* Enforce direction only if message is RTL - doesn't effect LTR users */
5931 str_embed_direction_chars(&mdate
);
5933 if (mtime
>= gtkconv
->newday
)
5934 pidgin_conv_calculate_newday(gtkconv
, mtime
);
5936 sml_attrib
= g_strdup_printf("sml=\"%s\"", purple_account_get_protocol_name(account
));
5938 gtk_font_options
|= GTK_IMHTML_NO_COMMENTS
;
5940 if ((flags
& PURPLE_MESSAGE_RECV
) &&
5941 !purple_prefs_get_bool(PIDGIN_PREFS_ROOT
"/conversations/show_incoming_formatting"))
5942 gtk_font_options
|= GTK_IMHTML_NO_COLOURS
| GTK_IMHTML_NO_FONTS
| GTK_IMHTML_NO_SIZES
| GTK_IMHTML_NO_FORMATTING
;
5944 /* this is gonna crash one day, I can feel it. */
5945 if (PURPLE_PLUGIN_PROTOCOL_INFO(purple_find_prpl(purple_account_get_protocol_id(conv
->account
)))->options
&
5946 OPT_PROTO_USE_POINTSIZE
) {
5947 gtk_font_options
|= GTK_IMHTML_USE_POINTSIZE
;
5950 if (!(flags
& PURPLE_MESSAGE_RECV
) && (conv
->features
& PURPLE_CONNECTION_ALLOW_CUSTOM_SMILEY
))
5952 /* We want to see our own smileys. Need to revert it after send*/
5953 pidgin_themes_smiley_themeize_custom(gtkconv
->imhtml
);
5956 /* TODO: These colors should not be hardcoded so log.c can use them */
5957 if (flags
& PURPLE_MESSAGE_RAW
) {
5958 gtk_imhtml_append_text(GTK_IMHTML(gtkconv
->imhtml
), message
, gtk_font_options_all
);
5959 } else if (flags
& PURPLE_MESSAGE_SYSTEM
) {
5960 g_snprintf(buf2
, sizeof(buf2
),
5961 "<FONT %s><FONT SIZE=\"2\"><!--%s --></FONT><B>%s</B></FONT>",
5962 sml_attrib
? sml_attrib
: "", mdate
, displaying
);
5964 gtk_imhtml_append_text(GTK_IMHTML(gtkconv
->imhtml
), buf2
, gtk_font_options_all
);
5966 } else if (flags
& PURPLE_MESSAGE_ERROR
) {
5967 g_snprintf(buf2
, sizeof(buf2
),
5968 "<FONT COLOR=\"#ff0000\"><FONT %s><FONT SIZE=\"2\"><!--%s --></FONT><B>%s</B></FONT></FONT>",
5969 sml_attrib
? sml_attrib
: "", mdate
, displaying
);
5971 gtk_imhtml_append_text(GTK_IMHTML(gtkconv
->imhtml
), buf2
, gtk_font_options_all
);
5973 } else if (flags
& PURPLE_MESSAGE_NO_LOG
) {
5974 g_snprintf(buf2
, BUF_LONG
,
5975 "<B><FONT %s COLOR=\"#777777\">%s</FONT></B>",
5976 sml_attrib
? sml_attrib
: "", displaying
);
5978 gtk_imhtml_append_text(GTK_IMHTML(gtkconv
->imhtml
), buf2
, gtk_font_options_all
);
5980 char *new_message
= g_memdup(displaying
, length
);
5981 char *alias_escaped
= (alias
? g_markup_escape_text(alias
, strlen(alias
)) : g_strdup(""));
5982 /* The initial offset is to deal with
5983 * escaped entities making the string longer */
5984 int tag_start_offset
= 0;
5985 const char *tagname
= NULL
;
5987 GtkTextIter start
, end
;
5990 GtkTextBuffer
*buffer
= GTK_IMHTML(gtkconv
->imhtml
)->text_buffer
;
5992 /* Enforce direction on alias */
5994 str_embed_direction_chars(&alias_escaped
);
5996 str
= g_malloc(1024);
5997 if (flags
& PURPLE_MESSAGE_WHISPER
) {
5998 /* If we're whispering, it's not an autoresponse. */
5999 if (purple_message_meify(new_message
, -1 )) {
6000 g_snprintf(str
, 1024, "***%s", alias_escaped
);
6001 tag_start_offset
+= 3;
6002 tagname
= "whisper-action-name";
6005 g_snprintf(str
, 1024, "*%s*:", alias_escaped
);
6006 tag_start_offset
+= 1;
6010 tagname
= "whisper-name";
6013 if (purple_message_meify(new_message
, -1)) {
6014 if (flags
& PURPLE_MESSAGE_AUTO_RESP
) {
6015 g_snprintf(str
, 1024, "%s ***%s", AUTO_RESPONSE
, alias_escaped
);
6016 tag_start_offset
+= strlen(AUTO_RESPONSE
) - 6 + 4;
6018 g_snprintf(str
, 1024, "***%s", alias_escaped
);
6019 tag_start_offset
+= 3;
6022 if (flags
& PURPLE_MESSAGE_NICK
)
6023 tagname
= "highlight-name";
6025 tagname
= "action-name";
6027 if (flags
& PURPLE_MESSAGE_AUTO_RESP
) {
6028 g_snprintf(str
, 1024, "%s %s", alias_escaped
, AUTO_RESPONSE
);
6029 tag_start_offset
+= strlen(AUTO_RESPONSE
) - 6 + 1;
6031 g_snprintf(str
, 1024, "%s:", alias_escaped
);
6037 if (flags
& PURPLE_MESSAGE_NICK
) {
6038 if (type
== PURPLE_CONV_TYPE_IM
) {
6039 tagname
= "highlight-name";
6041 } else if (flags
& PURPLE_MESSAGE_RECV
) {
6042 /* The tagname for chats is handled by get_buddy_tag */
6043 if (type
== PURPLE_CONV_TYPE_IM
) {
6044 tagname
= "receive-name";
6046 } else if (flags
& PURPLE_MESSAGE_SEND
) {
6047 tagname
= "send-name";
6049 purple_debug_error("gtkconv", "message missing flags\n");
6054 g_free(alias_escaped
);
6057 tag
= gtk_text_tag_table_lookup(gtk_text_buffer_get_tag_table(buffer
), tagname
);
6059 tag
= get_buddy_tag(conv
, name
, flags
, TRUE
);
6061 if (GTK_IMHTML(gtkconv
->imhtml
)->show_comments
) {
6062 /* The color for the timestamp has to be set in the font-tags, unfortunately.
6063 * Applying the nick-tag to timestamps would work, but that can make it
6064 * bold. I thought applying the "comment" tag again, which has "weight" set
6065 * to PANGO_WEIGHT_NORMAL, would remove the boldness. But it doesn't. So
6066 * this will have to do. I don't terribly like it. -- sadrul */
6067 const char *color
= get_text_tag_color(tag
);
6068 g_snprintf(buf2
, BUF_LONG
, "<FONT %s%s%s SIZE=\"2\"><!--%s --></FONT>",
6069 color
? "COLOR=\"" : "", color
? color
: "", color
? "\"" : "", mdate
);
6070 gtk_imhtml_append_text(GTK_IMHTML(gtkconv
->imhtml
), buf2
, gtk_font_options_all
| GTK_IMHTML_NO_SCROLL
);
6073 gtk_text_buffer_get_end_iter(buffer
, &end
);
6074 mark
= gtk_text_buffer_create_mark(buffer
, NULL
, &end
, TRUE
);
6076 g_snprintf(buf2
, BUF_LONG
, "<FONT %s>%s</FONT> ", sml_attrib
? sml_attrib
: "", str
);
6077 gtk_imhtml_append_text(GTK_IMHTML(gtkconv
->imhtml
), buf2
, gtk_font_options_all
| GTK_IMHTML_NO_SCROLL
);
6079 gtk_text_buffer_get_end_iter(buffer
, &end
);
6080 gtk_text_buffer_get_iter_at_mark(buffer
, &start
, mark
);
6081 gtk_text_buffer_apply_tag(buffer
, tag
, &start
, &end
);
6082 gtk_text_buffer_delete_mark(buffer
, mark
);
6087 char *pre
= g_strdup_printf("<font %s>", sml_attrib
? sml_attrib
: "");
6088 char *post
= "</font>";
6089 int pre_len
= strlen(pre
);
6090 int post_len
= strlen(post
);
6092 with_font_tag
= g_malloc(length
+ pre_len
+ post_len
+ 1);
6094 strcpy(with_font_tag
, pre
);
6095 memcpy(with_font_tag
+ pre_len
, new_message
, length
);
6096 strcpy(with_font_tag
+ pre_len
+ length
, post
);
6098 length
+= pre_len
+ post_len
;
6101 with_font_tag
= g_memdup(new_message
, length
);
6103 gtk_imhtml_append_text(GTK_IMHTML(gtkconv
->imhtml
),
6104 with_font_tag
, gtk_font_options
| gtk_font_options_all
);
6106 g_free(with_font_tag
);
6107 g_free(new_message
);
6113 /* Tab highlighting stuff */
6114 if (!(flags
& PURPLE_MESSAGE_SEND
) && !pidgin_conv_has_focus(conv
))
6116 PidginUnseenState unseen
= PIDGIN_UNSEEN_NONE
;
6118 if ((flags
& PURPLE_MESSAGE_NICK
) == PURPLE_MESSAGE_NICK
)
6119 unseen
= PIDGIN_UNSEEN_NICK
;
6120 else if (((flags
& PURPLE_MESSAGE_SYSTEM
) == PURPLE_MESSAGE_SYSTEM
) ||
6121 ((flags
& PURPLE_MESSAGE_ERROR
) == PURPLE_MESSAGE_ERROR
))
6122 unseen
= PIDGIN_UNSEEN_EVENT
;
6123 else if ((flags
& PURPLE_MESSAGE_NO_LOG
) == PURPLE_MESSAGE_NO_LOG
)
6124 unseen
= PIDGIN_UNSEEN_NO_LOG
;
6126 unseen
= PIDGIN_UNSEEN_TEXT
;
6128 gtkconv_set_unseen(gtkconv
, unseen
);
6131 if (!(flags
& PURPLE_MESSAGE_RECV
) && (conv
->features
& PURPLE_CONNECTION_ALLOW_CUSTOM_SMILEY
))
6133 /* Restore the smiley-data */
6134 pidgin_themes_smiley_themeize(gtkconv
->imhtml
);
6137 purple_signal_emit(pidgin_conversations_get_handle(),
6138 (type
== PURPLE_CONV_TYPE_IM
? "displayed-im-msg" : "displayed-chat-msg"),
6139 account
, name
, displaying
, conv
, flags
);
6141 update_typing_message(gtkconv
, NULL
);
6144 static gboolean
get_iter_from_chatbuddy(PurpleConvChatBuddy
*cb
, GtkTreeIter
*iter
)
6146 GtkTreeRowReference
*ref
;
6148 GtkTreeModel
*model
;
6150 g_return_val_if_fail(cb
!= NULL
, FALSE
);
6156 if ((path
= gtk_tree_row_reference_get_path(ref
)) == NULL
)
6159 model
= gtk_tree_row_reference_get_model(ref
);
6160 if (!gtk_tree_model_get_iter(GTK_TREE_MODEL(model
), iter
, path
)) {
6161 gtk_tree_path_free(path
);
6165 gtk_tree_path_free(path
);
6170 pidgin_conv_chat_add_users(PurpleConversation
*conv
, GList
*cbuddies
, gboolean new_arrivals
)
6172 PurpleConvChat
*chat
;
6173 PidginConversation
*gtkconv
;
6174 PidginChatPane
*gtkchat
;
6181 chat
= PURPLE_CONV_CHAT(conv
);
6182 gtkconv
= PIDGIN_CONVERSATION(conv
);
6183 gtkchat
= gtkconv
->u
.chat
;
6185 num_users
= g_list_length(purple_conv_chat_get_users(chat
));
6187 g_snprintf(tmp
, sizeof(tmp
),
6188 ngettext("%d person in room", "%d people in room",
6192 gtk_label_set_text(GTK_LABEL(gtkchat
->count
), tmp
);
6194 ls
= GTK_LIST_STORE(gtk_tree_view_get_model(GTK_TREE_VIEW(gtkchat
->list
)));
6196 gtk_tree_sortable_set_sort_column_id(GTK_TREE_SORTABLE(ls
), GTK_TREE_SORTABLE_UNSORTED_SORT_COLUMN_ID
,
6197 GTK_TREE_SORTABLE_UNSORTED_SORT_COLUMN_ID
);
6201 add_chat_buddy_common(conv
, (PurpleConvChatBuddy
*)l
->data
, NULL
);
6205 /* Currently GTK+ maintains our sorted list after it's in the tree.
6206 * This may change if it turns out we can manage it faster ourselves.
6208 gtk_tree_sortable_set_sort_column_id(GTK_TREE_SORTABLE(ls
), CHAT_USERS_ALIAS_KEY_COLUMN
,
6209 GTK_SORT_ASCENDING
);
6213 pidgin_conv_chat_rename_user(PurpleConversation
*conv
, const char *old_name
,
6214 const char *new_name
, const char *new_alias
)
6216 PurpleConvChat
*chat
;
6217 PidginConversation
*gtkconv
;
6218 PidginChatPane
*gtkchat
;
6219 PurpleConvChatBuddy
*old_cbuddy
, *new_cbuddy
;
6221 GtkTreeModel
*model
;
6224 chat
= PURPLE_CONV_CHAT(conv
);
6225 gtkconv
= PIDGIN_CONVERSATION(conv
);
6226 gtkchat
= gtkconv
->u
.chat
;
6228 model
= gtk_tree_view_get_model(GTK_TREE_VIEW(gtkchat
->list
));
6230 if (!gtk_tree_model_get_iter_first(GTK_TREE_MODEL(model
), &iter
))
6233 if ((tag
= get_buddy_tag(conv
, old_name
, 0, FALSE
)))
6234 g_object_set(G_OBJECT(tag
), "style", PANGO_STYLE_ITALIC
, NULL
);
6235 if ((tag
= get_buddy_tag(conv
, old_name
, PURPLE_MESSAGE_NICK
, FALSE
)))
6236 g_object_set(G_OBJECT(tag
), "style", PANGO_STYLE_ITALIC
, NULL
);
6238 old_cbuddy
= purple_conv_chat_cb_find(chat
, old_name
);
6242 if (get_iter_from_chatbuddy(old_cbuddy
, &iter
)) {
6243 GtkTreeRowReference
*ref
= old_cbuddy
->ui_data
;
6245 gtk_list_store_remove(GTK_LIST_STORE(model
), &iter
);
6246 gtk_tree_row_reference_free(ref
);
6247 old_cbuddy
->ui_data
= NULL
;
6250 g_return_if_fail(new_alias
!= NULL
);
6252 new_cbuddy
= purple_conv_chat_cb_find(chat
, new_name
);
6254 add_chat_buddy_common(conv
, new_cbuddy
, old_name
);
6258 pidgin_conv_chat_remove_users(PurpleConversation
*conv
, GList
*users
)
6260 PurpleConvChat
*chat
;
6261 PidginConversation
*gtkconv
;
6262 PidginChatPane
*gtkchat
;
6264 GtkTreeModel
*model
;
6271 chat
= PURPLE_CONV_CHAT(conv
);
6272 gtkconv
= PIDGIN_CONVERSATION(conv
);
6273 gtkchat
= gtkconv
->u
.chat
;
6275 num_users
= g_list_length(purple_conv_chat_get_users(chat
));
6277 for (l
= users
; l
!= NULL
; l
= l
->next
) {
6278 model
= gtk_tree_view_get_model(GTK_TREE_VIEW(gtkchat
->list
));
6280 if (!gtk_tree_model_get_iter_first(GTK_TREE_MODEL(model
), &iter
))
6287 gtk_tree_model_get(GTK_TREE_MODEL(model
), &iter
,
6288 CHAT_USERS_NAME_COLUMN
, &val
, -1);
6290 if (!purple_utf8_strcasecmp((char *)l
->data
, val
)) {
6291 f
= gtk_list_store_remove(GTK_LIST_STORE(model
), &iter
);
6294 f
= gtk_tree_model_iter_next(GTK_TREE_MODEL(model
), &iter
);
6299 if ((tag
= get_buddy_tag(conv
, l
->data
, 0, FALSE
)))
6300 g_object_set(G_OBJECT(tag
), "style", PANGO_STYLE_ITALIC
, NULL
);
6301 if ((tag
= get_buddy_tag(conv
, l
->data
, PURPLE_MESSAGE_NICK
, FALSE
)))
6302 g_object_set(G_OBJECT(tag
), "style", PANGO_STYLE_ITALIC
, NULL
);
6305 g_snprintf(tmp
, sizeof(tmp
),
6306 ngettext("%d person in room", "%d people in room",
6307 num_users
), num_users
);
6309 gtk_label_set_text(GTK_LABEL(gtkchat
->count
), tmp
);
6313 pidgin_conv_chat_update_user(PurpleConversation
*conv
, const char *user
)
6315 PurpleConvChat
*chat
;
6316 PurpleConvChatBuddy
*cbuddy
;
6317 PidginConversation
*gtkconv
;
6318 PidginChatPane
*gtkchat
;
6320 GtkTreeModel
*model
;
6322 chat
= PURPLE_CONV_CHAT(conv
);
6323 gtkconv
= PIDGIN_CONVERSATION(conv
);
6324 gtkchat
= gtkconv
->u
.chat
;
6326 model
= gtk_tree_view_get_model(GTK_TREE_VIEW(gtkchat
->list
));
6328 if (!gtk_tree_model_get_iter_first(GTK_TREE_MODEL(model
), &iter
))
6331 cbuddy
= purple_conv_chat_cb_find(chat
, user
);
6335 if (get_iter_from_chatbuddy(cbuddy
, &iter
)) {
6336 GtkTreeRowReference
*ref
= cbuddy
->ui_data
;
6337 gtk_list_store_remove(GTK_LIST_STORE(model
), &iter
);
6338 gtk_tree_row_reference_free(ref
);
6339 cbuddy
->ui_data
= NULL
;
6343 add_chat_buddy_common(conv
, cbuddy
, NULL
);
6347 pidgin_conv_has_focus(PurpleConversation
*conv
)
6349 PidginConversation
*gtkconv
= PIDGIN_CONVERSATION(conv
);
6355 g_object_get(G_OBJECT(win
->window
), "has-toplevel-focus", &has_focus
, NULL
);
6357 if (has_focus
&& pidgin_conv_window_is_active_conversation(conv
))
6364 add_custom_smiley_for_imhtml(GtkIMHtml
*imhtml
, const char *sml
, const char *smile
)
6366 GtkIMHtmlSmiley
*smiley
;
6368 smiley
= gtk_imhtml_smiley_get(imhtml
, sml
, smile
);
6371 if (!(smiley
->flags
& GTK_IMHTML_SMILEY_CUSTOM
)) {
6374 gtk_imhtml_smiley_reload(smiley
);
6378 smiley
= gtk_imhtml_smiley_create(NULL
, smile
, FALSE
, GTK_IMHTML_SMILEY_CUSTOM
);
6379 gtk_imhtml_associate_smiley(imhtml
, sml
, smiley
);
6380 g_signal_connect_swapped(imhtml
, "destroy", G_CALLBACK(gtk_imhtml_smiley_destroy
), smiley
);
6386 pidgin_conv_custom_smiley_add(PurpleConversation
*conv
, const char *smile
, gboolean remote
)
6388 PidginConversation
*gtkconv
;
6389 struct smiley_list
*list
;
6390 const char *sml
= NULL
, *conv_sml
;
6392 if (!conv
|| !smile
|| !*smile
) {
6396 /* If smileys are off, return false */
6397 if (pidgin_themes_smileys_disabled())
6400 /* If possible add this smiley to the current theme.
6401 * The addition is only temporary: custom smilies aren't saved to disk. */
6402 conv_sml
= purple_account_get_protocol_name(conv
->account
);
6403 gtkconv
= PIDGIN_CONVERSATION(conv
);
6405 for (list
= (struct smiley_list
*)current_smiley_theme
->list
; list
; list
= list
->next
) {
6406 if (purple_strequal(list
->sml
, conv_sml
)) {
6412 if (!add_custom_smiley_for_imhtml(GTK_IMHTML(gtkconv
->imhtml
), sml
, smile
))
6415 if (!remote
) /* If it's a local custom smiley, then add it for the entry */
6416 if (!add_custom_smiley_for_imhtml(GTK_IMHTML(gtkconv
->entry
), sml
, smile
))
6423 pidgin_conv_custom_smiley_write(PurpleConversation
*conv
, const char *smile
,
6424 const guchar
*data
, gsize size
)
6426 PidginConversation
*gtkconv
;
6427 GtkIMHtmlSmiley
*smiley
;
6429 GError
*error
= NULL
;
6431 sml
= purple_account_get_protocol_name(conv
->account
);
6432 gtkconv
= PIDGIN_CONVERSATION(conv
);
6433 smiley
= gtk_imhtml_smiley_get(GTK_IMHTML(gtkconv
->imhtml
), sml
, smile
);
6438 smiley
->data
= g_realloc(smiley
->data
, smiley
->datasize
+ size
);
6439 g_memmove((guchar
*)smiley
->data
+ smiley
->datasize
, data
, size
);
6440 smiley
->datasize
+= size
;
6442 if (!smiley
->loader
)
6445 if (!gdk_pixbuf_loader_write(smiley
->loader
, data
, size
, &error
) || error
) {
6446 purple_debug_warning("gtkconv", "gdk_pixbuf_loader_write() "
6447 "failed with size=%zu: %s\n", size
,
6448 error
? error
->message
: "(no error message)");
6450 g_error_free(error
);
6451 /* We must stop using the GdkPixbufLoader because trying to load
6452 certain invalid GIFs with at least gdk-pixbuf 2.23.3 can return
6453 a GdkPixbuf that will cause some operations (like
6454 gdk_pixbuf_scale_simple()) to consume memory in an infinite loop.
6455 But we also don't want to set smiley->loader to NULL because our
6456 code might expect it to be set. So create a new loader. */
6457 g_object_unref(G_OBJECT(smiley
->loader
));
6458 smiley
->loader
= gdk_pixbuf_loader_new();
6463 pidgin_conv_custom_smiley_close(PurpleConversation
*conv
, const char *smile
)
6465 PidginConversation
*gtkconv
;
6466 GtkIMHtmlSmiley
*smiley
;
6468 GError
*error
= NULL
;
6470 g_return_if_fail(conv
!= NULL
);
6471 g_return_if_fail(smile
!= NULL
);
6473 sml
= purple_account_get_protocol_name(conv
->account
);
6474 gtkconv
= PIDGIN_CONVERSATION(conv
);
6475 smiley
= gtk_imhtml_smiley_get(GTK_IMHTML(gtkconv
->imhtml
), sml
, smile
);
6480 if (!smiley
->loader
)
6483 purple_debug_info("gtkconv", "About to close the smiley pixbuf\n");
6485 if (!gdk_pixbuf_loader_close(smiley
->loader
, &error
) || error
) {
6486 purple_debug_warning("gtkconv", "gdk_pixbuf_loader_close() "
6488 error
? error
->message
: "(no error message)");
6490 g_error_free(error
);
6491 /* We must stop using the GdkPixbufLoader because if we tried to
6492 load certain invalid GIFs with all current versions of GDK (as
6493 of 2011-06-15) then it's possible the loader will contain data
6494 that could cause some operations (like gdk_pixbuf_scale_simple())
6495 to consume memory in an infinite loop. But we also don't want
6496 to set smiley->loader to NULL because our code might expect it
6497 to be set. So create a new loader. */
6498 g_object_unref(G_OBJECT(smiley
->loader
));
6499 smiley
->loader
= gdk_pixbuf_loader_new();
6504 pidgin_conv_send_confirm(PurpleConversation
*conv
, const char *message
)
6506 PidginConversation
*gtkconv
= PIDGIN_CONVERSATION(conv
);
6508 gtk_imhtml_append_text(GTK_IMHTML(gtkconv
->entry
), message
, 0);
6512 * Makes sure all the menu items and all the buttons are hidden/shown and
6513 * sensitive/insensitive. This is called after changing tabs and when an
6514 * account signs on or off.
6517 gray_stuff_out(PidginConversation
*gtkconv
)
6520 PurpleConversation
*conv
= gtkconv
->active_conv
;
6521 PurpleConnection
*gc
;
6522 PurplePluginProtocolInfo
*prpl_info
= NULL
;
6523 GdkPixbuf
*window_icon
= NULL
;
6524 GtkIMHtmlButtons buttons
;
6525 PurpleAccount
*account
;
6527 win
= pidgin_conv_get_window(gtkconv
);
6528 gc
= purple_conversation_get_gc(conv
);
6529 account
= purple_conversation_get_account(conv
);
6532 prpl_info
= PURPLE_PLUGIN_PROTOCOL_INFO(gc
->prpl
);
6534 if (win
->menu
.send_to
!= NULL
)
6535 update_send_to_selection(win
);
6538 * Handle hiding and showing stuff based on what type of conv this is.
6539 * Stuff that Purple IMs support in general should be shown for IM
6540 * conversations. Stuff that Purple chats support in general should be
6541 * shown for chat conversations. It doesn't matter whether the PRPL
6542 * supports it or not--that only affects if the button or menu item
6543 * is sensitive or not.
6545 if (purple_conversation_get_type(conv
) == PURPLE_CONV_TYPE_IM
) {
6546 /* Show stuff that applies to IMs, hide stuff that applies to chats */
6548 /* Deal with menu items */
6549 gtk_widget_show(win
->menu
.view_log
);
6550 gtk_widget_show(win
->menu
.send_file
);
6551 gtk_widget_show(g_object_get_data(G_OBJECT(win
->window
), "get_attention"));
6552 gtk_widget_show(win
->menu
.add_pounce
);
6553 gtk_widget_show(win
->menu
.get_info
);
6554 gtk_widget_hide(win
->menu
.invite
);
6555 gtk_widget_show(win
->menu
.alias
);
6556 if (purple_privacy_check(account
, purple_conversation_get_name(conv
))) {
6557 gtk_widget_hide(win
->menu
.unblock
);
6558 gtk_widget_show(win
->menu
.block
);
6560 gtk_widget_hide(win
->menu
.block
);
6561 gtk_widget_show(win
->menu
.unblock
);
6564 if (purple_find_buddy(account
, purple_conversation_get_name(conv
)) == NULL
) {
6565 gtk_widget_show(win
->menu
.add
);
6566 gtk_widget_hide(win
->menu
.remove
);
6568 gtk_widget_show(win
->menu
.remove
);
6569 gtk_widget_hide(win
->menu
.add
);
6572 gtk_widget_show(win
->menu
.insert_link
);
6573 gtk_widget_show(win
->menu
.insert_image
);
6574 } else if (purple_conversation_get_type(conv
) == PURPLE_CONV_TYPE_CHAT
) {
6575 /* Show stuff that applies to Chats, hide stuff that applies to IMs */
6577 /* Deal with menu items */
6578 gtk_widget_show(win
->menu
.view_log
);
6579 gtk_widget_hide(win
->menu
.send_file
);
6580 gtk_widget_hide(g_object_get_data(G_OBJECT(win
->window
), "get_attention"));
6581 gtk_widget_hide(win
->menu
.add_pounce
);
6582 gtk_widget_hide(win
->menu
.get_info
);
6583 gtk_widget_show(win
->menu
.invite
);
6584 gtk_widget_show(win
->menu
.alias
);
6585 gtk_widget_hide(win
->menu
.block
);
6586 gtk_widget_hide(win
->menu
.unblock
);
6588 if ((account
== NULL
) || purple_blist_find_chat(account
, purple_conversation_get_name(conv
)) == NULL
) {
6589 /* If the chat is NOT in the buddy list */
6590 gtk_widget_show(win
->menu
.add
);
6591 gtk_widget_hide(win
->menu
.remove
);
6593 /* If the chat IS in the buddy list */
6594 gtk_widget_hide(win
->menu
.add
);
6595 gtk_widget_show(win
->menu
.remove
);
6598 gtk_widget_show(win
->menu
.insert_link
);
6599 gtk_widget_show(win
->menu
.insert_image
);
6603 * Handle graying stuff out based on whether an account is connected
6604 * and what features that account supports.
6607 ((purple_conversation_get_type(conv
) != PURPLE_CONV_TYPE_CHAT
) ||
6608 !purple_conv_chat_has_left(PURPLE_CONV_CHAT(conv
)) ))
6610 /* Account is online */
6611 /* Deal with the toolbar */
6612 if (conv
->features
& PURPLE_CONNECTION_HTML
)
6614 buttons
= GTK_IMHTML_ALL
; /* Everything on */
6615 if (conv
->features
& PURPLE_CONNECTION_NO_BGCOLOR
)
6616 buttons
&= ~GTK_IMHTML_BACKCOLOR
;
6617 if (conv
->features
& PURPLE_CONNECTION_NO_FONTSIZE
)
6619 buttons
&= ~GTK_IMHTML_GROW
;
6620 buttons
&= ~GTK_IMHTML_SHRINK
;
6622 if (conv
->features
& PURPLE_CONNECTION_NO_URLDESC
)
6623 buttons
&= ~GTK_IMHTML_LINKDESC
;
6625 buttons
= GTK_IMHTML_SMILEY
| GTK_IMHTML_IMAGE
;
6628 if (!(prpl_info
->options
& OPT_PROTO_IM_IMAGE
))
6629 conv
->features
|= PURPLE_CONNECTION_NO_IMAGES
;
6631 if(conv
->features
& PURPLE_CONNECTION_NO_IMAGES
)
6632 buttons
&= ~GTK_IMHTML_IMAGE
;
6634 if (conv
->features
& PURPLE_CONNECTION_ALLOW_CUSTOM_SMILEY
)
6635 buttons
|= GTK_IMHTML_CUSTOM_SMILEY
;
6637 buttons
&= ~GTK_IMHTML_CUSTOM_SMILEY
;
6639 gtk_imhtml_set_format_functions(GTK_IMHTML(gtkconv
->entry
), buttons
);
6640 if (account
!= NULL
)
6641 gtk_imhtmltoolbar_associate_smileys(GTK_IMHTMLTOOLBAR(gtkconv
->toolbar
), purple_account_get_protocol_id(account
));
6643 /* Deal with menu items */
6644 gtk_widget_set_sensitive(win
->menu
.view_log
, TRUE
);
6645 gtk_widget_set_sensitive(win
->menu
.add_pounce
, TRUE
);
6646 gtk_widget_set_sensitive(win
->menu
.get_info
, (prpl_info
->get_info
!= NULL
));
6647 gtk_widget_set_sensitive(win
->menu
.invite
, (prpl_info
->chat_invite
!= NULL
));
6648 gtk_widget_set_sensitive(win
->menu
.insert_link
, (conv
->features
& PURPLE_CONNECTION_HTML
));
6649 gtk_widget_set_sensitive(win
->menu
.insert_image
, !(conv
->features
& PURPLE_CONNECTION_NO_IMAGES
));
6651 if (purple_conversation_get_type(conv
) == PURPLE_CONV_TYPE_IM
)
6653 gtk_widget_set_sensitive(win
->menu
.add
, (prpl_info
->add_buddy
!= NULL
) || (prpl_info
->add_buddy_with_invite
!= NULL
));
6654 gtk_widget_set_sensitive(win
->menu
.remove
, (prpl_info
->remove_buddy
!= NULL
));
6655 gtk_widget_set_sensitive(win
->menu
.send_file
,
6656 (prpl_info
->send_file
!= NULL
&& (!prpl_info
->can_receive_file
||
6657 prpl_info
->can_receive_file(gc
, purple_conversation_get_name(conv
)))));
6658 gtk_widget_set_sensitive(g_object_get_data(G_OBJECT(win
->window
), "get_attention"), (prpl_info
->send_attention
!= NULL
));
6659 gtk_widget_set_sensitive(win
->menu
.alias
,
6660 (account
!= NULL
) &&
6661 (purple_find_buddy(account
, purple_conversation_get_name(conv
)) != NULL
));
6663 else if (purple_conversation_get_type(conv
) == PURPLE_CONV_TYPE_CHAT
)
6665 gtk_widget_set_sensitive(win
->menu
.add
, (prpl_info
->join_chat
!= NULL
));
6666 gtk_widget_set_sensitive(win
->menu
.remove
, (prpl_info
->join_chat
!= NULL
));
6667 gtk_widget_set_sensitive(win
->menu
.alias
,
6668 (account
!= NULL
) &&
6669 (purple_blist_find_chat(account
, purple_conversation_get_name(conv
)) != NULL
));
6673 /* Account is offline */
6674 /* Or it's a chat that we've left. */
6676 /* Then deal with menu items */
6677 gtk_widget_set_sensitive(win
->menu
.view_log
, TRUE
);
6678 gtk_widget_set_sensitive(win
->menu
.send_file
, FALSE
);
6679 gtk_widget_set_sensitive(g_object_get_data(G_OBJECT(win
->window
),
6680 "get_attention"), FALSE
);
6681 gtk_widget_set_sensitive(win
->menu
.add_pounce
, TRUE
);
6682 gtk_widget_set_sensitive(win
->menu
.get_info
, FALSE
);
6683 gtk_widget_set_sensitive(win
->menu
.invite
, FALSE
);
6684 gtk_widget_set_sensitive(win
->menu
.alias
, FALSE
);
6685 gtk_widget_set_sensitive(win
->menu
.add
, FALSE
);
6686 gtk_widget_set_sensitive(win
->menu
.remove
, FALSE
);
6687 gtk_widget_set_sensitive(win
->menu
.insert_link
, TRUE
);
6688 gtk_widget_set_sensitive(win
->menu
.insert_image
, FALSE
);
6692 * Update the window's icon
6694 if (pidgin_conv_window_is_active_conversation(conv
))
6697 if ((purple_conversation_get_type(conv
) == PURPLE_CONV_TYPE_IM
) &&
6698 (gtkconv
->u
.im
->anim
))
6700 PurpleBuddy
*buddy
= purple_find_buddy(conv
->account
, conv
->name
);
6702 gdk_pixbuf_animation_get_static_image(gtkconv
->u
.im
->anim
);
6704 if (buddy
&& !PURPLE_BUDDY_IS_ONLINE(buddy
))
6705 gdk_pixbuf_saturate_and_pixelate(window_icon
, window_icon
, 0.0, FALSE
);
6707 g_object_ref(window_icon
);
6708 l
= g_list_append(l
, window_icon
);
6710 l
= pidgin_conv_get_tab_icons(conv
);
6712 gtk_window_set_icon_list(GTK_WINDOW(win
->window
), l
);
6713 if (window_icon
!= NULL
) {
6714 g_object_unref(G_OBJECT(window_icon
));
6721 pidgin_conv_update_fields(PurpleConversation
*conv
, PidginConvFields fields
)
6723 PidginConversation
*gtkconv
;
6726 gtkconv
= PIDGIN_CONVERSATION(conv
);
6729 win
= pidgin_conv_get_window(gtkconv
);
6733 if (fields
& PIDGIN_CONV_SET_TITLE
)
6735 purple_conversation_autoset_title(conv
);
6738 if (fields
& PIDGIN_CONV_BUDDY_ICON
)
6740 if (purple_conversation_get_type(conv
) == PURPLE_CONV_TYPE_IM
)
6741 pidgin_conv_update_buddy_icon(conv
);
6744 if (fields
& PIDGIN_CONV_MENU
)
6746 gray_stuff_out(PIDGIN_CONVERSATION(conv
));
6747 generate_send_to_items(win
);
6750 if (fields
& PIDGIN_CONV_TAB_ICON
)
6752 update_tab_icon(conv
);
6753 generate_send_to_items(win
); /* To update the icons in SendTo menu */
6756 if ((fields
& PIDGIN_CONV_TOPIC
) &&
6757 purple_conversation_get_type(conv
) == PURPLE_CONV_TYPE_CHAT
)
6760 PurpleConvChat
*chat
= PURPLE_CONV_CHAT(conv
);
6761 PidginChatPane
*gtkchat
= gtkconv
->u
.chat
;
6763 if (gtkchat
->topic_text
!= NULL
)
6765 topic
= purple_conv_chat_get_topic(chat
);
6767 gtk_entry_set_text(GTK_ENTRY(gtkchat
->topic_text
), topic
? topic
: "");
6768 gtk_tooltips_set_tip(gtkconv
->tooltips
, gtkchat
->topic_text
,
6769 topic
? topic
: "", NULL
);
6773 if (fields
& PIDGIN_CONV_SMILEY_THEME
)
6774 pidgin_themes_smiley_themeize(PIDGIN_CONVERSATION(conv
)->imhtml
);
6776 if ((fields
& PIDGIN_CONV_COLORIZE_TITLE
) ||
6777 (fields
& PIDGIN_CONV_SET_TITLE
) ||
6778 (fields
& PIDGIN_CONV_TOPIC
))
6781 PurpleConvIm
*im
= NULL
;
6782 PurpleAccount
*account
= purple_conversation_get_account(conv
);
6783 PurpleBuddy
*buddy
= NULL
;
6784 char *markup
= NULL
;
6785 AtkObject
*accessibility_obj
;
6786 /* I think this is a little longer than it needs to be but I'm lazy. */
6789 if (purple_conversation_get_type(conv
) == PURPLE_CONV_TYPE_IM
)
6790 im
= PURPLE_CONV_IM(conv
);
6792 if ((account
== NULL
) ||
6793 !purple_account_is_connected(account
) ||
6794 ((purple_conversation_get_type(conv
) == PURPLE_CONV_TYPE_CHAT
)
6795 && purple_conv_chat_has_left(PURPLE_CONV_CHAT(conv
))))
6796 title
= g_strdup_printf("(%s)", purple_conversation_get_title(conv
));
6798 title
= g_strdup(purple_conversation_get_title(conv
));
6800 if (purple_conversation_get_type(conv
) == PURPLE_CONV_TYPE_IM
) {
6801 buddy
= purple_find_buddy(account
, conv
->name
);
6803 markup
= pidgin_blist_get_name_markup(buddy
, FALSE
, FALSE
);
6807 } else if (purple_conversation_get_type(conv
) == PURPLE_CONV_TYPE_CHAT
) {
6808 const char *topic
= gtkconv
->u
.chat
->topic_text
6809 ? gtk_entry_get_text(GTK_ENTRY(gtkconv
->u
.chat
->topic_text
))
6811 char *esc
= NULL
, *tmp
;
6812 esc
= topic
? g_markup_escape_text(topic
, -1) : NULL
;
6813 tmp
= g_markup_escape_text(purple_conversation_get_title(conv
), -1);
6814 markup
= g_strdup_printf("%s%s<span color='%s' size='smaller'>%s</span>",
6815 tmp
, esc
&& *esc
? "\n" : "",
6816 pidgin_get_dim_grey_string(gtkconv
->infopane
),
6821 gtk_list_store_set(gtkconv
->infopane_model
, &(gtkconv
->infopane_iter
),
6822 CONV_TEXT_COLUMN
, markup
, -1);
6823 /* XXX seanegan Why do I have to do this? */
6824 gtk_widget_queue_draw(gtkconv
->infopane
);
6826 if (title
!= markup
)
6829 if (!GTK_WIDGET_REALIZED(gtkconv
->tab_label
))
6830 gtk_widget_realize(gtkconv
->tab_label
);
6832 accessibility_obj
= gtk_widget_get_accessible(gtkconv
->tab_cont
);
6834 purple_conv_im_get_typing_state(im
) == PURPLE_TYPING
) {
6835 atk_object_set_description(accessibility_obj
, _("Typing"));
6836 style
= "tab-label-typing";
6837 } else if (im
!= NULL
&&
6838 purple_conv_im_get_typing_state(im
) == PURPLE_TYPED
) {
6839 atk_object_set_description(accessibility_obj
, _("Stopped Typing"));
6840 style
= "tab-label-typed";
6841 } else if (gtkconv
->unseen_state
== PIDGIN_UNSEEN_NICK
) {
6842 atk_object_set_description(accessibility_obj
, _("Nick Said"));
6843 style
= "tab-label-attention";
6844 } else if (gtkconv
->unseen_state
== PIDGIN_UNSEEN_TEXT
) {
6845 atk_object_set_description(accessibility_obj
, _("Unread Messages"));
6846 if (gtkconv
->active_conv
->type
== PURPLE_CONV_TYPE_CHAT
)
6847 style
= "tab-label-unreadchat";
6849 style
= "tab-label-attention";
6850 } else if (gtkconv
->unseen_state
== PIDGIN_UNSEEN_EVENT
) {
6851 atk_object_set_description(accessibility_obj
, _("New Event"));
6852 style
= "tab-label-event";
6854 style
= "tab-label";
6857 gtk_widget_set_name(gtkconv
->tab_label
, style
);
6858 gtk_label_set_text(GTK_LABEL(gtkconv
->tab_label
), title
);
6859 gtk_widget_set_state(gtkconv
->tab_label
, GTK_STATE_ACTIVE
);
6861 if (gtkconv
->unseen_state
== PIDGIN_UNSEEN_TEXT
||
6862 gtkconv
->unseen_state
== PIDGIN_UNSEEN_NICK
||
6863 gtkconv
->unseen_state
== PIDGIN_UNSEEN_EVENT
) {
6864 PangoAttrList
*list
= pango_attr_list_new();
6865 PangoAttribute
*attr
= pango_attr_weight_new(PANGO_WEIGHT_BOLD
);
6866 attr
->start_index
= 0;
6867 attr
->end_index
= -1;
6868 pango_attr_list_insert(list
, attr
);
6869 gtk_label_set_attributes(GTK_LABEL(gtkconv
->tab_label
), list
);
6870 pango_attr_list_unref(list
);
6872 gtk_label_set_attributes(GTK_LABEL(gtkconv
->tab_label
), NULL
);
6874 if (pidgin_conv_window_is_active_conversation(conv
))
6875 update_typing_icon(gtkconv
);
6877 gtk_label_set_text(GTK_LABEL(gtkconv
->menu_label
), title
);
6878 if (pidgin_conv_window_is_active_conversation(conv
)) {
6879 const char* current_title
= gtk_window_get_title(GTK_WINDOW(win
->window
));
6880 if (current_title
== NULL
|| !purple_strequal(current_title
, title
))
6881 gtk_window_set_title(GTK_WINDOW(win
->window
), title
);
6889 pidgin_conv_updated(PurpleConversation
*conv
, PurpleConvUpdateType type
)
6891 PidginConvFields flags
= 0;
6893 g_return_if_fail(conv
!= NULL
);
6895 if (type
== PURPLE_CONV_UPDATE_ACCOUNT
)
6897 flags
= PIDGIN_CONV_ALL
;
6899 else if (type
== PURPLE_CONV_UPDATE_TYPING
||
6900 type
== PURPLE_CONV_UPDATE_UNSEEN
||
6901 type
== PURPLE_CONV_UPDATE_TITLE
)
6903 flags
= PIDGIN_CONV_COLORIZE_TITLE
;
6905 else if (type
== PURPLE_CONV_UPDATE_TOPIC
)
6907 flags
= PIDGIN_CONV_TOPIC
;
6909 else if (type
== PURPLE_CONV_ACCOUNT_ONLINE
||
6910 type
== PURPLE_CONV_ACCOUNT_OFFLINE
)
6912 flags
= PIDGIN_CONV_MENU
| PIDGIN_CONV_TAB_ICON
| PIDGIN_CONV_SET_TITLE
;
6914 else if (type
== PURPLE_CONV_UPDATE_AWAY
)
6916 flags
= PIDGIN_CONV_TAB_ICON
;
6918 else if (type
== PURPLE_CONV_UPDATE_ADD
||
6919 type
== PURPLE_CONV_UPDATE_REMOVE
||
6920 type
== PURPLE_CONV_UPDATE_CHATLEFT
)
6922 flags
= PIDGIN_CONV_SET_TITLE
| PIDGIN_CONV_MENU
;
6924 else if (type
== PURPLE_CONV_UPDATE_ICON
)
6926 flags
= PIDGIN_CONV_BUDDY_ICON
;
6928 else if (type
== PURPLE_CONV_UPDATE_FEATURES
)
6930 flags
= PIDGIN_CONV_MENU
;
6933 pidgin_conv_update_fields(conv
, flags
);
6937 wrote_msg_update_unseen_cb(PurpleAccount
*account
, const char *who
, const char *message
,
6938 PurpleConversation
*conv
, PurpleMessageFlags flags
, gpointer null
)
6940 PidginConversation
*gtkconv
= conv
? PIDGIN_CONVERSATION(conv
) : NULL
;
6941 if (conv
== NULL
|| (gtkconv
&& gtkconv
->win
!= hidden_convwin
))
6943 if (flags
& (PURPLE_MESSAGE_SEND
| PURPLE_MESSAGE_RECV
)) {
6944 PidginUnseenState unseen
= PIDGIN_UNSEEN_NONE
;
6946 if ((flags
& PURPLE_MESSAGE_NICK
) == PURPLE_MESSAGE_NICK
)
6947 unseen
= PIDGIN_UNSEEN_NICK
;
6948 else if (((flags
& PURPLE_MESSAGE_SYSTEM
) == PURPLE_MESSAGE_SYSTEM
) ||
6949 ((flags
& PURPLE_MESSAGE_ERROR
) == PURPLE_MESSAGE_ERROR
))
6950 unseen
= PIDGIN_UNSEEN_EVENT
;
6951 else if ((flags
& PURPLE_MESSAGE_NO_LOG
) == PURPLE_MESSAGE_NO_LOG
)
6952 unseen
= PIDGIN_UNSEEN_NO_LOG
;
6954 unseen
= PIDGIN_UNSEEN_TEXT
;
6956 conv_set_unseen(conv
, unseen
);
6960 static PurpleConversationUiOps conversation_ui_ops
=
6963 pidgin_conv_destroy
, /* destroy_conversation */
6964 NULL
, /* write_chat */
6965 pidgin_conv_write_im
, /* write_im */
6966 pidgin_conv_write_conv
, /* write_conv */
6967 pidgin_conv_chat_add_users
, /* chat_add_users */
6968 pidgin_conv_chat_rename_user
, /* chat_rename_user */
6969 pidgin_conv_chat_remove_users
, /* chat_remove_users */
6970 pidgin_conv_chat_update_user
, /* chat_update_user */
6971 pidgin_conv_present_conversation
, /* present */
6972 pidgin_conv_has_focus
, /* has_focus */
6973 pidgin_conv_custom_smiley_add
, /* custom_smiley_add */
6974 pidgin_conv_custom_smiley_write
, /* custom_smiley_write */
6975 pidgin_conv_custom_smiley_close
, /* custom_smiley_close */
6976 pidgin_conv_send_confirm
, /* send_confirm */
6983 PurpleConversationUiOps
*
6984 pidgin_conversations_get_conv_ui_ops(void)
6986 return &conversation_ui_ops
;
6989 /**************************************************************************
6990 * Public conversation utility functions
6991 **************************************************************************/
6993 pidgin_conv_update_buddy_icon(PurpleConversation
*conv
)
6995 PidginConversation
*gtkconv
;
7000 PurpleStoredImage
*custom_img
= NULL
;
7001 gconstpointer data
= NULL
;
7009 int scale_width
, scale_height
;
7012 PurpleAccount
*account
;
7014 PurpleBuddyIcon
*icon
;
7016 g_return_if_fail(conv
!= NULL
);
7017 g_return_if_fail(PIDGIN_IS_PIDGIN_CONVERSATION(conv
));
7018 g_return_if_fail(purple_conversation_get_type(conv
) == PURPLE_CONV_TYPE_IM
);
7020 gtkconv
= PIDGIN_CONVERSATION(conv
);
7022 if (conv
!= gtkconv
->active_conv
)
7025 if (!gtkconv
->u
.im
->show_icon
)
7028 account
= purple_conversation_get_account(conv
);
7030 /* Remove the current icon stuff */
7031 children
= gtk_container_get_children(GTK_CONTAINER(gtkconv
->u
.im
->icon_container
));
7033 /* We know there's only one child here. It'd be nice to shortcut to the
7034 event box, but we can't change the PidginConversation until 3.0 */
7035 event
= (GtkWidget
*)children
->data
;
7036 gtk_container_remove(GTK_CONTAINER(gtkconv
->u
.im
->icon_container
), event
);
7037 g_list_free(children
);
7040 if (gtkconv
->u
.im
->anim
!= NULL
)
7041 g_object_unref(G_OBJECT(gtkconv
->u
.im
->anim
));
7043 gtkconv
->u
.im
->anim
= NULL
;
7045 if (gtkconv
->u
.im
->icon_timer
!= 0)
7046 g_source_remove(gtkconv
->u
.im
->icon_timer
);
7048 gtkconv
->u
.im
->icon_timer
= 0;
7050 if (gtkconv
->u
.im
->iter
!= NULL
)
7051 g_object_unref(G_OBJECT(gtkconv
->u
.im
->iter
));
7053 gtkconv
->u
.im
->iter
= NULL
;
7055 if (!purple_prefs_get_bool(PIDGIN_PREFS_ROOT
"/conversations/im/show_buddy_icons"))
7058 if (purple_conversation_get_gc(conv
) == NULL
)
7061 buddy
= purple_find_buddy(account
, purple_conversation_get_name(conv
));
7064 PurpleContact
*contact
= purple_buddy_get_contact(buddy
);
7066 custom_img
= purple_buddy_icons_node_find_custom_icon((PurpleBlistNode
*)contact
);
7068 /* There is a custom icon for this user */
7069 data
= purple_imgstore_get_data(custom_img
);
7070 len
= purple_imgstore_get_size(custom_img
);
7076 icon
= purple_conv_im_get_icon(PURPLE_CONV_IM(conv
));
7079 gtk_widget_set_size_request(gtkconv
->u
.im
->icon_container
,
7080 -1, BUDDYICON_SIZE_MIN
);
7084 data
= purple_buddy_icon_get_data(icon
, &len
);
7087 gtk_widget_set_size_request(gtkconv
->u
.im
->icon_container
,
7088 -1, BUDDYICON_SIZE_MIN
);
7093 gtkconv
->u
.im
->anim
= pidgin_pixbuf_anim_from_data(data
, len
);
7094 purple_imgstore_unref(custom_img
);
7096 if (!gtkconv
->u
.im
->anim
) {
7097 purple_debug_error("gtkconv", "Couldn't load icon for conv %s\n",
7098 purple_conversation_get_name(conv
));
7102 if (gdk_pixbuf_animation_is_static_image(gtkconv
->u
.im
->anim
)) {
7104 gtkconv
->u
.im
->iter
= NULL
;
7105 stat
= gdk_pixbuf_animation_get_static_image(gtkconv
->u
.im
->anim
);
7106 buf
= gdk_pixbuf_add_alpha(stat
, FALSE
, 0, 0, 0);
7109 gtkconv
->u
.im
->iter
=
7110 gdk_pixbuf_animation_get_iter(gtkconv
->u
.im
->anim
, NULL
); /* LEAK */
7111 stat
= gdk_pixbuf_animation_iter_get_pixbuf(gtkconv
->u
.im
->iter
);
7112 buf
= gdk_pixbuf_add_alpha(stat
, FALSE
, 0, 0, 0);
7113 if (gtkconv
->u
.im
->animate
)
7114 start_anim(NULL
, gtkconv
);
7117 scale_width
= gdk_pixbuf_get_width(buf
);
7118 scale_height
= gdk_pixbuf_get_height(buf
);
7120 gtk_widget_get_size_request(gtkconv
->u
.im
->icon_container
, NULL
, &size
);
7121 size
= MIN(size
, MIN(scale_width
, scale_height
));
7123 /* Some sanity checks */
7124 size
= CLAMP(size
, BUDDYICON_SIZE_MIN
, BUDDYICON_SIZE_MAX
);
7125 if (scale_width
== scale_height
) {
7126 scale_width
= scale_height
= size
;
7127 } else if (scale_height
> scale_width
) {
7128 scale_width
= size
* scale_width
/ scale_height
;
7129 scale_height
= size
;
7131 scale_height
= size
* scale_height
/ scale_width
;
7134 scale
= gdk_pixbuf_scale_simple(buf
, scale_width
, scale_height
,
7135 GDK_INTERP_BILINEAR
);
7136 g_object_unref(buf
);
7137 if (pidgin_gdk_pixbuf_is_opaque(scale
))
7138 pidgin_gdk_pixbuf_make_round(scale
);
7140 event
= gtk_event_box_new();
7141 gtk_container_add(GTK_CONTAINER(gtkconv
->u
.im
->icon_container
), event
);
7142 gtk_event_box_set_visible_window(GTK_EVENT_BOX(event
), FALSE
);
7143 gtk_widget_add_events(event
,
7144 GDK_POINTER_MOTION_MASK
| GDK_LEAVE_NOTIFY_MASK
);
7145 g_signal_connect(G_OBJECT(event
), "button-press-event",
7146 G_CALLBACK(icon_menu
), gtkconv
);
7148 pidgin_tooltip_setup_for_widget(event
, gtkconv
, pidgin_conv_create_tooltip
, NULL
);
7149 gtk_widget_show(event
);
7151 gtkconv
->u
.im
->icon
= gtk_image_new_from_pixbuf(scale
);
7152 gtk_container_add(GTK_CONTAINER(event
), gtkconv
->u
.im
->icon
);
7153 gtk_widget_show(gtkconv
->u
.im
->icon
);
7155 g_object_unref(G_OBJECT(scale
));
7157 /* The buddy icon code needs badly to be fixed. */
7158 if(pidgin_conv_window_is_active_conversation(conv
))
7160 buf
= gdk_pixbuf_animation_get_static_image(gtkconv
->u
.im
->anim
);
7161 if (buddy
&& !PURPLE_BUDDY_IS_ONLINE(buddy
))
7162 gdk_pixbuf_saturate_and_pixelate(buf
, buf
, 0.0, FALSE
);
7163 gtk_window_set_icon(GTK_WINDOW(win
->window
), buf
);
7168 pidgin_conv_update_buttons_by_protocol(PurpleConversation
*conv
)
7172 if (!PIDGIN_IS_PIDGIN_CONVERSATION(conv
))
7175 win
= PIDGIN_CONVERSATION(conv
)->win
;
7177 if (win
!= NULL
&& pidgin_conv_window_is_active_conversation(conv
))
7178 gray_stuff_out(PIDGIN_CONVERSATION(conv
));
7182 pidgin_conv_xy_to_right_infopane(PidginWindow
*win
, int x
, int y
)
7184 gint pane_x
, pane_y
, x_rel
;
7185 PidginConversation
*gtkconv
;
7187 gdk_window_get_origin(win
->notebook
->window
, &pane_x
, &pane_y
);
7189 gtkconv
= pidgin_conv_window_get_active_gtkconv(win
);
7190 return (x_rel
> gtkconv
->infopane
->allocation
.x
+ gtkconv
->infopane
->allocation
.width
/ 2);
7194 pidgin_conv_get_tab_at_xy(PidginWindow
*win
, int x
, int y
, gboolean
*to_right
)
7196 gint nb_x
, nb_y
, x_rel
, y_rel
;
7197 GtkNotebook
*notebook
;
7198 GtkWidget
*page
, *tab
;
7199 gint i
, page_num
= -1;
7206 notebook
= GTK_NOTEBOOK(win
->notebook
);
7208 gdk_window_get_origin(win
->notebook
->window
, &nb_x
, &nb_y
);
7212 horiz
= (gtk_notebook_get_tab_pos(notebook
) == GTK_POS_TOP
||
7213 gtk_notebook_get_tab_pos(notebook
) == GTK_POS_BOTTOM
);
7215 count
= gtk_notebook_get_n_pages(GTK_NOTEBOOK(notebook
));
7217 for (i
= 0; i
< count
; i
++) {
7219 page
= gtk_notebook_get_nth_page(GTK_NOTEBOOK(notebook
), i
);
7220 tab
= gtk_notebook_get_tab_label(GTK_NOTEBOOK(notebook
), page
);
7222 /* Make sure the tab is not hidden beyond an arrow */
7223 if (!GTK_WIDGET_DRAWABLE(tab
) && gtk_notebook_get_show_tabs(notebook
))
7227 if (x_rel
>= tab
->allocation
.x
- PIDGIN_HIG_BOX_SPACE
&&
7228 x_rel
<= tab
->allocation
.x
+ tab
->allocation
.width
+ PIDGIN_HIG_BOX_SPACE
) {
7231 if (to_right
&& x_rel
>= tab
->allocation
.x
+ tab
->allocation
.width
/2)
7237 if (y_rel
>= tab
->allocation
.y
- PIDGIN_HIG_BOX_SPACE
&&
7238 y_rel
<= tab
->allocation
.y
+ tab
->allocation
.height
+ PIDGIN_HIG_BOX_SPACE
) {
7241 if (to_right
&& y_rel
>= tab
->allocation
.y
+ tab
->allocation
.height
/2)
7249 if (page_num
== -1) {
7250 /* Add after the last tab */
7251 page_num
= count
- 1;
7258 close_on_tabs_pref_cb(const char *name
, PurplePrefType type
,
7259 gconstpointer value
, gpointer data
)
7262 PurpleConversation
*conv
;
7263 PidginConversation
*gtkconv
;
7265 for (l
= purple_get_conversations(); l
!= NULL
; l
= l
->next
) {
7266 conv
= (PurpleConversation
*)l
->data
;
7268 if (!PIDGIN_IS_PIDGIN_CONVERSATION(conv
))
7271 gtkconv
= PIDGIN_CONVERSATION(conv
);
7274 gtk_widget_show(gtkconv
->close
);
7276 gtk_widget_hide(gtkconv
->close
);
7281 spellcheck_pref_cb(const char *name
, PurplePrefType type
,
7282 gconstpointer value
, gpointer data
)
7286 PurpleConversation
*conv
;
7287 PidginConversation
*gtkconv
;
7290 for (cl
= purple_get_conversations(); cl
!= NULL
; cl
= cl
->next
) {
7292 conv
= (PurpleConversation
*)cl
->data
;
7294 if (!PIDGIN_IS_PIDGIN_CONVERSATION(conv
))
7297 gtkconv
= PIDGIN_CONVERSATION(conv
);
7300 pidgin_setup_gtkspell(GTK_TEXT_VIEW(gtkconv
->entry
));
7302 spell
= gtkspell_get_from_text_view(GTK_TEXT_VIEW(gtkconv
->entry
));
7304 gtkspell_detach(spell
);
7311 tab_side_pref_cb(const char *name
, PurplePrefType type
,
7312 gconstpointer value
, gpointer data
)
7314 GList
*gtkwins
, *gtkconvs
;
7315 GtkPositionType pos
;
7316 PidginWindow
*gtkwin
;
7318 pos
= GPOINTER_TO_INT(value
);
7320 for (gtkwins
= pidgin_conv_windows_get_list(); gtkwins
!= NULL
; gtkwins
= gtkwins
->next
) {
7321 gtkwin
= gtkwins
->data
;
7322 gtk_notebook_set_tab_pos(GTK_NOTEBOOK(gtkwin
->notebook
), pos
&~8);
7323 for (gtkconvs
= gtkwin
->gtkconvs
; gtkconvs
!= NULL
; gtkconvs
= gtkconvs
->next
) {
7324 pidgin_conv_tab_pack(gtkwin
, gtkconvs
->data
);
7330 show_timestamps_pref_cb(const char *name
, PurplePrefType type
,
7331 gconstpointer value
, gpointer data
)
7334 PurpleConversation
*conv
;
7335 PidginConversation
*gtkconv
;
7338 for (l
= purple_get_conversations(); l
!= NULL
; l
= l
->next
)
7340 conv
= (PurpleConversation
*)l
->data
;
7342 if (!PIDGIN_IS_PIDGIN_CONVERSATION(conv
))
7345 gtkconv
= PIDGIN_CONVERSATION(conv
);
7348 gtk_check_menu_item_set_active(
7349 GTK_CHECK_MENU_ITEM(win
->menu
.show_timestamps
),
7350 (gboolean
)GPOINTER_TO_INT(value
));
7352 gtk_imhtml_show_comments(GTK_IMHTML(gtkconv
->imhtml
),
7353 (gboolean
)GPOINTER_TO_INT(value
));
7358 show_formatting_toolbar_pref_cb(const char *name
, PurplePrefType type
,
7359 gconstpointer value
, gpointer data
)
7362 PurpleConversation
*conv
;
7363 PidginConversation
*gtkconv
;
7366 for (l
= purple_get_conversations(); l
!= NULL
; l
= l
->next
)
7368 conv
= (PurpleConversation
*)l
->data
;
7370 if (!PIDGIN_IS_PIDGIN_CONVERSATION(conv
))
7373 gtkconv
= PIDGIN_CONVERSATION(conv
);
7376 gtk_check_menu_item_set_active(
7377 GTK_CHECK_MENU_ITEM(win
->menu
.show_formatting_toolbar
),
7378 (gboolean
)GPOINTER_TO_INT(value
));
7380 if ((gboolean
)GPOINTER_TO_INT(value
))
7381 gtk_widget_show(gtkconv
->toolbar
);
7383 gtk_widget_hide(gtkconv
->toolbar
);
7385 g_idle_add((GSourceFunc
)resize_imhtml_cb
,gtkconv
);
7390 animate_buddy_icons_pref_cb(const char *name
, PurplePrefType type
,
7391 gconstpointer value
, gpointer data
)
7394 PurpleConversation
*conv
;
7395 PidginConversation
*gtkconv
;
7398 if (!purple_prefs_get_bool(PIDGIN_PREFS_ROOT
"/conversations/im/show_buddy_icons"))
7401 /* Set the "animate" flag for each icon based on the new preference */
7402 for (l
= purple_get_ims(); l
!= NULL
; l
= l
->next
) {
7403 conv
= (PurpleConversation
*)l
->data
;
7404 gtkconv
= PIDGIN_CONVERSATION(conv
);
7406 gtkconv
->u
.im
->animate
= GPOINTER_TO_INT(value
);
7409 /* Now either stop or start animation for the active conversation in each window */
7410 for (l
= pidgin_conv_windows_get_list(); l
!= NULL
; l
= l
->next
) {
7412 conv
= pidgin_conv_window_get_active_conversation(win
);
7413 pidgin_conv_update_buddy_icon(conv
);
7418 show_buddy_icons_pref_cb(const char *name
, PurplePrefType type
,
7419 gconstpointer value
, gpointer data
)
7423 for (l
= purple_get_conversations(); l
!= NULL
; l
= l
->next
) {
7424 PurpleConversation
*conv
= l
->data
;
7425 if (!PIDGIN_CONVERSATION(conv
))
7427 if (GPOINTER_TO_INT(value
))
7428 gtk_widget_show(PIDGIN_CONVERSATION(conv
)->infopane_hbox
);
7430 gtk_widget_hide(PIDGIN_CONVERSATION(conv
)->infopane_hbox
);
7432 if (purple_conversation_get_type(conv
) == PURPLE_CONV_TYPE_IM
) {
7433 pidgin_conv_update_buddy_icon(conv
);
7437 /* Make the tabs show/hide correctly */
7438 for (l
= pidgin_conv_windows_get_list(); l
!= NULL
; l
= l
->next
) {
7439 PidginWindow
*win
= l
->data
;
7440 if (pidgin_conv_window_get_gtkconv_count(win
) == 1)
7441 gtk_notebook_set_show_tabs(GTK_NOTEBOOK(win
->notebook
),
7442 GPOINTER_TO_INT(value
) == 0);
7447 show_protocol_icons_pref_cb(const char *name
, PurplePrefType type
,
7448 gconstpointer value
, gpointer data
)
7451 for (l
= purple_get_conversations(); l
!= NULL
; l
= l
->next
) {
7452 PurpleConversation
*conv
= l
->data
;
7453 if (PIDGIN_CONVERSATION(conv
))
7454 update_tab_icon(conv
);
7459 conv_placement_usetabs_cb(const char *name
, PurplePrefType type
,
7460 gconstpointer value
, gpointer data
)
7462 purple_prefs_trigger_callback(PIDGIN_PREFS_ROOT
"/conversations/placement");
7466 account_status_changed_cb(PurpleAccount
*account
, PurpleStatus
*oldstatus
,
7467 PurpleStatus
*newstatus
)
7470 PurpleConversation
*conv
= NULL
;
7471 PidginConversation
*gtkconv
;
7473 if(!purple_strequal(purple_prefs_get_string(PIDGIN_PREFS_ROOT
"/conversations/im/hide_new"), "away"))
7476 if(purple_status_is_available(oldstatus
) || !purple_status_is_available(newstatus
))
7479 for (l
= hidden_convwin
->gtkconvs
; l
; ) {
7483 conv
= gtkconv
->active_conv
;
7484 if (purple_conversation_get_type(conv
) == PURPLE_CONV_TYPE_CHAT
||
7485 account
!= purple_conversation_get_account(conv
))
7488 pidgin_conv_attach_to_conversation(conv
);
7490 /* TODO: do we need to do anything for any other conversations that are in the same gtkconv here?
7491 * I'm a little concerned that not doing so will cause the "pending" indicator in the gtkblist not to be cleared. -DAA*/
7492 purple_conversation_update(conv
, PURPLE_CONV_UPDATE_UNSEEN
);
7497 hide_new_pref_cb(const char *name
, PurplePrefType type
,
7498 gconstpointer value
, gpointer data
)
7501 PurpleConversation
*conv
= NULL
;
7502 PidginConversation
*gtkconv
;
7503 gboolean when_away
= FALSE
;
7508 if(purple_strequal(purple_prefs_get_string(PIDGIN_PREFS_ROOT
"/conversations/im/hide_new"), "always"))
7511 if(purple_strequal(purple_prefs_get_string(PIDGIN_PREFS_ROOT
"/conversations/im/hide_new"), "away"))
7514 for (l
= hidden_convwin
->gtkconvs
; l
; )
7519 conv
= gtkconv
->active_conv
;
7521 if (conv
->type
== PURPLE_CONV_TYPE_CHAT
||
7522 gtkconv
->unseen_count
== 0 ||
7523 (when_away
&& !purple_status_is_available(
7524 purple_account_get_active_status(
7525 purple_conversation_get_account(conv
)))))
7528 pidgin_conv_attach_to_conversation(conv
);
7534 conv_placement_pref_cb(const char *name
, PurplePrefType type
,
7535 gconstpointer value
, gpointer data
)
7537 PidginConvPlacementFunc func
;
7539 if (!purple_strequal(name
, PIDGIN_PREFS_ROOT
"/conversations/placement"))
7542 func
= pidgin_conv_placement_get_fnc(value
);
7547 pidgin_conv_placement_set_current_func(func
);
7550 static PidginConversation
*
7551 get_gtkconv_with_contact(PurpleContact
*contact
)
7553 PurpleBlistNode
*node
;
7555 node
= ((PurpleBlistNode
*)contact
)->child
;
7557 for (; node
; node
= node
->next
)
7559 PurpleBuddy
*buddy
= (PurpleBuddy
*)node
;
7560 PurpleConversation
*conv
;
7561 conv
= purple_find_conversation_with_account(PURPLE_CONV_TYPE_IM
, buddy
->name
, buddy
->account
);
7563 return PIDGIN_CONVERSATION(conv
);
7569 account_signed_off_cb(PurpleConnection
*gc
, gpointer event
)
7573 for (iter
= purple_get_conversations(); iter
; iter
= iter
->next
)
7575 PurpleConversation
*conv
= iter
->data
;
7577 /* This seems fine in theory, but we also need to cover the
7578 * case of this account matching one of the other buddies in
7579 * one of the contacts containing the buddy corresponding to
7580 * a conversation. It's easier to just update them all. */
7581 /* if (purple_conversation_get_account(conv) == account) */
7582 pidgin_conv_update_fields(conv
, PIDGIN_CONV_TAB_ICON
|
7583 PIDGIN_CONV_MENU
| PIDGIN_CONV_COLORIZE_TITLE
);
7585 if (PURPLE_CONNECTION_IS_CONNECTED(gc
) &&
7586 conv
->type
== PURPLE_CONV_TYPE_CHAT
&&
7587 conv
->account
== gc
->account
&&
7588 purple_conversation_get_data(conv
, "want-to-rejoin")) {
7589 GHashTable
*comps
= NULL
;
7590 PurpleChat
*chat
= purple_blist_find_chat(conv
->account
, conv
->name
);
7592 if (PURPLE_PLUGIN_PROTOCOL_INFO(gc
->prpl
)->chat_info_defaults
!= NULL
)
7593 comps
= PURPLE_PLUGIN_PROTOCOL_INFO(gc
->prpl
)->chat_info_defaults(gc
, conv
->name
);
7595 comps
= chat
->components
;
7597 serv_join_chat(gc
, comps
);
7598 if (chat
== NULL
&& comps
!= NULL
)
7599 g_hash_table_destroy(comps
);
7605 account_signing_off(PurpleConnection
*gc
)
7607 GList
*list
= purple_get_chats();
7608 PurpleAccount
*account
= purple_connection_get_account(gc
);
7610 /* We are about to sign off. See which chats we are currently in, and mark
7611 * them for rejoin on reconnect. */
7613 PurpleConversation
*conv
= list
->data
;
7614 if (!purple_conv_chat_has_left(PURPLE_CONV_CHAT(conv
)) &&
7615 purple_conversation_get_account(conv
) == account
) {
7616 purple_conversation_set_data(conv
, "want-to-rejoin", GINT_TO_POINTER(TRUE
));
7617 purple_conversation_write(conv
, NULL
, _("The account has disconnected and you are no "
7618 "longer in this chat. You will automatically rejoin the chat when "
7619 "the account reconnects."),
7620 PURPLE_MESSAGE_SYSTEM
, time(NULL
));
7627 update_buddy_status_changed(PurpleBuddy
*buddy
, PurpleStatus
*old
, PurpleStatus
*newstatus
)
7629 PidginConversation
*gtkconv
;
7630 PurpleConversation
*conv
;
7632 gtkconv
= get_gtkconv_with_contact(purple_buddy_get_contact(buddy
));
7635 conv
= gtkconv
->active_conv
;
7636 pidgin_conv_update_fields(conv
, PIDGIN_CONV_TAB_ICON
7637 | PIDGIN_CONV_COLORIZE_TITLE
7638 | PIDGIN_CONV_BUDDY_ICON
);
7639 if ((purple_status_is_online(old
) ^ purple_status_is_online(newstatus
)) != 0)
7640 pidgin_conv_update_fields(conv
, PIDGIN_CONV_MENU
);
7645 update_buddy_privacy_changed(PurpleBuddy
*buddy
)
7647 PidginConversation
*gtkconv
;
7648 PurpleConversation
*conv
;
7650 gtkconv
= get_gtkconv_with_contact(purple_buddy_get_contact(buddy
));
7652 conv
= gtkconv
->active_conv
;
7653 pidgin_conv_update_fields(conv
, PIDGIN_CONV_TAB_ICON
| PIDGIN_CONV_MENU
);
7658 update_buddy_idle_changed(PurpleBuddy
*buddy
, gboolean old
, gboolean newidle
)
7660 PurpleConversation
*conv
;
7662 conv
= purple_find_conversation_with_account(PURPLE_CONV_TYPE_IM
, buddy
->name
, buddy
->account
);
7664 pidgin_conv_update_fields(conv
, PIDGIN_CONV_TAB_ICON
);
7668 update_buddy_icon(PurpleBuddy
*buddy
)
7670 PurpleConversation
*conv
;
7672 conv
= purple_find_conversation_with_account(PURPLE_CONV_TYPE_IM
, buddy
->name
, buddy
->account
);
7674 pidgin_conv_update_fields(conv
, PIDGIN_CONV_BUDDY_ICON
);
7678 update_buddy_sign(PurpleBuddy
*buddy
, const char *which
)
7680 PurplePresence
*presence
;
7681 PurpleStatus
*on
, *off
;
7683 presence
= purple_buddy_get_presence(buddy
);
7686 off
= purple_presence_get_status(presence
, "offline");
7687 on
= purple_presence_get_status(presence
, "available");
7689 if (*(which
+1) == 'f')
7690 update_buddy_status_changed(buddy
, on
, off
);
7692 update_buddy_status_changed(buddy
, off
, on
);
7696 update_conversation_switched(PurpleConversation
*conv
)
7698 pidgin_conv_update_fields(conv
, PIDGIN_CONV_TAB_ICON
| PIDGIN_CONV_SET_TITLE
|
7699 PIDGIN_CONV_MENU
| PIDGIN_CONV_BUDDY_ICON
);
7703 update_buddy_typing(PurpleAccount
*account
, const char *who
)
7705 PurpleConversation
*conv
;
7706 PidginConversation
*gtkconv
;
7708 conv
= purple_find_conversation_with_account(PURPLE_CONV_TYPE_IM
, who
, account
);
7712 gtkconv
= PIDGIN_CONVERSATION(conv
);
7713 if (gtkconv
&& gtkconv
->active_conv
== conv
)
7714 pidgin_conv_update_fields(conv
, PIDGIN_CONV_COLORIZE_TITLE
);
7718 update_chat(PurpleConversation
*conv
)
7720 pidgin_conv_update_fields(conv
, PIDGIN_CONV_TOPIC
|
7721 PIDGIN_CONV_MENU
| PIDGIN_CONV_SET_TITLE
);
7725 update_chat_topic(PurpleConversation
*conv
, const char *old
, const char *new)
7727 pidgin_conv_update_fields(conv
, PIDGIN_CONV_TOPIC
);
7730 /* Message history stuff */
7732 /* Compare two PurpleConvMessage's, according to time in ascending order. */
7734 message_compare(gconstpointer p1
, gconstpointer p2
)
7736 const PurpleConvMessage
*m1
= p1
, *m2
= p2
;
7737 return (m1
->when
> m2
->when
);
7740 /* Adds some message history to the gtkconv. This happens in a idle-callback. */
7742 add_message_history_to_gtkconv(gpointer data
)
7744 PidginConversation
*gtkconv
= data
;
7746 int timer
= gtkconv
->attach
.timer
;
7747 time_t when
= GPOINTER_TO_INT(g_object_get_data(G_OBJECT(gtkconv
->entry
), "attach-start-time"));
7748 gboolean im
= (gtkconv
->active_conv
->type
== PURPLE_CONV_TYPE_IM
);
7750 gtkconv
->attach
.timer
= 0;
7751 while (gtkconv
->attach
.current
&& count
< 100) { /* XXX: 100 is a random value here */
7752 PurpleConvMessage
*msg
= gtkconv
->attach
.current
->data
;
7753 if (!im
&& when
&& when
< msg
->when
) {
7754 gtk_imhtml_append_text(GTK_IMHTML(gtkconv
->imhtml
), "<BR><HR>", 0);
7755 g_object_set_data(G_OBJECT(gtkconv
->entry
), "attach-start-time", NULL
);
7757 pidgin_conv_write_conv(msg
->conv
, msg
->who
, msg
->alias
, msg
->what
, msg
->flags
, msg
->when
);
7759 gtkconv
->attach
.current
= g_list_delete_link(gtkconv
->attach
.current
, gtkconv
->attach
.current
);
7761 gtkconv
->attach
.current
= gtkconv
->attach
.current
->prev
;
7765 gtkconv
->attach
.timer
= timer
;
7766 if (gtkconv
->attach
.current
)
7769 g_source_remove(gtkconv
->attach
.timer
);
7770 gtkconv
->attach
.timer
= 0;
7772 /* Print any message that was sent while the old history was being added back. */
7774 GList
*iter
= gtkconv
->convs
;
7775 for (; iter
; iter
= iter
->next
) {
7776 PurpleConversation
*conv
= iter
->data
;
7777 GList
*history
= purple_conversation_get_message_history(conv
);
7778 for (; history
; history
= history
->next
) {
7779 PurpleConvMessage
*msg
= history
->data
;
7780 if (msg
->when
> when
)
7781 msgs
= g_list_prepend(msgs
, msg
);
7784 msgs
= g_list_sort(msgs
, message_compare
);
7785 for (; msgs
; msgs
= g_list_delete_link(msgs
, msgs
)) {
7786 PurpleConvMessage
*msg
= msgs
->data
;
7787 pidgin_conv_write_conv(msg
->conv
, msg
->who
, msg
->alias
, msg
->what
, msg
->flags
, msg
->when
);
7789 gtk_imhtml_append_text(GTK_IMHTML(gtkconv
->imhtml
), "<BR><HR>", 0);
7790 g_object_set_data(G_OBJECT(gtkconv
->entry
), "attach-start-time", NULL
);
7793 g_object_set_data(G_OBJECT(gtkconv
->entry
), "attach-start-time", NULL
);
7794 purple_signal_emit(pidgin_conversations_get_handle(),
7795 "conversation-displayed", gtkconv
);
7800 pidgin_conv_attach(PurpleConversation
*conv
)
7803 purple_conversation_set_data(conv
, "unseen-count", NULL
);
7804 purple_conversation_set_data(conv
, "unseen-state", NULL
);
7805 purple_conversation_set_ui_ops(conv
, pidgin_conversations_get_conv_ui_ops());
7806 if (!PIDGIN_CONVERSATION(conv
))
7807 private_gtkconv_new(conv
, FALSE
);
7808 timer
= GPOINTER_TO_INT(purple_conversation_get_data(conv
, "close-timer"));
7810 purple_timeout_remove(timer
);
7811 purple_conversation_set_data(conv
, "close-timer", NULL
);
7815 gboolean
pidgin_conv_attach_to_conversation(PurpleConversation
*conv
)
7818 PidginConversation
*gtkconv
;
7820 if (PIDGIN_IS_PIDGIN_CONVERSATION(conv
)) {
7821 /* This is pretty much always the case now. */
7822 gtkconv
= PIDGIN_CONVERSATION(conv
);
7823 if (gtkconv
->win
!= hidden_convwin
)
7825 pidgin_conv_window_remove_gtkconv(hidden_convwin
, gtkconv
);
7826 pidgin_conv_placement_place(gtkconv
);
7827 purple_signal_emit(pidgin_conversations_get_handle(),
7828 "conversation-displayed", gtkconv
);
7829 list
= gtkconv
->convs
;
7831 pidgin_conv_attach(list
->data
);
7837 pidgin_conv_attach(conv
);
7838 gtkconv
= PIDGIN_CONVERSATION(conv
);
7840 list
= purple_conversation_get_message_history(conv
);
7842 switch (purple_conversation_get_type(conv
)) {
7843 case PURPLE_CONV_TYPE_IM
:
7846 list
= g_list_copy(list
);
7847 for (convs
= purple_get_ims(); convs
; convs
= convs
->next
)
7848 if (convs
->data
!= conv
&&
7849 pidgin_conv_find_gtkconv(convs
->data
) == gtkconv
) {
7850 pidgin_conv_attach(convs
->data
);
7851 list
= g_list_concat(list
, g_list_copy(purple_conversation_get_message_history(convs
->data
)));
7853 list
= g_list_sort(list
, message_compare
);
7854 gtkconv
->attach
.current
= list
;
7855 list
= g_list_last(list
);
7858 case PURPLE_CONV_TYPE_CHAT
:
7859 gtkconv
->attach
.current
= g_list_last(list
);
7862 g_return_val_if_reached(TRUE
);
7864 g_object_set_data(G_OBJECT(gtkconv
->entry
), "attach-start-time",
7865 GINT_TO_POINTER(((PurpleConvMessage
*)(list
->data
))->when
));
7866 gtkconv
->attach
.timer
= g_idle_add(add_message_history_to_gtkconv
, gtkconv
);
7868 purple_signal_emit(pidgin_conversations_get_handle(),
7869 "conversation-displayed", gtkconv
);
7872 if (conv
->type
== PURPLE_CONV_TYPE_CHAT
) {
7873 pidgin_conv_update_fields(conv
, PIDGIN_CONV_TOPIC
);
7874 pidgin_conv_chat_add_users(conv
, PURPLE_CONV_CHAT(conv
)->in_room
, TRUE
);
7881 pidgin_conversations_get_handle(void)
7889 pidgin_conversations_init(void)
7891 void *handle
= pidgin_conversations_get_handle();
7892 void *blist_handle
= purple_blist_get_handle();
7895 purple_prefs_add_none(PIDGIN_PREFS_ROOT
"/conversations");
7896 purple_prefs_add_bool(PIDGIN_PREFS_ROOT
"/conversations/use_smooth_scrolling", TRUE
);
7897 purple_prefs_add_bool(PIDGIN_PREFS_ROOT
"/conversations/close_on_tabs", TRUE
);
7898 purple_prefs_add_bool(PIDGIN_PREFS_ROOT
"/conversations/send_bold", FALSE
);
7899 purple_prefs_add_bool(PIDGIN_PREFS_ROOT
"/conversations/send_italic", FALSE
);
7900 purple_prefs_add_bool(PIDGIN_PREFS_ROOT
"/conversations/send_underline", FALSE
);
7901 purple_prefs_add_bool(PIDGIN_PREFS_ROOT
"/conversations/spellcheck", TRUE
);
7902 purple_prefs_add_bool(PIDGIN_PREFS_ROOT
"/conversations/show_incoming_formatting", TRUE
);
7903 purple_prefs_add_bool(PIDGIN_PREFS_ROOT
"/conversations/resize_custom_smileys", TRUE
);
7904 purple_prefs_add_int(PIDGIN_PREFS_ROOT
"/conversations/custom_smileys_size", 96);
7905 purple_prefs_add_int(PIDGIN_PREFS_ROOT
"/conversations/minimum_entry_lines", 2);
7907 purple_prefs_add_bool(PIDGIN_PREFS_ROOT
"/conversations/show_timestamps", TRUE
);
7908 purple_prefs_add_bool(PIDGIN_PREFS_ROOT
"/conversations/show_formatting_toolbar", TRUE
);
7910 purple_prefs_add_string(PIDGIN_PREFS_ROOT
"/conversations/placement", "last");
7911 purple_prefs_add_int(PIDGIN_PREFS_ROOT
"/conversations/placement_number", 1);
7912 purple_prefs_add_string(PIDGIN_PREFS_ROOT
"/conversations/bgcolor", "");
7913 purple_prefs_add_string(PIDGIN_PREFS_ROOT
"/conversations/fgcolor", "");
7914 purple_prefs_add_string(PIDGIN_PREFS_ROOT
"/conversations/font_face", "");
7915 purple_prefs_add_int(PIDGIN_PREFS_ROOT
"/conversations/font_size", 3);
7916 purple_prefs_add_bool(PIDGIN_PREFS_ROOT
"/conversations/tabs", TRUE
);
7917 purple_prefs_add_int(PIDGIN_PREFS_ROOT
"/conversations/tab_side", GTK_POS_TOP
);
7918 purple_prefs_add_int(PIDGIN_PREFS_ROOT
"/conversations/scrollback_lines", 4000);
7921 purple_prefs_add_bool(PIDGIN_PREFS_ROOT
"/conversations/use_theme_font", TRUE
);
7922 purple_prefs_add_string(PIDGIN_PREFS_ROOT
"/conversations/custom_font", "");
7925 /* Conversations -> Chat */
7926 purple_prefs_add_none(PIDGIN_PREFS_ROOT
"/conversations/chat");
7927 purple_prefs_add_int(PIDGIN_PREFS_ROOT
"/conversations/chat/entry_height", 54);
7928 purple_prefs_add_int(PIDGIN_PREFS_ROOT
"/conversations/chat/userlist_width", 80);
7929 purple_prefs_add_int(PIDGIN_PREFS_ROOT
"/conversations/chat/x", 0);
7930 purple_prefs_add_int(PIDGIN_PREFS_ROOT
"/conversations/chat/y", 0);
7931 purple_prefs_add_int(PIDGIN_PREFS_ROOT
"/conversations/chat/width", 340);
7932 purple_prefs_add_int(PIDGIN_PREFS_ROOT
"/conversations/chat/height", 390);
7934 /* Conversations -> IM */
7935 purple_prefs_add_none(PIDGIN_PREFS_ROOT
"/conversations/im");
7936 purple_prefs_add_int(PIDGIN_PREFS_ROOT
"/conversations/im/x", 0);
7937 purple_prefs_add_int(PIDGIN_PREFS_ROOT
"/conversations/im/y", 0);
7938 purple_prefs_add_int(PIDGIN_PREFS_ROOT
"/conversations/im/width", 340);
7939 purple_prefs_add_int(PIDGIN_PREFS_ROOT
"/conversations/im/height", 390);
7941 purple_prefs_add_bool(PIDGIN_PREFS_ROOT
"/conversations/im/animate_buddy_icons", TRUE
);
7943 purple_prefs_add_int(PIDGIN_PREFS_ROOT
"/conversations/im/entry_height", 54);
7944 purple_prefs_add_bool(PIDGIN_PREFS_ROOT
"/conversations/im/show_buddy_icons", TRUE
);
7946 purple_prefs_add_string(PIDGIN_PREFS_ROOT
"/conversations/im/hide_new", "never");
7947 purple_prefs_add_bool(PIDGIN_PREFS_ROOT
"/conversations/im/close_immediately", TRUE
);
7950 purple_prefs_add_bool(PIDGIN_PREFS_ROOT
"/win32/minimize_new_convs", FALSE
);
7953 /* Connect callbacks. */
7954 purple_prefs_connect_callback(handle
, PIDGIN_PREFS_ROOT
"/conversations/close_on_tabs",
7955 close_on_tabs_pref_cb
, NULL
);
7956 purple_prefs_connect_callback(handle
, PIDGIN_PREFS_ROOT
"/conversations/show_timestamps",
7957 show_timestamps_pref_cb
, NULL
);
7958 purple_prefs_connect_callback(handle
, PIDGIN_PREFS_ROOT
"/conversations/show_formatting_toolbar",
7959 show_formatting_toolbar_pref_cb
, NULL
);
7960 purple_prefs_connect_callback(handle
, PIDGIN_PREFS_ROOT
"/conversations/spellcheck",
7961 spellcheck_pref_cb
, NULL
);
7962 purple_prefs_connect_callback(handle
, PIDGIN_PREFS_ROOT
"/conversations/tab_side",
7963 tab_side_pref_cb
, NULL
);
7965 purple_prefs_connect_callback(handle
, PIDGIN_PREFS_ROOT
"/conversations/tabs",
7966 conv_placement_usetabs_cb
, NULL
);
7968 purple_prefs_connect_callback(handle
, PIDGIN_PREFS_ROOT
"/conversations/placement",
7969 conv_placement_pref_cb
, NULL
);
7970 purple_prefs_trigger_callback(PIDGIN_PREFS_ROOT
"/conversations/placement");
7972 purple_prefs_connect_callback(handle
, PIDGIN_PREFS_ROOT
"/conversations/minimum_entry_lines",
7973 minimum_entry_lines_pref_cb
, NULL
);
7976 purple_prefs_connect_callback(handle
, PIDGIN_PREFS_ROOT
"/conversations/im/animate_buddy_icons",
7977 animate_buddy_icons_pref_cb
, NULL
);
7978 purple_prefs_connect_callback(handle
, PIDGIN_PREFS_ROOT
"/conversations/im/show_buddy_icons",
7979 show_buddy_icons_pref_cb
, NULL
);
7980 purple_prefs_connect_callback(handle
, PIDGIN_PREFS_ROOT
"/blist/show_protocol_icons",
7981 show_protocol_icons_pref_cb
, NULL
);
7982 purple_prefs_connect_callback(handle
, PIDGIN_PREFS_ROOT
"/conversations/im/hide_new",
7983 hide_new_pref_cb
, NULL
);
7987 /**********************************************************************
7989 **********************************************************************/
7990 purple_signal_register(handle
, "conversation-dragging",
7991 purple_marshal_VOID__POINTER_POINTER
, NULL
, 2,
7992 purple_value_new(PURPLE_TYPE_BOXED
,
7994 purple_value_new(PURPLE_TYPE_BOXED
,
7997 purple_signal_register(handle
, "conversation-timestamp",
7998 #if SIZEOF_TIME_T == 4
7999 purple_marshal_POINTER__POINTER_INT_BOOLEAN
,
8000 #elif SIZEOF_TIME_T == 8
8001 purple_marshal_POINTER__POINTER_INT64_BOOLEAN
,
8003 #error Unkown size of time_t
8005 purple_value_new(PURPLE_TYPE_STRING
), 3,
8006 purple_value_new(PURPLE_TYPE_SUBTYPE
,
8007 PURPLE_SUBTYPE_CONVERSATION
),
8008 #if SIZEOF_TIME_T == 4
8009 purple_value_new(PURPLE_TYPE_INT
),
8010 #elif SIZEOF_TIME_T == 8
8011 purple_value_new(PURPLE_TYPE_INT64
),
8013 # error Unknown size of time_t
8015 purple_value_new(PURPLE_TYPE_BOOLEAN
));
8017 purple_signal_register(handle
, "displaying-im-msg",
8018 purple_marshal_BOOLEAN__POINTER_POINTER_POINTER_POINTER_POINTER
,
8019 purple_value_new(PURPLE_TYPE_BOOLEAN
), 5,
8020 purple_value_new(PURPLE_TYPE_SUBTYPE
,
8021 PURPLE_SUBTYPE_ACCOUNT
),
8022 purple_value_new(PURPLE_TYPE_STRING
),
8023 purple_value_new_outgoing(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
, "displayed-im-msg",
8029 purple_marshal_VOID__POINTER_POINTER_POINTER_POINTER_UINT
,
8031 purple_value_new(PURPLE_TYPE_SUBTYPE
,
8032 PURPLE_SUBTYPE_ACCOUNT
),
8033 purple_value_new(PURPLE_TYPE_STRING
),
8034 purple_value_new(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
, "displaying-chat-msg",
8040 purple_marshal_BOOLEAN__POINTER_POINTER_POINTER_POINTER_POINTER
,
8041 purple_value_new(PURPLE_TYPE_BOOLEAN
), 5,
8042 purple_value_new(PURPLE_TYPE_SUBTYPE
,
8043 PURPLE_SUBTYPE_ACCOUNT
),
8044 purple_value_new(PURPLE_TYPE_STRING
),
8045 purple_value_new_outgoing(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
, "displayed-chat-msg",
8051 purple_marshal_VOID__POINTER_POINTER_POINTER_POINTER_UINT
,
8053 purple_value_new(PURPLE_TYPE_SUBTYPE
,
8054 PURPLE_SUBTYPE_ACCOUNT
),
8055 purple_value_new(PURPLE_TYPE_STRING
),
8056 purple_value_new(PURPLE_TYPE_STRING
),
8057 purple_value_new(PURPLE_TYPE_SUBTYPE
,
8058 PURPLE_SUBTYPE_CONVERSATION
),
8059 purple_value_new(PURPLE_TYPE_INT
));
8061 purple_signal_register(handle
, "conversation-switched",
8062 purple_marshal_VOID__POINTER
, NULL
, 1,
8063 purple_value_new(PURPLE_TYPE_SUBTYPE
,
8064 PURPLE_SUBTYPE_CONVERSATION
));
8066 purple_signal_register(handle
, "conversation-hiding",
8067 purple_marshal_VOID__POINTER
, NULL
, 1,
8068 purple_value_new(PURPLE_TYPE_BOXED
,
8069 "PidginConversation *"));
8071 purple_signal_register(handle
, "conversation-displayed",
8072 purple_marshal_VOID__POINTER
, NULL
, 1,
8073 purple_value_new(PURPLE_TYPE_BOXED
,
8074 "PidginConversation *"));
8076 purple_signal_register(handle
, "chat-nick-autocomplete",
8077 purple_marshal_BOOLEAN__POINTER_BOOLEAN
,
8078 purple_value_new(PURPLE_TYPE_BOOLEAN
), 1,
8079 purple_value_new(PURPLE_TYPE_SUBTYPE
,
8080 PURPLE_SUBTYPE_CONVERSATION
));
8082 purple_signal_register(handle
, "chat-nick-clicked",
8083 purple_marshal_BOOLEAN__POINTER_POINTER_UINT
,
8084 purple_value_new(PURPLE_TYPE_BOOLEAN
), 3,
8085 purple_value_new(PURPLE_TYPE_SUBTYPE
,
8086 PURPLE_SUBTYPE_CONVERSATION
),
8087 purple_value_new(PURPLE_TYPE_STRING
),
8088 purple_value_new(PURPLE_TYPE_UINT
));
8091 /**********************************************************************
8093 **********************************************************************/
8094 purple_cmd_register("say", "S", PURPLE_CMD_P_DEFAULT
,
8095 PURPLE_CMD_FLAG_CHAT
| PURPLE_CMD_FLAG_IM
, NULL
,
8096 say_command_cb
, _("say <message>: Send a message normally as if you weren't using a command."), NULL
);
8097 purple_cmd_register("me", "S", PURPLE_CMD_P_DEFAULT
,
8098 PURPLE_CMD_FLAG_CHAT
| PURPLE_CMD_FLAG_IM
, NULL
,
8099 me_command_cb
, _("me <action>: Send an IRC style action to a buddy or chat."), NULL
);
8100 purple_cmd_register("debug", "w", PURPLE_CMD_P_DEFAULT
,
8101 PURPLE_CMD_FLAG_CHAT
| PURPLE_CMD_FLAG_IM
, NULL
,
8102 debug_command_cb
, _("debug <option>: Send various debug information to the current conversation."), NULL
);
8103 purple_cmd_register("clear", "", PURPLE_CMD_P_DEFAULT
,
8104 PURPLE_CMD_FLAG_CHAT
| PURPLE_CMD_FLAG_IM
, NULL
,
8105 clear_command_cb
, _("clear: Clears the conversation scrollback."), NULL
);
8106 purple_cmd_register("clearall", "", PURPLE_CMD_P_DEFAULT
,
8107 PURPLE_CMD_FLAG_CHAT
| PURPLE_CMD_FLAG_IM
, NULL
,
8108 clearall_command_cb
, _("clear: Clears all conversation scrollbacks."), NULL
);
8109 purple_cmd_register("help", "w", PURPLE_CMD_P_DEFAULT
,
8110 PURPLE_CMD_FLAG_CHAT
| PURPLE_CMD_FLAG_IM
| PURPLE_CMD_FLAG_ALLOW_WRONG_ARGS
, NULL
,
8111 help_command_cb
, _("help <command>: Help on a specific command."), NULL
);
8113 /**********************************************************************
8115 **********************************************************************/
8117 purple_signal_connect(purple_connections_get_handle(), "signed-on", handle
,
8118 G_CALLBACK(account_signed_off_cb
),
8119 GINT_TO_POINTER(PURPLE_CONV_ACCOUNT_ONLINE
));
8120 purple_signal_connect(purple_connections_get_handle(), "signed-off", handle
,
8121 G_CALLBACK(account_signed_off_cb
),
8122 GINT_TO_POINTER(PURPLE_CONV_ACCOUNT_OFFLINE
));
8123 purple_signal_connect(purple_connections_get_handle(), "signing-off", handle
,
8124 G_CALLBACK(account_signing_off
), NULL
);
8126 purple_signal_connect(purple_conversations_get_handle(), "received-im-msg",
8127 handle
, G_CALLBACK(received_im_msg_cb
), NULL
);
8128 purple_signal_connect(purple_conversations_get_handle(), "cleared-message-history",
8129 handle
, G_CALLBACK(clear_conversation_scrollback_cb
), NULL
);
8131 purple_signal_connect(purple_conversations_get_handle(), "deleting-chat-buddy",
8132 handle
, G_CALLBACK(deleting_chat_buddy_cb
), NULL
);
8134 purple_conversations_set_ui_ops(&conversation_ui_ops
);
8136 hidden_convwin
= pidgin_conv_window_new();
8137 window_list
= g_list_remove(window_list
, hidden_convwin
);
8139 purple_signal_connect(purple_accounts_get_handle(), "account-status-changed",
8140 handle
, PURPLE_CALLBACK(account_status_changed_cb
), NULL
);
8142 /* Callbacks to update a conversation */
8143 purple_signal_connect(blist_handle
, "blist-node-added", handle
,
8144 G_CALLBACK(buddy_update_cb
), NULL
);
8145 purple_signal_connect(blist_handle
, "blist-node-removed", handle
,
8146 G_CALLBACK(buddy_update_cb
), NULL
);
8147 purple_signal_connect(blist_handle
, "buddy-signed-on",
8148 handle
, PURPLE_CALLBACK(update_buddy_sign
), "on");
8149 purple_signal_connect(blist_handle
, "buddy-signed-off",
8150 handle
, PURPLE_CALLBACK(update_buddy_sign
), "off");
8151 purple_signal_connect(blist_handle
, "buddy-status-changed",
8152 handle
, PURPLE_CALLBACK(update_buddy_status_changed
), NULL
);
8153 purple_signal_connect(blist_handle
, "buddy-privacy-changed",
8154 handle
, PURPLE_CALLBACK(update_buddy_privacy_changed
), NULL
);
8155 purple_signal_connect(blist_handle
, "buddy-idle-changed",
8156 handle
, PURPLE_CALLBACK(update_buddy_idle_changed
), NULL
);
8157 purple_signal_connect(blist_handle
, "buddy-icon-changed",
8158 handle
, PURPLE_CALLBACK(update_buddy_icon
), NULL
);
8159 purple_signal_connect(purple_conversations_get_handle(), "buddy-typing",
8160 handle
, PURPLE_CALLBACK(update_buddy_typing
), NULL
);
8161 purple_signal_connect(purple_conversations_get_handle(), "buddy-typing-stopped",
8162 handle
, PURPLE_CALLBACK(update_buddy_typing
), NULL
);
8163 purple_signal_connect(pidgin_conversations_get_handle(), "conversation-switched",
8164 handle
, PURPLE_CALLBACK(update_conversation_switched
), NULL
);
8165 purple_signal_connect(purple_conversations_get_handle(), "chat-left", handle
,
8166 PURPLE_CALLBACK(update_chat
), NULL
);
8167 purple_signal_connect(purple_conversations_get_handle(), "chat-joined", handle
,
8168 PURPLE_CALLBACK(update_chat
), NULL
);
8169 purple_signal_connect(purple_conversations_get_handle(), "chat-topic-changed", handle
,
8170 PURPLE_CALLBACK(update_chat_topic
), NULL
);
8171 purple_signal_connect_priority(purple_conversations_get_handle(), "conversation-updated", handle
,
8172 PURPLE_CALLBACK(pidgin_conv_updated
), NULL
,
8173 PURPLE_SIGNAL_PRIORITY_LOWEST
);
8174 purple_signal_connect(purple_conversations_get_handle(), "wrote-im-msg", handle
,
8175 PURPLE_CALLBACK(wrote_msg_update_unseen_cb
), NULL
);
8176 purple_signal_connect(purple_conversations_get_handle(), "wrote-chat-msg", handle
,
8177 PURPLE_CALLBACK(wrote_msg_update_unseen_cb
), NULL
);
8180 /* Set default tab colors */
8181 GString
*str
= g_string_new(NULL
);
8182 GtkSettings
*settings
= gtk_settings_get_default();
8183 GtkStyle
*parent
= gtk_rc_get_style_by_paths(settings
, "tab-container.tab-label*", NULL
, G_TYPE_NONE
), *now
;
8185 const char *stylename
;
8186 const char *labelname
;
8189 {"pidgin_tab_label_typing_default", "tab-label-typing", "#4e9a06"},
8190 {"pidgin_tab_label_typed_default", "tab-label-typed", "#c4a000"},
8191 {"pidgin_tab_label_attention_default", "tab-label-attention", "#006aff"},
8192 {"pidgin_tab_label_unreadchat_default", "tab-label-unreadchat", "#cc0000"},
8193 {"pidgin_tab_label_event_default", "tab-label-event", "#888a85"},
8197 for (iter
= 0; styles
[iter
].stylename
; iter
++) {
8198 now
= gtk_rc_get_style_by_paths(settings
, styles
[iter
].labelname
, NULL
, G_TYPE_NONE
);
8199 if (parent
== now
||
8200 (parent
&& now
&& parent
->rc_style
== now
->rc_style
)) {
8201 g_string_append_printf(str
, "style \"%s\" {\n"
8202 "fg[ACTIVE] = \"%s\"\n"
8204 "widget \"*%s\" style \"%s\"\n",
8205 styles
[iter
].stylename
,
8207 styles
[iter
].labelname
, styles
[iter
].stylename
);
8210 gtk_rc_parse_string(str
->str
);
8211 g_string_free(str
, TRUE
);
8212 gtk_rc_reset_styles(settings
);
8217 pidgin_conversations_uninit(void)
8219 purple_prefs_disconnect_by_handle(pidgin_conversations_get_handle());
8220 purple_signals_disconnect_by_handle(pidgin_conversations_get_handle());
8221 purple_signals_unregister_by_instance(pidgin_conversations_get_handle());
8239 /* down here is where gtkconvwin.c ought to start. except they share like every freaking function,
8240 * and touch each others' private members all day long */
8243 * @file gtkconvwin.c GTK+ Conversation Window API
8248 * Pidgin is the legal property of its developers, whose names are too numerous
8249 * to list here. Please refer to the COPYRIGHT file distributed with this
8250 * source distribution.
8252 * This program is free software; you can redistribute it and/or modify
8253 * it under the terms of the GNU General Public License as published by
8254 * the Free Software Foundation; either version 2 of the License, or
8255 * (at your option) any later version.
8257 * This program is distributed in the hope that it will be useful,
8258 * but WITHOUT ANY WARRANTY; without even the implied warranty of
8259 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
8260 * GNU General Public License for more details.
8262 * You should have received a copy of the GNU General Public License
8263 * along with this program; if not, write to the Free Software
8264 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA
8267 #include "internal.h"
8271 #include <gdk/gdkkeysyms.h>
8273 #include "account.h"
8276 #include "imgstore.h"
8280 #include "request.h"
8283 #include "gtkdnd-hints.h"
8284 #include "gtkblist.h"
8285 #include "gtkconv.h"
8286 #include "gtkdialogs.h"
8287 #include "gtkmenutray.h"
8288 #include "gtkpounce.h"
8289 #include "gtkprefs.h"
8290 #include "gtkprivacy.h"
8291 #include "gtkutils.h"
8292 #include "pidginstock.h"
8293 #include "gtkimhtml.h"
8294 #include "gtkimhtmltoolbar.h"
8297 do_close(GtkWidget
*w
, int resp
, PidginWindow
*win
)
8299 gtk_widget_destroy(warn_close_dialog
);
8300 warn_close_dialog
= NULL
;
8302 if (resp
== GTK_RESPONSE_OK
)
8303 pidgin_conv_window_destroy(win
);
8307 build_warn_close_dialog(PidginWindow
*gtkwin
)
8309 GtkWidget
*label
, *vbox
, *hbox
, *img
;
8311 g_return_if_fail(warn_close_dialog
== NULL
);
8313 warn_close_dialog
= gtk_dialog_new_with_buttons(_("Confirm close"),
8314 GTK_WINDOW(gtkwin
->window
), GTK_DIALOG_MODAL
,
8315 GTK_STOCK_CANCEL
, GTK_RESPONSE_CANCEL
,
8316 GTK_STOCK_CLOSE
, GTK_RESPONSE_OK
, NULL
);
8318 gtk_dialog_set_default_response(GTK_DIALOG(warn_close_dialog
),
8321 gtk_container_set_border_width(GTK_CONTAINER(warn_close_dialog
),
8323 gtk_window_set_resizable(GTK_WINDOW(warn_close_dialog
), FALSE
);
8324 gtk_dialog_set_has_separator(GTK_DIALOG(warn_close_dialog
),
8327 /* Setup the outside spacing. */
8328 vbox
= GTK_DIALOG(warn_close_dialog
)->vbox
;
8330 gtk_box_set_spacing(GTK_BOX(vbox
), 12);
8331 gtk_container_set_border_width(GTK_CONTAINER(vbox
), 6);
8333 img
= gtk_image_new_from_stock(PIDGIN_STOCK_DIALOG_WARNING
,
8334 gtk_icon_size_from_name(PIDGIN_ICON_SIZE_TANGO_HUGE
));
8335 /* Setup the inner hbox and put the dialog's icon in it. */
8336 hbox
= gtk_hbox_new(FALSE
, 12);
8337 gtk_container_add(GTK_CONTAINER(vbox
), hbox
);
8338 gtk_box_pack_start(GTK_BOX(hbox
), img
, FALSE
, FALSE
, 0);
8339 gtk_misc_set_alignment(GTK_MISC(img
), 0, 0);
8341 /* Setup the right vbox. */
8342 vbox
= gtk_vbox_new(FALSE
, 12);
8343 gtk_container_add(GTK_CONTAINER(hbox
), vbox
);
8345 label
= gtk_label_new(_("You have unread messages. Are you sure you want to close the window?"));
8346 gtk_widget_set_size_request(label
, 350, -1);
8347 gtk_label_set_line_wrap(GTK_LABEL(label
), TRUE
);
8348 gtk_misc_set_alignment(GTK_MISC(label
), 0, 0);
8349 gtk_box_pack_start(GTK_BOX(vbox
), label
, FALSE
, FALSE
, 0);
8351 /* Connect the signals. */
8352 g_signal_connect(G_OBJECT(warn_close_dialog
), "response",
8353 G_CALLBACK(do_close
), gtkwin
);
8357 /**************************************************************************
8359 **************************************************************************/
8362 close_win_cb(GtkWidget
*w
, GdkEventAny
*e
, gpointer d
)
8364 PidginWindow
*win
= d
;
8367 /* If there are unread messages then show a warning dialog */
8368 for (l
= pidgin_conv_window_get_gtkconvs(win
);
8369 l
!= NULL
; l
= l
->next
)
8371 PidginConversation
*gtkconv
= l
->data
;
8372 if (purple_conversation_get_type(gtkconv
->active_conv
) == PURPLE_CONV_TYPE_IM
&&
8373 gtkconv
->unseen_state
>= PIDGIN_UNSEEN_TEXT
)
8375 build_warn_close_dialog(win
);
8376 gtk_widget_show_all(warn_close_dialog
);
8382 pidgin_conv_window_destroy(win
);
8388 conv_set_unseen(PurpleConversation
*conv
, PidginUnseenState state
)
8390 int unseen_count
= 0;
8391 PidginUnseenState unseen_state
= PIDGIN_UNSEEN_NONE
;
8393 if(purple_conversation_get_data(conv
, "unseen-count"))
8394 unseen_count
= GPOINTER_TO_INT(purple_conversation_get_data(conv
, "unseen-count"));
8396 if(purple_conversation_get_data(conv
, "unseen-state"))
8397 unseen_state
= GPOINTER_TO_INT(purple_conversation_get_data(conv
, "unseen-state"));
8399 if (state
== PIDGIN_UNSEEN_NONE
)
8402 unseen_state
= PIDGIN_UNSEEN_NONE
;
8406 if (state
>= PIDGIN_UNSEEN_TEXT
)
8409 if (state
> unseen_state
)
8410 unseen_state
= state
;
8413 purple_conversation_set_data(conv
, "unseen-count", GINT_TO_POINTER(unseen_count
));
8414 purple_conversation_set_data(conv
, "unseen-state", GINT_TO_POINTER(unseen_state
));
8416 purple_conversation_update(conv
, PURPLE_CONV_UPDATE_UNSEEN
);
8420 gtkconv_set_unseen(PidginConversation
*gtkconv
, PidginUnseenState state
)
8422 if (state
== PIDGIN_UNSEEN_NONE
)
8424 gtkconv
->unseen_count
= 0;
8425 gtkconv
->unseen_state
= PIDGIN_UNSEEN_NONE
;
8429 if (state
>= PIDGIN_UNSEEN_TEXT
)
8430 gtkconv
->unseen_count
++;
8432 if (state
> gtkconv
->unseen_state
)
8433 gtkconv
->unseen_state
= state
;
8436 purple_conversation_set_data(gtkconv
->active_conv
, "unseen-count", GINT_TO_POINTER(gtkconv
->unseen_count
));
8437 purple_conversation_set_data(gtkconv
->active_conv
, "unseen-state", GINT_TO_POINTER(gtkconv
->unseen_state
));
8439 purple_conversation_update(gtkconv
->active_conv
, PURPLE_CONV_UPDATE_UNSEEN
);
8443 * When a conversation window is focused, we know the user
8444 * has looked at it so we know there are no longer unseen
8448 focus_win_cb(GtkWidget
*w
, GdkEventFocus
*e
, gpointer d
)
8450 PidginWindow
*win
= d
;
8451 PidginConversation
*gtkconv
= pidgin_conv_window_get_active_gtkconv(win
);
8454 gtkconv_set_unseen(gtkconv
, PIDGIN_UNSEEN_NONE
);
8460 notebook_init_grab(PidginWindow
*gtkwin
, GtkWidget
*widget
)
8462 static GdkCursor
*cursor
= NULL
;
8464 gtkwin
->in_drag
= TRUE
;
8466 if (gtkwin
->drag_leave_signal
) {
8467 g_signal_handler_disconnect(G_OBJECT(widget
),
8468 gtkwin
->drag_leave_signal
);
8469 gtkwin
->drag_leave_signal
= 0;
8473 cursor
= gdk_cursor_new(GDK_FLEUR
);
8475 /* Grab the pointer */
8476 gtk_grab_add(gtkwin
->notebook
);
8478 /* Currently for win32 GTK+ (as of 2.2.1), gdk_pointer_is_grabbed will
8479 always be true after a button press. */
8480 if (!gdk_pointer_is_grabbed())
8482 gdk_pointer_grab(gtkwin
->notebook
->window
, FALSE
,
8483 GDK_BUTTON1_MOTION_MASK
| GDK_BUTTON_RELEASE_MASK
,
8484 NULL
, cursor
, GDK_CURRENT_TIME
);
8488 notebook_motion_cb(GtkWidget
*widget
, GdkEventButton
*e
, PidginWindow
*win
)
8492 * Make sure the user moved the mouse far enough for the
8493 * drag to be initiated.
8495 if (win
->in_predrag
) {
8496 if (e
->x_root
< win
->drag_min_x
||
8497 e
->x_root
>= win
->drag_max_x
||
8498 e
->y_root
< win
->drag_min_y
||
8499 e
->y_root
>= win
->drag_max_y
) {
8501 win
->in_predrag
= FALSE
;
8502 notebook_init_grab(win
, widget
);
8505 else { /* Otherwise, draw the arrows. */
8506 PidginWindow
*dest_win
;
8507 GtkNotebook
*dest_notebook
;
8510 gboolean horiz_tabs
= FALSE
;
8511 gboolean to_right
= FALSE
;
8513 /* Get the window that the cursor is over. */
8514 dest_win
= pidgin_conv_window_get_at_xy(e
->x_root
, e
->y_root
);
8516 if (dest_win
== NULL
) {
8517 dnd_hints_hide_all();
8522 dest_notebook
= GTK_NOTEBOOK(dest_win
->notebook
);
8524 if (gtk_notebook_get_show_tabs(dest_notebook
)) {
8525 page_num
= pidgin_conv_get_tab_at_xy(dest_win
,
8526 e
->x_root
, e
->y_root
, &to_right
);
8527 to_right
= to_right
&& (win
!= dest_win
);
8528 tab
= pidgin_conv_window_get_gtkconv_at_index(dest_win
, page_num
)->tabby
;
8531 to_right
= pidgin_conv_xy_to_right_infopane(dest_win
, e
->x_root
, e
->y_root
);
8532 tab
= pidgin_conv_window_get_gtkconv_at_index(dest_win
, page_num
)->infopane_hbox
;
8535 if (gtk_notebook_get_tab_pos(dest_notebook
) == GTK_POS_TOP
||
8536 gtk_notebook_get_tab_pos(dest_notebook
) == GTK_POS_BOTTOM
) {
8540 if (gtk_notebook_get_show_tabs(dest_notebook
) == FALSE
&& win
== dest_win
)
8542 /* dragging a tab from a single-tabbed window over its own window */
8543 dnd_hints_hide_all();
8545 } else if (horiz_tabs
) {
8546 if (((gpointer
)win
== (gpointer
)dest_win
&& win
->drag_tab
< page_num
) || to_right
) {
8547 dnd_hints_show_relative(HINT_ARROW_DOWN
, tab
, HINT_POSITION_RIGHT
, HINT_POSITION_TOP
);
8548 dnd_hints_show_relative(HINT_ARROW_UP
, tab
, HINT_POSITION_RIGHT
, HINT_POSITION_BOTTOM
);
8550 dnd_hints_show_relative(HINT_ARROW_DOWN
, tab
, HINT_POSITION_LEFT
, HINT_POSITION_TOP
);
8551 dnd_hints_show_relative(HINT_ARROW_UP
, tab
, HINT_POSITION_LEFT
, HINT_POSITION_BOTTOM
);
8554 if (((gpointer
)win
== (gpointer
)dest_win
&& win
->drag_tab
< page_num
) || to_right
) {
8555 dnd_hints_show_relative(HINT_ARROW_RIGHT
, tab
, HINT_POSITION_LEFT
, HINT_POSITION_BOTTOM
);
8556 dnd_hints_show_relative(HINT_ARROW_LEFT
, tab
, HINT_POSITION_RIGHT
, HINT_POSITION_BOTTOM
);
8558 dnd_hints_show_relative(HINT_ARROW_RIGHT
, tab
, HINT_POSITION_LEFT
, HINT_POSITION_TOP
);
8559 dnd_hints_show_relative(HINT_ARROW_LEFT
, tab
, HINT_POSITION_RIGHT
, HINT_POSITION_TOP
);
8568 notebook_leave_cb(GtkWidget
*widget
, GdkEventCrossing
*e
, PidginWindow
*win
)
8573 if (e
->x_root
< win
->drag_min_x
||
8574 e
->x_root
>= win
->drag_max_x
||
8575 e
->y_root
< win
->drag_min_y
||
8576 e
->y_root
>= win
->drag_max_y
) {
8578 win
->in_predrag
= FALSE
;
8579 notebook_init_grab(win
, widget
);
8590 infopane_press_cb(GtkWidget
*widget
, GdkEventButton
*e
, PidginConversation
*gtkconv
)
8592 if (e
->type
== GDK_2BUTTON_PRESS
&& e
->button
== 1) {
8593 if (infopane_entry_activate(gtkconv
))
8597 if (e
->type
!= GDK_BUTTON_PRESS
)
8600 if (e
->button
== 1) {
8603 if (gtkconv
->win
->in_drag
)
8606 gtkconv
->win
->in_predrag
= TRUE
;
8607 gtkconv
->win
->drag_tab
= gtk_notebook_page_num(GTK_NOTEBOOK(gtkconv
->win
->notebook
), gtkconv
->tab_cont
);
8609 gdk_window_get_origin(gtkconv
->infopane_hbox
->window
, &nb_x
, &nb_y
);
8611 gtkconv
->win
->drag_min_x
= gtkconv
->infopane_hbox
->allocation
.x
+ nb_x
;
8612 gtkconv
->win
->drag_min_y
= gtkconv
->infopane_hbox
->allocation
.y
+ nb_y
;
8613 gtkconv
->win
->drag_max_x
= gtkconv
->infopane_hbox
->allocation
.width
+ gtkconv
->win
->drag_min_x
;
8614 gtkconv
->win
->drag_max_y
= gtkconv
->infopane_hbox
->allocation
.height
+ gtkconv
->win
->drag_min_y
;
8616 gtkconv
->win
->drag_motion_signal
= g_signal_connect(G_OBJECT(gtkconv
->win
->notebook
), "motion_notify_event",
8617 G_CALLBACK(notebook_motion_cb
), gtkconv
->win
);
8618 gtkconv
->win
->drag_leave_signal
= g_signal_connect(G_OBJECT(gtkconv
->win
->notebook
), "leave_notify_event",
8619 G_CALLBACK(notebook_leave_cb
), gtkconv
->win
);
8623 if (e
->button
== 3) {
8624 /* Right click was pressed. Popup the context menu. */
8625 GtkWidget
*menu
= gtk_menu_new(), *sub
;
8626 gboolean populated
= populate_menu_with_options(menu
, gtkconv
, TRUE
);
8627 sub
= gtk_menu_item_get_submenu(GTK_MENU_ITEM(gtkconv
->win
->menu
.send_to
));
8629 if (sub
&& GTK_WIDGET_IS_SENSITIVE(gtkconv
->win
->menu
.send_to
)) {
8630 GtkWidget
*item
= gtk_menu_item_new_with_mnemonic(_("S_end To"));
8632 pidgin_separator(menu
);
8633 gtk_menu_shell_append(GTK_MENU_SHELL(menu
), item
);
8634 gtk_menu_item_set_submenu(GTK_MENU_ITEM(item
), sub
);
8635 gtk_widget_show(item
);
8636 gtk_widget_show_all(sub
);
8637 } else if (!populated
) {
8638 gtk_widget_destroy(menu
);
8642 gtk_widget_show_all(menu
);
8643 gtk_menu_popup(GTK_MENU(menu
), NULL
, NULL
, NULL
, NULL
, e
->button
, e
->time
);
8650 notebook_press_cb(GtkWidget
*widget
, GdkEventButton
*e
, PidginWindow
*win
)
8657 if (e
->button
== 2 && e
->type
== GDK_BUTTON_PRESS
) {
8658 PidginConversation
*gtkconv
;
8659 tab_clicked
= pidgin_conv_get_tab_at_xy(win
, e
->x_root
, e
->y_root
, NULL
);
8661 if (tab_clicked
== -1)
8664 gtkconv
= pidgin_conv_window_get_gtkconv_at_index(win
, tab_clicked
);
8665 close_conv_cb(NULL
, gtkconv
);
8670 if (e
->button
!= 1 || e
->type
!= GDK_BUTTON_PRESS
)
8675 purple_debug(PURPLE_DEBUG_WARNING
, "gtkconv",
8676 "Already in the middle of a window drag at tab_press_cb\n");
8681 * Make sure a tab was actually clicked. The arrow buttons
8684 tab_clicked
= pidgin_conv_get_tab_at_xy(win
, e
->x_root
, e
->y_root
, NULL
);
8686 if (tab_clicked
== -1)
8690 * Get the relative position of the press event, with regards to
8691 * the position of the notebook.
8693 gdk_window_get_origin(win
->notebook
->window
, &nb_x
, &nb_y
);
8695 /* Reset the min/max x/y */
8696 win
->drag_min_x
= 0;
8697 win
->drag_min_y
= 0;
8698 win
->drag_max_x
= 0;
8699 win
->drag_max_y
= 0;
8701 /* Find out which tab was dragged. */
8702 page
= gtk_notebook_get_nth_page(GTK_NOTEBOOK(win
->notebook
), tab_clicked
);
8703 tab
= gtk_notebook_get_tab_label(GTK_NOTEBOOK(win
->notebook
), page
);
8705 win
->drag_min_x
= tab
->allocation
.x
+ nb_x
;
8706 win
->drag_min_y
= tab
->allocation
.y
+ nb_y
;
8707 win
->drag_max_x
= tab
->allocation
.width
+ win
->drag_min_x
;
8708 win
->drag_max_y
= tab
->allocation
.height
+ win
->drag_min_y
;
8710 /* Make sure the click occurred in the tab. */
8711 if (e
->x_root
< win
->drag_min_x
||
8712 e
->x_root
>= win
->drag_max_x
||
8713 e
->y_root
< win
->drag_min_y
||
8714 e
->y_root
>= win
->drag_max_y
) {
8719 win
->in_predrag
= TRUE
;
8720 win
->drag_tab
= tab_clicked
;
8722 /* Connect the new motion signals. */
8723 win
->drag_motion_signal
=
8724 g_signal_connect(G_OBJECT(widget
), "motion_notify_event",
8725 G_CALLBACK(notebook_motion_cb
), win
);
8727 win
->drag_leave_signal
=
8728 g_signal_connect(G_OBJECT(widget
), "leave_notify_event",
8729 G_CALLBACK(notebook_leave_cb
), win
);
8735 notebook_release_cb(GtkWidget
*widget
, GdkEventButton
*e
, PidginWindow
*win
)
8737 PidginWindow
*dest_win
;
8738 GtkNotebook
*dest_notebook
;
8739 PidginConversation
*active_gtkconv
;
8740 PidginConversation
*gtkconv
;
8741 gint dest_page_num
= 0;
8742 gboolean new_window
= FALSE
;
8743 gboolean to_right
= FALSE
;
8746 * Don't check to make sure that the event's window matches the
8747 * widget's, because we may be getting an event passed on from the
8750 if (e
->button
!= 1 && e
->type
!= GDK_BUTTON_RELEASE
)
8753 if (gdk_pointer_is_grabbed()) {
8754 gdk_pointer_ungrab(GDK_CURRENT_TIME
);
8755 gtk_grab_remove(widget
);
8758 if (!win
->in_predrag
&& !win
->in_drag
)
8761 /* Disconnect the motion signal. */
8762 if (win
->drag_motion_signal
) {
8763 g_signal_handler_disconnect(G_OBJECT(widget
),
8764 win
->drag_motion_signal
);
8766 win
->drag_motion_signal
= 0;
8770 * If we're in a pre-drag, we'll also need to disconnect the leave
8773 if (win
->in_predrag
) {
8774 win
->in_predrag
= FALSE
;
8776 if (win
->drag_leave_signal
) {
8777 g_signal_handler_disconnect(G_OBJECT(widget
),
8778 win
->drag_leave_signal
);
8780 win
->drag_leave_signal
= 0;
8784 /* If we're not in drag... */
8785 /* We're perfectly normal people! */
8789 win
->in_drag
= FALSE
;
8791 dnd_hints_hide_all();
8793 dest_win
= pidgin_conv_window_get_at_xy(e
->x_root
, e
->y_root
);
8795 active_gtkconv
= pidgin_conv_window_get_active_gtkconv(win
);
8797 if (dest_win
== NULL
) {
8798 /* If the current window doesn't have any other conversations,
8799 * there isn't much point transferring the conv to a new window. */
8800 if (pidgin_conv_window_get_gtkconv_count(win
) > 1) {
8801 /* Make a new window to stick this to. */
8802 dest_win
= pidgin_conv_window_new();
8807 if (dest_win
== NULL
)
8810 purple_signal_emit(pidgin_conversations_get_handle(),
8811 "conversation-dragging", win
, dest_win
);
8813 /* Get the destination page number. */
8815 dest_notebook
= GTK_NOTEBOOK(dest_win
->notebook
);
8816 if (gtk_notebook_get_show_tabs(dest_notebook
)) {
8817 dest_page_num
= pidgin_conv_get_tab_at_xy(dest_win
,
8818 e
->x_root
, e
->y_root
, &to_right
);
8821 to_right
= pidgin_conv_xy_to_right_infopane(dest_win
, e
->x_root
, e
->y_root
);
8825 gtkconv
= pidgin_conv_window_get_gtkconv_at_index(win
, win
->drag_tab
);
8827 if (win
== dest_win
) {
8828 gtk_notebook_reorder_child(GTK_NOTEBOOK(win
->notebook
), gtkconv
->tab_cont
, dest_page_num
);
8830 pidgin_conv_window_remove_gtkconv(win
, gtkconv
);
8831 pidgin_conv_window_add_gtkconv(dest_win
, gtkconv
);
8832 gtk_notebook_reorder_child(GTK_NOTEBOOK(dest_win
->notebook
), gtkconv
->tab_cont
, dest_page_num
+ to_right
);
8833 pidgin_conv_window_switch_gtkconv(dest_win
, gtkconv
);
8835 gint win_width
, win_height
;
8837 gtk_window_get_size(GTK_WINDOW(dest_win
->window
),
8838 &win_width
, &win_height
);
8839 #ifdef _WIN32 /* only override window manager placement on Windows */
8840 gtk_window_move(GTK_WINDOW(dest_win
->window
),
8841 e
->x_root
- (win_width
/ 2),
8842 e
->y_root
- (win_height
/ 2));
8845 pidgin_conv_window_show(dest_win
);
8849 gtk_widget_grab_focus(active_gtkconv
->entry
);
8856 before_switch_conv_cb(GtkNotebook
*notebook
, GtkWidget
*page
, gint page_num
,
8860 PurpleConversation
*conv
;
8861 PidginConversation
*gtkconv
;
8864 conv
= pidgin_conv_window_get_active_conversation(win
);
8866 g_return_if_fail(conv
!= NULL
);
8868 if (purple_conversation_get_type(conv
) != PURPLE_CONV_TYPE_IM
)
8871 gtkconv
= PIDGIN_CONVERSATION(conv
);
8873 if (gtkconv
->u
.im
->typing_timer
!= 0) {
8874 g_source_remove(gtkconv
->u
.im
->typing_timer
);
8875 gtkconv
->u
.im
->typing_timer
= 0;
8878 stop_anim(NULL
, gtkconv
);
8881 close_window(GtkWidget
*w
, PidginWindow
*win
)
8883 close_win_cb(w
, NULL
, win
);
8887 detach_tab_cb(GtkWidget
*w
, GObject
*menu
)
8889 PidginWindow
*win
, *new_window
;
8890 PidginConversation
*gtkconv
;
8892 gtkconv
= g_object_get_data(menu
, "clicked_tab");
8897 win
= pidgin_conv_get_window(gtkconv
);
8898 /* Nothing to do if there's only one tab in the window */
8899 if (pidgin_conv_window_get_gtkconv_count(win
) == 1)
8902 pidgin_conv_window_remove_gtkconv(win
, gtkconv
);
8904 new_window
= pidgin_conv_window_new();
8905 pidgin_conv_window_add_gtkconv(new_window
, gtkconv
);
8906 pidgin_conv_window_show(new_window
);
8910 close_others_cb(GtkWidget
*w
, GObject
*menu
)
8913 PidginConversation
*gtkconv
;
8916 gtkconv
= g_object_get_data(menu
, "clicked_tab");
8921 win
= pidgin_conv_get_window(gtkconv
);
8923 for (iter
= pidgin_conv_window_get_gtkconvs(win
); iter
; )
8925 PidginConversation
*gconv
= iter
->data
;
8928 if (gconv
!= gtkconv
)
8930 close_conv_cb(NULL
, gconv
);
8935 static void close_tab_cb(GtkWidget
*w
, GObject
*menu
)
8937 PidginConversation
*gtkconv
;
8939 gtkconv
= g_object_get_data(menu
, "clicked_tab");
8942 close_conv_cb(NULL
, gtkconv
);
8946 right_click_menu_cb(GtkNotebook
*notebook
, GdkEventButton
*event
, PidginWindow
*win
)
8948 GtkWidget
*item
, *menu
;
8949 PidginConversation
*gtkconv
;
8951 if (event
->type
!= GDK_BUTTON_PRESS
|| event
->button
!= 3)
8954 gtkconv
= pidgin_conv_window_get_gtkconv_at_index(win
,
8955 pidgin_conv_get_tab_at_xy(win
, event
->x_root
, event
->y_root
, NULL
));
8957 if (g_object_get_data(G_OBJECT(notebook
->menu
), "clicked_tab"))
8959 g_object_set_data(G_OBJECT(notebook
->menu
), "clicked_tab", gtkconv
);
8963 g_object_set_data(G_OBJECT(notebook
->menu
), "clicked_tab", gtkconv
);
8965 menu
= notebook
->menu
;
8966 pidgin_separator(GTK_WIDGET(menu
));
8968 item
= gtk_menu_item_new_with_label(_("Close other tabs"));
8969 gtk_widget_show(item
);
8970 gtk_menu_shell_append(GTK_MENU_SHELL(menu
), item
);
8971 g_signal_connect(G_OBJECT(item
), "activate",
8972 G_CALLBACK(close_others_cb
), menu
);
8974 item
= gtk_menu_item_new_with_label(_("Close all tabs"));
8975 gtk_widget_show(item
);
8976 gtk_menu_shell_append(GTK_MENU_SHELL(menu
), item
);
8977 g_signal_connect(G_OBJECT(item
), "activate",
8978 G_CALLBACK(close_window
), win
);
8980 pidgin_separator(menu
);
8982 item
= gtk_menu_item_new_with_label(_("Detach this tab"));
8983 gtk_widget_show(item
);
8984 gtk_menu_shell_append(GTK_MENU_SHELL(menu
), item
);
8985 g_signal_connect(G_OBJECT(item
), "activate",
8986 G_CALLBACK(detach_tab_cb
), menu
);
8988 item
= gtk_menu_item_new_with_label(_("Close this tab"));
8989 gtk_widget_show(item
);
8990 gtk_menu_shell_append(GTK_MENU_SHELL(menu
), item
);
8991 g_signal_connect(G_OBJECT(item
), "activate",
8992 G_CALLBACK(close_tab_cb
), menu
);
8998 remove_edit_entry(PidginConversation
*gtkconv
, GtkWidget
*entry
)
9000 g_signal_handlers_disconnect_matched(G_OBJECT(entry
), G_SIGNAL_MATCH_DATA
,
9001 0, 0, NULL
, NULL
, gtkconv
);
9002 gtk_widget_show(gtkconv
->infopane
);
9003 gtk_widget_grab_focus(gtkconv
->entry
);
9004 gtk_widget_destroy(entry
);
9008 alias_focus_cb(GtkWidget
*widget
, GdkEventFocus
*event
, gpointer user_data
)
9010 remove_edit_entry(user_data
, widget
);
9015 alias_key_press_cb(GtkWidget
*widget
, GdkEventKey
*event
, gpointer user_data
)
9017 if (event
->keyval
== GDK_Escape
) {
9018 remove_edit_entry(user_data
, widget
);
9025 alias_cb(GtkEntry
*entry
, gpointer user_data
)
9027 PidginConversation
*gtkconv
;
9028 PurpleConversation
*conv
;
9029 PurpleAccount
*account
;
9032 gtkconv
= (PidginConversation
*)user_data
;
9033 if (gtkconv
== NULL
) {
9036 conv
= gtkconv
->active_conv
;
9037 account
= purple_conversation_get_account(conv
);
9038 name
= purple_conversation_get_name(conv
);
9040 if (purple_conversation_get_type(conv
) == PURPLE_CONV_TYPE_IM
) {
9042 buddy
= purple_find_buddy(account
, name
);
9043 if (buddy
!= NULL
) {
9044 purple_blist_alias_buddy(buddy
,
9045 gtk_entry_get_text(entry
));
9047 serv_alias_buddy(buddy
);
9048 } else if (purple_conversation_get_type(conv
) == PURPLE_CONV_TYPE_CHAT
) {
9049 gtk_entry_set_text(GTK_ENTRY(gtkconv
->u
.chat
->topic_text
), gtk_entry_get_text(entry
));
9050 topic_callback(NULL
, gtkconv
);
9052 remove_edit_entry(user_data
, GTK_WIDGET(entry
));
9056 infopane_entry_activate(PidginConversation
*gtkconv
)
9058 GtkWidget
*entry
= NULL
;
9059 PurpleConversation
*conv
= gtkconv
->active_conv
;
9060 const char *text
= NULL
;
9062 if (!GTK_WIDGET_VISIBLE(gtkconv
->infopane
)) {
9063 /* There's already an entry for alias. Let's not create another one. */
9067 if (!purple_account_is_connected(gtkconv
->active_conv
->account
)) {
9068 /* Do not allow aliasing someone on a disconnected account. */
9072 if (purple_conversation_get_type(conv
) == PURPLE_CONV_TYPE_IM
) {
9073 PurpleBuddy
*buddy
= purple_find_buddy(gtkconv
->active_conv
->account
, gtkconv
->active_conv
->name
);
9075 /* This buddy isn't in your buddy list, so we can't alias him */
9078 text
= purple_buddy_get_contact_alias(buddy
);
9079 } else if (purple_conversation_get_type(conv
) == PURPLE_CONV_TYPE_CHAT
) {
9080 PurpleConnection
*gc
;
9081 PurplePluginProtocolInfo
*prpl_info
= NULL
;
9083 gc
= purple_conversation_get_gc(conv
);
9085 prpl_info
= PURPLE_PLUGIN_PROTOCOL_INFO(gc
->prpl
);
9086 if (prpl_info
&& prpl_info
->set_chat_topic
== NULL
)
9087 /* This protocol doesn't support setting the chat room topic */
9090 text
= purple_conv_chat_get_topic(PURPLE_CONV_CHAT(conv
));
9094 entry
= gtk_entry_new();
9095 gtk_entry_set_has_frame(GTK_ENTRY(entry
), FALSE
);
9096 gtk_entry_set_width_chars(GTK_ENTRY(entry
), 10);
9097 gtk_entry_set_alignment(GTK_ENTRY(entry
), 0.5);
9099 gtk_box_pack_start(GTK_BOX(gtkconv
->infopane_hbox
), entry
, TRUE
, TRUE
, 0);
9100 /* after the tab label */
9101 gtk_box_reorder_child(GTK_BOX(gtkconv
->infopane_hbox
), entry
, 0);
9103 g_signal_connect(G_OBJECT(entry
), "activate", G_CALLBACK(alias_cb
), gtkconv
);
9104 g_signal_connect(G_OBJECT(entry
), "focus-out-event", G_CALLBACK(alias_focus_cb
), gtkconv
);
9105 g_signal_connect(G_OBJECT(entry
), "key-press-event", G_CALLBACK(alias_key_press_cb
), gtkconv
);
9108 gtk_entry_set_text(GTK_ENTRY(entry
), text
);
9109 gtk_widget_show(entry
);
9110 gtk_widget_hide(gtkconv
->infopane
);
9111 gtk_widget_grab_focus(entry
);
9117 window_keypress_cb(GtkWidget
*widget
, GdkEventKey
*event
, PidginWindow
*win
)
9119 PidginConversation
*gtkconv
= pidgin_conv_window_get_active_gtkconv(win
);
9121 return conv_keypress_common(gtkconv
, event
);
9125 switch_conv_cb(GtkNotebook
*notebook
, GtkWidget
*page
, gint page_num
,
9129 PurpleConversation
*conv
;
9130 PidginConversation
*gtkconv
;
9131 const char *sound_method
;
9134 gtkconv
= pidgin_conv_window_get_gtkconv_at_index(win
, page_num
);
9135 conv
= gtkconv
->active_conv
;
9137 g_return_if_fail(conv
!= NULL
);
9139 /* clear unseen flag if conversation is not hidden */
9140 if(!pidgin_conv_is_hidden(gtkconv
)) {
9141 gtkconv_set_unseen(gtkconv
, PIDGIN_UNSEEN_NONE
);
9144 /* Update the menubar */
9146 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(gtkconv
->win
->menu
.logging
),
9147 purple_conversation_is_logging(conv
));
9149 generate_send_to_items(win
);
9150 regenerate_options_items(win
);
9151 regenerate_plugins_items(win
);
9153 pidgin_conv_switch_active_conversation(conv
);
9155 sound_method
= purple_prefs_get_string(PIDGIN_PREFS_ROOT
"/sound/method");
9156 if (!purple_strequal(sound_method
, "none"))
9157 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(win
->menu
.sounds
),
9158 gtkconv
->make_sound
);
9160 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(win
->menu
.show_formatting_toolbar
),
9161 purple_prefs_get_bool(PIDGIN_PREFS_ROOT
"/conversations/show_formatting_toolbar"));
9163 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(win
->menu
.show_timestamps
),
9164 purple_prefs_get_bool(PIDGIN_PREFS_ROOT
"/conversations/show_timestamps"));
9167 * We pause icons when they are not visible. If this icon should
9168 * be animated then start it back up again.
9170 if ((purple_conversation_get_type(conv
) == PURPLE_CONV_TYPE_IM
) &&
9171 (gtkconv
->u
.im
->animate
))
9172 start_anim(NULL
, gtkconv
);
9174 purple_signal_emit(pidgin_conversations_get_handle(), "conversation-switched", conv
);
9177 /**************************************************************************
9179 **************************************************************************/
9182 pidgin_conv_windows_get_list()
9188 make_status_icon_list(const char *stock
, GtkWidget
*w
)
9191 l
= g_list_append(l
, gtk_widget_render_icon (w
, stock
,
9192 gtk_icon_size_from_name(PIDGIN_ICON_SIZE_TANGO_EXTRA_SMALL
), "GtkWindow"));
9193 l
= g_list_append(l
, gtk_widget_render_icon (w
, stock
,
9194 gtk_icon_size_from_name(PIDGIN_ICON_SIZE_TANGO_SMALL
), "GtkWindow"));
9195 l
= g_list_append(l
, gtk_widget_render_icon (w
, stock
,
9196 gtk_icon_size_from_name(PIDGIN_ICON_SIZE_TANGO_MEDIUM
), "GtkWindow"));
9197 l
= g_list_append(l
, gtk_widget_render_icon (w
, stock
,
9198 gtk_icon_size_from_name(PIDGIN_ICON_SIZE_TANGO_LARGE
), "GtkWindow"));
9203 create_icon_lists(GtkWidget
*w
)
9205 available_list
= make_status_icon_list(PIDGIN_STOCK_STATUS_AVAILABLE
, w
);
9206 busy_list
= make_status_icon_list(PIDGIN_STOCK_STATUS_BUSY
, w
);
9207 xa_list
= make_status_icon_list(PIDGIN_STOCK_STATUS_XA
, w
);
9208 offline_list
= make_status_icon_list(PIDGIN_STOCK_STATUS_OFFLINE
, w
);
9209 away_list
= make_status_icon_list(PIDGIN_STOCK_STATUS_AWAY
, w
);
9210 prpl_lists
= g_hash_table_new(g_str_hash
, g_str_equal
);
9214 plugin_changed_cb(PurplePlugin
*p
, gpointer data
)
9216 regenerate_plugins_items(data
);
9219 static gboolean
gtk_conv_configure_cb(GtkWidget
*w
, GdkEventConfigure
*event
, gpointer data
) {
9222 if (GTK_WIDGET_VISIBLE(w
))
9223 gtk_window_get_position(GTK_WINDOW(w
), &x
, &y
);
9225 return FALSE
; /* carry on normally */
9227 /* Workaround for GTK+ bug # 169811 - "configure_event" is fired
9228 * when the window is being maximized */
9229 if (gdk_window_get_state(w
->window
) & GDK_WINDOW_STATE_MAXIMIZED
)
9232 /* don't save off-screen positioning */
9233 if (x
+ event
->width
< 0 ||
9234 y
+ event
->height
< 0 ||
9235 x
> gdk_screen_width() ||
9236 y
> gdk_screen_height())
9237 return FALSE
; /* carry on normally */
9239 /* store the position */
9240 purple_prefs_set_int(PIDGIN_PREFS_ROOT
"/conversations/im/x", x
);
9241 purple_prefs_set_int(PIDGIN_PREFS_ROOT
"/conversations/im/y", y
);
9242 purple_prefs_set_int(PIDGIN_PREFS_ROOT
"/conversations/im/width", event
->width
);
9243 purple_prefs_set_int(PIDGIN_PREFS_ROOT
"/conversations/im/height", event
->height
);
9245 /* continue to handle event normally */
9251 pidgin_conv_set_position_size(PidginWindow
*win
, int conv_x
, int conv_y
,
9252 int conv_width
, int conv_height
)
9254 /* if the window exists, is hidden, we're saving positions, and the
9255 * position is sane... */
9256 if (win
&& win
->window
&&
9257 !GTK_WIDGET_VISIBLE(win
->window
) && conv_width
!= 0) {
9259 #ifdef _WIN32 /* only override window manager placement on Windows */
9260 /* ...check position is on screen... */
9261 if (conv_x
>= gdk_screen_width())
9262 conv_x
= gdk_screen_width() - 100;
9263 else if (conv_x
+ conv_width
< 0)
9266 if (conv_y
>= gdk_screen_height())
9267 conv_y
= gdk_screen_height() - 100;
9268 else if (conv_y
+ conv_height
< 0)
9271 /* ...and move it back. */
9272 gtk_window_move(GTK_WINDOW(win
->window
), conv_x
, conv_y
);
9274 gtk_window_resize(GTK_WINDOW(win
->window
), conv_width
, conv_height
);
9279 pidgin_conv_restore_position(PidginWindow
*win
) {
9280 pidgin_conv_set_position_size(win
,
9281 purple_prefs_get_int(PIDGIN_PREFS_ROOT
"/conversations/im/x"),
9282 purple_prefs_get_int(PIDGIN_PREFS_ROOT
"/conversations/im/y"),
9283 purple_prefs_get_int(PIDGIN_PREFS_ROOT
"/conversations/im/width"),
9284 purple_prefs_get_int(PIDGIN_PREFS_ROOT
"/conversations/im/height"));
9288 pidgin_conv_window_new()
9291 GtkPositionType pos
;
9292 GtkWidget
*testidea
;
9294 GdkModifierType state
;
9296 win
= g_malloc0(sizeof(PidginWindow
));
9298 window_list
= g_list_append(window_list
, win
);
9300 /* Create the window. */
9301 win
->window
= pidgin_create_window(NULL
, 0, "conversation", TRUE
);
9302 if (!gtk_get_current_event_state(&state
))
9303 gtk_window_set_focus_on_map(GTK_WINDOW(win
->window
), FALSE
);
9305 /* Etan: I really think this entire function call should happen only
9306 * when we are on Windows but I was informed that back before we used
9307 * to save the window position we stored the window size, so I'm
9308 * leaving it for now. */
9309 #if TRUE || defined(_WIN32)
9310 pidgin_conv_restore_position(win
);
9313 if (available_list
== NULL
) {
9314 create_icon_lists(win
->window
);
9317 g_signal_connect(G_OBJECT(win
->window
), "delete_event",
9318 G_CALLBACK(close_win_cb
), win
);
9319 g_signal_connect(G_OBJECT(win
->window
), "focus_in_event",
9320 G_CALLBACK(focus_win_cb
), win
);
9322 /* Intercept keystrokes from the menu items */
9323 g_signal_connect(G_OBJECT(win
->window
), "key_press_event",
9324 G_CALLBACK(window_keypress_cb
), win
);
9327 /* Create the notebook. */
9328 win
->notebook
= gtk_notebook_new();
9330 pos
= purple_prefs_get_int(PIDGIN_PREFS_ROOT
"/conversations/tab_side");
9333 gtk_notebook_set_tab_hborder(GTK_NOTEBOOK(win
->notebook
), 0);
9334 gtk_notebook_set_tab_vborder(GTK_NOTEBOOK(win
->notebook
), 0);
9336 gtk_notebook_set_tab_pos(GTK_NOTEBOOK(win
->notebook
), pos
);
9337 gtk_notebook_set_scrollable(GTK_NOTEBOOK(win
->notebook
), TRUE
);
9338 gtk_notebook_popup_enable(GTK_NOTEBOOK(win
->notebook
));
9339 gtk_notebook_set_show_tabs(GTK_NOTEBOOK(win
->notebook
), FALSE
);
9340 gtk_notebook_set_show_border(GTK_NOTEBOOK(win
->notebook
), TRUE
);
9342 g_signal_connect(G_OBJECT(win
->notebook
), "button-press-event",
9343 G_CALLBACK(right_click_menu_cb
), win
);
9345 gtk_widget_show(win
->notebook
);
9347 g_signal_connect(G_OBJECT(win
->notebook
), "switch_page",
9348 G_CALLBACK(before_switch_conv_cb
), win
);
9349 g_signal_connect_after(G_OBJECT(win
->notebook
), "switch_page",
9350 G_CALLBACK(switch_conv_cb
), win
);
9352 /* Setup the tab drag and drop signals. */
9353 gtk_widget_add_events(win
->notebook
,
9354 GDK_BUTTON1_MOTION_MASK
| GDK_LEAVE_NOTIFY_MASK
);
9355 g_signal_connect(G_OBJECT(win
->notebook
), "button_press_event",
9356 G_CALLBACK(notebook_press_cb
), win
);
9357 g_signal_connect(G_OBJECT(win
->notebook
), "button_release_event",
9358 G_CALLBACK(notebook_release_cb
), win
);
9360 testidea
= gtk_vbox_new(FALSE
, 0);
9362 /* Setup the menubar. */
9363 menubar
= setup_menubar(win
);
9364 gtk_box_pack_start(GTK_BOX(testidea
), menubar
, FALSE
, TRUE
, 0);
9366 gtk_box_pack_start(GTK_BOX(testidea
), win
->notebook
, TRUE
, TRUE
, 0);
9368 gtk_container_add(GTK_CONTAINER(win
->window
), testidea
);
9370 gtk_widget_show(testidea
);
9372 /* Update the plugin actions when plugins are (un)loaded */
9373 purple_signal_connect(purple_plugins_get_handle(), "plugin-load",
9374 win
, PURPLE_CALLBACK(plugin_changed_cb
), win
);
9375 purple_signal_connect(purple_plugins_get_handle(), "plugin-unload",
9376 win
, PURPLE_CALLBACK(plugin_changed_cb
), win
);
9380 g_signal_connect(G_OBJECT(win
->window
), "show",
9381 G_CALLBACK(winpidgin_ensure_onscreen
), win
->window
);
9383 if (purple_prefs_get_bool(PIDGIN_PREFS_ROOT
"/win32/minimize_new_convs")
9384 && !gtk_get_current_event_state(&state
))
9385 gtk_window_iconify(GTK_WINDOW(win
->window
));
9392 pidgin_conv_window_destroy(PidginWindow
*win
)
9394 if (win
->gtkconvs
) {
9395 GList
*iter
= win
->gtkconvs
;
9398 PidginConversation
*gtkconv
= iter
->data
;
9400 close_conv_cb(NULL
, gtkconv
);
9405 purple_prefs_disconnect_by_handle(win
);
9406 window_list
= g_list_remove(window_list
, win
);
9408 /* Close the "Find" dialog if it's open */
9409 if (win
->dialogs
.search
)
9410 gtk_widget_destroy(win
->dialogs
.search
);
9412 gtk_widget_destroy(win
->window
);
9414 g_object_unref(G_OBJECT(win
->menu
.item_factory
));
9416 purple_notify_close_with_handle(win
);
9417 purple_signals_disconnect_by_handle(win
);
9423 pidgin_conv_window_show(PidginWindow
*win
)
9425 gtk_widget_show(win
->window
);
9429 pidgin_conv_window_hide(PidginWindow
*win
)
9431 gtk_widget_hide(win
->window
);
9435 pidgin_conv_window_raise(PidginWindow
*win
)
9437 gdk_window_raise(GDK_WINDOW(win
->window
->window
));
9441 pidgin_conv_window_switch_gtkconv(PidginWindow
*win
, PidginConversation
*gtkconv
)
9443 gtk_notebook_set_current_page(GTK_NOTEBOOK(win
->notebook
),
9444 gtk_notebook_page_num(GTK_NOTEBOOK(win
->notebook
),
9445 gtkconv
->tab_cont
));
9449 gtkconv_tab_set_tip(GtkWidget
*widget
, GdkEventCrossing
*event
, PidginConversation
*gtkconv
)
9451 #if GTK_CHECK_VERSION(2, 12, 0)
9452 #define gtk_tooltips_set_tip(tips, w, l, p) gtk_widget_set_tooltip_text(w, l)
9454 /* PANGO_VERSION_CHECK macro was introduced in 1.15. So we need this double check. */
9455 #ifndef PANGO_VERSION_CHECK
9456 #define pango_layout_is_ellipsized(l) TRUE
9457 #elif !PANGO_VERSION_CHECK(1,16,0)
9458 #define pango_layout_is_ellipsized(l) TRUE
9460 PangoLayout
*layout
;
9462 layout
= gtk_label_get_layout(GTK_LABEL(gtkconv
->tab_label
));
9463 gtk_tooltips_set_tip(gtkconv
->tooltips
, widget
,
9464 pango_layout_is_ellipsized(layout
) ? gtk_label_get_text(GTK_LABEL(gtkconv
->tab_label
)) : NULL
,
9467 #if GTK_CHECK_VERSION(2, 12, 0)
9468 #undef gtk_tooltips_set_tip
9473 pidgin_conv_window_add_gtkconv(PidginWindow
*win
, PidginConversation
*gtkconv
)
9475 PurpleConversation
*conv
= gtkconv
->active_conv
;
9476 PidginConversation
*focus_gtkconv
;
9477 GtkWidget
*tab_cont
= gtkconv
->tab_cont
;
9478 PurpleConversationType conv_type
;
9479 const gchar
*tmp_lab
;
9481 conv_type
= purple_conversation_get_type(conv
);
9483 win
->gtkconvs
= g_list_append(win
->gtkconvs
, gtkconv
);
9486 if (win
->gtkconvs
&& win
->gtkconvs
->next
&& win
->gtkconvs
->next
->next
== NULL
)
9487 pidgin_conv_tab_pack(win
, ((PidginConversation
*)win
->gtkconvs
->data
));
9491 gtkconv
->close
= pidgin_create_small_button(gtk_label_new("×"));
9492 gtk_tooltips_set_tip(gtkconv
->tooltips
, gtkconv
->close
,
9493 _("Close conversation"), NULL
);
9495 g_signal_connect(gtkconv
->close
, "clicked", G_CALLBACK (close_conv_cb
), gtkconv
);
9498 gtkconv
->icon
= gtk_image_new();
9499 gtkconv
->menu_icon
= gtk_image_new();
9500 g_object_set(G_OBJECT(gtkconv
->icon
),
9501 "icon-size", gtk_icon_size_from_name(PIDGIN_ICON_SIZE_TANGO_MICROSCOPIC
),
9503 g_object_set(G_OBJECT(gtkconv
->menu_icon
),
9504 "icon-size", gtk_icon_size_from_name(PIDGIN_ICON_SIZE_TANGO_MICROSCOPIC
),
9506 gtk_widget_show(gtkconv
->icon
);
9507 update_tab_icon(conv
);
9510 gtkconv
->tab_label
= gtk_label_new(tmp_lab
= purple_conversation_get_title(conv
));
9511 gtk_widget_set_name(gtkconv
->tab_label
, "tab-label");
9513 gtkconv
->menu_tabby
= gtk_hbox_new(FALSE
, PIDGIN_HIG_BOX_SPACE
);
9514 gtkconv
->menu_label
= gtk_label_new(tmp_lab
);
9515 gtk_box_pack_start(GTK_BOX(gtkconv
->menu_tabby
), gtkconv
->menu_icon
, FALSE
, FALSE
, 0);
9517 gtk_widget_show_all(gtkconv
->menu_icon
);
9519 gtk_box_pack_start(GTK_BOX(gtkconv
->menu_tabby
), gtkconv
->menu_label
, TRUE
, TRUE
, 0);
9520 gtk_widget_show(gtkconv
->menu_label
);
9521 gtk_misc_set_alignment(GTK_MISC(gtkconv
->menu_label
), 0, 0);
9523 gtk_widget_show(gtkconv
->menu_tabby
);
9525 if (conv_type
== PURPLE_CONV_TYPE_IM
)
9526 pidgin_conv_update_buddy_icon(conv
);
9528 /* Build and set conversations tab */
9529 pidgin_conv_tab_pack(win
, gtkconv
);
9531 gtk_notebook_set_menu_label(GTK_NOTEBOOK(win
->notebook
), tab_cont
, gtkconv
->menu_tabby
);
9533 gtk_widget_show(tab_cont
);
9535 if (pidgin_conv_window_get_gtkconv_count(win
) == 1) {
9536 /* Er, bug in notebooks? Switch to the page manually. */
9537 gtk_notebook_set_current_page(GTK_NOTEBOOK(win
->notebook
), 0);
9539 gtk_notebook_set_show_tabs(GTK_NOTEBOOK(win
->notebook
), TRUE
);
9542 focus_gtkconv
= g_list_nth_data(pidgin_conv_window_get_gtkconvs(win
),
9543 gtk_notebook_get_current_page(GTK_NOTEBOOK(win
->notebook
)));
9544 gtk_widget_grab_focus(focus_gtkconv
->entry
);
9546 if (pidgin_conv_window_get_gtkconv_count(win
) == 1)
9547 update_send_to_selection(win
);
9551 pidgin_conv_tab_pack(PidginWindow
*win
, PidginConversation
*gtkconv
)
9553 gboolean tabs_side
= FALSE
;
9555 GtkWidget
*first
, *third
, *ebox
;
9557 if (purple_prefs_get_int(PIDGIN_PREFS_ROOT
"/conversations/tab_side") == GTK_POS_LEFT
||
9558 purple_prefs_get_int(PIDGIN_PREFS_ROOT
"/conversations/tab_side") == GTK_POS_RIGHT
)
9560 else if (purple_prefs_get_int(PIDGIN_PREFS_ROOT
"/conversations/tab_side") == (GTK_POS_LEFT
|8))
9562 else if (purple_prefs_get_int(PIDGIN_PREFS_ROOT
"/conversations/tab_side") == (GTK_POS_RIGHT
|8))
9566 g_object_set(G_OBJECT(gtkconv
->tab_label
), "ellipsize", PANGO_ELLIPSIZE_END
, NULL
);
9567 gtk_label_set_width_chars(GTK_LABEL(gtkconv
->tab_label
), 4);
9569 g_object_set(G_OBJECT(gtkconv
->tab_label
), "ellipsize", PANGO_ELLIPSIZE_NONE
, NULL
);
9570 gtk_label_set_width_chars(GTK_LABEL(gtkconv
->tab_label
), -1);
9574 gtk_label_set_width_chars(
9575 GTK_LABEL(gtkconv
->tab_label
),
9576 MIN(g_utf8_strlen(gtk_label_get_text(GTK_LABEL(gtkconv
->tab_label
)), -1), 12)
9580 gtk_label_set_angle(GTK_LABEL(gtkconv
->tab_label
), angle
);
9583 gtk_misc_set_alignment(GTK_MISC(gtkconv
->tab_label
), 0.00, 0.5);
9584 gtk_misc_set_padding(GTK_MISC(gtkconv
->tab_label
), 4, 0);
9588 gtkconv
->tabby
= gtk_vbox_new(FALSE
, PIDGIN_HIG_BOX_SPACE
);
9590 gtkconv
->tabby
= gtk_hbox_new(FALSE
, PIDGIN_HIG_BOX_SPACE
);
9591 gtk_widget_set_name(gtkconv
->tabby
, "tab-container");
9593 /* select the correct ordering for verticle tabs */
9595 first
= gtkconv
->close
;
9596 third
= gtkconv
->icon
;
9598 first
= gtkconv
->icon
;
9599 third
= gtkconv
->close
;
9602 ebox
= gtk_event_box_new();
9603 gtk_event_box_set_visible_window(GTK_EVENT_BOX(ebox
), FALSE
);
9604 gtk_container_add(GTK_CONTAINER(ebox
), gtkconv
->tabby
);
9605 g_signal_connect(G_OBJECT(ebox
), "enter-notify-event",
9606 G_CALLBACK(gtkconv_tab_set_tip
), gtkconv
);
9608 if (gtkconv
->tab_label
->parent
== NULL
) {
9609 /* Pack if it's a new widget */
9610 gtk_box_pack_start(GTK_BOX(gtkconv
->tabby
), first
, FALSE
, FALSE
, 0);
9611 gtk_box_pack_start(GTK_BOX(gtkconv
->tabby
), gtkconv
->tab_label
, TRUE
, TRUE
, 0);
9612 gtk_box_pack_start(GTK_BOX(gtkconv
->tabby
), third
, FALSE
, FALSE
, 0);
9614 /* Add this pane to the conversation's notebook. */
9615 gtk_notebook_append_page(GTK_NOTEBOOK(win
->notebook
), gtkconv
->tab_cont
, ebox
);
9617 /* reparent old widgets on preference changes */
9618 gtk_widget_reparent(first
, gtkconv
->tabby
);
9619 gtk_widget_reparent(gtkconv
->tab_label
, gtkconv
->tabby
);
9620 gtk_widget_reparent(third
, gtkconv
->tabby
);
9621 gtk_box_set_child_packing(GTK_BOX(gtkconv
->tabby
), first
, FALSE
, FALSE
, 0, GTK_PACK_START
);
9622 gtk_box_set_child_packing(GTK_BOX(gtkconv
->tabby
), gtkconv
->tab_label
, TRUE
, TRUE
, 0, GTK_PACK_START
);
9623 gtk_box_set_child_packing(GTK_BOX(gtkconv
->tabby
), third
, FALSE
, FALSE
, 0, GTK_PACK_START
);
9625 /* Reset the tabs label to the new version */
9626 gtk_notebook_set_tab_label(GTK_NOTEBOOK(win
->notebook
), gtkconv
->tab_cont
, ebox
);
9629 gtk_notebook_set_tab_label_packing(GTK_NOTEBOOK(win
->notebook
), gtkconv
->tab_cont
,
9630 !tabs_side
&& !angle
,
9631 TRUE
, GTK_PACK_START
);
9633 if (pidgin_conv_window_get_gtkconv_count(win
) == 1)
9634 gtk_notebook_set_show_tabs(GTK_NOTEBOOK(win
->notebook
),
9635 purple_prefs_get_bool(PIDGIN_PREFS_ROOT
"/conversations/tabs") &&
9636 (!purple_prefs_get_bool(PIDGIN_PREFS_ROOT
"/conversations/im/show_buddy_icons") ||
9637 purple_prefs_get_int(PIDGIN_PREFS_ROOT
"/conversations/tab_side") != GTK_POS_TOP
));
9639 /* show the widgets */
9640 /* gtk_widget_show(gtkconv->icon); */
9641 gtk_widget_show(gtkconv
->tab_label
);
9642 if (purple_prefs_get_bool(PIDGIN_PREFS_ROOT
"/conversations/close_on_tabs"))
9643 gtk_widget_show(gtkconv
->close
);
9644 gtk_widget_show(gtkconv
->tabby
);
9645 gtk_widget_show(ebox
);
9649 pidgin_conv_window_remove_gtkconv(PidginWindow
*win
, PidginConversation
*gtkconv
)
9653 index
= gtk_notebook_page_num(GTK_NOTEBOOK(win
->notebook
), gtkconv
->tab_cont
);
9655 g_object_ref(gtkconv
->tab_cont
);
9656 gtk_object_sink(GTK_OBJECT(gtkconv
->tab_cont
));
9658 gtk_notebook_remove_page(GTK_NOTEBOOK(win
->notebook
), index
);
9660 win
->gtkconvs
= g_list_remove(win
->gtkconvs
, gtkconv
);
9662 g_signal_handlers_disconnect_matched(win
->window
, G_SIGNAL_MATCH_DATA
,
9663 0, 0, NULL
, NULL
, gtkconv
);
9665 if (win
->gtkconvs
&& win
->gtkconvs
->next
== NULL
)
9666 pidgin_conv_tab_pack(win
, win
->gtkconvs
->data
);
9668 if (!win
->gtkconvs
&& win
!= hidden_convwin
)
9669 pidgin_conv_window_destroy(win
);
9672 PidginConversation
*
9673 pidgin_conv_window_get_gtkconv_at_index(const PidginWindow
*win
, int index
)
9675 GtkWidget
*tab_cont
;
9679 tab_cont
= gtk_notebook_get_nth_page(GTK_NOTEBOOK(win
->notebook
), index
);
9680 return tab_cont
? g_object_get_data(G_OBJECT(tab_cont
), "PidginConversation") : NULL
;
9683 PidginConversation
*
9684 pidgin_conv_window_get_active_gtkconv(const PidginWindow
*win
)
9687 GtkWidget
*tab_cont
;
9689 index
= gtk_notebook_get_current_page(GTK_NOTEBOOK(win
->notebook
));
9692 tab_cont
= gtk_notebook_get_nth_page(GTK_NOTEBOOK(win
->notebook
), index
);
9695 return g_object_get_data(G_OBJECT(tab_cont
), "PidginConversation");
9699 PurpleConversation
*
9700 pidgin_conv_window_get_active_conversation(const PidginWindow
*win
)
9702 PidginConversation
*gtkconv
;
9704 gtkconv
= pidgin_conv_window_get_active_gtkconv(win
);
9705 return gtkconv
? gtkconv
->active_conv
: NULL
;
9709 pidgin_conv_window_is_active_conversation(const PurpleConversation
*conv
)
9711 return conv
== pidgin_conv_window_get_active_conversation(PIDGIN_CONVERSATION(conv
)->win
);
9715 pidgin_conv_window_has_focus(PidginWindow
*win
)
9717 gboolean has_focus
= FALSE
;
9719 g_object_get(G_OBJECT(win
->window
), "has-toplevel-focus", &has_focus
, NULL
);
9725 pidgin_conv_window_get_at_xy(int x
, int y
)
9731 gdkwin
= gdk_window_at_pointer(&x
, &y
);
9734 gdkwin
= gdk_window_get_toplevel(gdkwin
);
9736 for (l
= pidgin_conv_windows_get_list(); l
!= NULL
; l
= l
->next
) {
9739 if (gdkwin
== win
->window
->window
)
9747 pidgin_conv_window_get_gtkconvs(PidginWindow
*win
)
9749 return win
->gtkconvs
;
9753 pidgin_conv_window_get_gtkconv_count(PidginWindow
*win
)
9755 return g_list_length(win
->gtkconvs
);
9759 pidgin_conv_window_first_with_type(PurpleConversationType type
)
9761 GList
*wins
, *convs
;
9763 PidginConversation
*conv
;
9765 if (type
== PURPLE_CONV_TYPE_UNKNOWN
)
9768 for (wins
= pidgin_conv_windows_get_list(); wins
!= NULL
; wins
= wins
->next
) {
9771 for (convs
= win
->gtkconvs
;
9773 convs
= convs
->next
) {
9777 if (purple_conversation_get_type(conv
->active_conv
) == type
)
9786 pidgin_conv_window_last_with_type(PurpleConversationType type
)
9788 GList
*wins
, *convs
;
9790 PidginConversation
*conv
;
9792 if (type
== PURPLE_CONV_TYPE_UNKNOWN
)
9795 for (wins
= g_list_last(pidgin_conv_windows_get_list());
9797 wins
= wins
->prev
) {
9801 for (convs
= win
->gtkconvs
;
9803 convs
= convs
->next
) {
9807 if (purple_conversation_get_type(conv
->active_conv
) == type
)
9816 /**************************************************************************
9817 * Conversation placement functions
9818 **************************************************************************/
9823 PidginConvPlacementFunc fnc
;
9825 } ConvPlacementData
;
9827 static GList
*conv_placement_fncs
= NULL
;
9828 static PidginConvPlacementFunc place_conv
= NULL
;
9830 /* This one places conversations in the last made window. */
9832 conv_placement_last_created_win(PidginConversation
*conv
)
9836 GList
*l
= g_list_last(pidgin_conv_windows_get_list());
9837 win
= l
? l
->data
: NULL
;;
9840 win
= pidgin_conv_window_new();
9842 g_signal_connect(G_OBJECT(win
->window
), "configure_event",
9843 G_CALLBACK(gtk_conv_configure_cb
), NULL
);
9845 pidgin_conv_window_add_gtkconv(win
, conv
);
9846 pidgin_conv_window_show(win
);
9848 pidgin_conv_window_add_gtkconv(win
, conv
);
9852 /* This one places conversations in the last made window of the same type. */
9854 conv_placement_last_created_win_type_configured_cb(GtkWidget
*w
,
9855 GdkEventConfigure
*event
, PidginConversation
*conv
)
9858 PurpleConversationType type
= purple_conversation_get_type(conv
->active_conv
);
9861 if (GTK_WIDGET_VISIBLE(w
))
9862 gtk_window_get_position(GTK_WINDOW(w
), &x
, &y
);
9864 return FALSE
; /* carry on normally */
9866 /* Workaround for GTK+ bug # 169811 - "configure_event" is fired
9867 * when the window is being maximized */
9868 if (gdk_window_get_state(w
->window
) & GDK_WINDOW_STATE_MAXIMIZED
)
9871 /* don't save off-screen positioning */
9872 if (x
+ event
->width
< 0 ||
9873 y
+ event
->height
< 0 ||
9874 x
> gdk_screen_width() ||
9875 y
> gdk_screen_height())
9876 return FALSE
; /* carry on normally */
9878 for (all
= conv
->convs
; all
!= NULL
; all
= all
->next
) {
9879 if (type
!= purple_conversation_get_type(all
->data
)) {
9880 /* this window has different types of conversation, don't save */
9885 if (type
== PURPLE_CONV_TYPE_IM
) {
9886 purple_prefs_set_int(PIDGIN_PREFS_ROOT
"/conversations/im/x", x
);
9887 purple_prefs_set_int(PIDGIN_PREFS_ROOT
"/conversations/im/y", y
);
9888 purple_prefs_set_int(PIDGIN_PREFS_ROOT
"/conversations/im/width", event
->width
);
9889 purple_prefs_set_int(PIDGIN_PREFS_ROOT
"/conversations/im/height", event
->height
);
9890 } else if (type
== PURPLE_CONV_TYPE_CHAT
) {
9891 purple_prefs_set_int(PIDGIN_PREFS_ROOT
"/conversations/chat/x", x
);
9892 purple_prefs_set_int(PIDGIN_PREFS_ROOT
"/conversations/chat/y", y
);
9893 purple_prefs_set_int(PIDGIN_PREFS_ROOT
"/conversations/chat/width", event
->width
);
9894 purple_prefs_set_int(PIDGIN_PREFS_ROOT
"/conversations/chat/height", event
->height
);
9901 conv_placement_last_created_win_type(PidginConversation
*conv
)
9905 win
= pidgin_conv_window_last_with_type(purple_conversation_get_type(conv
->active_conv
));
9908 win
= pidgin_conv_window_new();
9910 if (PURPLE_CONV_TYPE_IM
== purple_conversation_get_type(conv
->active_conv
) ||
9911 purple_prefs_get_int(PIDGIN_PREFS_ROOT
"/conversations/chat/width") == 0) {
9912 pidgin_conv_set_position_size(win
,
9913 purple_prefs_get_int(PIDGIN_PREFS_ROOT
"/conversations/im/x"),
9914 purple_prefs_get_int(PIDGIN_PREFS_ROOT
"/conversations/im/y"),
9915 purple_prefs_get_int(PIDGIN_PREFS_ROOT
"/conversations/im/width"),
9916 purple_prefs_get_int(PIDGIN_PREFS_ROOT
"/conversations/im/height"));
9917 } else if (PURPLE_CONV_TYPE_CHAT
== purple_conversation_get_type(conv
->active_conv
)) {
9918 pidgin_conv_set_position_size(win
,
9919 purple_prefs_get_int(PIDGIN_PREFS_ROOT
"/conversations/chat/x"),
9920 purple_prefs_get_int(PIDGIN_PREFS_ROOT
"/conversations/chat/y"),
9921 purple_prefs_get_int(PIDGIN_PREFS_ROOT
"/conversations/chat/width"),
9922 purple_prefs_get_int(PIDGIN_PREFS_ROOT
"/conversations/chat/height"));
9925 pidgin_conv_window_add_gtkconv(win
, conv
);
9926 pidgin_conv_window_show(win
);
9928 g_signal_connect(G_OBJECT(win
->window
), "configure_event",
9929 G_CALLBACK(conv_placement_last_created_win_type_configured_cb
), conv
);
9931 pidgin_conv_window_add_gtkconv(win
, conv
);
9934 /* This one places each conversation in its own window. */
9936 conv_placement_new_window(PidginConversation
*conv
)
9940 win
= pidgin_conv_window_new();
9942 g_signal_connect(G_OBJECT(win
->window
), "configure_event",
9943 G_CALLBACK(gtk_conv_configure_cb
), NULL
);
9945 pidgin_conv_window_add_gtkconv(win
, conv
);
9947 pidgin_conv_window_show(win
);
9950 static PurpleGroup
*
9951 conv_get_group(PidginConversation
*conv
)
9953 PurpleGroup
*group
= NULL
;
9955 if (purple_conversation_get_type(conv
->active_conv
) == PURPLE_CONV_TYPE_IM
) {
9958 buddy
= purple_find_buddy(purple_conversation_get_account(conv
->active_conv
),
9959 purple_conversation_get_name(conv
->active_conv
));
9962 group
= purple_buddy_get_group(buddy
);
9964 } else if (purple_conversation_get_type(conv
->active_conv
) == PURPLE_CONV_TYPE_CHAT
) {
9967 chat
= purple_blist_find_chat(purple_conversation_get_account(conv
->active_conv
),
9968 purple_conversation_get_name(conv
->active_conv
));
9971 group
= purple_chat_get_group(chat
);
9978 * This groups things by, well, group. Buddies from groups will always be
9979 * grouped together, and a buddy from a group not belonging to any currently
9980 * open windows will get a new window.
9983 conv_placement_by_group(PidginConversation
*conv
)
9985 PurpleGroup
*group
= NULL
;
9988 group
= conv_get_group(conv
);
9990 /* Go through the list of IMs and find one with this group. */
9991 for (wl
= pidgin_conv_windows_get_list(); wl
!= NULL
; wl
= wl
->next
) {
9993 PidginConversation
*conv2
;
9994 PurpleGroup
*group2
= NULL
;
9998 for (cl
= win2
->gtkconvs
;
10003 group2
= conv_get_group(conv2
);
10005 if (group
== group2
) {
10006 pidgin_conv_window_add_gtkconv(win2
, conv
);
10013 /* Make a new window. */
10014 conv_placement_new_window(conv
);
10017 /* This groups things by account. Otherwise, the same semantics as above */
10019 conv_placement_by_account(PidginConversation
*conv
)
10021 GList
*wins
, *convs
;
10022 PurpleAccount
*account
;
10024 account
= purple_conversation_get_account(conv
->active_conv
);
10026 /* Go through the list of IMs and find one with this group. */
10027 for (wins
= pidgin_conv_windows_get_list(); wins
!= NULL
; wins
= wins
->next
) {
10028 PidginWindow
*win2
;
10029 PidginConversation
*conv2
;
10033 for (convs
= win2
->gtkconvs
;
10035 convs
= convs
->next
) {
10036 conv2
= convs
->data
;
10038 if (account
== purple_conversation_get_account(conv2
->active_conv
)) {
10039 pidgin_conv_window_add_gtkconv(win2
, conv
);
10045 /* Make a new window. */
10046 conv_placement_new_window(conv
);
10049 static ConvPlacementData
*
10050 get_conv_placement_data(const char *id
)
10052 ConvPlacementData
*data
= NULL
;
10055 for (n
= conv_placement_fncs
; n
; n
= n
->next
) {
10057 if (purple_strequal(data
->id
, id
))
10065 add_conv_placement_fnc(const char *id
, const char *name
,
10066 PidginConvPlacementFunc fnc
)
10068 ConvPlacementData
*data
;
10070 data
= g_new(ConvPlacementData
, 1);
10072 data
->id
= g_strdup(id
);
10073 data
->name
= g_strdup(name
);
10076 conv_placement_fncs
= g_list_append(conv_placement_fncs
, data
);
10080 ensure_default_funcs(void)
10082 if (conv_placement_fncs
== NULL
) {
10083 add_conv_placement_fnc("last", _("Last created window"),
10084 conv_placement_last_created_win
);
10085 add_conv_placement_fnc("im_chat", _("Separate IM and Chat windows"),
10086 conv_placement_last_created_win_type
);
10087 add_conv_placement_fnc("new", _("New window"),
10088 conv_placement_new_window
);
10089 add_conv_placement_fnc("group", _("By group"),
10090 conv_placement_by_group
);
10091 add_conv_placement_fnc("account", _("By account"),
10092 conv_placement_by_account
);
10097 pidgin_conv_placement_get_options(void)
10099 GList
*n
, *list
= NULL
;
10100 ConvPlacementData
*data
;
10102 ensure_default_funcs();
10104 for (n
= conv_placement_fncs
; n
; n
= n
->next
) {
10106 list
= g_list_append(list
, data
->name
);
10107 list
= g_list_append(list
, data
->id
);
10115 pidgin_conv_placement_add_fnc(const char *id
, const char *name
,
10116 PidginConvPlacementFunc fnc
)
10118 g_return_if_fail(id
!= NULL
);
10119 g_return_if_fail(name
!= NULL
);
10120 g_return_if_fail(fnc
!= NULL
);
10122 ensure_default_funcs();
10124 add_conv_placement_fnc(id
, name
, fnc
);
10128 pidgin_conv_placement_remove_fnc(const char *id
)
10130 ConvPlacementData
*data
= get_conv_placement_data(id
);
10135 conv_placement_fncs
= g_list_remove(conv_placement_fncs
, data
);
10138 g_free(data
->name
);
10143 pidgin_conv_placement_get_name(const char *id
)
10145 ConvPlacementData
*data
;
10147 ensure_default_funcs();
10149 data
= get_conv_placement_data(id
);
10157 PidginConvPlacementFunc
10158 pidgin_conv_placement_get_fnc(const char *id
)
10160 ConvPlacementData
*data
;
10162 ensure_default_funcs();
10164 data
= get_conv_placement_data(id
);
10173 pidgin_conv_placement_set_current_func(PidginConvPlacementFunc func
)
10175 g_return_if_fail(func
!= NULL
);
10177 /* If tabs are enabled, set the function, otherwise, NULL it out. */
10178 if (purple_prefs_get_bool(PIDGIN_PREFS_ROOT
"/conversations/tabs"))
10184 PidginConvPlacementFunc
10185 pidgin_conv_placement_get_current_func(void)
10191 pidgin_conv_placement_place(PidginConversation
*gtkconv
)
10194 place_conv(gtkconv
);
10196 conv_placement_new_window(gtkconv
);
10200 pidgin_conv_is_hidden(PidginConversation
*gtkconv
)
10202 g_return_val_if_fail(gtkconv
!= NULL
, FALSE
);
10204 return (gtkconv
->win
== hidden_convwin
);
10208 /* Algorithm from http://www.w3.org/TR/AERT#color-contrast */
10210 color_is_visible(GdkColor foreground
, GdkColor background
, guint color_contrast
, guint brightness_contrast
)
10212 gulong fg_brightness
;
10213 gulong bg_brightness
;
10216 int fred
, fgreen
, fblue
, bred
, bgreen
, bblue
;
10218 /* this algorithm expects colors between 0 and 255 for each of red green and blue.
10219 * GTK on the other hand has values between 0 and 65535
10220 * Err suggested I >> 8, which grabbed the high bits.
10223 fred
= foreground
.red
>> 8 ;
10224 fgreen
= foreground
.green
>> 8 ;
10225 fblue
= foreground
.blue
>> 8 ;
10228 bred
= background
.red
>> 8 ;
10229 bgreen
= background
.green
>> 8 ;
10230 bblue
= background
.blue
>> 8 ;
10232 fg_brightness
= (fred
* 299 + fgreen
* 587 + fblue
* 114) / 1000;
10233 bg_brightness
= (bred
* 299 + bgreen
* 587 + bblue
* 114) / 1000;
10234 br_diff
= abs(fg_brightness
- bg_brightness
);
10236 col_diff
= abs(fred
- bred
) + abs(fgreen
- bgreen
) + abs(fblue
- bblue
);
10238 return ((col_diff
> color_contrast
) && (br_diff
> brightness_contrast
));
10243 generate_nick_colors(guint
*color_count
, GdkColor background
)
10245 guint numcolors
= *color_count
;
10246 guint i
= 0, j
= 0;
10247 GdkColor
*colors
= g_new(GdkColor
, numcolors
);
10248 GdkColor nick_highlight
;
10249 GdkColor send_color
;
10250 time_t breakout_time
;
10252 gdk_color_parse(DEFAULT_HIGHLIGHT_COLOR
, &nick_highlight
);
10253 gdk_color_parse(DEFAULT_SEND_COLOR
, &send_color
);
10255 srand(background
.red
+ background
.green
+ background
.blue
+ 1);
10257 breakout_time
= time(NULL
) + 3;
10259 /* first we look through the list of "good" colors: colors that differ from every other color in the
10260 * list. only some of them will differ from the background color though. lets see if we can find
10261 * numcolors of them that do
10263 while (i
< numcolors
&& j
< NUM_NICK_SEED_COLORS
&& time(NULL
) < breakout_time
)
10265 GdkColor color
= nick_seed_colors
[j
];
10267 if (color_is_visible(color
, background
, MIN_COLOR_CONTRAST
, MIN_BRIGHTNESS_CONTRAST
) &&
10268 color_is_visible(color
, nick_highlight
, MIN_COLOR_CONTRAST
/ 2, 0) &&
10269 color_is_visible(color
, send_color
, MIN_COLOR_CONTRAST
/ 4, 0))
10277 /* we might not have found numcolors in the last loop. if we did, we'll never enter this one.
10278 * if we did not, lets just find some colors that don't conflict with the background. its
10279 * expensive to find colors that not only don't conflict with the background, but also do not
10280 * conflict with each other.
10282 while(i
< numcolors
&& time(NULL
) < breakout_time
)
10284 GdkColor color
= { 0, rand() % 65536, rand() % 65536, rand() % 65536 };
10286 if (color_is_visible(color
, background
, MIN_COLOR_CONTRAST
, MIN_BRIGHTNESS_CONTRAST
) &&
10287 color_is_visible(color
, nick_highlight
, MIN_COLOR_CONTRAST
/ 2, 0) &&
10288 color_is_visible(color
, send_color
, MIN_COLOR_CONTRAST
/ 4, 0))
10295 if (i
< numcolors
) {
10296 GdkColor
*c
= colors
;
10297 purple_debug_warning("gtkconv", "Unable to generate enough random colors before timeout. %u colors found.\n", i
);
10298 colors
= g_memdup(c
, i
* sizeof(GdkColor
));