3 * Pidgin is the legal property of its developers, whose names are too numerous
4 * to list here. Please refer to the COPYRIGHT file distributed with this
7 * This program is free software; you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation; either version 2 of the License, or
10 * (at your option) any later version.
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
17 * You should have received a copy of the GNU General Public License
18 * along with this program; if not, write to the Free Software
19 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA
27 # include <X11/Xlib.h>
30 #include <gdk/gdkkeysyms.h>
35 #include "attention.h"
40 #include "glibcompat.h"
42 #include "image-store.h"
48 #include "smiley-parser.h"
52 #include "gtkinternal.h"
53 #include "gtkdnd-hints.h"
56 #include "gtkconvwin.h"
57 #include "gtkdialogs.h"
58 #include "gtkmenutray.h"
59 #include "gtkpounce.h"
61 #include "gtkprivacy.h"
64 #include "pidgingdkpixbuf.h"
65 #include "pidgininvitedialog.h"
66 #include "pidginlog.h"
67 #include "pidginmessage.h"
68 #include "pidginstock.h"
69 #include "pidgintooltip.h"
71 #include "gtknickcolors.h"
73 #define GTK_TOOLTIPS_VAR gtkconv->tooltips
74 #include "gtk3compat.h"
76 #define ADD_MESSAGE_HISTORY_AT_ONCE 100
79 * A GTK+ Instant Message pane.
91 /* Buddy icon stuff */
92 GtkWidget
*icon_container
;
96 GdkPixbufAnimation
*anim
;
97 GdkPixbufAnimationIter
*iter
;
104 struct _PidginChatPane
108 GtkWidget
*topic_text
;
111 #define CLOSE_CONV_TIMEOUT_SECS (10 * 60)
113 #define AUTO_RESPONSE "<AUTO-REPLY> : "
117 PIDGIN_CONV_SET_TITLE
= 1 << 0,
118 PIDGIN_CONV_BUDDY_ICON
= 1 << 1,
119 PIDGIN_CONV_MENU
= 1 << 2,
120 PIDGIN_CONV_TAB_ICON
= 1 << 3,
121 PIDGIN_CONV_TOPIC
= 1 << 4,
122 PIDGIN_CONV_SMILEY_THEME
= 1 << 5,
123 PIDGIN_CONV_COLORIZE_TITLE
= 1 << 6,
124 PIDGIN_CONV_E2EE
= 1 << 7
131 CONV_PROTOCOL_ICON_COLUMN
,
133 } PidginInfopaneColumns
;
135 #define PIDGIN_CONV_ALL ((1 << 7) - 1)
137 /* XXX: These color defines shouldn't really be here. But the nick-color
138 * generation algorithm uses them, so keeping these around until we fix that. */
139 #define DEFAULT_SEND_COLOR "#204a87"
140 #define DEFAULT_HIGHLIGHT_COLOR "#AF7F00"
142 #define BUDDYICON_SIZE_MIN 32
143 #define BUDDYICON_SIZE_MAX 96
145 #define MIN_LUMINANCE_CONTRAST_RATIO 4.5
147 #define NICK_COLOR_GENERATE_COUNT 220
148 static GArray
*generated_nick_colors
= NULL
;
150 /* These probably won't conflict with any WebKit values. */
151 #define PIDGIN_DRAG_BLIST_NODE (1337)
152 #define PIDGIN_DRAG_IM_CONTACT (31337)
154 static GtkWidget
*invite_dialog
= NULL
;
155 static GtkWidget
*warn_close_dialog
= NULL
;
157 static PidginConvWindow
*hidden_convwin
= NULL
;
158 static GList
*window_list
= NULL
;
160 /* Lists of status icons at all available sizes for use as window icons */
161 static GList
*available_list
= NULL
;
162 static GList
*away_list
= NULL
;
163 static GList
*busy_list
= NULL
;
164 static GList
*xa_list
= NULL
;
165 static GList
*offline_list
= NULL
;
166 static GHashTable
*protocol_lists
= NULL
;
167 static GHashTable
*e2ee_stock
= NULL
;
169 static gboolean
update_send_to_selection(PidginConvWindow
*win
);
170 static void generate_send_to_items(PidginConvWindow
*win
);
172 /* Prototypes. <-- because Paco-Paco hates this comment. */
173 static gboolean
infopane_entry_activate(PidginConversation
*gtkconv
);
174 static void got_typing_keypress(PidginConversation
*gtkconv
, gboolean first
);
175 static void gray_stuff_out(PidginConversation
*gtkconv
);
176 static void add_chat_user_common(PurpleChatConversation
*chat
, PurpleChatUser
*cb
, const char *old_name
);
177 static void pidgin_conv_updated(PurpleConversation
*conv
, PurpleConversationUpdateType type
);
178 static void conv_set_unseen(PurpleConversation
*gtkconv
, PidginUnseenState state
);
179 static void gtkconv_set_unseen(PidginConversation
*gtkconv
, PidginUnseenState state
);
180 static void update_typing_icon(PidginConversation
*gtkconv
);
181 static void update_typing_message(PidginConversation
*gtkconv
, const char *message
);
182 gboolean
pidgin_conv_has_focus(PurpleConversation
*conv
);
183 static GArray
* generate_nick_colors(guint numcolors
, GdkRGBA background
);
184 gdouble
luminance(GdkRGBA color
);
185 static gboolean
color_is_visible(GdkRGBA foreground
, GdkRGBA background
, gdouble min_contrast_ratio
);
186 static GtkTextTag
*get_buddy_tag(PurpleChatConversation
*chat
, const char *who
, PurpleMessageFlags flag
, gboolean create
);
187 static void pidgin_conv_update_fields(PurpleConversation
*conv
, PidginConvFields fields
);
188 static void focus_out_from_menubar(GtkWidget
*wid
, PidginConvWindow
*win
);
189 static void pidgin_conv_tab_pack(PidginConvWindow
*win
, PidginConversation
*gtkconv
);
190 static gboolean
infopane_press_cb(GtkWidget
*widget
, GdkEventButton
*e
, PidginConversation
*conv
);
191 static void hide_conv(PidginConversation
*gtkconv
, gboolean closetimer
);
193 static void pidgin_conv_set_position_size(PidginConvWindow
*win
, int x
, int y
,
194 int width
, int height
);
195 static gboolean
pidgin_conv_xy_to_right_infopane(PidginConvWindow
*win
, int x
, int y
);
197 static const GdkRGBA
*
198 get_nick_color(PidginConversation
*gtkconv
, const gchar
*name
)
203 col
.red
= col
.green
= col
.blue
= 0;
208 col
= g_array_index(gtkconv
->nick_colors
, GdkRGBA
,
209 g_str_hash(name
) % gtkconv
->nick_colors
->len
);
214 static PurpleBlistNode
*
215 get_conversation_blist_node(PurpleConversation
*conv
)
217 PurpleAccount
*account
= purple_conversation_get_account(conv
);
218 PurpleBlistNode
*node
= NULL
;
220 if (PURPLE_IS_IM_CONVERSATION(conv
)) {
221 node
= PURPLE_BLIST_NODE(purple_blist_find_buddy(account
, purple_conversation_get_name(conv
)));
222 node
= node
? node
->parent
: NULL
;
223 } else if (PURPLE_IS_CHAT_CONVERSATION(conv
)) {
224 node
= PURPLE_BLIST_NODE(purple_blist_find_chat(account
, purple_conversation_get_name(conv
)));
230 /**************************************************************************
232 **************************************************************************/
235 close_this_sucker(gpointer data
)
237 PidginConversation
*gtkconv
= data
;
238 GList
*list
= g_list_copy(gtkconv
->convs
);
239 g_list_foreach(list
, (GFunc
)g_object_unref
, NULL
);
245 close_conv_cb(GtkButton
*button
, PidginConversation
*gtkconv
)
247 /* We are going to destroy the conversations immediately only if the 'close immediately'
248 * preference is selected. Otherwise, close the conversation after a reasonable timeout
249 * (I am going to consider 10 minutes as a 'reasonable timeout' here.
250 * For chats, close immediately if the chat is not in the buddylist, or if the chat is
251 * not marked 'Persistent' */
252 PurpleConversation
*conv
= gtkconv
->active_conv
;
253 PurpleAccount
*account
= purple_conversation_get_account(conv
);
254 const char *name
= purple_conversation_get_name(conv
);
256 if (PURPLE_IS_IM_CONVERSATION(conv
)) {
257 if (purple_prefs_get_bool(PIDGIN_PREFS_ROOT
"/conversations/im/close_immediately"))
258 close_this_sucker(gtkconv
);
260 hide_conv(gtkconv
, TRUE
);
261 } else if (PURPLE_IS_CHAT_CONVERSATION(conv
)) {
262 PurpleChat
*chat
= purple_blist_find_chat(account
, name
);
264 !purple_blist_node_get_bool(&chat
->node
, "gtk-persistent"))
265 close_this_sucker(gtkconv
);
267 hide_conv(gtkconv
, FALSE
);
274 lbox_size_allocate_cb(GtkWidget
*w
, GtkAllocation
*allocation
, gpointer data
)
276 purple_prefs_set_int(PIDGIN_PREFS_ROOT
"/conversations/chat/userlist_width", allocation
->width
== 1 ? 0 : allocation
->width
);
282 pidgin_get_cmd_prefix(void)
288 say_command_cb(PurpleConversation
*conv
,
289 const char *cmd
, char **args
, char **error
, void *data
)
291 purple_conversation_send(conv
, args
[0]);
293 return PURPLE_CMD_RET_OK
;
297 me_command_cb(PurpleConversation
*conv
,
298 const char *cmd
, char **args
, char **error
, void *data
)
302 tmp
= g_strdup_printf("/me %s", args
[0]);
303 purple_conversation_send(conv
, tmp
);
306 return PURPLE_CMD_RET_OK
;
310 debug_command_cb(PurpleConversation
*conv
,
311 const char *cmd
, char **args
, char **error
, void *data
)
315 if (!g_ascii_strcasecmp(args
[0], "version")) {
316 tmp
= g_strdup_printf("Using Pidgin v%s with libpurple v%s.",
317 DISPLAY_VERSION
, purple_core_get_version());
318 } else if (!g_ascii_strcasecmp(args
[0], "plugins")) {
319 /* Show all the loaded plugins, including plugins marked internal.
320 * This is intentional, since third party protocols are often sources of bugs, and some
321 * plugin loaders can also be buggy.
323 GString
*str
= g_string_new("Loaded Plugins: ");
324 const GList
*plugins
= purple_plugins_get_loaded();
326 for (; plugins
; plugins
= plugins
->next
) {
327 GPluginPluginInfo
*info
= GPLUGIN_PLUGIN_INFO(
328 purple_plugin_get_info(
329 PURPLE_PLUGIN(plugins
->data
)));
330 str
= g_string_append(
332 gplugin_plugin_info_get_name(info
));
335 str
= g_string_append(str
, ", ");
338 str
= g_string_append(str
, "(none)");
341 tmp
= g_string_free(str
, FALSE
);
342 } else if (!g_ascii_strcasecmp(args
[0], "unsafe")) {
343 if (purple_debug_is_unsafe()) {
344 purple_debug_set_unsafe(FALSE
);
345 purple_conversation_write_system_message(conv
,
346 _("Unsafe debugging is now disabled."),
347 PURPLE_MESSAGE_NO_LOG
);
349 purple_debug_set_unsafe(TRUE
);
350 purple_conversation_write_system_message(conv
,
351 _("Unsafe debugging is now enabled."),
352 PURPLE_MESSAGE_NO_LOG
);
355 return PURPLE_CMD_RET_OK
;
356 } else if (!g_ascii_strcasecmp(args
[0], "verbose")) {
357 if (purple_debug_is_verbose()) {
358 purple_debug_set_verbose(FALSE
);
359 purple_conversation_write_system_message(conv
,
360 _("Verbose debugging is now disabled."),
361 PURPLE_MESSAGE_NO_LOG
);
363 purple_debug_set_verbose(TRUE
);
364 purple_conversation_write_system_message(conv
,
365 _("Verbose debugging is now enabled."),
366 PURPLE_MESSAGE_NO_LOG
);
369 return PURPLE_CMD_RET_OK
;
371 purple_conversation_write_system_message(conv
,
372 _("Supported debug options are: plugins, version, unsafe, verbose"),
373 PURPLE_MESSAGE_NO_LOG
);
374 return PURPLE_CMD_RET_OK
;
377 markup
= g_markup_escape_text(tmp
, -1);
378 purple_conversation_send(conv
, markup
);
382 return PURPLE_CMD_RET_OK
;
385 static void clear_conversation_scrollback_cb(PurpleConversation
*conv
,
388 PidginConversation
*gtkconv
= PIDGIN_CONVERSATION(conv
);
390 if (PIDGIN_CONVERSATION(conv
)) {
391 gtkconv
->last_flags
= 0;
396 clear_command_cb(PurpleConversation
*conv
,
397 const char *cmd
, char **args
, char **error
, void *data
)
399 purple_conversation_clear_message_history(conv
);
400 return PURPLE_CMD_RET_OK
;
404 clearall_command_cb(PurpleConversation
*conv
,
405 const char *cmd
, char **args
, char **error
, void *data
)
408 for (l
= purple_conversations_get_all(); l
!= NULL
; l
= l
->next
)
409 purple_conversation_clear_message_history(PURPLE_CONVERSATION(l
->data
));
411 return PURPLE_CMD_RET_OK
;
415 help_command_cb(PurpleConversation
*conv
,
416 const char *cmd
, char **args
, char **error
, void *data
)
421 if (args
[0] != NULL
) {
422 s
= g_string_new("");
423 text
= purple_cmd_help(conv
, args
[0]);
426 for (l
= text
; l
; l
= l
->next
)
428 g_string_append_printf(s
, "%s\n", (char *)l
->data
);
430 g_string_append_printf(s
, "%s", (char *)l
->data
);
432 g_string_append(s
, _("No such command (in this context)."));
435 s
= g_string_new(_("Use \"/help <command>\" for help with a "
436 "specific command.<br/>The following commands are available "
437 "in this context:<br/>"));
439 text
= purple_cmd_list(conv
);
440 for (l
= text
; l
; l
= l
->next
)
442 g_string_append_printf(s
, "%s, ", (char *)l
->data
);
444 g_string_append_printf(s
, "%s.", (char *)l
->data
);
448 purple_conversation_write_system_message(conv
, s
->str
, PURPLE_MESSAGE_NO_LOG
);
449 g_string_free(s
, TRUE
);
451 return PURPLE_CMD_RET_OK
;
455 send_history_add(PidginConversation
*gtkconv
, const char *message
)
459 first
= g_list_first(gtkconv
->send_history
);
461 first
->data
= g_strdup(message
);
462 gtkconv
->send_history
= g_list_prepend(first
, NULL
);
466 check_for_and_do_command(PurpleConversation
*conv
)
468 PidginConversation
*gtkconv
;
469 GtkWidget
*view
= NULL
;
470 GtkTextBuffer
*buffer
= NULL
;
473 gboolean retval
= FALSE
;
475 gtkconv
= PIDGIN_CONVERSATION(conv
);
476 prefix
= pidgin_get_cmd_prefix();
478 view
= talkatu_editor_get_view(TALKATU_EDITOR(gtkconv
->editor
));
479 buffer
= gtk_text_view_get_buffer(GTK_TEXT_VIEW(view
));
481 cmd
= talkatu_buffer_get_plain_text(TALKATU_BUFFER(buffer
));
483 if (cmd
&& purple_str_has_prefix(cmd
, prefix
)) {
484 PurpleCmdStatus status
;
485 char *error
, *cmdline
, *markup
, *send_history
;
487 send_history
= talkatu_markup_get_html(buffer
, NULL
);
488 send_history_add(gtkconv
, send_history
);
489 g_free(send_history
);
491 cmdline
= cmd
+ strlen(prefix
);
493 if (purple_strequal(cmdline
, "xyzzy")) {
494 purple_conversation_write_system_message(conv
,
495 "Nothing happens", PURPLE_MESSAGE_NO_LOG
);
500 /* Docs are unclear on whether or not prefix should be removed from
501 * the markup so, ignoring for now. Notably if the markup is
502 * `<b>/foo arg1</b>` we now have to move the bold tag around?
504 markup
= talkatu_markup_get_html(buffer
, NULL
);
505 status
= purple_cmd_do_command(conv
, cmdline
, markup
, &error
);
509 case PURPLE_CMD_STATUS_OK
:
512 case PURPLE_CMD_STATUS_NOT_FOUND
:
514 PurpleProtocol
*protocol
= NULL
;
515 PurpleConnection
*gc
;
517 if ((gc
= purple_conversation_get_connection(conv
)))
518 protocol
= purple_connection_get_protocol(gc
);
520 if ((protocol
!= NULL
) && (purple_protocol_get_options(protocol
) & OPT_PROTO_SLASH_COMMANDS_NATIVE
)) {
523 /* If the first word in the entered text has a '/' in it, then the user
524 * probably didn't mean it as a command. So send the text as message. */
525 spaceslash
= cmdline
;
526 while (*spaceslash
&& *spaceslash
!= ' ' && *spaceslash
!= '/')
529 if (*spaceslash
!= '/') {
530 purple_conversation_write_system_message(conv
,
531 _("Unknown command."), PURPLE_MESSAGE_NO_LOG
);
537 case PURPLE_CMD_STATUS_WRONG_ARGS
:
538 purple_conversation_write_system_message(conv
,
539 _("Syntax Error: You typed the wrong "
540 "number of arguments to that command."),
541 PURPLE_MESSAGE_NO_LOG
);
544 case PURPLE_CMD_STATUS_FAILED
:
545 purple_conversation_write_system_message(conv
,
546 error
? error
: _("Your command failed for an unknown reason."),
547 PURPLE_MESSAGE_NO_LOG
);
551 case PURPLE_CMD_STATUS_WRONG_TYPE
:
552 if(PURPLE_IS_IM_CONVERSATION(conv
))
553 purple_conversation_write_system_message(conv
,
554 _("That command only works in chats, not IMs."),
555 PURPLE_MESSAGE_NO_LOG
);
557 purple_conversation_write_system_message(conv
,
558 _("That command only works in IMs, not chats."),
559 PURPLE_MESSAGE_NO_LOG
);
562 case PURPLE_CMD_STATUS_WRONG_PROTOCOL
:
563 purple_conversation_write_system_message(conv
,
564 _("That command doesn't work on this protocol."),
565 PURPLE_MESSAGE_NO_LOG
);
577 send_cb(GtkWidget
*widget
, PidginConversation
*gtkconv
)
579 PurpleConversation
*conv
= gtkconv
->active_conv
;
580 PurpleAccount
*account
;
581 PurpleMessageFlags flags
= 0;
582 GtkTextBuffer
*buffer
= NULL
;
585 account
= purple_conversation_get_account(conv
);
587 buffer
= talkatu_editor_get_buffer(TALKATU_EDITOR(gtkconv
->editor
));
589 if (check_for_and_do_command(conv
)) {
590 talkatu_buffer_clear(TALKATU_BUFFER(buffer
));
594 if (PURPLE_IS_CHAT_CONVERSATION(conv
) &&
595 purple_chat_conversation_has_left(PURPLE_CHAT_CONVERSATION(conv
))) {
599 if (!purple_account_is_connected(account
)) {
603 content
= talkatu_markup_get_html(buffer
, NULL
);
604 if (purple_strequal(content
, "")) {
611 /* XXX: is there a better way to tell if the message has images? */
612 // if (strstr(buf, "<img ") != NULL)
613 // flags |= PURPLE_MESSAGE_IMAGES;
615 purple_conversation_send_with_flags(conv
, content
, flags
);
619 talkatu_buffer_clear(TALKATU_BUFFER(buffer
));
620 gtkconv_set_unseen(gtkconv
, PIDGIN_UNSEEN_NONE
);
624 add_remove_cb(GtkWidget
*widget
, PidginConversation
*gtkconv
)
626 PurpleAccount
*account
;
628 PurpleConversation
*conv
= gtkconv
->active_conv
;
630 account
= purple_conversation_get_account(conv
);
631 name
= purple_conversation_get_name(conv
);
633 if (PURPLE_IS_IM_CONVERSATION(conv
)) {
636 b
= purple_blist_find_buddy(account
, name
);
638 pidgin_dialogs_remove_buddy(b
);
639 else if (account
!= NULL
&& purple_account_is_connected(account
))
640 purple_blist_request_add_buddy(account
, (char *)name
, NULL
, NULL
);
641 } else if (PURPLE_IS_CHAT_CONVERSATION(conv
)) {
644 c
= purple_blist_find_chat(account
, name
);
646 pidgin_dialogs_remove_chat(c
);
647 else if (account
!= NULL
&& purple_account_is_connected(account
))
648 purple_blist_request_add_chat(account
, NULL
, NULL
, name
);
652 static void chat_do_info(PidginConversation
*gtkconv
, const char *who
)
654 PurpleChatConversation
*chat
= PURPLE_CHAT_CONVERSATION(gtkconv
->active_conv
);
655 PurpleConnection
*gc
;
657 if ((gc
= purple_conversation_get_connection(gtkconv
->active_conv
))) {
658 pidgin_retrieve_user_info_in_chat(gc
, who
, purple_chat_conversation_get_id(chat
));
664 info_cb(GtkWidget
*widget
, PidginConversation
*gtkconv
)
666 PurpleConversation
*conv
= gtkconv
->active_conv
;
668 if (PURPLE_IS_IM_CONVERSATION(conv
)) {
669 pidgin_retrieve_user_info(purple_conversation_get_connection(conv
),
670 purple_conversation_get_name(conv
));
671 } else if (PURPLE_IS_CHAT_CONVERSATION(conv
)) {
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
));
707 unblock_cb(GtkWidget
*widget
, PidginConversation
*gtkconv
)
709 PurpleConversation
*conv
= gtkconv
->active_conv
;
710 PurpleAccount
*account
;
712 account
= purple_conversation_get_account(conv
);
714 if (account
!= NULL
&& purple_account_is_connected(account
))
715 pidgin_request_add_permit(account
, purple_conversation_get_name(conv
));
719 do_invite(GtkWidget
*w
, int resp
, gpointer data
)
721 PidginInviteDialog
*dialog
= PIDGIN_INVITE_DIALOG(w
);
722 PurpleChatConversation
*chat
= pidgin_invite_dialog_get_conversation(dialog
);
723 const gchar
*contact
, *message
;
725 if (resp
== GTK_RESPONSE_ACCEPT
) {
726 contact
= pidgin_invite_dialog_get_contact(dialog
);
727 if (!g_ascii_strcasecmp(contact
, ""))
730 message
= pidgin_invite_dialog_get_message(dialog
);
732 purple_serv_chat_invite(purple_conversation_get_connection(PURPLE_CONVERSATION(chat
)),
733 purple_chat_conversation_get_id(chat
),
737 g_clear_pointer(&invite_dialog
, gtk_widget_destroy
);
741 invite_cb(GtkWidget
*widget
, PidginConversation
*gtkconv
)
743 PurpleChatConversation
*chat
= PURPLE_CHAT_CONVERSATION(gtkconv
->active_conv
);
745 if (invite_dialog
== NULL
) {
746 invite_dialog
= pidgin_invite_dialog_new(chat
);
748 /* Connect the signals. */
749 g_signal_connect(G_OBJECT(invite_dialog
), "response",
750 G_CALLBACK(do_invite
), NULL
);
753 gtk_widget_show_all(invite_dialog
);
757 menu_new_conv_cb(GtkAction
*action
, gpointer data
)
763 menu_join_chat_cb(GtkAction
*action
, gpointer data
)
765 pidgin_blist_joinchat_show();
769 savelog_writefile_cb(void *user_data
, const char *filename
)
771 PurpleConversation
*conv
= (PurpleConversation
*)user_data
;
772 PidginConversation
*gtkconv
= PIDGIN_CONVERSATION(conv
);
773 GtkTextBuffer
*buffer
= NULL
;
778 if ((fp
= g_fopen(filename
, "w+")) == NULL
) {
779 purple_notify_error(PIDGIN_CONVERSATION(conv
), NULL
,
780 _("Unable to open file."), NULL
,
781 purple_request_cpar_from_conversation(conv
));
785 name
= purple_conversation_get_name(conv
);
787 fprintf(fp
, "<html>\n");
788 fprintf(fp
, "<head>\n");
789 fprintf(fp
, "<meta http-equiv=\"content-type\" content=\"text/html; charset=UTF-8\">\n");
790 fprintf(fp
, "<title>%s</title>\n", name
);
791 fprintf(fp
, "</head>\n");
793 fprintf(fp
, "<body>\n");
794 fprintf(fp
, _("<h1>Conversation with %s</h1>\n"), name
);
795 buffer
= gtk_text_view_get_buffer(GTK_TEXT_VIEW(gtkconv
->history
));
796 text
= talkatu_markup_get_html(buffer
, NULL
);
797 fprintf(fp
, "%s", text
);
799 fprintf(fp
, "\n</body>\n");
801 fprintf(fp
, "</html>\n");
806 * It would be kinda cool if this gave the option of saving a
807 * plaintext v. HTML file.
810 menu_save_as_cb(GtkAction
*action
, gpointer data
)
812 PidginConvWindow
*win
= data
;
813 PurpleConversation
*conv
= pidgin_conv_window_get_active_conversation(win
);
814 PurpleAccount
*account
= purple_conversation_get_account(conv
);
815 PurpleBuddy
*buddy
= purple_blist_find_buddy(account
, purple_conversation_get_name(conv
));
821 name
= purple_buddy_get_contact_alias(buddy
);
823 name
= purple_normalize(account
, purple_conversation_get_name(conv
));
825 buf
= g_strdup_printf("%s.html", name
);
826 for (c
= buf
; *c
; c
++)
828 if (*c
== '/' || *c
== '\\')
831 purple_request_file(PIDGIN_CONVERSATION(conv
), _("Save Conversation"),
832 buf
, TRUE
, G_CALLBACK(savelog_writefile_cb
), NULL
,
833 purple_request_cpar_from_conversation(conv
), conv
);
839 menu_view_log_cb(GtkAction
*action
, gpointer data
)
841 PidginConvWindow
*win
= data
;
842 PurpleConversation
*conv
;
844 PidginBuddyList
*gtkblist
;
846 PurpleAccount
*account
;
850 conv
= pidgin_conv_window_get_active_conversation(win
);
852 if (PURPLE_IS_IM_CONVERSATION(conv
))
853 type
= PURPLE_LOG_IM
;
854 else if (PURPLE_IS_CHAT_CONVERSATION(conv
))
855 type
= PURPLE_LOG_CHAT
;
859 gtkblist
= pidgin_blist_get_default_gtk_blist();
861 pidgin_set_cursor(gtkblist
->window
, GDK_WATCH
);
862 pidgin_set_cursor(win
->window
, GDK_WATCH
);
864 name
= purple_conversation_get_name(conv
);
865 account
= purple_conversation_get_account(conv
);
867 buddies
= purple_blist_find_buddies(account
, name
);
868 for (cur
= buddies
; cur
!= NULL
; cur
= cur
->next
)
870 PurpleBlistNode
*node
= cur
->data
;
871 if ((node
!= NULL
) && ((node
->prev
!= NULL
) || (node
->next
!= NULL
)))
873 pidgin_log_show_contact((PurpleContact
*)node
->parent
);
874 g_slist_free(buddies
);
875 pidgin_clear_cursor(gtkblist
->window
);
876 pidgin_clear_cursor(win
->window
);
880 g_slist_free(buddies
);
882 pidgin_log_show(type
, name
, account
);
884 pidgin_clear_cursor(gtkblist
->window
);
885 pidgin_clear_cursor(win
->window
);
889 menu_clear_cb(GtkAction
*action
, gpointer data
)
891 PidginConvWindow
*win
= data
;
892 PurpleConversation
*conv
;
894 conv
= pidgin_conv_window_get_active_conversation(win
);
895 purple_conversation_clear_message_history(conv
);
899 menu_find_cb(GtkAction
*action
, gpointer data
)
901 PidginConvWindow
*gtkwin
= data
;
902 PidginConversation
*gtkconv
= pidgin_conv_window_get_active_gtkconv(gtkwin
);
903 gtk_widget_show_all(gtkconv
->quickfind_container
);
904 gtk_widget_grab_focus(gtkconv
->quickfind_entry
);
909 menu_initiate_media_call_cb(GtkAction
*action
, gpointer data
)
911 PidginConvWindow
*win
= (PidginConvWindow
*)data
;
912 PurpleConversation
*conv
= pidgin_conv_window_get_active_conversation(win
);
913 PurpleAccount
*account
= purple_conversation_get_account(conv
);
915 purple_protocol_initiate_media(account
,
916 purple_conversation_get_name(conv
),
917 action
== win
->menu
->audio_call
? PURPLE_MEDIA_AUDIO
:
918 action
== win
->menu
->video_call
? PURPLE_MEDIA_VIDEO
:
919 action
== win
->menu
->audio_video_call
? PURPLE_MEDIA_AUDIO
|
920 PURPLE_MEDIA_VIDEO
: PURPLE_MEDIA_NONE
);
925 menu_send_file_cb(GtkAction
*action
, gpointer data
)
927 PidginConvWindow
*win
= data
;
928 PurpleConversation
*conv
= pidgin_conv_window_get_active_conversation(win
);
930 if (PURPLE_IS_IM_CONVERSATION(conv
)) {
931 purple_serv_send_file(purple_conversation_get_connection(conv
), purple_conversation_get_name(conv
), NULL
);
937 menu_get_attention_cb(GObject
*obj
, gpointer data
)
939 PidginConvWindow
*win
= data
;
940 PurpleConversation
*conv
= pidgin_conv_window_get_active_conversation(win
);
942 if (PURPLE_IS_IM_CONVERSATION(conv
)) {
944 if ((GtkAction
*)obj
== win
->menu
->get_attention
)
947 index
= GPOINTER_TO_INT(g_object_get_data(G_OBJECT(obj
), "index"));
948 purple_protocol_send_attention(purple_conversation_get_connection(conv
),
949 purple_conversation_get_name(conv
), index
);
954 menu_add_pounce_cb(GtkAction
*action
, gpointer data
)
956 PidginConvWindow
*win
= data
;
957 PurpleConversation
*conv
;
959 conv
= pidgin_conv_window_get_active_gtkconv(win
)->active_conv
;
961 pidgin_pounce_editor_show(purple_conversation_get_account(conv
),
962 purple_conversation_get_name(conv
), NULL
);
966 menu_alias_cb(GtkAction
*action
, gpointer data
)
968 PidginConvWindow
*win
= data
;
969 PurpleConversation
*conv
;
970 PurpleAccount
*account
;
973 conv
= pidgin_conv_window_get_active_conversation(win
);
974 account
= purple_conversation_get_account(conv
);
975 name
= purple_conversation_get_name(conv
);
977 if (PURPLE_IS_IM_CONVERSATION(conv
)) {
980 b
= purple_blist_find_buddy(account
, name
);
982 pidgin_dialogs_alias_buddy(b
);
983 } else if (PURPLE_IS_CHAT_CONVERSATION(conv
)) {
986 c
= purple_blist_find_chat(account
, name
);
988 pidgin_dialogs_alias_chat(c
);
993 menu_get_info_cb(GtkAction
*action
, gpointer data
)
995 PidginConvWindow
*win
= data
;
996 PurpleConversation
*conv
;
998 conv
= pidgin_conv_window_get_active_conversation(win
);
1000 info_cb(NULL
, PIDGIN_CONVERSATION(conv
));
1004 menu_invite_cb(GtkAction
*action
, gpointer data
)
1006 PidginConvWindow
*win
= data
;
1007 PurpleConversation
*conv
;
1009 conv
= pidgin_conv_window_get_active_conversation(win
);
1011 invite_cb(NULL
, PIDGIN_CONVERSATION(conv
));
1015 menu_block_cb(GtkAction
*action
, gpointer data
)
1017 PidginConvWindow
*win
= data
;
1018 PurpleConversation
*conv
;
1020 conv
= pidgin_conv_window_get_active_conversation(win
);
1022 block_cb(NULL
, PIDGIN_CONVERSATION(conv
));
1026 menu_unblock_cb(GtkAction
*action
, gpointer data
)
1028 PidginConvWindow
*win
= data
;
1029 PurpleConversation
*conv
;
1031 conv
= pidgin_conv_window_get_active_conversation(win
);
1033 unblock_cb(NULL
, PIDGIN_CONVERSATION(conv
));
1037 menu_add_remove_cb(GtkAction
*action
, gpointer data
)
1039 PidginConvWindow
*win
= data
;
1040 PurpleConversation
*conv
;
1042 conv
= pidgin_conv_window_get_active_conversation(win
);
1044 add_remove_cb(NULL
, PIDGIN_CONVERSATION(conv
));
1048 close_already(gpointer data
)
1050 g_object_unref(data
);
1055 hide_conv(PidginConversation
*gtkconv
, gboolean closetimer
)
1059 purple_signal_emit(pidgin_conversations_get_handle(),
1060 "conversation-hiding", gtkconv
);
1062 for (list
= g_list_copy(gtkconv
->convs
); list
; list
= g_list_delete_link(list
, list
)) {
1063 PurpleConversation
*conv
= list
->data
;
1065 guint timer
= GPOINTER_TO_INT(g_object_get_data(G_OBJECT(conv
), "close-timer"));
1067 g_source_remove(timer
);
1068 timer
= g_timeout_add_seconds(CLOSE_CONV_TIMEOUT_SECS
, close_already
, conv
);
1069 g_object_set_data(G_OBJECT(conv
), "close-timer", GINT_TO_POINTER(timer
));
1071 pidgin_conv_window_remove_gtkconv(gtkconv
->win
, gtkconv
);
1072 pidgin_conv_window_add_gtkconv(hidden_convwin
, gtkconv
);
1077 menu_close_conv_cb(GtkAction
*action
, gpointer data
)
1079 PidginConvWindow
*win
= data
;
1081 close_conv_cb(NULL
, PIDGIN_CONVERSATION(pidgin_conv_window_get_active_conversation(win
)));
1085 menu_logging_cb(GtkAction
*action
, gpointer data
)
1087 PidginConvWindow
*win
= data
;
1088 PurpleConversation
*conv
;
1090 PurpleBlistNode
*node
;
1092 conv
= pidgin_conv_window_get_active_conversation(win
);
1097 logging
= gtk_toggle_action_get_active(GTK_TOGGLE_ACTION(action
));
1099 if (logging
== purple_conversation_is_logging(conv
))
1102 node
= get_conversation_blist_node(conv
);
1106 /* Enable logging first so the message below can be logged. */
1107 purple_conversation_set_logging(conv
, TRUE
);
1109 purple_conversation_write_system_message(conv
,
1110 _("Logging started. Future messages in this conversation will be logged."), 0);
1114 purple_conversation_write_system_message(conv
,
1115 _("Logging stopped. Future messages in this conversation will not be logged."), 0);
1117 /* Disable the logging second, so that the above message can be logged. */
1118 purple_conversation_set_logging(conv
, FALSE
);
1121 /* Save the setting IFF it's different than the pref. */
1122 if (PURPLE_IS_IM_CONVERSATION(conv
)) {
1123 if (logging
== purple_prefs_get_bool("/purple/logging/log_ims"))
1124 purple_blist_node_remove_setting(node
, "enable-logging");
1126 purple_blist_node_set_bool(node
, "enable-logging", logging
);
1127 } else if (PURPLE_IS_CHAT_CONVERSATION(conv
)) {
1128 if (logging
== purple_prefs_get_bool("/purple/logging/log_chats"))
1129 purple_blist_node_remove_setting(node
, "enable-logging");
1131 purple_blist_node_set_bool(node
, "enable-logging", logging
);
1136 menu_toolbar_cb(GtkAction
*action
, gpointer data
)
1138 purple_prefs_set_bool(PIDGIN_PREFS_ROOT
"/conversations/show_formatting_toolbar",
1139 gtk_toggle_action_get_active(GTK_TOGGLE_ACTION(action
)));
1143 menu_sounds_cb(GtkAction
*action
, gpointer data
)
1145 PidginConvWindow
*win
= data
;
1146 PurpleConversation
*conv
;
1147 PidginConversation
*gtkconv
;
1148 PurpleBlistNode
*node
;
1150 conv
= pidgin_conv_window_get_active_conversation(win
);
1155 gtkconv
= PIDGIN_CONVERSATION(conv
);
1157 gtkconv
->make_sound
=
1158 gtk_toggle_action_get_active(GTK_TOGGLE_ACTION(action
));
1159 node
= get_conversation_blist_node(conv
);
1161 purple_blist_node_set_bool(node
, "gtk-mute-sound", !gtkconv
->make_sound
);
1165 chat_do_im(PidginConversation
*gtkconv
, const char *who
)
1167 PurpleConversation
*conv
= gtkconv
->active_conv
;
1168 PurpleAccount
*account
;
1169 PurpleConnection
*gc
;
1170 PurpleProtocol
*protocol
= NULL
;
1171 gchar
*real_who
= NULL
;
1173 account
= purple_conversation_get_account(conv
);
1174 g_return_if_fail(account
!= NULL
);
1176 gc
= purple_account_get_connection(account
);
1177 g_return_if_fail(gc
!= NULL
);
1179 protocol
= purple_connection_get_protocol(gc
);
1182 real_who
= purple_protocol_chat_iface_get_user_real_name(protocol
, gc
,
1183 purple_chat_conversation_get_id(PURPLE_CHAT_CONVERSATION(conv
)), who
);
1185 if(!who
&& !real_who
)
1188 pidgin_dialogs_im_with_user(account
, real_who
? real_who
: who
);
1193 static void pidgin_conv_chat_update_user(PurpleChatUser
*chatuser
);
1196 ignore_cb(GtkWidget
*w
, PidginConversation
*gtkconv
)
1198 PurpleChatConversation
*chat
= PURPLE_CHAT_CONVERSATION(gtkconv
->active_conv
);
1201 name
= g_object_get_data(G_OBJECT(w
), "user_data");
1206 if (purple_chat_conversation_is_ignored_user(chat
, name
))
1207 purple_chat_conversation_unignore(chat
, name
);
1209 purple_chat_conversation_ignore(chat
, name
);
1211 pidgin_conv_chat_update_user(purple_chat_conversation_find_user(chat
, name
));
1215 menu_chat_im_cb(GtkWidget
*w
, PidginConversation
*gtkconv
)
1217 const char *who
= g_object_get_data(G_OBJECT(w
), "user_data");
1219 chat_do_im(gtkconv
, who
);
1223 menu_chat_send_file_cb(GtkWidget
*w
, PidginConversation
*gtkconv
)
1225 PurpleProtocol
*protocol
;
1226 PurpleConversation
*conv
= gtkconv
->active_conv
;
1227 const char *who
= g_object_get_data(G_OBJECT(w
), "user_data");
1228 PurpleConnection
*gc
= purple_conversation_get_connection(conv
);
1229 gchar
*real_who
= NULL
;
1231 g_return_if_fail(gc
!= NULL
);
1233 protocol
= purple_connection_get_protocol(gc
);
1236 real_who
= purple_protocol_chat_iface_get_user_real_name(protocol
, gc
,
1237 purple_chat_conversation_get_id(PURPLE_CHAT_CONVERSATION(conv
)), who
);
1239 purple_serv_send_file(gc
, real_who
? real_who
: who
, NULL
);
1244 menu_chat_info_cb(GtkWidget
*w
, PidginConversation
*gtkconv
)
1248 who
= g_object_get_data(G_OBJECT(w
), "user_data");
1250 chat_do_info(gtkconv
, who
);
1254 menu_chat_add_remove_cb(GtkWidget
*w
, PidginConversation
*gtkconv
)
1256 PurpleConversation
*conv
= gtkconv
->active_conv
;
1257 PurpleAccount
*account
;
1261 account
= purple_conversation_get_account(conv
);
1262 name
= g_object_get_data(G_OBJECT(w
), "user_data");
1263 b
= purple_blist_find_buddy(account
, name
);
1266 pidgin_dialogs_remove_buddy(b
);
1267 else if (account
!= NULL
&& purple_account_is_connected(account
))
1268 purple_blist_request_add_buddy(account
, name
, NULL
, NULL
);
1270 gtk_widget_grab_focus(PIDGIN_CONVERSATION(conv
)->entry
);
1274 create_chat_menu(PurpleChatConversation
*chat
, const char *who
, PurpleConnection
*gc
)
1276 static GtkWidget
*menu
= NULL
;
1277 PurpleProtocol
*protocol
= NULL
;
1278 PurpleConversation
*conv
= PURPLE_CONVERSATION(chat
);
1279 PurpleAccount
*account
= purple_conversation_get_account(conv
);
1280 gboolean is_me
= FALSE
;
1282 PurpleBuddy
*buddy
= NULL
;
1285 protocol
= purple_connection_get_protocol(gc
);
1288 * If a menu already exists, destroy it before creating a new one,
1289 * thus freeing-up the memory it occupied.
1292 gtk_widget_destroy(menu
);
1294 if (purple_strequal(purple_chat_conversation_get_nick(chat
), purple_normalize(account
, who
)))
1297 menu
= gtk_menu_new();
1300 button
= pidgin_new_menu_item(menu
, _("IM"),
1301 PIDGIN_STOCK_TOOLBAR_MESSAGE_NEW
,
1302 G_CALLBACK(menu_chat_im_cb
),
1303 PIDGIN_CONVERSATION(conv
));
1306 gtk_widget_set_sensitive(button
, FALSE
);
1308 g_object_set_data_full(G_OBJECT(button
), "user_data", g_strdup(who
), g_free
);
1311 if (protocol
&& PURPLE_IS_PROTOCOL_XFER(protocol
))
1313 gboolean can_receive_file
= TRUE
;
1315 button
= pidgin_new_menu_item(menu
, _("Send File"),
1316 PIDGIN_STOCK_TOOLBAR_SEND_FILE
, G_CALLBACK(menu_chat_send_file_cb
),
1317 PIDGIN_CONVERSATION(conv
));
1319 if (gc
== NULL
|| protocol
== NULL
)
1320 can_receive_file
= FALSE
;
1322 gchar
*real_who
= NULL
;
1323 real_who
= purple_protocol_chat_iface_get_user_real_name(protocol
, gc
,
1324 purple_chat_conversation_get_id(chat
), who
);
1326 if (!purple_protocol_xfer_can_receive(
1327 PURPLE_PROTOCOL_XFER(protocol
),
1328 gc
, real_who
? real_who
: who
)) {
1329 can_receive_file
= FALSE
;
1335 if (!can_receive_file
)
1336 gtk_widget_set_sensitive(button
, FALSE
);
1338 g_object_set_data_full(G_OBJECT(button
), "user_data", g_strdup(who
), g_free
);
1342 if (purple_chat_conversation_is_ignored_user(chat
, who
))
1343 button
= pidgin_new_menu_item(menu
, _("Un-Ignore"),
1344 PIDGIN_STOCK_IGNORE
, G_CALLBACK(ignore_cb
),
1345 PIDGIN_CONVERSATION(conv
));
1347 button
= pidgin_new_menu_item(menu
, _("Ignore"),
1348 PIDGIN_STOCK_IGNORE
, G_CALLBACK(ignore_cb
),
1349 PIDGIN_CONVERSATION(conv
));
1352 gtk_widget_set_sensitive(button
, FALSE
);
1354 g_object_set_data_full(G_OBJECT(button
), "user_data", g_strdup(who
), g_free
);
1357 if (protocol
&& PURPLE_PROTOCOL_IMPLEMENTS(protocol
, SERVER
, get_info
)) {
1358 button
= pidgin_new_menu_item(menu
, _("Info"),
1359 PIDGIN_STOCK_TOOLBAR_USER_INFO
,
1360 G_CALLBACK(menu_chat_info_cb
),
1361 PIDGIN_CONVERSATION(conv
));
1364 gtk_widget_set_sensitive(button
, FALSE
);
1366 g_object_set_data_full(G_OBJECT(button
), "user_data", g_strdup(who
), g_free
);
1369 if (!is_me
&& protocol
&& !(purple_protocol_get_options(protocol
) & OPT_PROTO_UNIQUE_CHATNAME
) && PURPLE_PROTOCOL_IMPLEMENTS(protocol
, SERVER
, add_buddy
)) {
1370 if ((buddy
= purple_blist_find_buddy(account
, who
)) != NULL
)
1371 button
= pidgin_new_menu_item(menu
, _("Remove"),
1373 G_CALLBACK(menu_chat_add_remove_cb
),
1374 PIDGIN_CONVERSATION(conv
));
1376 button
= pidgin_new_menu_item(menu
, _("Add"),
1378 G_CALLBACK(menu_chat_add_remove_cb
),
1379 PIDGIN_CONVERSATION(conv
));
1382 gtk_widget_set_sensitive(button
, FALSE
);
1384 g_object_set_data_full(G_OBJECT(button
), "user_data", g_strdup(who
), g_free
);
1389 if (purple_account_is_connected(account
))
1390 pidgin_append_blist_node_proto_menu(menu
, purple_account_get_connection(account
),
1391 (PurpleBlistNode
*)buddy
);
1392 pidgin_append_blist_node_extended_menu(menu
, (PurpleBlistNode
*)buddy
);
1393 gtk_widget_show_all(menu
);
1401 gtkconv_chat_popup_menu_cb(GtkWidget
*widget
, PidginConversation
*gtkconv
)
1403 PurpleConversation
*conv
= gtkconv
->active_conv
;
1404 PidginChatPane
*gtkchat
;
1405 PurpleConnection
*gc
;
1406 PurpleAccount
*account
;
1407 GtkTreeSelection
*sel
;
1409 GtkTreeModel
*model
;
1413 gtkconv
= PIDGIN_CONVERSATION(conv
);
1414 gtkchat
= gtkconv
->u
.chat
;
1415 account
= purple_conversation_get_account(conv
);
1416 gc
= purple_account_get_connection(account
);
1418 model
= gtk_tree_view_get_model(GTK_TREE_VIEW(gtkchat
->list
));
1420 sel
= gtk_tree_view_get_selection(GTK_TREE_VIEW(gtkchat
->list
));
1421 if(!gtk_tree_selection_get_selected(sel
, NULL
, &iter
))
1424 gtk_tree_model_get(GTK_TREE_MODEL(model
), &iter
, CHAT_USERS_NAME_COLUMN
, &who
, -1);
1425 menu
= create_chat_menu (PURPLE_CHAT_CONVERSATION(conv
), who
, gc
);
1426 pidgin_menu_popup_at_treeview_selection(menu
, widget
);
1434 right_click_chat_cb(GtkWidget
*widget
, GdkEventButton
*event
,
1435 PidginConversation
*gtkconv
)
1437 PurpleConversation
*conv
= gtkconv
->active_conv
;
1438 PidginChatPane
*gtkchat
;
1439 PurpleConnection
*gc
;
1440 PurpleAccount
*account
;
1443 GtkTreeModel
*model
;
1444 GtkTreeViewColumn
*column
;
1448 gtkchat
= gtkconv
->u
.chat
;
1449 account
= purple_conversation_get_account(conv
);
1450 gc
= purple_account_get_connection(account
);
1452 model
= gtk_tree_view_get_model(GTK_TREE_VIEW(gtkchat
->list
));
1454 gtk_tree_view_get_path_at_pos(GTK_TREE_VIEW(gtkchat
->list
),
1455 event
->x
, event
->y
, &path
, &column
, &x
, &y
);
1460 gtk_tree_selection_select_path(GTK_TREE_SELECTION(
1461 gtk_tree_view_get_selection(GTK_TREE_VIEW(gtkchat
->list
))), path
);
1462 gtk_tree_view_set_cursor(GTK_TREE_VIEW(gtkchat
->list
),
1464 gtk_widget_grab_focus(GTK_WIDGET(gtkchat
->list
));
1466 gtk_tree_model_get_iter(GTK_TREE_MODEL(model
), &iter
, path
);
1467 gtk_tree_model_get(GTK_TREE_MODEL(model
), &iter
, CHAT_USERS_NAME_COLUMN
, &who
, -1);
1469 /* emit chat-nick-clicked signal */
1470 if (event
->type
== GDK_BUTTON_PRESS
) {
1471 gint plugin_return
= GPOINTER_TO_INT(purple_signal_emit_return_1(
1472 pidgin_conversations_get_handle(), "chat-nick-clicked",
1473 conv
, who
, event
->button
));
1478 if (event
->button
== GDK_BUTTON_PRIMARY
&& event
->type
== GDK_2BUTTON_PRESS
) {
1479 chat_do_im(gtkconv
, who
);
1480 } else if (gdk_event_triggers_context_menu((GdkEvent
*)event
)) {
1481 GtkWidget
*menu
= create_chat_menu (PURPLE_CHAT_CONVERSATION(conv
), who
, gc
);
1482 gtk_menu_popup_at_pointer(GTK_MENU(menu
), (GdkEvent
*)event
);
1487 gtk_tree_path_free(path
);
1493 activate_list_cb(GtkTreeView
*list
, GtkTreePath
*path
, GtkTreeViewColumn
*column
, PidginConversation
*gtkconv
)
1496 GtkTreeModel
*model
;
1499 model
= gtk_tree_view_get_model(GTK_TREE_VIEW(list
));
1501 gtk_tree_model_get_iter(GTK_TREE_MODEL(model
), &iter
, path
);
1502 gtk_tree_model_get(GTK_TREE_MODEL(model
), &iter
, CHAT_USERS_NAME_COLUMN
, &who
, -1);
1503 chat_do_im(gtkconv
, who
);
1509 move_to_next_unread_tab(PidginConversation
*gtkconv
, gboolean forward
)
1511 PidginConversation
*next_gtkconv
= NULL
, *most_active
= NULL
;
1512 PidginUnseenState unseen_state
= PIDGIN_UNSEEN_NONE
;
1513 PidginConvWindow
*win
;
1514 int initial
, i
, total
, diff
;
1517 initial
= gtk_notebook_page_num(GTK_NOTEBOOK(win
->notebook
),
1519 total
= pidgin_conv_window_get_gtkconv_count(win
);
1520 /* By adding total here, the moduli calculated later will always have two
1521 * positive arguments. x % y where x < 0 is not guaranteed to return a
1524 diff
= (forward
? 1 : -1) + total
;
1526 for (i
= (initial
+ diff
) % total
; i
!= initial
; i
= (i
+ diff
) % total
) {
1527 next_gtkconv
= pidgin_conv_window_get_gtkconv_at_index(win
, i
);
1528 if (next_gtkconv
->unseen_state
> unseen_state
) {
1529 most_active
= next_gtkconv
;
1530 unseen_state
= most_active
->unseen_state
;
1531 if(PIDGIN_UNSEEN_NICK
== unseen_state
) /* highest possible state */
1536 if (most_active
== NULL
) { /* no new messages */
1537 i
= (i
+ diff
) % total
;
1538 most_active
= pidgin_conv_window_get_gtkconv_at_index(win
, i
);
1541 if (most_active
!= NULL
&& most_active
!= gtkconv
)
1542 pidgin_conv_window_switch_gtkconv(win
, most_active
);
1546 gtkconv_cycle_focus(PidginConversation
*gtkconv
, GtkDirectionType dir
)
1548 PurpleConversation
*conv
= gtkconv
->active_conv
;
1549 gboolean chat
= PURPLE_IS_CHAT_CONVERSATION(conv
);
1550 GtkWidget
*next
= NULL
;
1555 {gtkconv
->entry
, gtkconv
->history
},
1556 {gtkconv
->history
, chat
? gtkconv
->u
.chat
->list
: gtkconv
->entry
},
1557 {chat
? gtkconv
->u
.chat
->list
: NULL
, gtkconv
->entry
},
1561 for (ptr
= transitions
; !next
&& ptr
->from
; ptr
++) {
1562 GtkWidget
*from
, *to
;
1563 if (dir
== GTK_DIR_TAB_FORWARD
) {
1570 if (gtk_widget_is_focus(from
))
1575 gtk_widget_grab_focus(next
);
1580 update_typing_inserting(PidginConversation
*gtkconv
)
1582 GtkTextBuffer
*buffer
= NULL
;
1583 gboolean is_empty
= FALSE
;
1585 g_return_if_fail(gtkconv
!= NULL
);
1587 buffer
= talkatu_editor_get_buffer(TALKATU_EDITOR(gtkconv
->editor
));
1588 is_empty
= talkatu_buffer_get_is_empty(TALKATU_BUFFER(buffer
));
1590 got_typing_keypress(gtkconv
, is_empty
);
1594 update_typing_deleting_cb(PidginConversation
*gtkconv
)
1596 PurpleIMConversation
*im
= PURPLE_IM_CONVERSATION(gtkconv
->active_conv
);
1597 GtkTextBuffer
*buffer
= NULL
;
1599 buffer
= talkatu_editor_get_buffer(TALKATU_EDITOR(gtkconv
->editor
));
1601 if (!talkatu_buffer_get_is_empty(TALKATU_BUFFER(buffer
))) {
1602 /* We deleted all the text, so turn off typing. */
1603 purple_im_conversation_stop_send_typed_timeout(im
);
1605 purple_serv_send_typing(purple_conversation_get_connection(gtkconv
->active_conv
),
1606 purple_conversation_get_name(gtkconv
->active_conv
),
1607 PURPLE_IM_NOT_TYPING
);
1609 /* We're deleting, but not all of it, so it counts as typing. */
1610 got_typing_keypress(gtkconv
, FALSE
);
1617 update_typing_deleting(PidginConversation
*gtkconv
)
1619 GtkTextBuffer
*buffer
= NULL
;
1621 g_return_if_fail(gtkconv
!= NULL
);
1623 buffer
= talkatu_editor_get_buffer(TALKATU_EDITOR(gtkconv
->editor
));
1625 if (!talkatu_buffer_get_is_empty(TALKATU_BUFFER(buffer
))) {
1626 g_timeout_add(0, (GSourceFunc
)update_typing_deleting_cb
, gtkconv
);
1631 conv_keypress_common(PidginConversation
*gtkconv
, GdkEventKey
*event
)
1633 PidginConvWindow
*win
;
1637 curconv
= gtk_notebook_get_current_page(GTK_NOTEBOOK(win
->notebook
));
1639 /* clear any tooltips */
1640 pidgin_tooltip_destroy();
1642 /* If CTRL was held down... */
1643 if (event
->state
& GDK_CONTROL_MASK
) {
1644 switch (event
->keyval
) {
1645 case GDK_KEY_Page_Down
:
1646 case GDK_KEY_KP_Page_Down
:
1648 if (!pidgin_conv_window_get_gtkconv_at_index(win
, curconv
+ 1))
1649 gtk_notebook_set_current_page(GTK_NOTEBOOK(win
->notebook
), 0);
1651 gtk_notebook_set_current_page(GTK_NOTEBOOK(win
->notebook
), curconv
+ 1);
1655 case GDK_KEY_Page_Up
:
1656 case GDK_KEY_KP_Page_Up
:
1658 if (!pidgin_conv_window_get_gtkconv_at_index(win
, curconv
- 1))
1659 gtk_notebook_set_current_page(GTK_NOTEBOOK(win
->notebook
), -1);
1661 gtk_notebook_set_current_page(GTK_NOTEBOOK(win
->notebook
), curconv
- 1);
1666 case GDK_KEY_KP_Tab
:
1667 case GDK_KEY_ISO_Left_Tab
:
1668 if (event
->state
& GDK_SHIFT_MASK
) {
1669 move_to_next_unread_tab(gtkconv
, FALSE
);
1671 move_to_next_unread_tab(gtkconv
, TRUE
);
1678 gtk_notebook_reorder_child(GTK_NOTEBOOK(win
->notebook
),
1679 gtk_notebook_get_nth_page(GTK_NOTEBOOK(win
->notebook
), curconv
),
1684 case GDK_KEY_period
:
1685 gtk_notebook_reorder_child(GTK_NOTEBOOK(win
->notebook
),
1686 gtk_notebook_get_nth_page(GTK_NOTEBOOK(win
->notebook
), curconv
),
1687 (curconv
+ 1) % gtk_notebook_get_n_pages(GTK_NOTEBOOK(win
->notebook
)));
1691 if (gtkconv_cycle_focus(gtkconv
, event
->state
& GDK_SHIFT_MASK
? GTK_DIR_TAB_BACKWARD
: GTK_DIR_TAB_FORWARD
))
1694 } /* End of switch */
1697 /* If ALT (or whatever) was held down... */
1698 else if (event
->state
& GDK_MOD1_MASK
)
1700 if (event
->keyval
> '0' && event
->keyval
<= '9')
1702 guint switchto
= event
->keyval
- '1';
1703 if (switchto
< pidgin_conv_window_get_gtkconv_count(win
))
1704 gtk_notebook_set_current_page(GTK_NOTEBOOK(win
->notebook
), switchto
);
1710 /* If neither CTRL nor ALT were held down... */
1713 switch (event
->keyval
) {
1715 if (gtk_widget_is_focus(GTK_WIDGET(win
->notebook
))) {
1716 infopane_entry_activate(gtkconv
);
1721 if (gtkconv_cycle_focus(gtkconv
, event
->state
& GDK_SHIFT_MASK
? GTK_DIR_TAB_BACKWARD
: GTK_DIR_TAB_FORWARD
))
1730 entry_key_press_cb(GtkWidget
*entry
, GdkEventKey
*event
, gpointer data
)
1732 PurpleConversation
*conv
;
1733 PidginConversation
*gtkconv
;
1735 gtkconv
= (PidginConversation
*)data
;
1736 conv
= gtkconv
->active_conv
;
1738 if (conv_keypress_common(gtkconv
, event
))
1741 /* If CTRL was held down... */
1742 if (event
->state
& GDK_CONTROL_MASK
) {
1744 /* If ALT (or whatever) was held down... */
1745 else if (event
->state
& GDK_MOD1_MASK
) {
1748 /* If neither CTRL nor ALT were held down... */
1750 switch (event
->keyval
) {
1752 case GDK_KEY_KP_Tab
:
1753 case GDK_KEY_ISO_Left_Tab
:
1754 if (gtkconv
->entry
!= entry
)
1758 plugin_return
= GPOINTER_TO_INT(purple_signal_emit_return_1(
1759 pidgin_conversations_get_handle(), "chat-nick-autocomplete",
1760 conv
, event
->state
& GDK_SHIFT_MASK
));
1761 return plugin_return
;
1765 case GDK_KEY_Page_Up
:
1766 case GDK_KEY_KP_Page_Up
:
1767 talkatu_history_page_up(TALKATU_HISTORY(gtkconv
->history
));
1771 case GDK_KEY_Page_Down
:
1772 case GDK_KEY_KP_Page_Down
:
1773 talkatu_history_page_down(TALKATU_HISTORY(gtkconv
->history
));
1777 case GDK_KEY_KP_Enter
:
1778 case GDK_KEY_Return
:
1779 send_cb(entry
, gtkconv
);
1786 if (PURPLE_IS_IM_CONVERSATION(conv
) &&
1787 purple_prefs_get_bool("/purple/conversations/im/send_typing")) {
1789 switch (event
->keyval
) {
1790 case GDK_KEY_BackSpace
:
1791 case GDK_KEY_Delete
:
1792 case GDK_KEY_KP_Delete
:
1793 update_typing_deleting(gtkconv
);
1796 update_typing_inserting(gtkconv
);
1804 * If someone tries to type into the conversation backlog of a
1805 * conversation window then we yank focus from the conversation backlog
1806 * and give it to the text entry box so that people can type
1807 * all the live long day and it will get entered into the entry box.
1810 refocus_entry_cb(GtkWidget
*widget
, GdkEventKey
*event
, gpointer data
)
1812 GtkWidget
*view
= NULL
;
1813 PidginConversation
*gtkconv
= data
;
1815 /* If we have a valid key for the conversation display, then exit */
1816 if ((event
->state
& GDK_CONTROL_MASK
) ||
1817 (event
->keyval
== GDK_KEY_F6
) ||
1818 (event
->keyval
== GDK_KEY_F10
) ||
1819 (event
->keyval
== GDK_KEY_Menu
) ||
1820 (event
->keyval
== GDK_KEY_Shift_L
) ||
1821 (event
->keyval
== GDK_KEY_Shift_R
) ||
1822 (event
->keyval
== GDK_KEY_Control_L
) ||
1823 (event
->keyval
== GDK_KEY_Control_R
) ||
1824 (event
->keyval
== GDK_KEY_Escape
) ||
1825 (event
->keyval
== GDK_KEY_Up
) ||
1826 (event
->keyval
== GDK_KEY_Down
) ||
1827 (event
->keyval
== GDK_KEY_Left
) ||
1828 (event
->keyval
== GDK_KEY_Right
) ||
1829 (event
->keyval
== GDK_KEY_Page_Up
) ||
1830 (event
->keyval
== GDK_KEY_KP_Page_Up
) ||
1831 (event
->keyval
== GDK_KEY_Page_Down
) ||
1832 (event
->keyval
== GDK_KEY_KP_Page_Down
) ||
1833 (event
->keyval
== GDK_KEY_Home
) ||
1834 (event
->keyval
== GDK_KEY_End
) ||
1835 (event
->keyval
== GDK_KEY_Tab
) ||
1836 (event
->keyval
== GDK_KEY_KP_Tab
) ||
1837 (event
->keyval
== GDK_KEY_ISO_Left_Tab
))
1839 if (event
->type
== GDK_KEY_PRESS
)
1840 return conv_keypress_common(gtkconv
, event
);
1844 view
= talkatu_editor_get_view(TALKATU_EDITOR(gtkconv
->editor
));
1845 gtk_widget_grab_focus(view
);
1846 gtk_widget_event(view
, (GdkEvent
*)event
);
1852 regenerate_options_items(PidginConvWindow
*win
);
1855 pidgin_conv_switch_active_conversation(PurpleConversation
*conv
)
1857 PidginConversation
*gtkconv
;
1858 PurpleConversation
*old_conv
;
1859 PurpleConnectionFlags features
;
1861 g_return_if_fail(conv
!= NULL
);
1863 gtkconv
= PIDGIN_CONVERSATION(conv
);
1864 old_conv
= gtkconv
->active_conv
;
1866 purple_debug_info("gtkconv", "setting active conversation on toolbar %p\n",
1869 if (old_conv
== conv
)
1872 purple_conversation_close_logs(old_conv
);
1873 gtkconv
->active_conv
= conv
;
1875 purple_conversation_set_logging(conv
,
1876 gtk_toggle_action_get_active(GTK_TOGGLE_ACTION(gtkconv
->win
->menu
->logging
)));
1878 purple_signal_emit(pidgin_conversations_get_handle(), "conversation-switched", conv
);
1880 gray_stuff_out(gtkconv
);
1881 update_typing_icon(gtkconv
);
1882 g_object_set_data(G_OBJECT(gtkconv
->entry
), "transient_buddy", NULL
);
1883 regenerate_options_items(gtkconv
->win
);
1885 gtk_window_set_title(GTK_WINDOW(gtkconv
->win
->window
),
1886 gtk_label_get_text(GTK_LABEL(gtkconv
->tab_label
)));
1890 menu_conv_sel_send_cb(GObject
*m
, gpointer data
)
1892 PurpleAccount
*account
= g_object_get_data(m
, "purple_account");
1893 gchar
*name
= g_object_get_data(m
, "purple_buddy_name");
1894 PurpleIMConversation
*im
;
1896 if (gtk_check_menu_item_get_active((GtkCheckMenuItem
*) m
) == FALSE
)
1899 im
= purple_im_conversation_new(account
, name
);
1900 pidgin_conv_switch_active_conversation(PURPLE_CONVERSATION(im
));
1903 /**************************************************************************
1904 * A bunch of buddy icon functions
1905 **************************************************************************/
1907 static GList
*get_protocol_icon_list(PurpleAccount
*account
)
1910 PurpleProtocol
*protocol
=
1911 purple_protocols_find(purple_account_get_protocol_id(account
));
1912 const char *protoname
= purple_protocol_class_list_icon(protocol
, account
, NULL
);
1913 l
= g_hash_table_lookup(protocol_lists
, protoname
);
1917 l
= g_list_prepend(l
, pidgin_create_protocol_icon(account
, PIDGIN_PROTOCOL_ICON_LARGE
));
1918 l
= g_list_prepend(l
, pidgin_create_protocol_icon(account
, PIDGIN_PROTOCOL_ICON_MEDIUM
));
1919 l
= g_list_prepend(l
, pidgin_create_protocol_icon(account
, PIDGIN_PROTOCOL_ICON_SMALL
));
1921 g_hash_table_insert(protocol_lists
, g_strdup(protoname
), l
);
1926 pidgin_conv_get_tab_icons(PurpleConversation
*conv
)
1928 PurpleAccount
*account
= NULL
;
1929 const char *name
= NULL
;
1931 g_return_val_if_fail(conv
!= NULL
, NULL
);
1933 account
= purple_conversation_get_account(conv
);
1934 name
= purple_conversation_get_name(conv
);
1936 g_return_val_if_fail(account
!= NULL
, NULL
);
1937 g_return_val_if_fail(name
!= NULL
, NULL
);
1939 /* Use the buddy icon, if possible */
1940 if (PURPLE_IS_IM_CONVERSATION(conv
)) {
1941 PurpleBuddy
*b
= purple_blist_find_buddy(account
, name
);
1944 p
= purple_buddy_get_presence(b
);
1945 if (purple_presence_is_status_primitive_active(p
, PURPLE_STATUS_AWAY
))
1947 if (purple_presence_is_status_primitive_active(p
, PURPLE_STATUS_UNAVAILABLE
))
1949 if (purple_presence_is_status_primitive_active(p
, PURPLE_STATUS_EXTENDED_AWAY
))
1951 if (purple_presence_is_status_primitive_active(p
, PURPLE_STATUS_OFFLINE
))
1952 return offline_list
;
1954 return available_list
;
1958 return get_protocol_icon_list(account
);
1962 pidgin_conv_get_icon_stock(PurpleConversation
*conv
)
1964 PurpleAccount
*account
= NULL
;
1965 const char *stock
= NULL
;
1967 g_return_val_if_fail(conv
!= NULL
, NULL
);
1969 account
= purple_conversation_get_account(conv
);
1970 g_return_val_if_fail(account
!= NULL
, NULL
);
1972 /* Use the buddy icon, if possible */
1973 if (PURPLE_IS_IM_CONVERSATION(conv
)) {
1974 const char *name
= NULL
;
1976 name
= purple_conversation_get_name(conv
);
1977 b
= purple_blist_find_buddy(account
, name
);
1979 PurplePresence
*p
= purple_buddy_get_presence(b
);
1980 PurpleStatus
*active
= purple_presence_get_active_status(p
);
1981 PurpleStatusType
*type
= purple_status_get_status_type(active
);
1982 PurpleStatusPrimitive prim
= purple_status_type_get_primitive(type
);
1983 stock
= pidgin_stock_id_from_status_primitive(prim
);
1985 stock
= PIDGIN_STOCK_STATUS_PERSON
;
1988 stock
= PIDGIN_STOCK_STATUS_CHAT
;
1995 pidgin_conv_get_icon(PurpleConversation
*conv
, GtkWidget
*parent
, const char *icon_size
)
1997 PurpleAccount
*account
= NULL
;
1998 const char *name
= NULL
;
1999 const char *stock
= NULL
;
2000 GdkPixbuf
*status
= NULL
;
2003 g_return_val_if_fail(conv
!= NULL
, NULL
);
2005 account
= purple_conversation_get_account(conv
);
2006 name
= purple_conversation_get_name(conv
);
2008 g_return_val_if_fail(account
!= NULL
, NULL
);
2009 g_return_val_if_fail(name
!= NULL
, NULL
);
2011 /* Use the buddy icon, if possible */
2012 if (PURPLE_IS_IM_CONVERSATION(conv
)) {
2013 PurpleBuddy
*b
= purple_blist_find_buddy(account
, name
);
2015 /* I hate this hack. It fixes a bug where the pending message icon
2016 * displays in the conv tab even though it shouldn't.
2017 * A better solution would be great. */
2018 purple_blist_update_node(NULL
, PURPLE_BLIST_NODE(b
));
2022 stock
= pidgin_conv_get_icon_stock(conv
);
2023 size
= gtk_icon_size_from_name(icon_size
);
2024 status
= gtk_widget_render_icon (parent
, stock
, size
, "GtkWidget");
2029 pidgin_conv_get_tab_icon(PurpleConversation
*conv
, gboolean small_icon
)
2031 const char *icon_size
= small_icon
? PIDGIN_ICON_SIZE_TANGO_MICROSCOPIC
: PIDGIN_ICON_SIZE_TANGO_EXTRA_SMALL
;
2032 return pidgin_conv_get_icon(conv
, PIDGIN_CONVERSATION(conv
)->icon
, icon_size
);
2037 update_tab_icon(PurpleConversation
*conv
)
2039 PidginConversation
*gtkconv
;
2040 PidginConvWindow
*win
;
2042 GdkPixbuf
*emblem
= NULL
;
2043 const char *status
= NULL
;
2044 const char *infopane_status
= NULL
;
2046 g_return_if_fail(conv
!= NULL
);
2048 gtkconv
= PIDGIN_CONVERSATION(conv
);
2050 if (conv
!= gtkconv
->active_conv
)
2053 status
= infopane_status
= pidgin_conv_get_icon_stock(conv
);
2055 if (PURPLE_IS_IM_CONVERSATION(conv
)) {
2056 PurpleBuddy
*b
= purple_blist_find_buddy(purple_conversation_get_account(conv
), purple_conversation_get_name(conv
));
2058 emblem
= pidgin_blist_get_emblem((PurpleBlistNode
*)b
);
2061 g_return_if_fail(status
!= NULL
);
2063 g_object_set(G_OBJECT(gtkconv
->icon
), "stock", status
, NULL
);
2064 g_object_set(G_OBJECT(gtkconv
->menu_icon
), "stock", status
, NULL
);
2066 gtk_list_store_set(GTK_LIST_STORE(gtkconv
->infopane_model
),
2067 &(gtkconv
->infopane_iter
),
2068 CONV_ICON_COLUMN
, infopane_status
, -1);
2070 gtk_list_store_set(GTK_LIST_STORE(gtkconv
->infopane_model
),
2071 &(gtkconv
->infopane_iter
),
2072 CONV_EMBLEM_COLUMN
, emblem
, -1);
2074 g_object_unref(emblem
);
2076 if (purple_prefs_get_bool(PIDGIN_PREFS_ROOT
"/blist/show_protocol_icons")) {
2077 emblem
= pidgin_create_protocol_icon(purple_conversation_get_account(gtkconv
->active_conv
), PIDGIN_PROTOCOL_ICON_SMALL
);
2082 gtk_list_store_set(GTK_LIST_STORE(gtkconv
->infopane_model
),
2083 &(gtkconv
->infopane_iter
),
2084 CONV_PROTOCOL_ICON_COLUMN
, emblem
, -1);
2086 g_object_unref(emblem
);
2088 /* XXX seanegan Why do I have to do this? */
2089 gtk_widget_queue_resize(gtkconv
->infopane
);
2090 gtk_widget_queue_draw(gtkconv
->infopane
);
2092 if (pidgin_conv_window_is_active_conversation(conv
) &&
2093 (!PURPLE_IS_IM_CONVERSATION(conv
) || gtkconv
->u
.im
->anim
== NULL
))
2095 l
= pidgin_conv_get_tab_icons(conv
);
2097 gtk_window_set_icon_list(GTK_WINDOW(win
->window
), l
);
2102 redraw_icon(gpointer data
)
2104 PidginConversation
*gtkconv
= (PidginConversation
*)data
;
2105 PurpleConversation
*conv
= gtkconv
->active_conv
;
2106 PurpleAccount
*account
;
2111 int scale_width
, scale_height
;
2114 gtkconv
= PIDGIN_CONVERSATION(conv
);
2115 account
= purple_conversation_get_account(conv
);
2117 if (!(account
&& purple_account_get_connection(account
))) {
2118 gtkconv
->u
.im
->icon_timer
= 0;
2122 gdk_pixbuf_animation_iter_advance(gtkconv
->u
.im
->iter
, NULL
);
2123 buf
= gdk_pixbuf_animation_iter_get_pixbuf(gtkconv
->u
.im
->iter
);
2125 scale_width
= gdk_pixbuf_get_width(buf
);
2126 scale_height
= gdk_pixbuf_get_height(buf
);
2128 gtk_widget_get_size_request(gtkconv
->u
.im
->icon_container
, NULL
, &size
);
2129 size
= MIN(size
, MIN(scale_width
, scale_height
));
2130 size
= CLAMP(size
, BUDDYICON_SIZE_MIN
, BUDDYICON_SIZE_MAX
);
2132 if (scale_width
== scale_height
) {
2133 scale_width
= scale_height
= size
;
2134 } else if (scale_height
> scale_width
) {
2135 scale_width
= size
* scale_width
/ scale_height
;
2136 scale_height
= size
;
2138 scale_height
= size
* scale_height
/ scale_width
;
2142 scale
= gdk_pixbuf_scale_simple(buf
, scale_width
, scale_height
,
2143 GDK_INTERP_BILINEAR
);
2144 if (pidgin_gdk_pixbuf_is_opaque(scale
))
2145 pidgin_gdk_pixbuf_make_round(scale
);
2147 gtk_image_set_from_pixbuf(GTK_IMAGE(gtkconv
->u
.im
->icon
), scale
);
2148 g_object_unref(G_OBJECT(scale
));
2149 gtk_widget_queue_draw(gtkconv
->u
.im
->icon
);
2151 delay
= gdk_pixbuf_animation_iter_get_delay_time(gtkconv
->u
.im
->iter
);
2156 gtkconv
->u
.im
->icon_timer
= g_timeout_add(delay
, redraw_icon
, gtkconv
);
2162 start_anim(GtkWidget
*widget
, PidginConversation
*gtkconv
)
2166 if (gtkconv
->u
.im
->anim
== NULL
)
2169 if (gtkconv
->u
.im
->icon_timer
!= 0)
2172 if (gdk_pixbuf_animation_is_static_image(gtkconv
->u
.im
->anim
))
2175 delay
= gdk_pixbuf_animation_iter_get_delay_time(gtkconv
->u
.im
->iter
);
2180 gtkconv
->u
.im
->icon_timer
= g_timeout_add(delay
, redraw_icon
, gtkconv
);
2184 remove_icon(GtkWidget
*widget
, PidginConversation
*gtkconv
)
2188 PurpleConversation
*conv
= gtkconv
->active_conv
;
2190 g_return_if_fail(conv
!= NULL
);
2192 gtk_widget_set_size_request(gtkconv
->u
.im
->icon_container
, -1, BUDDYICON_SIZE_MIN
);
2193 children
= gtk_container_get_children(GTK_CONTAINER(gtkconv
->u
.im
->icon_container
));
2195 /* We know there's only one child here. It'd be nice to shortcut to the
2196 event box, but we can't change the PidginConversation until 3.0 */
2197 event
= (GtkWidget
*)children
->data
;
2198 gtk_container_remove(GTK_CONTAINER(gtkconv
->u
.im
->icon_container
), event
);
2199 g_list_free(children
);
2202 if (gtkconv
->u
.im
->anim
!= NULL
)
2203 g_object_unref(G_OBJECT(gtkconv
->u
.im
->anim
));
2205 if (gtkconv
->u
.im
->icon_timer
!= 0)
2206 g_source_remove(gtkconv
->u
.im
->icon_timer
);
2208 if (gtkconv
->u
.im
->iter
!= NULL
)
2209 g_object_unref(G_OBJECT(gtkconv
->u
.im
->iter
));
2211 gtkconv
->u
.im
->icon_timer
= 0;
2212 gtkconv
->u
.im
->icon
= NULL
;
2213 gtkconv
->u
.im
->anim
= NULL
;
2214 gtkconv
->u
.im
->iter
= NULL
;
2215 gtkconv
->u
.im
->show_icon
= FALSE
;
2219 saveicon_writefile_cb(void *user_data
, const char *filename
)
2221 PidginConversation
*gtkconv
= (PidginConversation
*)user_data
;
2222 PurpleIMConversation
*im
= PURPLE_IM_CONVERSATION(gtkconv
->active_conv
);
2223 PurpleBuddyIcon
*icon
;
2227 icon
= purple_im_conversation_get_icon(im
);
2228 data
= purple_buddy_icon_get_data(icon
, &len
);
2230 if ((len
<= 0) || (data
== NULL
) || !purple_util_write_data_to_file_absolute(filename
, data
, len
)) {
2231 purple_notify_error(gtkconv
, NULL
, _("Unable to save icon file to disk."), NULL
, NULL
);
2236 custom_icon_sel_cb(const char *filename
, gpointer data
)
2239 PurpleContact
*contact
= data
;
2241 purple_buddy_icons_node_set_custom_icon_from_file((PurpleBlistNode
*)contact
, filename
);
2243 g_object_set_data(G_OBJECT(data
), "buddy-icon-chooser", NULL
);
2247 set_custom_icon_cb(GtkWidget
*widget
, PurpleContact
*contact
)
2249 GtkWidget
*win
= NULL
;
2251 /* Should not happen as menu item should be disabled. */
2252 g_return_if_fail(contact
!= NULL
);
2254 win
= g_object_get_data(G_OBJECT(contact
), "buddy-icon-chooser");
2256 GtkMenu
*menu
= GTK_MENU(gtk_widget_get_parent(widget
));
2257 GtkWidget
*toplevel
=
2258 gtk_widget_get_toplevel(gtk_menu_get_attach_widget(menu
));
2259 win
= pidgin_buddy_icon_chooser_new(GTK_WINDOW(toplevel
),
2260 custom_icon_sel_cb
, contact
);
2261 g_object_set_data_full(G_OBJECT(contact
), "buddy-icon-chooser", win
,
2262 (GDestroyNotify
)gtk_widget_destroy
);
2264 gtk_widget_show_all(win
);
2268 change_size_cb(GtkWidget
*widget
, PidginConversation
*gtkconv
)
2271 PurpleConversation
*conv
= gtkconv
->active_conv
;
2274 gtk_widget_get_size_request(gtkconv
->u
.im
->icon_container
, NULL
, &size
);
2276 if (size
== BUDDYICON_SIZE_MAX
) {
2277 size
= BUDDYICON_SIZE_MIN
;
2279 size
= BUDDYICON_SIZE_MAX
;
2282 gtk_widget_set_size_request(gtkconv
->u
.im
->icon_container
, -1, size
);
2283 pidgin_conv_update_buddy_icon(PURPLE_IM_CONVERSATION(conv
));
2285 buddies
= purple_blist_find_buddies(purple_conversation_get_account(conv
),
2286 purple_conversation_get_name(conv
));
2287 for (; buddies
; buddies
= g_slist_delete_link(buddies
, buddies
)) {
2288 PurpleBuddy
*buddy
= buddies
->data
;
2289 PurpleContact
*contact
= purple_buddy_get_contact(buddy
);
2290 purple_blist_node_set_int((PurpleBlistNode
*)contact
, "pidgin-infopane-iconsize", size
);
2295 remove_custom_icon_cb(GtkWidget
*widget
, PidginConversation
*gtkconv
)
2299 PurpleAccount
*account
;
2300 PurpleContact
*contact
;
2301 PurpleConversation
*conv
= gtkconv
->active_conv
;
2303 account
= purple_conversation_get_account(conv
);
2304 name
= purple_conversation_get_name(conv
);
2305 buddy
= purple_blist_find_buddy(account
, name
);
2309 contact
= purple_buddy_get_contact(buddy
);
2311 purple_buddy_icons_node_set_custom_icon_from_file((PurpleBlistNode
*)contact
, NULL
);
2315 icon_menu_save_cb(GtkWidget
*widget
, PidginConversation
*gtkconv
)
2317 PurpleConversation
*conv
= gtkconv
->active_conv
;
2321 g_return_if_fail(conv
!= NULL
);
2323 ext
= purple_buddy_icon_get_extension(purple_im_conversation_get_icon(PURPLE_IM_CONVERSATION(conv
)));
2325 buf
= g_strdup_printf("%s.%s", purple_normalize(purple_conversation_get_account(conv
), purple_conversation_get_name(conv
)), ext
);
2327 purple_request_file(gtkconv
, _("Save Icon"), buf
, TRUE
,
2328 G_CALLBACK(saveicon_writefile_cb
), NULL
,
2329 purple_request_cpar_from_conversation(conv
), gtkconv
);
2335 stop_anim(GtkWidget
*widget
, PidginConversation
*gtkconv
)
2337 if (gtkconv
->u
.im
->icon_timer
!= 0)
2338 g_source_remove(gtkconv
->u
.im
->icon_timer
);
2340 gtkconv
->u
.im
->icon_timer
= 0;
2345 toggle_icon_animate_cb(GtkWidget
*w
, PidginConversation
*gtkconv
)
2347 gtkconv
->u
.im
->animate
=
2348 gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(w
));
2350 if (gtkconv
->u
.im
->animate
)
2351 start_anim(NULL
, gtkconv
);
2353 stop_anim(NULL
, gtkconv
);
2357 icon_menu(GtkWidget
*widget
, GdkEventButton
*e
, PidginConversation
*gtkconv
)
2359 GtkWidget
*menu
= NULL
;
2360 GList
*old_menus
= NULL
;
2361 PurpleConversation
*conv
;
2364 if (e
->button
== GDK_BUTTON_PRIMARY
&& e
->type
== GDK_BUTTON_PRESS
) {
2365 change_size_cb(NULL
, gtkconv
);
2369 if (!gdk_event_triggers_context_menu((GdkEvent
*)e
)) {
2374 * If a menu already exists, destroy it before creating a new one,
2375 * thus freeing-up the memory it occupied.
2377 while ((old_menus
= gtk_menu_get_for_attach_widget(widget
)) != NULL
) {
2378 menu
= old_menus
->data
;
2379 gtk_menu_detach(GTK_MENU(menu
));
2380 gtk_widget_destroy(menu
);
2383 menu
= gtk_menu_new();
2384 gtk_menu_attach_to_widget(GTK_MENU(menu
), widget
, NULL
);
2386 if (gtkconv
->u
.im
->anim
&&
2387 !(gdk_pixbuf_animation_is_static_image(gtkconv
->u
.im
->anim
)))
2389 pidgin_new_check_item(menu
, _("Animate"),
2390 G_CALLBACK(toggle_icon_animate_cb
), gtkconv
,
2391 gtkconv
->u
.im
->icon_timer
);
2394 conv
= gtkconv
->active_conv
;
2395 buddy
= purple_blist_find_buddy(purple_conversation_get_account(conv
),
2396 purple_conversation_get_name(conv
));
2398 pidgin_new_menu_item(menu
, _("Hide Icon"), NULL
,
2399 G_CALLBACK(remove_icon
), gtkconv
);
2401 pidgin_new_menu_item(menu
, _("Save Icon As..."), GTK_STOCK_SAVE_AS
,
2402 G_CALLBACK(icon_menu_save_cb
), gtkconv
);
2405 PurpleContact
*contact
= purple_buddy_get_contact(buddy
);
2406 pidgin_new_menu_item(menu
, _("Set Custom Icon..."), NULL
,
2407 G_CALLBACK(set_custom_icon_cb
), contact
);
2410 pidgin_new_menu_item(menu
, _("Set Custom Icon..."), NULL
,
2411 G_CALLBACK(set_custom_icon_cb
), NULL
);
2412 gtk_widget_set_sensitive(item
, FALSE
);
2415 pidgin_new_menu_item(menu
, _("Change Size"), NULL
,
2416 G_CALLBACK(change_size_cb
), gtkconv
);
2418 /* Is there a custom icon for this person? */
2421 PurpleContact
*contact
= purple_buddy_get_contact(buddy
);
2422 if (contact
&& purple_buddy_icons_node_has_custom_icon((PurpleBlistNode
*)contact
))
2424 pidgin_new_menu_item(menu
, _("Remove Custom Icon"),
2425 NULL
, G_CALLBACK(remove_custom_icon_cb
),
2430 gtk_menu_popup_at_pointer(GTK_MENU(menu
), (GdkEvent
*)e
);
2435 /**************************************************************************
2436 * End of the bunch of buddy icon functions
2437 **************************************************************************/
2439 pidgin_conv_present_conversation(PurpleConversation
*conv
)
2441 PidginConversation
*gtkconv
;
2442 GdkModifierType state
;
2444 pidgin_conv_attach_to_conversation(conv
);
2445 gtkconv
= PIDGIN_CONVERSATION(conv
);
2447 pidgin_conv_switch_active_conversation(conv
);
2448 /* Switch the tab only if the user initiated the event by pressing
2449 * a button or hitting a key. */
2450 if (gtk_get_current_event_state(&state
))
2451 pidgin_conv_window_switch_gtkconv(gtkconv
->win
, gtkconv
);
2452 gtk_window_present(GTK_WINDOW(gtkconv
->win
->window
));
2456 pidgin_conversations_get_unseen(GList
*l
,
2457 PidginUnseenState min_state
,
2458 gboolean hidden_only
,
2464 for (; l
!= NULL
&& (max_count
== 0 || c
< max_count
); l
= l
->next
) {
2465 PurpleConversation
*conv
= (PurpleConversation
*)l
->data
;
2466 PidginConversation
*gtkconv
= PIDGIN_CONVERSATION(conv
);
2468 if(gtkconv
== NULL
|| gtkconv
->active_conv
!= conv
)
2471 if (gtkconv
->unseen_state
>= min_state
2473 (hidden_only
&& gtkconv
->win
== hidden_convwin
))) {
2475 r
= g_list_prepend(r
, conv
);
2484 pidgin_conversations_get_unseen_all(PidginUnseenState min_state
,
2485 gboolean hidden_only
,
2488 return pidgin_conversations_get_unseen(purple_conversations_get_all(),
2489 min_state
, hidden_only
, max_count
);
2493 pidgin_conversations_get_unseen_ims(PidginUnseenState min_state
,
2494 gboolean hidden_only
,
2497 return pidgin_conversations_get_unseen(purple_conversations_get_ims(),
2498 min_state
, hidden_only
, max_count
);
2502 pidgin_conversations_get_unseen_chats(PidginUnseenState min_state
,
2503 gboolean hidden_only
,
2506 return pidgin_conversations_get_unseen(purple_conversations_get_chats(),
2507 min_state
, hidden_only
, max_count
);
2511 unseen_conv_menu_cb(GtkMenuItem
*item
, PurpleConversation
*conv
)
2513 g_return_if_fail(conv
!= NULL
);
2514 pidgin_conv_present_conversation(conv
);
2518 unseen_all_conv_menu_cb(GtkMenuItem
*item
, GList
*list
)
2520 g_return_if_fail(list
!= NULL
);
2521 /* Do not free the list from here. It will be freed from the
2522 * 'destroy' callback on the menuitem. */
2524 pidgin_conv_present_conversation(list
->data
);
2530 pidgin_conversations_fill_menu(GtkWidget
*menu
, GList
*convs
)
2535 g_return_val_if_fail(menu
!= NULL
, 0);
2536 g_return_val_if_fail(convs
!= NULL
, 0);
2538 for (l
= convs
; l
!= NULL
; l
= l
->next
) {
2539 PurpleConversation
*conv
= (PurpleConversation
*)l
->data
;
2540 PidginConversation
*gtkconv
= PIDGIN_CONVERSATION(conv
);
2542 GtkWidget
*icon
= gtk_image_new_from_stock(pidgin_conv_get_icon_stock(conv
),
2543 gtk_icon_size_from_name(PIDGIN_ICON_SIZE_TANGO_MICROSCOPIC
));
2545 gchar
*text
= g_strdup_printf("%s (%d)",
2546 gtk_label_get_text(GTK_LABEL(gtkconv
->tab_label
)),
2547 gtkconv
->unseen_count
);
2549 item
= gtk_image_menu_item_new_with_label(text
);
2550 gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(item
), icon
);
2551 g_signal_connect(G_OBJECT(item
), "activate", G_CALLBACK(unseen_conv_menu_cb
), conv
);
2552 gtk_menu_shell_append(GTK_MENU_SHELL(menu
), item
);
2558 /* There are more than one conversation. Add an option to show all conversations. */
2560 GList
*list
= g_list_copy(convs
);
2562 pidgin_separator(menu
);
2564 item
= gtk_menu_item_new_with_label(_("Show All"));
2565 g_signal_connect(G_OBJECT(item
), "activate", G_CALLBACK(unseen_all_conv_menu_cb
), list
);
2566 g_signal_connect_swapped(G_OBJECT(item
), "destroy", G_CALLBACK(g_list_free
), list
);
2567 gtk_menu_shell_append(GTK_MENU_SHELL(menu
), item
);
2574 pidgin_conv_get_window(PidginConversation
*gtkconv
)
2576 g_return_val_if_fail(gtkconv
!= NULL
, NULL
);
2577 return gtkconv
->win
;
2580 static GtkActionEntry menu_entries
[] =
2581 /* TODO: fill out tooltips... */
2583 /* Conversation menu */
2584 { "ConversationMenu", NULL
, N_("_Conversation"), NULL
, NULL
, NULL
},
2585 { "NewInstantMessage", PIDGIN_STOCK_TOOLBAR_MESSAGE_NEW
, N_("New Instant _Message..."), "<control>M", NULL
, G_CALLBACK(menu_new_conv_cb
) },
2586 { "JoinAChat", PIDGIN_STOCK_CHAT
, N_("Join a _Chat..."), NULL
, NULL
, G_CALLBACK(menu_join_chat_cb
) },
2587 { "Find", GTK_STOCK_FIND
, N_("_Find..."), NULL
, NULL
, G_CALLBACK(menu_find_cb
) },
2588 { "ViewLog", NULL
, N_("View _Log"), NULL
, NULL
, G_CALLBACK(menu_view_log_cb
) },
2589 { "SaveAs", GTK_STOCK_SAVE_AS
, N_("_Save As..."), NULL
, NULL
, G_CALLBACK(menu_save_as_cb
) },
2590 { "ClearScrollback", GTK_STOCK_CLEAR
, N_("Clea_r Scrollback"), "<control>L", NULL
, G_CALLBACK(menu_clear_cb
) },
2593 { "MediaMenu", NULL
, N_("M_edia"), NULL
, NULL
, NULL
},
2594 { "AudioCall", PIDGIN_STOCK_TOOLBAR_AUDIO_CALL
, N_("_Audio Call"), NULL
, NULL
, G_CALLBACK(menu_initiate_media_call_cb
) },
2595 { "VideoCall", PIDGIN_STOCK_TOOLBAR_VIDEO_CALL
, N_("_Video Call"), NULL
, NULL
, G_CALLBACK(menu_initiate_media_call_cb
) },
2596 { "AudioVideoCall", PIDGIN_STOCK_TOOLBAR_VIDEO_CALL
, N_("Audio/Video _Call"), NULL
, NULL
, G_CALLBACK(menu_initiate_media_call_cb
) },
2599 { "SendFile", PIDGIN_STOCK_TOOLBAR_SEND_FILE
, N_("Se_nd File..."), NULL
, NULL
, G_CALLBACK(menu_send_file_cb
) },
2600 { "GetAttention", PIDGIN_STOCK_TOOLBAR_SEND_ATTENTION
, N_("Get _Attention"), NULL
, NULL
, G_CALLBACK(menu_get_attention_cb
) },
2601 { "AddBuddyPounce", NULL
, N_("Add Buddy _Pounce..."), NULL
, NULL
, G_CALLBACK(menu_add_pounce_cb
) },
2602 { "GetInfo", PIDGIN_STOCK_TOOLBAR_USER_INFO
, N_("_Get Info"), "<control>O", NULL
, G_CALLBACK(menu_get_info_cb
) },
2603 { "Invite", NULL
, N_("In_vite..."), NULL
, NULL
, G_CALLBACK(menu_invite_cb
) },
2604 { "MoreMenu", NULL
, N_("M_ore"), NULL
, NULL
, NULL
},
2605 { "Alias", NULL
, N_("Al_ias..."), NULL
, NULL
, G_CALLBACK(menu_alias_cb
) },
2606 { "Block", PIDGIN_STOCK_TOOLBAR_BLOCK
, N_("_Block..."), NULL
, NULL
, G_CALLBACK(menu_block_cb
) },
2607 { "Unblock", PIDGIN_STOCK_TOOLBAR_UNBLOCK
, N_("_Unblock..."), NULL
, NULL
, G_CALLBACK(menu_unblock_cb
) },
2608 { "Add", GTK_STOCK_ADD
, N_("_Add..."), NULL
, NULL
, G_CALLBACK(menu_add_remove_cb
) },
2609 { "Remove", GTK_STOCK_REMOVE
, N_("_Remove..."), NULL
, NULL
, G_CALLBACK(menu_add_remove_cb
) },
2610 { "InsertLink", PIDGIN_STOCK_TOOLBAR_INSERT_LINK
, N_("Insert Lin_k..."), NULL
, NULL
, NULL
},
2611 { "InsertImage", PIDGIN_STOCK_TOOLBAR_INSERT_IMAGE
, N_("Insert Imag_e..."), NULL
, NULL
, NULL
},
2612 { "Close", GTK_STOCK_CLOSE
, N_("_Close"), "<control>W", NULL
, G_CALLBACK(menu_close_conv_cb
) },
2615 { "OptionsMenu", NULL
, N_("_Options"), NULL
, NULL
, NULL
},
2619 static const GtkToggleActionEntry menu_toggle_entries
[] = {
2620 { "EnableLogging", NULL
, N_("Enable _Logging"), NULL
, NULL
, G_CALLBACK(menu_logging_cb
), FALSE
},
2621 { "EnableSounds", NULL
, N_("Enable _Sounds"), NULL
, NULL
, G_CALLBACK(menu_sounds_cb
), FALSE
},
2622 { "ShowFormattingToolbars", NULL
, N_("Show Formatting _Toolbars"), NULL
, NULL
, G_CALLBACK(menu_toolbar_cb
), FALSE
},
2625 static const char *conversation_menu
=
2627 "<menubar name='Conversation'>"
2628 "<menu action='ConversationMenu'>"
2629 "<menuitem action='NewInstantMessage'/>"
2630 "<menuitem action='JoinAChat'/>"
2632 "<menuitem action='Find'/>"
2633 "<menuitem action='ViewLog'/>"
2634 "<menuitem action='SaveAs'/>"
2635 "<menuitem action='ClearScrollback'/>"
2638 "<menu action='MediaMenu'>"
2639 "<menuitem action='AudioCall'/>"
2640 "<menuitem action='VideoCall'/>"
2641 "<menuitem action='AudioVideoCall'/>"
2644 "<menuitem action='SendFile'/>"
2645 "<menuitem action='GetAttention'/>"
2646 "<menuitem action='AddBuddyPounce'/>"
2647 "<menuitem action='GetInfo'/>"
2648 "<menuitem action='Invite'/>"
2649 "<menu action='MoreMenu'/>"
2651 "<menuitem action='Alias'/>"
2652 "<menuitem action='Block'/>"
2653 "<menuitem action='Unblock'/>"
2654 "<menuitem action='Add'/>"
2655 "<menuitem action='Remove'/>"
2657 "<menuitem action='InsertLink'/>"
2658 "<menuitem action='InsertImage'/>"
2660 "<menuitem action='Close'/>"
2662 "<menu action='OptionsMenu'>"
2663 "<menuitem action='EnableLogging'/>"
2664 "<menuitem action='EnableSounds'/>"
2666 "<menuitem action='ShowFormattingToolbars'/>"
2672 sound_method_pref_changed_cb(const char *name
, PurplePrefType type
,
2673 gconstpointer value
, gpointer data
)
2675 PidginConvWindow
*win
= data
;
2676 const char *method
= value
;
2678 if (purple_strequal(method
, "none"))
2680 gtk_toggle_action_set_active(GTK_TOGGLE_ACTION(win
->menu
->sounds
),
2682 gtk_action_set_sensitive(win
->menu
->sounds
, FALSE
);
2686 PidginConversation
*gtkconv
= pidgin_conv_window_get_active_gtkconv(win
);
2688 if (gtkconv
!= NULL
)
2689 gtk_toggle_action_set_active(GTK_TOGGLE_ACTION(win
->menu
->sounds
),
2690 gtkconv
->make_sound
);
2691 gtk_action_set_sensitive(win
->menu
->sounds
, TRUE
);
2695 /* Returns TRUE if some items were added to the menu, FALSE otherwise */
2697 populate_menu_with_options(GtkWidget
*menu
, PidginConversation
*gtkconv
, gboolean all
)
2700 PurpleConversation
*conv
;
2701 PurpleAccount
*account
;
2702 PurpleBlistNode
*node
= NULL
;
2703 PurpleChat
*chat
= NULL
;
2704 PurpleBuddy
*buddy
= NULL
;
2707 conv
= gtkconv
->active_conv
;
2708 account
= purple_conversation_get_account(conv
);
2710 if (PURPLE_IS_CHAT_CONVERSATION(conv
)) {
2711 chat
= purple_blist_find_chat(account
, purple_conversation_get_name(conv
));
2713 if ((chat
== NULL
) && (gtkconv
->history
!= NULL
)) {
2714 chat
= g_object_get_data(G_OBJECT(gtkconv
->history
), "transient_chat");
2717 if ((chat
== NULL
) && (gtkconv
->history
!= NULL
)) {
2718 GHashTable
*components
;
2719 PurpleAccount
*account
= purple_conversation_get_account(conv
);
2720 PurpleProtocol
*protocol
=
2721 purple_protocols_find(purple_account_get_protocol_id(account
));
2722 if (purple_account_get_connection(account
) != NULL
&&
2723 PURPLE_PROTOCOL_IMPLEMENTS(protocol
, CHAT
, info_defaults
)) {
2724 components
= purple_protocol_chat_iface_info_defaults(protocol
, purple_account_get_connection(account
),
2725 purple_conversation_get_name(conv
));
2727 components
= g_hash_table_new_full(g_str_hash
, g_str_equal
,
2729 g_hash_table_replace(components
, g_strdup("channel"),
2730 g_strdup(purple_conversation_get_name(conv
)));
2732 chat
= purple_chat_new(account
, NULL
, components
);
2733 purple_blist_node_set_transient((PurpleBlistNode
*)chat
, TRUE
);
2734 g_object_set_data_full(G_OBJECT(gtkconv
->history
), "transient_chat",
2735 chat
, (GDestroyNotify
)purple_blist_remove_chat
);
2738 if (!purple_account_is_connected(account
))
2741 buddy
= purple_blist_find_buddy(account
, purple_conversation_get_name(conv
));
2742 if (!buddy
&& gtkconv
->history
) {
2743 buddy
= g_object_get_data(G_OBJECT(gtkconv
->history
), "transient_buddy");
2746 buddy
= purple_buddy_new(account
, purple_conversation_get_name(conv
), NULL
);
2747 purple_blist_node_set_transient((PurpleBlistNode
*)buddy
, TRUE
);
2748 g_object_set_data_full(G_OBJECT(gtkconv
->history
), "transient_buddy",
2749 buddy
, (GDestroyNotify
)g_object_unref
);
2755 node
= (PurpleBlistNode
*)chat
;
2757 node
= (PurpleBlistNode
*)buddy
;
2759 /* Now add the stuff */
2762 pidgin_blist_make_buddy_menu(menu
, buddy
, TRUE
);
2767 if (purple_account_is_connected(account
))
2768 pidgin_append_blist_node_proto_menu(menu
, purple_account_get_connection(account
), node
);
2769 pidgin_append_blist_node_extended_menu(menu
, node
);
2772 if ((list
= gtk_container_get_children(GTK_CONTAINER(menu
))) == NULL
) {
2782 regenerate_media_items(PidginConvWindow
*win
)
2785 PurpleAccount
*account
;
2786 PurpleConversation
*conv
;
2788 conv
= pidgin_conv_window_get_active_conversation(win
);
2791 purple_debug_error("gtkconv", "couldn't get active conversation"
2792 " when regenerating media items\n");
2796 account
= purple_conversation_get_account(conv
);
2798 if (account
== NULL
) {
2799 purple_debug_error("gtkconv", "couldn't get account when"
2800 " regenerating media items\n");
2805 * Check if account support voice and/or calls, and
2806 * if the current buddy supports it.
2808 if (account
!= NULL
&& PURPLE_IS_IM_CONVERSATION(conv
)) {
2809 PurpleMediaCaps caps
=
2810 purple_protocol_get_media_caps(account
,
2811 purple_conversation_get_name(conv
));
2813 gtk_action_set_sensitive(win
->menu
->audio_call
,
2814 caps
& PURPLE_MEDIA_CAPS_AUDIO
2816 gtk_action_set_sensitive(win
->menu
->video_call
,
2817 caps
& PURPLE_MEDIA_CAPS_VIDEO
2819 gtk_action_set_sensitive(win
->menu
->audio_video_call
,
2820 caps
& PURPLE_MEDIA_CAPS_AUDIO_VIDEO
2822 } else if (PURPLE_IS_CHAT_CONVERSATION(conv
)) {
2823 /* for now, don't care about chats... */
2824 gtk_action_set_sensitive(win
->menu
->audio_call
, FALSE
);
2825 gtk_action_set_sensitive(win
->menu
->video_call
, FALSE
);
2826 gtk_action_set_sensitive(win
->menu
->audio_video_call
, FALSE
);
2828 gtk_action_set_sensitive(win
->menu
->audio_call
, FALSE
);
2829 gtk_action_set_sensitive(win
->menu
->video_call
, FALSE
);
2830 gtk_action_set_sensitive(win
->menu
->audio_video_call
, FALSE
);
2836 regenerate_attention_items(PidginConvWindow
*win
)
2838 GtkWidget
*attention
;
2840 PurpleConversation
*conv
;
2841 PurpleConnection
*pc
;
2842 PurpleProtocol
*protocol
= NULL
;
2845 conv
= pidgin_conv_window_get_active_conversation(win
);
2849 attention
= gtk_ui_manager_get_widget(win
->menu
->ui
,
2850 "/Conversation/ConversationMenu/GetAttention");
2852 /* Remove the previous entries */
2853 gtk_menu_item_set_submenu(GTK_MENU_ITEM(attention
), NULL
);
2855 pc
= purple_conversation_get_connection(conv
);
2857 protocol
= purple_connection_get_protocol(pc
);
2859 if (protocol
&& PURPLE_IS_PROTOCOL_ATTENTION(protocol
)) {
2860 list
= purple_protocol_attention_get_types(PURPLE_PROTOCOL_ATTENTION(protocol
), purple_connection_get_account(pc
));
2862 /* Multiple attention types */
2863 if (list
&& list
->next
) {
2866 menu
= gtk_menu_new();
2868 PurpleAttentionType
*type
;
2869 GtkWidget
*menuitem
;
2873 menuitem
= gtk_menu_item_new_with_label(purple_attention_type_get_name(type
));
2874 g_object_set_data(G_OBJECT(menuitem
), "index", GINT_TO_POINTER(index
));
2875 g_signal_connect(G_OBJECT(menuitem
), "activate",
2876 G_CALLBACK(menu_get_attention_cb
),
2878 gtk_menu_shell_append(GTK_MENU_SHELL(menu
), menuitem
);
2881 list
= g_list_delete_link(list
, list
);
2884 gtk_menu_item_set_submenu(GTK_MENU_ITEM(attention
), menu
);
2885 gtk_widget_show_all(menu
);
2891 regenerate_options_items(PidginConvWindow
*win
)
2894 PidginConversation
*gtkconv
;
2896 GtkWidget
*more_menu
;
2898 gtkconv
= pidgin_conv_window_get_active_gtkconv(win
);
2899 more_menu
= gtk_ui_manager_get_widget(win
->menu
->ui
,
2900 "/Conversation/ConversationMenu/MoreMenu");
2901 gtk_widget_show(more_menu
);
2902 menu
= gtk_menu_item_get_submenu(GTK_MENU_ITEM(more_menu
));
2904 /* Remove the previous entries */
2905 for (list
= gtk_container_get_children(GTK_CONTAINER(menu
)); list
; )
2907 GtkWidget
*w
= list
->data
;
2908 list
= g_list_delete_link(list
, list
);
2909 gtk_widget_destroy(w
);
2912 if (!populate_menu_with_options(menu
, gtkconv
, FALSE
))
2914 GtkWidget
*item
= gtk_menu_item_new_with_label(_("No actions available"));
2915 gtk_menu_shell_append(GTK_MENU_SHELL(menu
), item
);
2916 gtk_widget_set_sensitive(item
, FALSE
);
2919 gtk_widget_show_all(menu
);
2923 remove_from_list(GtkWidget
*widget
, PidginConvWindow
*win
)
2925 GList
*list
= g_object_get_data(G_OBJECT(win
->window
), "plugin-actions");
2926 list
= g_list_remove(list
, widget
);
2927 g_object_set_data(G_OBJECT(win
->window
), "plugin-actions", list
);
2931 regenerate_plugins_items(PidginConvWindow
*win
)
2933 GList
*action_items
;
2936 PidginConversation
*gtkconv
;
2937 PurpleConversation
*conv
;
2940 if (win
->window
== NULL
|| win
== hidden_convwin
)
2943 gtkconv
= pidgin_conv_window_get_active_gtkconv(win
);
2944 if (gtkconv
== NULL
)
2947 conv
= gtkconv
->active_conv
;
2948 action_items
= g_object_get_data(G_OBJECT(win
->window
), "plugin-actions");
2950 /* Remove the old menuitems */
2951 while (action_items
) {
2952 g_signal_handlers_disconnect_by_func(G_OBJECT(action_items
->data
),
2953 G_CALLBACK(remove_from_list
), win
);
2954 gtk_widget_destroy(action_items
->data
);
2955 action_items
= g_list_delete_link(action_items
, action_items
);
2958 item
= gtk_ui_manager_get_widget(win
->menu
->ui
, "/Conversation/OptionsMenu");
2959 menu
= gtk_menu_item_get_submenu(GTK_MENU_ITEM(item
));
2961 list
= purple_conversation_get_extended_menu(conv
);
2963 action_items
= g_list_prepend(NULL
, (item
= pidgin_separator(menu
)));
2964 g_signal_connect(G_OBJECT(item
), "destroy", G_CALLBACK(remove_from_list
), win
);
2967 for(; list
; list
= g_list_delete_link(list
, list
)) {
2968 PurpleActionMenu
*act
= (PurpleActionMenu
*) list
->data
;
2969 item
= pidgin_append_menu_action(menu
, act
, conv
);
2970 action_items
= g_list_prepend(action_items
, item
);
2971 gtk_widget_show_all(item
);
2972 g_signal_connect(G_OBJECT(item
), "destroy", G_CALLBACK(remove_from_list
), win
);
2974 g_object_set_data(G_OBJECT(win
->window
), "plugin-actions", action_items
);
2977 static void menubar_activated(GtkWidget
*item
, gpointer data
)
2979 PidginConvWindow
*win
= data
;
2980 regenerate_media_items(win
);
2981 regenerate_options_items(win
);
2982 regenerate_plugins_items(win
);
2983 regenerate_attention_items(win
);
2985 /* The following are to make sure the 'More' submenu is not regenerated every time
2986 * the focus shifts from 'Conversations' to some other menu and back. */
2987 g_signal_handlers_block_by_func(G_OBJECT(item
), G_CALLBACK(menubar_activated
), data
);
2988 g_signal_connect(G_OBJECT(win
->menu
->menubar
), "deactivate", G_CALLBACK(focus_out_from_menubar
), data
);
2992 focus_out_from_menubar(GtkWidget
*wid
, PidginConvWindow
*win
)
2994 /* The menubar has been deactivated. Make sure the 'More' submenu is regenerated next time
2995 * the 'Conversation' menu pops up. */
2996 GtkWidget
*menuitem
= gtk_ui_manager_get_widget(win
->menu
->ui
, "/Conversation/ConversationMenu");
2997 g_signal_handlers_unblock_by_func(G_OBJECT(menuitem
), G_CALLBACK(menubar_activated
), win
);
2998 g_signal_handlers_disconnect_by_func(G_OBJECT(win
->menu
->menubar
),
2999 G_CALLBACK(focus_out_from_menubar
), win
);
3003 setup_menubar(PidginConvWindow
*win
)
3005 GtkAccelGroup
*accel_group
;
3007 GtkActionGroup
*action_group
;
3009 GtkWidget
*menuitem
;
3011 action_group
= gtk_action_group_new("ConversationActions");
3012 gtk_action_group_set_translation_domain(action_group
, PACKAGE
);
3013 gtk_action_group_add_actions(action_group
,
3015 G_N_ELEMENTS(menu_entries
),
3017 gtk_action_group_add_toggle_actions(action_group
,
3018 menu_toggle_entries
,
3019 G_N_ELEMENTS(menu_toggle_entries
),
3022 win
->menu
->ui
= gtk_ui_manager_new();
3023 gtk_ui_manager_insert_action_group(win
->menu
->ui
, action_group
, 0);
3025 accel_group
= gtk_ui_manager_get_accel_group(win
->menu
->ui
);
3026 gtk_window_add_accel_group(GTK_WINDOW(win
->window
), accel_group
);
3027 g_signal_connect(G_OBJECT(accel_group
), "accel-changed",
3028 G_CALLBACK(pidgin_save_accels_cb
), NULL
);
3031 if (!gtk_ui_manager_add_ui_from_string(win
->menu
->ui
, conversation_menu
, -1, &error
))
3033 g_message("building menus failed: %s", error
->message
);
3034 g_error_free(error
);
3038 win
->menu
->menubar
=
3039 gtk_ui_manager_get_widget(win
->menu
->ui
, "/Conversation");
3041 /* Make sure the 'Conversation ⇨ More' menuitems are regenerated whenever
3042 * the 'Conversation' menu pops up because the entries can change after the
3043 * conversation is created. */
3044 menuitem
= gtk_ui_manager_get_widget(win
->menu
->ui
, "/Conversation/ConversationMenu");
3045 g_signal_connect(G_OBJECT(menuitem
), "activate", G_CALLBACK(menubar_activated
), win
);
3047 win
->menu
->view_log
=
3048 gtk_ui_manager_get_action(win
->menu
->ui
,
3049 "/Conversation/ConversationMenu/ViewLog");
3052 win
->menu
->audio_call
=
3053 gtk_ui_manager_get_action(win
->menu
->ui
,
3054 "/Conversation/ConversationMenu/MediaMenu/AudioCall");
3055 win
->menu
->video_call
=
3056 gtk_ui_manager_get_action(win
->menu
->ui
,
3057 "/Conversation/ConversationMenu/MediaMenu/VideoCall");
3058 win
->menu
->audio_video_call
=
3059 gtk_ui_manager_get_action(win
->menu
->ui
,
3060 "/Conversation/ConversationMenu/MediaMenu/AudioVideoCall");
3065 win
->menu
->send_file
=
3066 gtk_ui_manager_get_action(win
->menu
->ui
,
3067 "/Conversation/ConversationMenu/SendFile");
3069 win
->menu
->get_attention
=
3070 gtk_ui_manager_get_action(win
->menu
->ui
,
3071 "/Conversation/ConversationMenu/GetAttention");
3073 win
->menu
->add_pounce
=
3074 gtk_ui_manager_get_action(win
->menu
->ui
,
3075 "/Conversation/ConversationMenu/AddBuddyPounce");
3079 win
->menu
->get_info
=
3080 gtk_ui_manager_get_action(win
->menu
->ui
,
3081 "/Conversation/ConversationMenu/GetInfo");
3084 gtk_ui_manager_get_action(win
->menu
->ui
,
3085 "/Conversation/ConversationMenu/Invite");
3090 gtk_ui_manager_get_action(win
->menu
->ui
,
3091 "/Conversation/ConversationMenu/Alias");
3094 gtk_ui_manager_get_action(win
->menu
->ui
,
3095 "/Conversation/ConversationMenu/Block");
3097 win
->menu
->unblock
=
3098 gtk_ui_manager_get_action(win
->menu
->ui
,
3099 "/Conversation/ConversationMenu/Unblock");
3102 gtk_ui_manager_get_action(win
->menu
->ui
,
3103 "/Conversation/ConversationMenu/Add");
3106 gtk_ui_manager_get_action(win
->menu
->ui
,
3107 "/Conversation/ConversationMenu/Remove");
3111 win
->menu
->insert_link
=
3112 gtk_ui_manager_get_action(win
->menu
->ui
,
3113 "/Conversation/ConversationMenu/InsertLink");
3115 win
->menu
->insert_image
=
3116 gtk_ui_manager_get_action(win
->menu
->ui
,
3117 "/Conversation/ConversationMenu/InsertImage");
3121 win
->menu
->logging
=
3122 gtk_ui_manager_get_action(win
->menu
->ui
,
3123 "/Conversation/OptionsMenu/EnableLogging");
3125 gtk_ui_manager_get_action(win
->menu
->ui
,
3126 "/Conversation/OptionsMenu/EnableSounds");
3127 method
= purple_prefs_get_string(PIDGIN_PREFS_ROOT
"/sound/method");
3128 if (purple_strequal(method
, "none"))
3130 gtk_toggle_action_set_active(GTK_TOGGLE_ACTION(win
->menu
->sounds
),
3132 gtk_action_set_sensitive(win
->menu
->sounds
, FALSE
);
3134 purple_prefs_connect_callback(win
, PIDGIN_PREFS_ROOT
"/sound/method",
3135 sound_method_pref_changed_cb
, win
);
3137 win
->menu
->show_formatting_toolbar
=
3138 gtk_ui_manager_get_action(win
->menu
->ui
,
3139 "/Conversation/OptionsMenu/ShowFormattingToolbars");
3141 win
->menu
->tray
= pidgin_menu_tray_new();
3142 gtk_menu_shell_append(GTK_MENU_SHELL(win
->menu
->menubar
),
3144 gtk_widget_show(win
->menu
->tray
);
3146 gtk_widget_show(win
->menu
->menubar
);
3148 return win
->menu
->menubar
;
3152 /**************************************************************************
3154 **************************************************************************/
3157 got_typing_keypress(PidginConversation
*gtkconv
, gboolean first
)
3159 PurpleConversation
*conv
= gtkconv
->active_conv
;
3160 PurpleIMConversation
*im
;
3163 * We know we got something, so we at least have to make sure we don't
3164 * send PURPLE_IM_TYPED any time soon.
3167 im
= PURPLE_IM_CONVERSATION(conv
);
3169 purple_im_conversation_stop_send_typed_timeout(im
);
3170 purple_im_conversation_start_send_typed_timeout(im
);
3172 /* Check if we need to send another PURPLE_IM_TYPING message */
3173 if (first
|| (purple_im_conversation_get_type_again(im
) != 0 &&
3174 time(NULL
) > purple_im_conversation_get_type_again(im
)))
3176 unsigned int timeout
;
3177 timeout
= purple_serv_send_typing(purple_conversation_get_connection(conv
),
3178 purple_conversation_get_name(conv
),
3180 purple_im_conversation_set_type_again(im
, timeout
);
3186 typing_animation(gpointer data
) {
3187 PidginConversation
*gtkconv
= data
;
3188 PidginConvWindow
*gtkwin
= gtkconv
->win
;
3189 const char *stock_id
= NULL
;
3191 if(gtkconv
!= pidgin_conv_window_get_active_gtkconv(gtkwin
)) {
3195 switch (rand() % 5) {
3197 stock_id
= PIDGIN_STOCK_ANIMATION_TYPING0
;
3200 stock_id
= PIDGIN_STOCK_ANIMATION_TYPING1
;
3203 stock_id
= PIDGIN_STOCK_ANIMATION_TYPING2
;
3206 stock_id
= PIDGIN_STOCK_ANIMATION_TYPING3
;
3209 stock_id
= PIDGIN_STOCK_ANIMATION_TYPING4
;
3212 if (gtkwin
->menu
->typing_icon
== NULL
) {
3213 gtkwin
->menu
->typing_icon
= gtk_image_new_from_stock(stock_id
, GTK_ICON_SIZE_MENU
);
3214 pidgin_menu_tray_append(PIDGIN_MENU_TRAY(gtkwin
->menu
->tray
),
3215 gtkwin
->menu
->typing_icon
,
3216 _("User is typing..."));
3218 gtk_image_set_from_stock(GTK_IMAGE(gtkwin
->menu
->typing_icon
), stock_id
, GTK_ICON_SIZE_MENU
);
3220 gtk_widget_show(gtkwin
->menu
->typing_icon
);
3226 update_typing_message(PidginConversation
*gtkconv
, const char *message
)
3228 /* TODO WEBKIT: this is not handled at all */
3230 GtkTextBuffer
*buffer
;
3231 GtkTextMark
*stmark
, *enmark
;
3233 if (g_object_get_data(G_OBJECT(gtkconv
->imhtml
), "disable-typing-notification"))
3236 buffer
= gtk_text_view_get_buffer(GTK_TEXT_VIEW(gtkconv
->imhtml
));
3237 stmark
= gtk_text_buffer_get_mark(buffer
, "typing-notification-start");
3238 enmark
= gtk_text_buffer_get_mark(buffer
, "typing-notification-end");
3239 if (stmark
&& enmark
) {
3240 GtkTextIter start
, end
;
3241 gtk_text_buffer_get_iter_at_mark(buffer
, &start
, stmark
);
3242 gtk_text_buffer_get_iter_at_mark(buffer
, &end
, enmark
);
3243 gtk_text_buffer_delete_mark(buffer
, stmark
);
3244 gtk_text_buffer_delete_mark(buffer
, enmark
);
3245 gtk_text_buffer_delete(buffer
, &start
, &end
);
3246 } else if (message
&& *message
== '\n' && message
[1] == ' ' && message
[2] == '\0')
3251 message
= "\n "; /* The blank space is required to avoid a GTK+/Pango bug */
3256 gtk_text_buffer_get_end_iter(buffer
, &iter
);
3257 gtk_text_buffer_create_mark(buffer
, "typing-notification-start", &iter
, TRUE
);
3258 gtk_text_buffer_insert_with_tags_by_name(buffer
, &iter
, message
, -1, "TYPING-NOTIFICATION", NULL
);
3259 gtk_text_buffer_get_end_iter(buffer
, &iter
);
3260 gtk_text_buffer_create_mark(buffer
, "typing-notification-end", &iter
, TRUE
);
3266 update_typing_icon(PidginConversation
*gtkconv
)
3268 PurpleIMConversation
*im
;
3269 char *message
= NULL
;
3271 if (!PURPLE_IS_IM_CONVERSATION(gtkconv
->active_conv
))
3274 im
= PURPLE_IM_CONVERSATION(gtkconv
->active_conv
);
3276 if (purple_im_conversation_get_typing_state(im
) == PURPLE_IM_NOT_TYPING
) {
3278 update_typing_message(gtkconv
, NULL
);
3280 update_typing_message(gtkconv
, "\n ");
3285 if (purple_im_conversation_get_typing_state(im
) == PURPLE_IM_TYPING
) {
3286 message
= g_strdup_printf(_("\n%s is typing..."), purple_conversation_get_title(PURPLE_CONVERSATION(im
)));
3288 message
= g_strdup_printf(_("\n%s has stopped typing"), purple_conversation_get_title(PURPLE_CONVERSATION(im
)));
3291 update_typing_message(gtkconv
, message
);
3296 update_send_to_selection(PidginConvWindow
*win
)
3298 PurpleAccount
*account
;
3299 PurpleConversation
*conv
;
3304 conv
= pidgin_conv_window_get_active_conversation(win
);
3309 account
= purple_conversation_get_account(conv
);
3311 if (account
== NULL
)
3314 if (win
->menu
->send_to
== NULL
)
3317 if (!(b
= purple_blist_find_buddy(account
, purple_conversation_get_name(conv
))))
3320 gtk_widget_show(win
->menu
->send_to
);
3322 menu
= gtk_menu_item_get_submenu(GTK_MENU_ITEM(win
->menu
->send_to
));
3324 for (child
= gtk_container_get_children(GTK_CONTAINER(menu
));
3326 child
= g_list_delete_link(child
, child
)) {
3328 GtkWidget
*item
= child
->data
;
3329 PurpleBuddy
*item_buddy
;
3330 PurpleAccount
*item_account
= g_object_get_data(G_OBJECT(item
), "purple_account");
3331 gchar
*buddy_name
= g_object_get_data(G_OBJECT(item
),
3332 "purple_buddy_name");
3333 item_buddy
= purple_blist_find_buddy(item_account
, buddy_name
);
3335 if (b
== item_buddy
) {
3336 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(item
), TRUE
);
3346 send_to_item_enter_notify_cb(GtkWidget
*menuitem
, GdkEventCrossing
*event
, GtkWidget
*label
)
3348 gtk_widget_set_sensitive(GTK_WIDGET(label
), TRUE
);
3353 send_to_item_leave_notify_cb(GtkWidget
*menuitem
, GdkEventCrossing
*event
, GtkWidget
*label
)
3355 gtk_widget_set_sensitive(GTK_WIDGET(label
), FALSE
);
3360 e2ee_state_to_gtkimage(PurpleE2eeState
*state
)
3364 img
= _pidgin_e2ee_stock_icon_get(
3365 purple_e2ee_state_get_stock_icon(state
));
3369 return gtk_image_new_from_pixbuf(pidgin_pixbuf_from_image(img
));
3373 create_sendto_item(GtkWidget
*menu
, GtkSizeGroup
*sg
, GSList
**group
,
3374 PurpleBuddy
*buddy
, PurpleAccount
*account
, const char *name
,
3375 gboolean e2ee_enabled
)
3380 GtkWidget
*e2ee_image
= NULL
;
3381 GtkWidget
*menuitem
;
3385 /* Create a pixmap for the protocol icon. */
3386 pixbuf
= pidgin_create_protocol_icon(account
, PIDGIN_PROTOCOL_ICON_SMALL
);
3388 /* Now convert it to GtkImage */
3390 image
= gtk_image_new();
3393 image
= gtk_image_new_from_pixbuf(pixbuf
);
3394 g_object_unref(G_OBJECT(pixbuf
));
3398 PurpleIMConversation
*im
;
3399 PurpleE2eeState
*state
= NULL
;
3401 im
= purple_conversations_find_im_with_account(
3402 purple_buddy_get_name(buddy
), purple_buddy_get_account(buddy
));
3404 state
= purple_conversation_get_e2ee_state(PURPLE_CONVERSATION(im
));
3406 e2ee_image
= e2ee_state_to_gtkimage(state
);
3408 e2ee_image
= gtk_image_new();
3411 gtk_size_group_add_widget(sg
, image
);
3413 /* Make our menu item */
3414 text
= g_strdup_printf("%s (%s)", name
, purple_account_get_name_for_display(account
));
3415 menuitem
= gtk_radio_menu_item_new_with_label(*group
, text
);
3417 *group
= gtk_radio_menu_item_get_group(GTK_RADIO_MENU_ITEM(menuitem
));
3419 /* Do some evil, see some evil, speak some evil. */
3420 box
= gtk_box_new(GTK_ORIENTATION_HORIZONTAL
, 0);
3422 label
= gtk_bin_get_child(GTK_BIN(menuitem
));
3423 g_object_ref(label
);
3424 gtk_container_remove(GTK_CONTAINER(menuitem
), label
);
3426 gtk_box_pack_start(GTK_BOX(box
), image
, FALSE
, FALSE
, 0);
3428 gtk_box_pack_start(GTK_BOX(box
), label
, TRUE
, TRUE
, 4);
3430 gtk_box_pack_start(GTK_BOX(box
), e2ee_image
, FALSE
, FALSE
, 0);
3432 if (buddy
!= NULL
&&
3433 !purple_presence_is_online(purple_buddy_get_presence(buddy
)))
3435 gtk_widget_set_sensitive(label
, FALSE
);
3437 /* Set the label sensitive when the menuitem is highlighted and
3438 * insensitive again when the mouse leaves it. This way, it
3439 * doesn't appear weird from the highlighting of the embossed
3440 * (insensitive style) text.*/
3441 g_signal_connect(menuitem
, "enter-notify-event",
3442 G_CALLBACK(send_to_item_enter_notify_cb
), label
);
3443 g_signal_connect(menuitem
, "leave-notify-event",
3444 G_CALLBACK(send_to_item_leave_notify_cb
), label
);
3447 g_object_unref(label
);
3449 gtk_container_add(GTK_CONTAINER(menuitem
), box
);
3451 gtk_widget_show(label
);
3452 gtk_widget_show(image
);
3454 gtk_widget_show(e2ee_image
);
3455 gtk_widget_show(box
);
3457 /* Set our data and callbacks. */
3458 g_object_set_data(G_OBJECT(menuitem
), "purple_account", account
);
3459 g_object_set_data_full(G_OBJECT(menuitem
), "purple_buddy_name", g_strdup(name
), g_free
);
3461 g_signal_connect(G_OBJECT(menuitem
), "activate",
3462 G_CALLBACK(menu_conv_sel_send_cb
), NULL
);
3464 gtk_widget_show(menuitem
);
3465 gtk_menu_shell_append(GTK_MENU_SHELL(menu
), menuitem
);
3469 compare_buddy_presence(PurplePresence
*p1
, PurplePresence
*p2
)
3471 /* This is necessary because multiple PurpleBuddy's don't share the same
3472 * PurplePresence anymore.
3474 PurpleBuddy
*b1
= purple_buddy_presence_get_buddy(PURPLE_BUDDY_PRESENCE(p1
));
3475 PurpleBuddy
*b2
= purple_buddy_presence_get_buddy(PURPLE_BUDDY_PRESENCE(p2
));
3476 if (purple_buddy_get_account(b1
) == purple_buddy_get_account(b2
) &&
3477 purple_strequal(purple_buddy_get_name(b1
), purple_buddy_get_name(b2
)))
3483 generate_send_to_items(PidginConvWindow
*win
)
3486 GSList
*group
= NULL
;
3487 GtkSizeGroup
*sg
= gtk_size_group_new(GTK_SIZE_GROUP_HORIZONTAL
);
3488 PidginConversation
*gtkconv
;
3491 g_return_if_fail(win
!= NULL
);
3493 gtkconv
= pidgin_conv_window_get_active_gtkconv(win
);
3495 g_return_if_fail(gtkconv
!= NULL
);
3497 if (win
->menu
->send_to
!= NULL
)
3498 gtk_widget_destroy(win
->menu
->send_to
);
3500 /* Build the Send To menu */
3501 win
->menu
->send_to
= gtk_menu_item_new_with_mnemonic(_("S_end To"));
3502 gtk_widget_show(win
->menu
->send_to
);
3504 menu
= gtk_menu_new();
3505 gtk_menu_shell_insert(GTK_MENU_SHELL(win
->menu
->menubar
),
3506 win
->menu
->send_to
, 2);
3507 gtk_menu_item_set_submenu(GTK_MENU_ITEM(win
->menu
->send_to
), menu
);
3509 gtk_widget_show(menu
);
3511 if (PURPLE_IS_IM_CONVERSATION(gtkconv
->active_conv
)) {
3512 buds
= purple_blist_find_buddies(purple_conversation_get_account(gtkconv
->active_conv
), purple_conversation_get_name(gtkconv
->active_conv
));
3516 /* The user isn't on the buddy list. So we don't create any sendto menu. */
3520 gboolean e2ee_enabled
= FALSE
;
3521 GList
*list
= NULL
, *iter
;
3522 for (l
= buds
; l
!= NULL
; l
= l
->next
)
3524 PurpleBlistNode
*node
;
3526 node
= PURPLE_BLIST_NODE(purple_buddy_get_contact(PURPLE_BUDDY(l
->data
)));
3528 for (node
= node
->child
; node
!= NULL
; node
= node
->next
)
3530 PurpleBuddy
*buddy
= (PurpleBuddy
*)node
;
3531 PurpleAccount
*account
;
3532 PurpleIMConversation
*im
;
3534 if (!PURPLE_IS_BUDDY(node
))
3537 im
= purple_conversations_find_im_with_account(purple_buddy_get_name(buddy
), purple_buddy_get_account(buddy
));
3538 if (im
&& purple_conversation_get_e2ee_state(PURPLE_CONVERSATION(im
)) != NULL
)
3539 e2ee_enabled
= TRUE
;
3541 account
= purple_buddy_get_account(buddy
);
3542 /* TODO WEBKIT: (I'm not actually sure if this is webkit-related --Mark Doliner) */
3543 if (purple_account_is_connected(account
) /*|| account == purple_conversation_get_account(gtkconv->active_conv)*/)
3545 /* Use the PurplePresence to get unique buddies. */
3546 PurplePresence
*presence
= purple_buddy_get_presence(buddy
);
3547 if (!g_list_find_custom(list
, presence
, (GCompareFunc
)compare_buddy_presence
))
3548 list
= g_list_prepend(list
, presence
);
3553 /* Create the sendto menu only if it has more than one item to show */
3554 if (list
&& list
->next
) {
3555 /* Loop over the list backwards so we get the items in the right order,
3556 * since we did a g_list_prepend() earlier. */
3557 for (iter
= g_list_last(list
); iter
!= NULL
; iter
= iter
->prev
) {
3558 PurplePresence
*pre
= iter
->data
;
3559 PurpleBuddy
*buddy
= purple_buddy_presence_get_buddy(PURPLE_BUDDY_PRESENCE(pre
));
3560 create_sendto_item(menu
, sg
, &group
, buddy
,
3561 purple_buddy_get_account(buddy
), purple_buddy_get_name(buddy
), e2ee_enabled
);
3571 gtk_widget_show(win
->menu
->send_to
);
3572 /* TODO: This should never be insensitive. Possibly hidden or not. */
3574 gtk_widget_set_sensitive(win
->menu
->send_to
, FALSE
);
3575 update_send_to_selection(win
);
3579 _pidgin_e2ee_stock_icon_get(const gchar
*stock_name
)
3581 gchar filename
[100], *path
;
3584 /* core is quitting */
3585 if (e2ee_stock
== NULL
)
3588 if (g_hash_table_lookup_extended(e2ee_stock
, stock_name
, NULL
, (gpointer
*)&image
))
3591 g_snprintf(filename
, sizeof(filename
), "e2ee-%s.png", stock_name
);
3592 path
= g_build_filename(PURPLE_DATADIR
, "pidgin", "icons",
3593 "hicolor", "16x16", "status", filename
, NULL
);
3594 image
= purple_image_new_from_file(path
, NULL
);
3597 g_hash_table_insert(e2ee_stock
, g_strdup(stock_name
), image
);
3602 generate_e2ee_controls(PidginConvWindow
*win
)
3604 PidginConversation
*gtkconv
;
3605 PurpleConversation
*conv
;
3606 PurpleE2eeState
*state
;
3607 PurpleE2eeProvider
*provider
;
3609 GList
*menu_actions
, *it
;
3610 GtkWidget
*e2ee_image
;
3612 gtkconv
= pidgin_conv_window_get_active_gtkconv(win
);
3613 g_return_if_fail(gtkconv
!= NULL
);
3615 conv
= gtkconv
->active_conv
;
3616 g_return_if_fail(conv
!= NULL
);
3618 if (win
->menu
->e2ee
!= NULL
) {
3619 gtk_widget_destroy(win
->menu
->e2ee
);
3620 win
->menu
->e2ee
= NULL
;
3623 provider
= purple_e2ee_provider_get_main();
3624 state
= purple_conversation_get_e2ee_state(conv
);
3625 if (state
== NULL
|| provider
== NULL
)
3627 if (purple_e2ee_state_get_provider(state
) != provider
)
3630 win
->menu
->e2ee
= gtk_image_menu_item_new_with_label(
3631 purple_e2ee_provider_get_name(provider
));
3633 menu
= gtk_menu_new();
3634 gtk_menu_shell_insert(GTK_MENU_SHELL(win
->menu
->menubar
),
3635 win
->menu
->e2ee
, 3);
3636 gtk_menu_item_set_submenu(GTK_MENU_ITEM(win
->menu
->e2ee
), menu
);
3638 e2ee_image
= e2ee_state_to_gtkimage(state
);
3640 gtk_image_menu_item_set_image(
3641 GTK_IMAGE_MENU_ITEM(win
->menu
->e2ee
), e2ee_image
);
3644 gtk_widget_set_tooltip_text(win
->menu
->e2ee
,
3645 purple_e2ee_state_get_name(state
));
3647 menu_actions
= purple_e2ee_provider_get_conv_menu_actions(provider
, conv
);
3648 for (it
= menu_actions
; it
; it
= g_list_next(it
)) {
3649 PurpleActionMenu
*action
= it
->data
;
3651 gtk_widget_show_all(
3652 pidgin_append_menu_action(menu
, action
, conv
));
3654 g_list_free(menu_actions
);
3656 gtk_widget_show(win
->menu
->e2ee
);
3657 gtk_widget_show(menu
);
3661 get_chat_user_status_icon(PurpleChatConversation
*chat
, const char *name
, PurpleChatUserFlags flags
)
3663 const char *image
= NULL
;
3665 if (flags
& PURPLE_CHAT_USER_FOUNDER
) {
3666 image
= PIDGIN_STOCK_STATUS_FOUNDER
;
3667 } else if (flags
& PURPLE_CHAT_USER_OP
) {
3668 image
= PIDGIN_STOCK_STATUS_OPERATOR
;
3669 } else if (flags
& PURPLE_CHAT_USER_HALFOP
) {
3670 image
= PIDGIN_STOCK_STATUS_HALFOP
;
3671 } else if (flags
& PURPLE_CHAT_USER_VOICE
) {
3672 image
= PIDGIN_STOCK_STATUS_VOICE
;
3673 } else if ((!flags
) && purple_chat_conversation_is_ignored_user(chat
, name
)) {
3674 image
= PIDGIN_STOCK_STATUS_IGNORED
;
3682 deleting_chat_user_cb(PurpleChatUser
*cb
)
3684 GtkTreeRowReference
*ref
= purple_chat_user_get_ui_data(cb
);
3687 gtk_tree_row_reference_free(ref
);
3688 purple_chat_user_set_ui_data(cb
, NULL
);
3693 add_chat_user_common(PurpleChatConversation
*chat
, PurpleChatUser
*cb
, const char *old_name
)
3695 PidginConversation
*gtkconv
;
3696 PurpleConversation
*conv
;
3697 PidginChatPane
*gtkchat
;
3698 PurpleConnection
*gc
;
3699 PurpleProtocol
*protocol
;
3702 GtkTreePath
*newpath
;
3705 gboolean is_me
= FALSE
;
3707 const gchar
*name
, *alias
;
3708 gchar
*tmp
, *alias_key
;
3709 PurpleChatUserFlags flags
;
3710 GdkRGBA
*color
= NULL
;
3712 alias
= purple_chat_user_get_alias(cb
);
3713 name
= purple_chat_user_get_name(cb
);
3714 flags
= purple_chat_user_get_flags(cb
);
3716 conv
= PURPLE_CONVERSATION(chat
);
3717 gtkconv
= PIDGIN_CONVERSATION(conv
);
3718 gtkchat
= gtkconv
->u
.chat
;
3719 gc
= purple_conversation_get_connection(conv
);
3721 if (!gc
|| !(protocol
= purple_connection_get_protocol(gc
)))
3724 tm
= gtk_tree_view_get_model(GTK_TREE_VIEW(gtkchat
->list
));
3725 ls
= GTK_LIST_STORE(tm
);
3727 stock
= get_chat_user_status_icon(chat
, name
, flags
);
3729 if (purple_strequal(purple_chat_conversation_get_nick(chat
), purple_normalize(purple_conversation_get_account(conv
), old_name
!= NULL
? old_name
: name
)))
3732 is_buddy
= purple_chat_user_is_buddy(cb
);
3734 tmp
= g_utf8_casefold(alias
, -1);
3735 alias_key
= g_utf8_collate_key(tmp
, -1);
3740 /* TODO WEBKIT: No tags in webkit stuff, yet. */
3741 GtkTextTag
*tag
= gtk_text_tag_table_lookup(
3742 gtk_text_buffer_get_tag_table(GTK_IMHTML(gtkconv
->webview
)->text_buffer
),
3744 g_object_get(tag
, "foreground-rgba", &color
, NULL
);
3748 if ((tag
= get_buddy_tag(chat
, name
, 0, FALSE
)))
3749 g_object_set(G_OBJECT(tag
), "style", PANGO_STYLE_NORMAL
, NULL
);
3750 if ((tag
= get_buddy_tag(chat
, name
, PURPLE_MESSAGE_NICK
, FALSE
)))
3751 g_object_set(G_OBJECT(tag
), "style", PANGO_STYLE_NORMAL
, NULL
);
3752 color
= (GdkRGBA
*)get_nick_color(gtkconv
, name
);
3755 gtk_list_store_insert_with_values(ls
, &iter
,
3757 * The GTK docs are mute about the effects of the "row" value for performance.
3758 * X-Chat hardcodes their value to 0 (prepend) and -1 (append), so we will too.
3759 * It *might* be faster to search the gtk_list_store and set row accurately,
3760 * but no one in #gtk+ seems to know anything about it either.
3761 * Inserting in the "wrong" location has no visible ill effects. - F.P.
3764 CHAT_USERS_ICON_STOCK_COLUMN
, stock
,
3765 CHAT_USERS_ALIAS_COLUMN
, alias
,
3766 CHAT_USERS_ALIAS_KEY_COLUMN
, alias_key
,
3767 CHAT_USERS_NAME_COLUMN
, name
,
3768 CHAT_USERS_FLAGS_COLUMN
, flags
,
3769 CHAT_USERS_COLOR_COLUMN
, color
,
3770 CHAT_USERS_WEIGHT_COLUMN
, is_buddy
? PANGO_WEIGHT_BOLD
: PANGO_WEIGHT_NORMAL
,
3773 if (purple_chat_user_get_ui_data(cb
)) {
3774 GtkTreeRowReference
*ref
= purple_chat_user_get_ui_data(cb
);
3775 gtk_tree_row_reference_free(ref
);
3778 newpath
= gtk_tree_model_get_path(tm
, &iter
);
3779 purple_chat_user_set_ui_data(cb
, gtk_tree_row_reference_new(tm
, newpath
));
3780 gtk_tree_path_free(newpath
);
3784 gdk_rgba_free(color
);
3789 static void topic_callback(GtkWidget
*w
, PidginConversation
*gtkconv
)
3791 PurpleProtocol
*protocol
= NULL
;
3792 PurpleConnection
*gc
;
3793 PurpleConversation
*conv
= gtkconv
->active_conv
;
3794 PidginChatPane
*gtkchat
;
3796 const char *current_topic
;
3798 gc
= purple_conversation_get_connection(conv
);
3800 if(!gc
|| !(protocol
= purple_connection_get_protocol(gc
)))
3803 if(!PURPLE_PROTOCOL_IMPLEMENTS(protocol
, CHAT
, set_topic
))
3806 gtkconv
= PIDGIN_CONVERSATION(conv
);
3807 gtkchat
= gtkconv
->u
.chat
;
3808 new_topic
= g_strdup(gtk_entry_get_text(GTK_ENTRY(gtkchat
->topic_text
)));
3809 current_topic
= purple_chat_conversation_get_topic(PURPLE_CHAT_CONVERSATION(conv
));
3811 if(current_topic
&& !g_utf8_collate(new_topic
, current_topic
)){
3817 gtk_entry_set_text(GTK_ENTRY(gtkchat
->topic_text
), current_topic
);
3819 gtk_entry_set_text(GTK_ENTRY(gtkchat
->topic_text
), "");
3821 purple_protocol_chat_iface_set_topic(protocol
, gc
, purple_chat_conversation_get_id(PURPLE_CHAT_CONVERSATION(conv
)),
3828 sort_chat_users(GtkTreeModel
*model
, GtkTreeIter
*a
, GtkTreeIter
*b
, gpointer userdata
)
3830 PurpleChatUserFlags f1
= 0, f2
= 0;
3831 char *user1
= NULL
, *user2
= NULL
;
3832 gboolean buddy1
= FALSE
, buddy2
= FALSE
;
3835 gtk_tree_model_get(model
, a
,
3836 CHAT_USERS_ALIAS_KEY_COLUMN
, &user1
,
3837 CHAT_USERS_FLAGS_COLUMN
, &f1
,
3838 CHAT_USERS_WEIGHT_COLUMN
, &buddy1
,
3840 gtk_tree_model_get(model
, b
,
3841 CHAT_USERS_ALIAS_KEY_COLUMN
, &user2
,
3842 CHAT_USERS_FLAGS_COLUMN
, &f2
,
3843 CHAT_USERS_WEIGHT_COLUMN
, &buddy2
,
3846 /* Only sort by membership levels */
3847 f1
&= PURPLE_CHAT_USER_VOICE
| PURPLE_CHAT_USER_HALFOP
| PURPLE_CHAT_USER_OP
|
3848 PURPLE_CHAT_USER_FOUNDER
;
3849 f2
&= PURPLE_CHAT_USER_VOICE
| PURPLE_CHAT_USER_HALFOP
| PURPLE_CHAT_USER_OP
|
3850 PURPLE_CHAT_USER_FOUNDER
;
3852 ret
= g_strcmp0(user1
, user2
);
3854 if (user1
!= NULL
&& user2
!= NULL
) {
3856 /* sort more important users first */
3857 ret
= (f1
> f2
) ? -1 : 1;
3858 } else if (buddy1
!= buddy2
) {
3859 ret
= (buddy1
> buddy2
) ? -1 : 1;
3870 update_chat_alias(PurpleBuddy
*buddy
, PurpleChatConversation
*chat
, PurpleConnection
*gc
, PurpleProtocol
*protocol
)
3872 PidginConversation
*gtkconv
= PIDGIN_CONVERSATION(PURPLE_CONVERSATION(chat
));
3873 PurpleAccount
*account
= purple_conversation_get_account(PURPLE_CONVERSATION(chat
));
3874 GtkTreeModel
*model
;
3875 char *normalized_name
;
3879 g_return_if_fail(buddy
!= NULL
);
3880 g_return_if_fail(chat
!= NULL
);
3882 /* This is safe because this callback is only used in chats, not IMs. */
3883 model
= gtk_tree_view_get_model(GTK_TREE_VIEW(gtkconv
->u
.chat
->list
));
3885 if (!gtk_tree_model_get_iter_first(GTK_TREE_MODEL(model
), &iter
))
3888 normalized_name
= g_strdup(purple_normalize(account
, purple_buddy_get_name(buddy
)));
3893 gtk_tree_model_get(model
, &iter
, CHAT_USERS_NAME_COLUMN
, &name
, -1);
3895 if (purple_strequal(normalized_name
, purple_normalize(account
, name
))) {
3896 const char *alias
= name
;
3898 char *alias_key
= NULL
;
3899 PurpleBuddy
*buddy2
;
3901 if (!purple_strequal(purple_chat_conversation_get_nick(chat
), purple_normalize(account
, name
))) {
3902 /* This user is not me, so look into updating the alias. */
3904 if ((buddy2
= purple_blist_find_buddy(account
, name
)) != NULL
) {
3905 alias
= purple_buddy_get_contact_alias(buddy2
);
3908 tmp
= g_utf8_casefold(alias
, -1);
3909 alias_key
= g_utf8_collate_key(tmp
, -1);
3912 gtk_list_store_set(GTK_LIST_STORE(model
), &iter
,
3913 CHAT_USERS_ALIAS_COLUMN
, alias
,
3914 CHAT_USERS_ALIAS_KEY_COLUMN
, alias_key
,
3922 f
= gtk_tree_model_iter_next(model
, &iter
);
3927 g_free(normalized_name
);
3931 blist_node_aliased_cb(PurpleBlistNode
*node
, const char *old_alias
, PurpleChatConversation
*chat
)
3933 PurpleConnection
*gc
;
3934 PurpleProtocol
*protocol
;
3935 PurpleConversation
*conv
= PURPLE_CONVERSATION(chat
);
3937 g_return_if_fail(node
!= NULL
);
3938 g_return_if_fail(conv
!= NULL
);
3940 gc
= purple_conversation_get_connection(conv
);
3941 g_return_if_fail(gc
!= NULL
);
3942 g_return_if_fail(purple_connection_get_protocol(gc
) != NULL
);
3943 protocol
= purple_connection_get_protocol(gc
);
3945 if (purple_protocol_get_options(protocol
) & OPT_PROTO_UNIQUE_CHATNAME
)
3948 if (PURPLE_IS_CONTACT(node
))
3950 PurpleBlistNode
*bnode
;
3952 for(bnode
= node
->child
; bnode
; bnode
= bnode
->next
) {
3954 if(!PURPLE_IS_BUDDY(bnode
))
3957 update_chat_alias((PurpleBuddy
*)bnode
, chat
, gc
, protocol
);
3960 else if (PURPLE_IS_BUDDY(node
))
3961 update_chat_alias((PurpleBuddy
*)node
, chat
, gc
, protocol
);
3962 else if (PURPLE_IS_CHAT(node
) &&
3963 purple_conversation_get_account(conv
) == purple_chat_get_account((PurpleChat
*)node
))
3965 if (old_alias
== NULL
|| g_utf8_collate(old_alias
, purple_conversation_get_title(conv
)) == 0)
3966 pidgin_conv_update_fields(conv
, PIDGIN_CONV_SET_TITLE
);
3971 buddy_cb_common(PurpleBuddy
*buddy
, PurpleChatConversation
*chat
, gboolean is_buddy
)
3973 GtkTreeModel
*model
;
3974 char *normalized_name
;
3976 GtkTextTag
*texttag
;
3977 PurpleConversation
*conv
= PURPLE_CONVERSATION(chat
);
3980 g_return_if_fail(buddy
!= NULL
);
3981 g_return_if_fail(conv
!= NULL
);
3983 /* Do nothing if the buddy does not belong to the conv's account */
3984 if (purple_buddy_get_account(buddy
) != purple_conversation_get_account(conv
))
3987 /* This is safe because this callback is only used in chats, not IMs. */
3988 model
= gtk_tree_view_get_model(GTK_TREE_VIEW(PIDGIN_CONVERSATION(conv
)->u
.chat
->list
));
3990 if (!gtk_tree_model_get_iter_first(GTK_TREE_MODEL(model
), &iter
))
3993 normalized_name
= g_strdup(purple_normalize(purple_conversation_get_account(conv
), purple_buddy_get_name(buddy
)));
3998 gtk_tree_model_get(model
, &iter
, CHAT_USERS_NAME_COLUMN
, &name
, -1);
4000 if (purple_strequal(normalized_name
, purple_normalize(purple_conversation_get_account(conv
), name
))) {
4001 gtk_list_store_set(GTK_LIST_STORE(model
), &iter
,
4002 CHAT_USERS_WEIGHT_COLUMN
, is_buddy
? PANGO_WEIGHT_BOLD
: PANGO_WEIGHT_NORMAL
, -1);
4007 f
= gtk_tree_model_iter_next(model
, &iter
);
4012 g_free(normalized_name
);
4014 blist_node_aliased_cb((PurpleBlistNode
*)buddy
, NULL
, chat
);
4016 texttag
= get_buddy_tag(chat
, purple_buddy_get_name(buddy
), 0, FALSE
); /* XXX: do we want the normalized name? */
4018 g_object_set(texttag
, "weight", is_buddy
? PANGO_WEIGHT_BOLD
: PANGO_WEIGHT_NORMAL
, NULL
);
4023 buddy_added_cb(PurpleBlistNode
*node
, PurpleChatConversation
*chat
)
4025 if (!PURPLE_IS_BUDDY(node
))
4028 buddy_cb_common(PURPLE_BUDDY(node
), chat
, TRUE
);
4032 buddy_removed_cb(PurpleBlistNode
*node
, PurpleChatConversation
*chat
)
4034 if (!PURPLE_IS_BUDDY(node
))
4037 /* If there's another buddy for the same "dude" on the list, do nothing. */
4038 if (purple_blist_find_buddy(purple_buddy_get_account(PURPLE_BUDDY(node
)),
4039 purple_buddy_get_name(PURPLE_BUDDY(node
))) != NULL
)
4042 buddy_cb_common(PURPLE_BUDDY(node
), chat
, FALSE
);
4046 minimum_entry_lines_pref_cb(const char *name
,
4047 PurplePrefType type
,
4048 gconstpointer value
,
4052 GList
*l
= purple_conversations_get_all();
4053 PurpleConversation
*conv
;
4056 conv
= (PurpleConversation
*)l
->data
;
4058 if (PIDGIN_IS_PIDGIN_CONVERSATION(conv
))
4059 resize_webview_cb(PIDGIN_CONVERSATION(conv
));
4066 setup_chat_topic(PidginConversation
*gtkconv
, GtkWidget
*vbox
)
4068 PurpleConversation
*conv
= gtkconv
->active_conv
;
4069 PurpleConnection
*gc
= purple_conversation_get_connection(conv
);
4070 PurpleProtocol
*protocol
= purple_connection_get_protocol(gc
);
4071 if (purple_protocol_get_options(protocol
) & OPT_PROTO_CHAT_TOPIC
)
4073 GtkWidget
*hbox
, *label
;
4074 PidginChatPane
*gtkchat
= gtkconv
->u
.chat
;
4076 hbox
= gtk_box_new(GTK_ORIENTATION_HORIZONTAL
, PIDGIN_HIG_BOX_SPACE
);
4077 gtk_box_pack_start(GTK_BOX(vbox
), hbox
, FALSE
, FALSE
, 0);
4079 label
= gtk_label_new(_("Topic:"));
4080 gtk_box_pack_start(GTK_BOX(hbox
), label
, FALSE
, FALSE
, 0);
4082 gtkchat
->topic_text
= gtk_entry_new();
4083 gtk_widget_set_size_request(gtkchat
->topic_text
, -1, BUDDYICON_SIZE_MIN
);
4085 if(!PURPLE_PROTOCOL_IMPLEMENTS(protocol
, CHAT
, set_topic
)) {
4086 gtk_editable_set_editable(GTK_EDITABLE(gtkchat
->topic_text
), FALSE
);
4088 g_signal_connect(G_OBJECT(gtkchat
->topic_text
), "activate",
4089 G_CALLBACK(topic_callback
), gtkconv
);
4092 gtk_box_pack_start(GTK_BOX(hbox
), gtkchat
->topic_text
, TRUE
, TRUE
, 0);
4093 g_signal_connect(G_OBJECT(gtkchat
->topic_text
), "key_press_event",
4094 G_CALLBACK(entry_key_press_cb
), gtkconv
);
4099 pidgin_conv_userlist_create_tooltip(GtkWidget
*tipwindow
, GtkTreePath
*path
,
4100 gpointer userdata
, int *w
, int *h
)
4102 PidginConversation
*gtkconv
= userdata
;
4104 GtkTreeModel
*model
= gtk_tree_view_get_model(GTK_TREE_VIEW(gtkconv
->u
.chat
->list
));
4105 PurpleConversation
*conv
= gtkconv
->active_conv
;
4106 PurpleBlistNode
*node
;
4107 PurpleProtocol
*protocol
;
4108 PurpleAccount
*account
= purple_conversation_get_account(conv
);
4111 if (purple_account_get_connection(account
) == NULL
)
4114 if (!gtk_tree_model_get_iter(GTK_TREE_MODEL(model
), &iter
, path
))
4117 gtk_tree_model_get(GTK_TREE_MODEL(model
), &iter
, CHAT_USERS_NAME_COLUMN
, &who
, -1);
4119 protocol
= purple_connection_get_protocol(purple_account_get_connection(account
));
4120 node
= (PurpleBlistNode
*)(purple_blist_find_buddy(purple_conversation_get_account(conv
), who
));
4121 if (node
&& protocol
&& (purple_protocol_get_options(protocol
) & OPT_PROTO_UNIQUE_CHATNAME
))
4122 pidgin_blist_draw_tooltip(node
, gtkconv
->infopane
);
4129 setup_chat_userlist(PidginConversation
*gtkconv
, GtkWidget
*hpaned
)
4131 PidginChatPane
*gtkchat
= gtkconv
->u
.chat
;
4132 GtkWidget
*lbox
, *list
;
4134 GtkCellRenderer
*rend
;
4135 GtkTreeViewColumn
*col
;
4137 void *blist_handle
= purple_blist_get_handle();
4138 PurpleConversation
*conv
= gtkconv
->active_conv
;
4140 /* Build the right pane. */
4141 lbox
= gtk_box_new(GTK_ORIENTATION_VERTICAL
, PIDGIN_HIG_BOX_SPACE
);
4142 gtk_paned_pack2(GTK_PANED(hpaned
), lbox
, FALSE
, TRUE
);
4143 gtk_widget_show(lbox
);
4145 /* Setup the label telling how many people are in the room. */
4146 gtkchat
->count
= gtk_label_new(_("0 people in room"));
4147 gtk_label_set_ellipsize(GTK_LABEL(gtkchat
->count
), PANGO_ELLIPSIZE_END
);
4148 gtk_box_pack_start(GTK_BOX(lbox
), gtkchat
->count
, FALSE
, FALSE
, 0);
4149 gtk_widget_show(gtkchat
->count
);
4151 /* Setup the list of users. */
4153 ls
= gtk_list_store_new(CHAT_USERS_COLUMNS
, GDK_TYPE_PIXBUF
, G_TYPE_STRING
,
4154 G_TYPE_STRING
, G_TYPE_STRING
, G_TYPE_INT
,
4155 GDK_TYPE_RGBA
, G_TYPE_INT
, G_TYPE_STRING
);
4156 gtk_tree_sortable_set_sort_func(GTK_TREE_SORTABLE(ls
), CHAT_USERS_ALIAS_KEY_COLUMN
,
4157 sort_chat_users
, NULL
, NULL
);
4159 list
= gtk_tree_view_new_with_model(GTK_TREE_MODEL(ls
));
4161 /* Allow a user to specify gtkrc settings for the chat userlist only */
4162 gtk_widget_set_name(list
, "pidgin_conv_userlist");
4164 rend
= gtk_cell_renderer_pixbuf_new();
4165 g_object_set(G_OBJECT(rend
),
4166 "stock-size", gtk_icon_size_from_name(PIDGIN_ICON_SIZE_TANGO_EXTRA_SMALL
),
4168 col
= gtk_tree_view_column_new_with_attributes(NULL
, rend
,
4169 "stock-id", CHAT_USERS_ICON_STOCK_COLUMN
, NULL
);
4170 gtk_tree_view_column_set_sizing(col
, GTK_TREE_VIEW_COLUMN_AUTOSIZE
);
4171 gtk_tree_view_append_column(GTK_TREE_VIEW(list
), col
);
4172 ul_width
= purple_prefs_get_int(PIDGIN_PREFS_ROOT
"/conversations/chat/userlist_width");
4173 gtk_widget_set_size_request(lbox
, ul_width
, -1);
4175 /* Hack to prevent completely collapsed userlist coming back with a 1 pixel width.
4176 * I would have liked to use the GtkPaned "max-position", but for some reason that didn't work */
4178 gtk_paned_set_position(GTK_PANED(hpaned
), 999999);
4180 g_signal_connect(G_OBJECT(list
), "button_press_event",
4181 G_CALLBACK(right_click_chat_cb
), gtkconv
);
4182 g_signal_connect(G_OBJECT(list
), "row-activated",
4183 G_CALLBACK(activate_list_cb
), gtkconv
);
4184 g_signal_connect(G_OBJECT(list
), "popup-menu",
4185 G_CALLBACK(gtkconv_chat_popup_menu_cb
), gtkconv
);
4186 g_signal_connect(G_OBJECT(lbox
), "size-allocate", G_CALLBACK(lbox_size_allocate_cb
), gtkconv
);
4188 pidgin_tooltip_setup_for_treeview(list
, gtkconv
,
4189 pidgin_conv_userlist_create_tooltip
, NULL
);
4191 rend
= gtk_cell_renderer_text_new();
4193 "foreground-set", TRUE
,
4196 g_object_set(G_OBJECT(rend
), "editable", TRUE
, NULL
);
4198 col
= gtk_tree_view_column_new_with_attributes(NULL
, rend
,
4199 "text", CHAT_USERS_ALIAS_COLUMN
,
4200 "foreground-rgba", CHAT_USERS_COLOR_COLUMN
,
4201 "weight", CHAT_USERS_WEIGHT_COLUMN
,
4204 purple_signal_connect(blist_handle
, "blist-node-added",
4205 gtkchat
, PURPLE_CALLBACK(buddy_added_cb
), conv
);
4206 purple_signal_connect(blist_handle
, "blist-node-removed",
4207 gtkchat
, PURPLE_CALLBACK(buddy_removed_cb
), conv
);
4208 purple_signal_connect(blist_handle
, "blist-node-aliased",
4209 gtkchat
, PURPLE_CALLBACK(blist_node_aliased_cb
), conv
);
4211 gtk_tree_view_column_set_expand(col
, TRUE
);
4212 g_object_set(rend
, "ellipsize", PANGO_ELLIPSIZE_END
, NULL
);
4214 gtk_tree_view_append_column(GTK_TREE_VIEW(list
), col
);
4216 gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(list
), FALSE
);
4217 gtk_widget_show(list
);
4219 gtkchat
->list
= list
;
4221 gtk_box_pack_start(GTK_BOX(lbox
),
4222 pidgin_make_scrollable(list
, GTK_POLICY_AUTOMATIC
, GTK_POLICY_AUTOMATIC
, GTK_SHADOW_IN
, -1, -1),
4227 pidgin_conv_create_tooltip(GtkWidget
*tipwindow
, gpointer userdata
, int *w
, int *h
)
4229 PurpleBlistNode
*node
= NULL
;
4230 PurpleConversation
*conv
;
4231 PidginConversation
*gtkconv
= userdata
;
4233 conv
= gtkconv
->active_conv
;
4234 if (PURPLE_IS_CHAT_CONVERSATION(conv
)) {
4235 node
= (PurpleBlistNode
*)(purple_blist_find_chat(purple_conversation_get_account(conv
), purple_conversation_get_name(conv
)));
4237 node
= g_object_get_data(G_OBJECT(gtkconv
->history
), "transient_chat");
4239 node
= (PurpleBlistNode
*)(purple_blist_find_buddy(purple_conversation_get_account(conv
), purple_conversation_get_name(conv
)));
4241 /* Using the transient blist nodes to show the tooltip doesn't quite work yet. */
4243 node
= g_object_get_data(G_OBJECT(gtkconv
->webview
), "transient_buddy");
4248 pidgin_blist_draw_tooltip(node
, gtkconv
->infopane
);
4253 setup_common_pane(PidginConversation
*gtkconv
)
4255 GtkWidget
*vbox
, *sw
, *event_box
, *view
;
4256 GtkCellRenderer
*rend
;
4258 PurpleConversation
*conv
= gtkconv
->active_conv
;
4260 gboolean chat
= PURPLE_IS_CHAT_CONVERSATION(conv
);
4261 int buddyicon_size
= 0;
4263 /* Setup the top part of the pane */
4264 vbox
= gtk_box_new(GTK_ORIENTATION_VERTICAL
, PIDGIN_HIG_BOX_SPACE
);
4265 gtk_widget_show(vbox
);
4267 /* Setup the info pane */
4268 event_box
= gtk_event_box_new();
4269 gtk_event_box_set_visible_window(GTK_EVENT_BOX(event_box
), FALSE
);
4270 gtk_widget_show(event_box
);
4271 gtkconv
->infopane_hbox
= gtk_box_new(GTK_ORIENTATION_HORIZONTAL
, 0);
4272 gtk_box_pack_start(GTK_BOX(vbox
), event_box
, FALSE
, FALSE
, 0);
4273 gtk_container_add(GTK_CONTAINER(event_box
), gtkconv
->infopane_hbox
);
4274 gtk_widget_show(gtkconv
->infopane_hbox
);
4275 gtk_widget_add_events(event_box
,
4276 GDK_POINTER_MOTION_MASK
| GDK_LEAVE_NOTIFY_MASK
);
4277 g_signal_connect(G_OBJECT(event_box
), "button-press-event",
4278 G_CALLBACK(infopane_press_cb
), gtkconv
);
4280 pidgin_tooltip_setup_for_widget(event_box
, gtkconv
,
4281 pidgin_conv_create_tooltip
, NULL
);
4283 gtkconv
->infopane
= gtk_cell_view_new();
4284 gtkconv
->infopane_model
= gtk_list_store_new(CONV_NUM_COLUMNS
, G_TYPE_STRING
, G_TYPE_STRING
, GDK_TYPE_PIXBUF
, GDK_TYPE_PIXBUF
);
4285 gtk_cell_view_set_model(GTK_CELL_VIEW(gtkconv
->infopane
),
4286 GTK_TREE_MODEL(gtkconv
->infopane_model
));
4287 g_object_unref(gtkconv
->infopane_model
);
4288 gtk_list_store_append(gtkconv
->infopane_model
, &(gtkconv
->infopane_iter
));
4289 gtk_box_pack_start(GTK_BOX(gtkconv
->infopane_hbox
), gtkconv
->infopane
, TRUE
, TRUE
, 0);
4290 path
= gtk_tree_path_new_from_string("0");
4291 gtk_cell_view_set_displayed_row(GTK_CELL_VIEW(gtkconv
->infopane
), path
);
4292 gtk_tree_path_free(path
);
4295 /* This empty widget is used to ensure that the infopane is consistently
4296 sized for chat windows. The correct fix is to put an icon in the chat
4297 window as well, because that would make "Set Custom Icon" consistent
4298 for both the buddy list and the chat window, but PidginConversation
4299 is pretty much stuck until 3.0. */
4300 GtkWidget
*sizing_vbox
;
4301 sizing_vbox
= gtk_box_new(GTK_ORIENTATION_VERTICAL
, 0);
4302 gtk_widget_set_size_request(sizing_vbox
, -1, BUDDYICON_SIZE_MIN
);
4303 gtk_box_pack_start(GTK_BOX(gtkconv
->infopane_hbox
), sizing_vbox
, FALSE
, FALSE
, 0);
4304 gtk_widget_show(sizing_vbox
);
4307 gtkconv
->u
.im
->icon_container
= gtk_box_new(GTK_ORIENTATION_VERTICAL
, 0);
4309 if ((buddy
= purple_blist_find_buddy(purple_conversation_get_account(conv
),
4310 purple_conversation_get_name(conv
))) != NULL
) {
4311 PurpleContact
*contact
= purple_buddy_get_contact(buddy
);
4313 buddyicon_size
= purple_blist_node_get_int((PurpleBlistNode
*)contact
, "pidgin-infopane-iconsize");
4316 buddyicon_size
= CLAMP(buddyicon_size
, BUDDYICON_SIZE_MIN
, BUDDYICON_SIZE_MAX
);
4317 gtk_widget_set_size_request(gtkconv
->u
.im
->icon_container
, -1, buddyicon_size
);
4319 gtk_box_pack_start(GTK_BOX(gtkconv
->infopane_hbox
),
4320 gtkconv
->u
.im
->icon_container
, FALSE
, FALSE
, 0);
4322 gtk_widget_show(gtkconv
->u
.im
->icon_container
);
4325 gtk_widget_show(gtkconv
->infopane
);
4327 rend
= gtk_cell_renderer_pixbuf_new();
4328 gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(gtkconv
->infopane
), rend
, FALSE
);
4329 gtk_cell_layout_set_attributes(GTK_CELL_LAYOUT(gtkconv
->infopane
), rend
, "stock-id", CONV_ICON_COLUMN
, NULL
);
4330 g_object_set(rend
, "xalign", 0.0, "xpad", 6, "ypad", 0,
4331 "stock-size", gtk_icon_size_from_name(PIDGIN_ICON_SIZE_TANGO_EXTRA_SMALL
),
4334 rend
= gtk_cell_renderer_text_new();
4335 gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(gtkconv
->infopane
), rend
, TRUE
);
4336 gtk_cell_layout_set_attributes(GTK_CELL_LAYOUT(gtkconv
->infopane
), rend
, "markup", CONV_TEXT_COLUMN
, NULL
);
4337 g_object_set(rend
, "ypad", 0, "yalign", 0.5, NULL
);
4339 g_object_set(rend
, "ellipsize", PANGO_ELLIPSIZE_END
, NULL
);
4341 rend
= gtk_cell_renderer_pixbuf_new();
4342 gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(gtkconv
->infopane
), rend
, FALSE
);
4343 gtk_cell_layout_set_attributes(GTK_CELL_LAYOUT(gtkconv
->infopane
), rend
, "pixbuf", CONV_PROTOCOL_ICON_COLUMN
, NULL
);
4344 g_object_set(rend
, "xalign", 0.0, "xpad", 3, "ypad", 0, NULL
);
4346 rend
= gtk_cell_renderer_pixbuf_new();
4347 gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(gtkconv
->infopane
), rend
, FALSE
);
4348 gtk_cell_layout_set_attributes(GTK_CELL_LAYOUT(gtkconv
->infopane
), rend
, "pixbuf", CONV_EMBLEM_COLUMN
, NULL
);
4349 g_object_set(rend
, "xalign", 0.0, "xpad", 6, "ypad", 0, NULL
);
4351 /* Setup the history widget */
4352 sw
= gtk_scrolled_window_new(NULL
, NULL
);
4353 gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(sw
), GTK_SHADOW_IN
);
4354 gtk_scrolled_window_set_policy(
4355 GTK_SCROLLED_WINDOW(sw
),
4360 gtkconv
->history_buffer
= talkatu_history_buffer_new();
4361 gtkconv
->history
= talkatu_history_new();
4362 gtk_text_view_set_buffer(GTK_TEXT_VIEW(gtkconv
->history
), gtkconv
->history_buffer
);
4363 gtk_text_view_set_wrap_mode(GTK_TEXT_VIEW(gtkconv
->history
), GTK_WRAP_WORD
);
4364 gtk_container_add(GTK_CONTAINER(sw
), gtkconv
->history
);
4370 setup_chat_topic(gtkconv
, vbox
);
4372 /* Add the talkatu history */
4373 hpaned
= gtk_paned_new(GTK_ORIENTATION_HORIZONTAL
);
4374 gtk_box_pack_start(GTK_BOX(vbox
), hpaned
, TRUE
, TRUE
, 0);
4375 gtk_widget_show(hpaned
);
4376 gtk_paned_pack1(GTK_PANED(hpaned
), sw
, TRUE
, TRUE
);
4378 /* Now add the userlist */
4379 setup_chat_userlist(gtkconv
, hpaned
);
4381 gtk_box_pack_start(GTK_BOX(vbox
), sw
, TRUE
, TRUE
, 0);
4383 gtk_widget_show_all(sw
);
4385 g_object_set_data(G_OBJECT(gtkconv
->history
), "gtkconv", gtkconv
);
4387 g_signal_connect(G_OBJECT(gtkconv
->history
), "key_press_event",
4388 G_CALLBACK(refocus_entry_cb
), gtkconv
);
4389 g_signal_connect(G_OBJECT(gtkconv
->history
), "key_release_event",
4390 G_CALLBACK(refocus_entry_cb
), gtkconv
);
4392 gtkconv
->lower_hbox
= gtk_box_new(GTK_ORIENTATION_HORIZONTAL
, PIDGIN_HIG_BOX_SPACE
);
4393 gtk_box_pack_start(GTK_BOX(vbox
), gtkconv
->lower_hbox
, FALSE
, FALSE
, 0);
4394 gtk_widget_show(gtkconv
->lower_hbox
);
4396 /* Setup the entry widget and all signals */
4397 gtkconv
->editor
= talkatu_editor_new();
4398 talkatu_editor_set_buffer(TALKATU_EDITOR(gtkconv
->editor
), talkatu_html_buffer_new());
4399 gtk_box_pack_start(GTK_BOX(gtkconv
->lower_hbox
), gtkconv
->editor
, TRUE
, TRUE
, 0);
4401 view
= talkatu_editor_get_view(TALKATU_EDITOR(gtkconv
->editor
));
4402 gtk_widget_set_name(view
, "pidgin_conv_entry");
4403 talkatu_view_set_send_binding(TALKATU_VIEW(view
), TALKATU_VIEW_SEND_BINDING_RETURN
| TALKATU_VIEW_SEND_BINDING_KP_ENTER
);
4407 G_CALLBACK(send_cb
),
4412 /* For sending typing notifications for IMs */
4413 gtkconv
->u
.im
->typing_timer
= 0;
4414 gtkconv
->u
.im
->animate
= purple_prefs_get_bool(PIDGIN_PREFS_ROOT
"/conversations/im/animate_buddy_icons");
4415 gtkconv
->u
.im
->show_icon
= TRUE
;
4421 static PidginConversation
*
4422 pidgin_conv_find_gtkconv(PurpleConversation
* conv
)
4424 PurpleBuddy
*bud
= purple_blist_find_buddy(purple_conversation_get_account(conv
), purple_conversation_get_name(conv
));
4426 PurpleBlistNode
*cn
, *bn
;
4431 if (!(c
= purple_buddy_get_contact(bud
)))
4434 cn
= PURPLE_BLIST_NODE(c
);
4435 for (bn
= purple_blist_node_get_first_child(cn
); bn
; bn
= purple_blist_node_get_sibling_next(bn
)) {
4436 PurpleBuddy
*b
= PURPLE_BUDDY(bn
);
4437 PurpleIMConversation
*im
;
4438 if ((im
= purple_conversations_find_im_with_account(purple_buddy_get_name(b
), purple_buddy_get_account(b
)))) {
4439 if (PIDGIN_CONVERSATION(PURPLE_CONVERSATION(im
)))
4440 return PIDGIN_CONVERSATION(PURPLE_CONVERSATION(im
));
4448 buddy_update_cb(PurpleBlistNode
*bnode
, gpointer null
)
4452 g_return_if_fail(bnode
);
4453 if (!PURPLE_IS_BUDDY(bnode
))
4456 for (list
= pidgin_conv_windows_get_list(); list
; list
= list
->next
)
4458 PidginConvWindow
*win
= list
->data
;
4459 PurpleConversation
*conv
= pidgin_conv_window_get_active_conversation(win
);
4461 if (!PURPLE_IS_IM_CONVERSATION(conv
))
4464 pidgin_conv_update_fields(conv
, PIDGIN_CONV_MENU
);
4469 ignore_middle_click(GtkWidget
*widget
, GdkEventButton
*e
, gpointer null
)
4471 /* A click on the pane is propagated to the notebook containing the pane.
4472 * So if Stu accidentally aims high and middle clicks on the pane-handle,
4473 * it causes a conversation tab to close. Let's stop that from happening.
4475 if (e
->button
== GDK_BUTTON_MIDDLE
&& e
->type
== GDK_BUTTON_PRESS
)
4480 /**************************************************************************
4481 * Conversation UI operations
4482 **************************************************************************/
4484 private_gtkconv_new(PurpleConversation
*conv
, gboolean hidden
)
4486 PidginConversation
*gtkconv
;
4487 GtkWidget
*pane
= NULL
;
4488 GtkWidget
*tab_cont
;
4489 PurpleBlistNode
*convnode
;
4491 if (PURPLE_IS_IM_CONVERSATION(conv
) && (gtkconv
= pidgin_conv_find_gtkconv(conv
))) {
4492 purple_conversation_set_ui_data(conv
, gtkconv
);
4493 if (!g_list_find(gtkconv
->convs
, conv
))
4494 gtkconv
->convs
= g_list_prepend(gtkconv
->convs
, conv
);
4495 pidgin_conv_switch_active_conversation(conv
);
4499 gtkconv
= g_new0(PidginConversation
, 1);
4500 purple_conversation_set_ui_data(conv
, gtkconv
);
4501 gtkconv
->active_conv
= conv
;
4502 gtkconv
->convs
= g_list_prepend(gtkconv
->convs
, conv
);
4503 gtkconv
->send_history
= g_list_append(NULL
, NULL
);
4505 /* Setup some initial variables. */
4506 gtkconv
->unseen_state
= PIDGIN_UNSEEN_NONE
;
4507 gtkconv
->unseen_count
= 0;
4508 gtkconv
->last_flags
= 0;
4510 if (PURPLE_IS_IM_CONVERSATION(conv
)) {
4511 gtkconv
->u
.im
= g_malloc0(sizeof(PidginImPane
));
4512 } else if (PURPLE_IS_CHAT_CONVERSATION(conv
)) {
4513 gtkconv
->u
.chat
= g_malloc0(sizeof(PidginChatPane
));
4515 pane
= setup_common_pane(gtkconv
);
4518 if (PURPLE_IS_CHAT_CONVERSATION(conv
))
4519 g_free(gtkconv
->u
.chat
);
4520 else if (PURPLE_IS_IM_CONVERSATION(conv
))
4521 g_free(gtkconv
->u
.im
);
4524 purple_conversation_set_ui_data(conv
, NULL
);
4528 g_signal_connect(G_OBJECT(pane
), "button_press_event",
4529 G_CALLBACK(ignore_middle_click
), NULL
);
4531 /* Setup the container for the tab. */
4532 gtkconv
->tab_cont
= tab_cont
= gtk_box_new(GTK_ORIENTATION_VERTICAL
, PIDGIN_HIG_BOX_SPACE
);
4533 g_object_set_data(G_OBJECT(tab_cont
), "PidginConversation", gtkconv
);
4534 gtk_container_set_border_width(GTK_CONTAINER(tab_cont
), PIDGIN_HIG_BOX_SPACE
);
4535 gtk_box_pack_start(GTK_BOX(tab_cont
), pane
, TRUE
, TRUE
, 0);
4536 gtk_widget_show(pane
);
4538 convnode
= get_conversation_blist_node(conv
);
4539 if (convnode
== NULL
|| !purple_blist_node_get_bool(convnode
, "gtk-mute-sound"))
4540 gtkconv
->make_sound
= TRUE
;
4542 if (convnode
!= NULL
&& purple_blist_node_has_setting(convnode
, "enable-logging")) {
4543 gboolean logging
= purple_blist_node_get_bool(convnode
, "enable-logging");
4544 purple_conversation_set_logging(conv
, logging
);
4547 talkatu_editor_set_toolbar_visible(
4548 TALKATU_EDITOR(gtkconv
->editor
),
4549 purple_prefs_get_bool(PIDGIN_PREFS_ROOT
"/conversations/show_formatting_toolbar")
4552 if (purple_prefs_get_bool(PIDGIN_PREFS_ROOT
"/conversations/im/show_buddy_icons"))
4553 gtk_widget_show(gtkconv
->infopane_hbox
);
4555 gtk_widget_hide(gtkconv
->infopane_hbox
);
4558 g_signal_connect_swapped(G_OBJECT(pane
), "focus",
4559 G_CALLBACK(gtk_widget_grab_focus
),
4563 pidgin_conv_window_add_gtkconv(hidden_convwin
, gtkconv
);
4565 pidgin_conv_placement_place(gtkconv
);
4567 if (generated_nick_colors
== NULL
) {
4570 color
= gtk_widget_get_style(gtkconv
->history
)->base
[GTK_STATE_NORMAL
];
4571 rgba
.red
= color
.red
/ 65535.0;
4572 rgba
.green
= color
.green
/ 65535.0;
4573 rgba
.blue
= color
.blue
/ 65535.0;
4575 generated_nick_colors
= generate_nick_colors(NICK_COLOR_GENERATE_COUNT
, rgba
);
4578 gtkconv
->nick_colors
= g_array_ref(generated_nick_colors
);
4582 pidgin_conv_new_hidden(PurpleConversation
*conv
)
4584 private_gtkconv_new(conv
, TRUE
);
4588 pidgin_conv_new(PurpleConversation
*conv
)
4590 private_gtkconv_new(conv
, FALSE
);
4591 if (PIDGIN_IS_PIDGIN_CONVERSATION(conv
))
4592 purple_signal_emit(pidgin_conversations_get_handle(),
4593 "conversation-displayed", PIDGIN_CONVERSATION(conv
));
4597 received_im_msg_cb(PurpleAccount
*account
, char *sender
, char *message
,
4598 PurpleConversation
*conv
, PurpleMessageFlags flags
)
4600 PurpleConversationUiOps
*ui_ops
= pidgin_conversations_get_conv_ui_ops();
4601 gboolean hide
= FALSE
;
4604 /* create hidden conv if hide_new pref is always */
4605 if (purple_strequal(purple_prefs_get_string(PIDGIN_PREFS_ROOT
"/conversations/im/hide_new"), "always"))
4608 /* create hidden conv if hide_new pref is away and account is away */
4609 if (purple_strequal(purple_prefs_get_string(PIDGIN_PREFS_ROOT
"/conversations/im/hide_new"), "away") &&
4610 !purple_status_is_available(purple_account_get_active_status(account
)))
4613 if (conv
&& PIDGIN_IS_PIDGIN_CONVERSATION(conv
) && !hide
) {
4614 PidginConversation
*gtkconv
= PIDGIN_CONVERSATION(conv
);
4615 if (gtkconv
->win
== hidden_convwin
) {
4616 pidgin_conv_attach_to_conversation(gtkconv
->active_conv
);
4622 ui_ops
->create_conversation
= pidgin_conv_new_hidden
;
4623 purple_im_conversation_new(account
, sender
);
4624 ui_ops
->create_conversation
= pidgin_conv_new
;
4627 /* Somebody wants to keep this conversation around, so don't time it out */
4629 timer
= GPOINTER_TO_INT(g_object_get_data(G_OBJECT(conv
), "close-timer"));
4631 g_source_remove(timer
);
4632 g_object_set_data(G_OBJECT(conv
), "close-timer", GINT_TO_POINTER(0));
4638 pidgin_conv_destroy(PurpleConversation
*conv
)
4640 PidginConversation
*gtkconv
= PIDGIN_CONVERSATION(conv
);
4642 gtkconv
->convs
= g_list_remove(gtkconv
->convs
, conv
);
4643 /* Don't destroy ourselves until all our convos are gone */
4644 if (gtkconv
->convs
) {
4645 /* Make sure the destroyed conversation is not the active one */
4646 if (gtkconv
->active_conv
== conv
) {
4647 gtkconv
->active_conv
= gtkconv
->convs
->data
;
4648 purple_conversation_update(gtkconv
->active_conv
, PURPLE_CONVERSATION_UPDATE_FEATURES
);
4653 pidgin_conv_window_remove_gtkconv(gtkconv
->win
, gtkconv
);
4655 /* If the "Save Conversation" or "Save Icon" dialogs are open then close them */
4656 purple_request_close_with_handle(gtkconv
);
4657 purple_notify_close_with_handle(gtkconv
);
4659 gtk_widget_destroy(gtkconv
->tab_cont
);
4660 g_object_unref(gtkconv
->tab_cont
);
4662 if (PURPLE_IS_IM_CONVERSATION(conv
)) {
4663 if (gtkconv
->u
.im
->icon_timer
!= 0)
4664 g_source_remove(gtkconv
->u
.im
->icon_timer
);
4666 if (gtkconv
->u
.im
->anim
!= NULL
)
4667 g_object_unref(G_OBJECT(gtkconv
->u
.im
->anim
));
4669 if (gtkconv
->u
.im
->typing_timer
!= 0)
4670 g_source_remove(gtkconv
->u
.im
->typing_timer
);
4672 g_free(gtkconv
->u
.im
);
4673 } else if (PURPLE_IS_CHAT_CONVERSATION(conv
)) {
4674 purple_signals_disconnect_by_handle(gtkconv
->u
.chat
);
4675 g_free(gtkconv
->u
.chat
);
4678 gtkconv
->send_history
= g_list_first(gtkconv
->send_history
);
4679 g_list_foreach(gtkconv
->send_history
, (GFunc
)g_free
, NULL
);
4680 g_list_free(gtkconv
->send_history
);
4682 if (gtkconv
->attach_timer
) {
4683 g_source_remove(gtkconv
->attach_timer
);
4686 g_array_unref(gtkconv
->nick_colors
);
4693 get_text_tag_color(GtkTextTag
*tag
)
4695 GdkRGBA
*color
= NULL
;
4696 gboolean set
= FALSE
;
4697 static char colcode
[] = "#XXXXXX";
4699 g_object_get(G_OBJECT(tag
), "foreground-set", &set
, "foreground-rgba", &color
, NULL
);
4701 g_snprintf(colcode
, sizeof(colcode
), "#%02x%02x%02x",
4702 (unsigned int)(color
->red
* 255),
4703 (unsigned int)(color
->green
* 255),
4704 (unsigned int)(color
->blue
* 255));
4708 gdk_rgba_free(color
);
4712 /* The callback for an event on a link tag. */
4713 static gboolean
buddytag_event(GtkTextTag
*tag
, GObject
*imhtml
,
4714 GdkEvent
*event
, GtkTextIter
*arg2
, gpointer data
)
4716 if (event
->type
== GDK_BUTTON_PRESS
4717 || event
->type
== GDK_2BUTTON_PRESS
) {
4718 GdkEventButton
*btn_event
= (GdkEventButton
*) event
;
4719 PurpleConversation
*conv
= data
;
4723 g_object_get(G_OBJECT(tag
), "name", &name
, NULL
);
4725 /* strlen("BUDDY " or "HILIT ") == 6 */
4726 g_return_val_if_fail((name
!= NULL
) && (strlen(name
) > 6), FALSE
);
4728 buddyname
= name
+ 6;
4730 /* emit chat-nick-clicked signal */
4731 if (event
->type
== GDK_BUTTON_PRESS
) {
4732 gint plugin_return
= GPOINTER_TO_INT(purple_signal_emit_return_1(
4733 pidgin_conversations_get_handle(), "chat-nick-clicked",
4734 data
, buddyname
, btn_event
->button
));
4735 if (plugin_return
) {
4741 if (btn_event
->button
== GDK_BUTTON_PRIMARY
&& event
->type
== GDK_2BUTTON_PRESS
) {
4742 chat_do_im(PIDGIN_CONVERSATION(conv
), buddyname
);
4746 } else if (btn_event
->button
== GDK_BUTTON_MIDDLE
&& event
->type
== GDK_2BUTTON_PRESS
) {
4747 chat_do_info(PIDGIN_CONVERSATION(conv
), buddyname
);
4751 } else if (gdk_event_triggers_context_menu(event
)) {
4752 GtkTextIter start
, end
;
4754 /* we shouldn't display the popup
4755 * if the user has selected something: */
4756 if (!gtk_text_buffer_get_selection_bounds(
4757 gtk_text_iter_get_buffer(arg2
),
4759 GtkWidget
*menu
= NULL
;
4760 PurpleConnection
*gc
=
4761 purple_conversation_get_connection(conv
);
4763 menu
= create_chat_menu(conv
, buddyname
, gc
);
4764 gtk_menu_popup_at_pointer(GTK_MENU(menu
), event
);
4768 /* Don't propagate the event any further */
4780 static GtkTextTag
*get_buddy_tag(PurpleChatConversation
*chat
, const char *who
, PurpleMessageFlags flag
,
4785 PidginConversation
*gtkconv
= PIDGIN_CONVERSATION(conv
);
4786 GtkTextTag
*buddytag
;
4788 gboolean highlight
= (flag
& PURPLE_MESSAGE_NICK
);
4789 GtkTextBuffer
*buffer
= GTK_IMHTML(gtkconv
->imhtml
)->text_buffer
;
4791 str
= g_strdup_printf(highlight
? "HILIT %s" : "BUDDY %s", who
);
4793 buddytag
= gtk_text_tag_table_lookup(
4794 gtk_text_buffer_get_tag_table(buffer
), str
);
4796 if (buddytag
== NULL
&& create
) {
4798 buddytag
= gtk_text_buffer_create_tag(buffer
, str
,
4799 "foreground", get_text_tag_color(gtk_text_tag_table_lookup(
4800 gtk_text_buffer_get_tag_table(buffer
), "highlight-name")),
4801 "weight", PANGO_WEIGHT_BOLD
,
4804 buddytag
= gtk_text_buffer_create_tag(
4806 "foreground-rgba", get_nick_color(gtkconv
, who
),
4807 "weight", purple_blist_find_buddy(purple_conversation_get_account(conv
), who
) ? PANGO_WEIGHT_BOLD
: PANGO_WEIGHT_NORMAL
,
4810 g_object_set_data(G_OBJECT(buddytag
), "cursor", "");
4811 g_signal_connect(G_OBJECT(buddytag
), "event",
4812 G_CALLBACK(buddytag_event
), conv
);
4823 writing_msg(PurpleConversation
*conv
, PurpleMessage
*msg
, gpointer _unused
)
4825 PidginConversation
*gtkconv
;
4827 g_return_val_if_fail(msg
!= NULL
, FALSE
);
4829 if (!(purple_message_get_flags(msg
) & PURPLE_MESSAGE_ACTIVE_ONLY
))
4832 g_return_val_if_fail(conv
!= NULL
, FALSE
);
4833 gtkconv
= PIDGIN_CONVERSATION(conv
);
4834 g_return_val_if_fail(gtkconv
!= NULL
, FALSE
);
4836 if (conv
== gtkconv
->active_conv
)
4839 purple_debug_info("gtkconv",
4840 "Suppressing message for an inactive conversation");
4846 pidgin_conv_write_conv(PurpleConversation
*conv
, PurpleMessage
*pmsg
)
4848 PidginMessage
*pidgin_msg
= NULL
;
4849 PurpleMessageFlags flags
;
4850 PidginConversation
*gtkconv
;
4851 PurpleConnection
*gc
;
4852 PurpleAccount
*account
;
4853 gboolean plugin_return
;
4855 g_return_if_fail(conv
!= NULL
);
4856 gtkconv
= PIDGIN_CONVERSATION(conv
);
4857 g_return_if_fail(gtkconv
!= NULL
);
4858 flags
= purple_message_get_flags(pmsg
);
4861 if (gtkconv
->attach_timer
) {
4862 /* We are currently in the process of filling up the buffer with the message
4863 * history of the conversation. So we do not need to add the message here.
4864 * Instead, this message will be added to the message-list, which in turn will
4865 * be processed and displayed by the attach-callback.
4870 if (conv
!= gtkconv
->active_conv
)
4872 /* Set the active conversation to the one that just messaged us. */
4873 /* TODO: consider not doing this if the account is offline or something */
4874 if (flags
& (PURPLE_MESSAGE_SEND
| PURPLE_MESSAGE_RECV
))
4875 pidgin_conv_switch_active_conversation(conv
);
4879 account
= purple_conversation_get_account(conv
);
4880 g_return_if_fail(account
!= NULL
);
4881 gc
= purple_account_get_connection(account
);
4882 g_return_if_fail(gc
!= NULL
|| !(flags
& (PURPLE_MESSAGE_SEND
| PURPLE_MESSAGE_RECV
)));
4884 plugin_return
= GPOINTER_TO_INT(purple_signal_emit_return_1(
4885 pidgin_conversations_get_handle(),
4886 (PURPLE_IS_IM_CONVERSATION(conv
) ? "displaying-im-msg" : "displaying-chat-msg"),
4893 pidgin_msg
= pidgin_message_new(pmsg
);
4894 talkatu_history_buffer_write_message(
4895 TALKATU_HISTORY_BUFFER(gtkconv
->history_buffer
),
4896 TALKATU_MESSAGE(pidgin_msg
)
4899 /* Tab highlighting stuff */
4900 if (!(flags
& PURPLE_MESSAGE_SEND
) && !pidgin_conv_has_focus(conv
))
4902 PidginUnseenState unseen
= PIDGIN_UNSEEN_NONE
;
4904 if ((flags
& PURPLE_MESSAGE_NICK
) == PURPLE_MESSAGE_NICK
)
4905 unseen
= PIDGIN_UNSEEN_NICK
;
4906 else if (((flags
& PURPLE_MESSAGE_SYSTEM
) == PURPLE_MESSAGE_SYSTEM
) ||
4907 ((flags
& PURPLE_MESSAGE_ERROR
) == PURPLE_MESSAGE_ERROR
))
4908 unseen
= PIDGIN_UNSEEN_EVENT
;
4909 else if ((flags
& PURPLE_MESSAGE_NO_LOG
) == PURPLE_MESSAGE_NO_LOG
)
4910 unseen
= PIDGIN_UNSEEN_NO_LOG
;
4912 unseen
= PIDGIN_UNSEEN_TEXT
;
4914 gtkconv_set_unseen(gtkconv
, unseen
);
4917 /* on rejoin only request message history from after this message */
4918 if (flags
& (PURPLE_MESSAGE_SEND
| PURPLE_MESSAGE_RECV
) &&
4919 PURPLE_IS_CHAT_CONVERSATION(conv
)) {
4920 PurpleChat
*chat
= purple_blist_find_chat(
4921 purple_conversation_get_account(conv
),
4922 purple_conversation_get_name(conv
));
4924 GHashTable
*comps
= purple_chat_get_components(chat
);
4925 time_t now
, history_since
, prev_history_since
= 0;
4926 struct tm
*history_since_tm
;
4927 const char *history_since_s
, *prev_history_since_s
;
4929 history_since
= purple_message_get_time(pmsg
) + 1;
4931 prev_history_since_s
= g_hash_table_lookup(comps
,
4933 if (prev_history_since_s
!= NULL
)
4934 prev_history_since
= purple_str_to_time(
4935 prev_history_since_s
, TRUE
, NULL
, NULL
,
4939 /* in case of incorrectly stored timestamps */
4940 if (prev_history_since
> now
)
4941 prev_history_since
= now
;
4942 /* in case of delayed messages */
4943 if (history_since
< prev_history_since
)
4944 history_since
= prev_history_since
;
4946 history_since_tm
= gmtime(&history_since
);
4947 history_since_s
= purple_utf8_strftime(
4948 "%Y-%m-%dT%H:%M:%SZ", history_since_tm
);
4949 if (!purple_strequal(prev_history_since_s
,
4951 g_hash_table_replace(comps
,
4952 g_strdup("history_since"),
4953 g_strdup(history_since_s
));
4957 purple_signal_emit(pidgin_conversations_get_handle(),
4958 (PURPLE_IS_IM_CONVERSATION(conv
) ? "displayed-im-msg" : "displayed-chat-msg"),
4960 update_typing_message(gtkconv
, NULL
);
4963 static gboolean
get_iter_from_chatuser(PurpleChatUser
*cb
, GtkTreeIter
*iter
)
4965 GtkTreeRowReference
*ref
;
4967 GtkTreeModel
*model
;
4969 g_return_val_if_fail(cb
!= NULL
, FALSE
);
4971 ref
= purple_chat_user_get_ui_data(cb
);
4975 if ((path
= gtk_tree_row_reference_get_path(ref
)) == NULL
)
4978 model
= gtk_tree_row_reference_get_model(ref
);
4979 if (!gtk_tree_model_get_iter(GTK_TREE_MODEL(model
), iter
, path
)) {
4980 gtk_tree_path_free(path
);
4984 gtk_tree_path_free(path
);
4989 pidgin_conv_chat_add_users(PurpleChatConversation
*chat
, GList
*cbuddies
, gboolean new_arrivals
)
4991 PidginConversation
*gtkconv
;
4992 PidginChatPane
*gtkchat
;
4999 gtkconv
= PIDGIN_CONVERSATION(PURPLE_CONVERSATION(chat
));
5000 gtkchat
= gtkconv
->u
.chat
;
5002 num_users
= purple_chat_conversation_get_users_count(chat
);
5004 g_snprintf(tmp
, sizeof(tmp
),
5005 ngettext("%d person in room", "%d people in room",
5009 gtk_label_set_text(GTK_LABEL(gtkchat
->count
), tmp
);
5011 ls
= GTK_LIST_STORE(gtk_tree_view_get_model(GTK_TREE_VIEW(gtkchat
->list
)));
5013 gtk_tree_sortable_set_sort_column_id(GTK_TREE_SORTABLE(ls
), GTK_TREE_SORTABLE_UNSORTED_SORT_COLUMN_ID
,
5014 GTK_TREE_SORTABLE_UNSORTED_SORT_COLUMN_ID
);
5018 add_chat_user_common(chat
, (PurpleChatUser
*)l
->data
, NULL
);
5022 /* Currently GTK+ maintains our sorted list after it's in the tree.
5023 * This may change if it turns out we can manage it faster ourselves.
5025 gtk_tree_sortable_set_sort_column_id(GTK_TREE_SORTABLE(ls
), CHAT_USERS_ALIAS_KEY_COLUMN
,
5026 GTK_SORT_ASCENDING
);
5030 pidgin_conv_chat_rename_user(PurpleChatConversation
*chat
, const char *old_name
,
5031 const char *new_name
, const char *new_alias
)
5033 PidginConversation
*gtkconv
;
5034 PidginChatPane
*gtkchat
;
5035 PurpleChatUser
*old_chatuser
, *new_chatuser
;
5037 GtkTreeModel
*model
;
5040 gtkconv
= PIDGIN_CONVERSATION(PURPLE_CONVERSATION(chat
));
5041 gtkchat
= gtkconv
->u
.chat
;
5043 model
= gtk_tree_view_get_model(GTK_TREE_VIEW(gtkchat
->list
));
5045 if (!gtk_tree_model_get_iter_first(GTK_TREE_MODEL(model
), &iter
))
5048 if ((tag
= get_buddy_tag(chat
, old_name
, 0, FALSE
)))
5049 g_object_set(G_OBJECT(tag
), "style", PANGO_STYLE_ITALIC
, NULL
);
5050 if ((tag
= get_buddy_tag(chat
, old_name
, PURPLE_MESSAGE_NICK
, FALSE
)))
5051 g_object_set(G_OBJECT(tag
), "style", PANGO_STYLE_ITALIC
, NULL
);
5053 old_chatuser
= purple_chat_conversation_find_user(chat
, old_name
);
5057 if (get_iter_from_chatuser(old_chatuser
, &iter
)) {
5058 GtkTreeRowReference
*ref
= purple_chat_user_get_ui_data(old_chatuser
);
5060 gtk_list_store_remove(GTK_LIST_STORE(model
), &iter
);
5061 gtk_tree_row_reference_free(ref
);
5062 purple_chat_user_set_ui_data(old_chatuser
, NULL
);
5065 g_return_if_fail(new_alias
!= NULL
);
5067 new_chatuser
= purple_chat_conversation_find_user(chat
, new_name
);
5069 add_chat_user_common(chat
, new_chatuser
, old_name
);
5073 pidgin_conv_chat_remove_users(PurpleChatConversation
*chat
, GList
*users
)
5075 PidginConversation
*gtkconv
;
5076 PidginChatPane
*gtkchat
;
5078 GtkTreeModel
*model
;
5085 gtkconv
= PIDGIN_CONVERSATION(PURPLE_CONVERSATION(chat
));
5086 gtkchat
= gtkconv
->u
.chat
;
5088 num_users
= purple_chat_conversation_get_users_count(chat
);
5090 for (l
= users
; l
!= NULL
; l
= l
->next
) {
5091 model
= gtk_tree_view_get_model(GTK_TREE_VIEW(gtkchat
->list
));
5093 if (!gtk_tree_model_get_iter_first(GTK_TREE_MODEL(model
), &iter
))
5100 gtk_tree_model_get(GTK_TREE_MODEL(model
), &iter
,
5101 CHAT_USERS_NAME_COLUMN
, &val
, -1);
5103 if (!purple_utf8_strcasecmp((char *)l
->data
, val
)) {
5104 f
= gtk_list_store_remove(GTK_LIST_STORE(model
), &iter
);
5107 f
= gtk_tree_model_iter_next(GTK_TREE_MODEL(model
), &iter
);
5112 if ((tag
= get_buddy_tag(chat
, l
->data
, 0, FALSE
)))
5113 g_object_set(G_OBJECT(tag
), "style", PANGO_STYLE_ITALIC
, NULL
);
5114 if ((tag
= get_buddy_tag(chat
, l
->data
, PURPLE_MESSAGE_NICK
, FALSE
)))
5115 g_object_set(G_OBJECT(tag
), "style", PANGO_STYLE_ITALIC
, NULL
);
5118 g_snprintf(tmp
, sizeof(tmp
),
5119 ngettext("%d person in room", "%d people in room",
5120 num_users
), num_users
);
5122 gtk_label_set_text(GTK_LABEL(gtkchat
->count
), tmp
);
5126 pidgin_conv_chat_update_user(PurpleChatUser
*chatuser
)
5128 PurpleChatConversation
*chat
;
5129 PidginConversation
*gtkconv
;
5130 PidginChatPane
*gtkchat
;
5132 GtkTreeModel
*model
;
5137 chat
= purple_chat_user_get_chat(chatuser
);
5138 gtkconv
= PIDGIN_CONVERSATION(PURPLE_CONVERSATION(chat
));
5139 gtkchat
= gtkconv
->u
.chat
;
5141 model
= gtk_tree_view_get_model(GTK_TREE_VIEW(gtkchat
->list
));
5143 if (!gtk_tree_model_get_iter_first(GTK_TREE_MODEL(model
), &iter
))
5146 if (get_iter_from_chatuser(chatuser
, &iter
)) {
5147 GtkTreeRowReference
*ref
= purple_chat_user_get_ui_data(chatuser
);
5148 gtk_list_store_remove(GTK_LIST_STORE(model
), &iter
);
5149 gtk_tree_row_reference_free(ref
);
5150 purple_chat_user_set_ui_data(chatuser
, NULL
);
5154 add_chat_user_common(chat
, chatuser
, NULL
);
5158 pidgin_conv_has_focus(PurpleConversation
*conv
)
5160 PidginConversation
*gtkconv
= PIDGIN_CONVERSATION(conv
);
5161 PidginConvWindow
*win
;
5166 g_object_get(G_OBJECT(win
->window
), "has-toplevel-focus", &has_focus
, NULL
);
5168 if (has_focus
&& pidgin_conv_window_is_active_conversation(conv
))
5175 * Makes sure all the menu items and all the buttons are hidden/shown and
5176 * sensitive/insensitive. This is called after changing tabs and when an
5177 * account signs on or off.
5180 gray_stuff_out(PidginConversation
*gtkconv
)
5182 PidginConvWindow
*win
;
5183 PurpleConversation
*conv
= gtkconv
->active_conv
;
5184 PurpleConnection
*gc
;
5185 PurpleProtocol
*protocol
= NULL
;
5186 GdkPixbuf
*window_icon
= NULL
;
5187 // PidginWebViewButtons buttons;
5188 PurpleAccount
*account
;
5190 win
= pidgin_conv_get_window(gtkconv
);
5191 gc
= purple_conversation_get_connection(conv
);
5192 account
= purple_conversation_get_account(conv
);
5195 protocol
= purple_connection_get_protocol(gc
);
5197 if (win
->menu
->send_to
!= NULL
)
5198 update_send_to_selection(win
);
5201 * Handle hiding and showing stuff based on what type of conv this is.
5202 * Stuff that Purple IMs support in general should be shown for IM
5203 * conversations. Stuff that Purple chats support in general should be
5204 * shown for chat conversations. It doesn't matter whether the protocol
5205 * supports it or not--that only affects if the button or menu item
5206 * is sensitive or not.
5208 if (PURPLE_IS_IM_CONVERSATION(conv
)) {
5209 /* Show stuff that applies to IMs, hide stuff that applies to chats */
5211 /* Deal with menu items */
5212 gtk_action_set_visible(win
->menu
->view_log
, TRUE
);
5213 gtk_action_set_visible(win
->menu
->send_file
, TRUE
);
5214 gtk_action_set_visible(win
->menu
->get_attention
, TRUE
);
5215 gtk_action_set_visible(win
->menu
->add_pounce
, TRUE
);
5216 gtk_action_set_visible(win
->menu
->get_info
, TRUE
);
5217 gtk_action_set_visible(win
->menu
->invite
, FALSE
);
5218 gtk_action_set_visible(win
->menu
->alias
, TRUE
);
5219 if (purple_account_privacy_check(account
, purple_conversation_get_name(conv
))) {
5220 gtk_action_set_visible(win
->menu
->unblock
, FALSE
);
5221 gtk_action_set_visible(win
->menu
->block
, TRUE
);
5223 gtk_action_set_visible(win
->menu
->block
, FALSE
);
5224 gtk_action_set_visible(win
->menu
->unblock
, TRUE
);
5227 if (purple_blist_find_buddy(account
, purple_conversation_get_name(conv
)) == NULL
) {
5228 gtk_action_set_visible(win
->menu
->add
, TRUE
);
5229 gtk_action_set_visible(win
->menu
->remove
, FALSE
);
5231 gtk_action_set_visible(win
->menu
->remove
, TRUE
);
5232 gtk_action_set_visible(win
->menu
->add
, FALSE
);
5235 gtk_action_set_visible(win
->menu
->insert_link
, TRUE
);
5236 gtk_action_set_visible(win
->menu
->insert_image
, TRUE
);
5237 } else if (PURPLE_IS_CHAT_CONVERSATION(conv
)) {
5238 /* Show stuff that applies to Chats, hide stuff that applies to IMs */
5240 /* Deal with menu items */
5241 gtk_action_set_visible(win
->menu
->view_log
, TRUE
);
5242 gtk_action_set_visible(win
->menu
->send_file
, FALSE
);
5243 gtk_action_set_visible(win
->menu
->get_attention
, FALSE
);
5244 gtk_action_set_visible(win
->menu
->add_pounce
, FALSE
);
5245 gtk_action_set_visible(win
->menu
->get_info
, FALSE
);
5246 gtk_action_set_visible(win
->menu
->invite
, TRUE
);
5247 gtk_action_set_visible(win
->menu
->alias
, TRUE
);
5248 gtk_action_set_visible(win
->menu
->block
, FALSE
);
5249 gtk_action_set_visible(win
->menu
->unblock
, FALSE
);
5251 if ((account
== NULL
) || purple_blist_find_chat(account
, purple_conversation_get_name(conv
)) == NULL
) {
5252 /* If the chat is NOT in the buddy list */
5253 gtk_action_set_visible(win
->menu
->add
, TRUE
);
5254 gtk_action_set_visible(win
->menu
->remove
, FALSE
);
5256 /* If the chat IS in the buddy list */
5257 gtk_action_set_visible(win
->menu
->add
, FALSE
);
5258 gtk_action_set_visible(win
->menu
->remove
, TRUE
);
5261 gtk_action_set_visible(win
->menu
->insert_link
, TRUE
);
5262 gtk_action_set_visible(win
->menu
->insert_image
, TRUE
);
5266 * Handle graying stuff out based on whether an account is connected
5267 * and what features that account supports.
5270 (!PURPLE_IS_CHAT_CONVERSATION(conv
) ||
5271 !purple_chat_conversation_has_left(PURPLE_CHAT_CONVERSATION(conv
)) ))
5273 PurpleConnectionFlags features
= purple_conversation_get_features(conv
);
5274 /* Account is online */
5275 /* Deal with the toolbar */
5277 if (features
& PURPLE_CONNECTION_FLAG_HTML
)
5279 buttons
= PIDGIN_WEBVIEW_ALL
; /* Everything on */
5280 if (features
& PURPLE_CONNECTION_FLAG_NO_BGCOLOR
)
5281 buttons
&= ~PIDGIN_WEBVIEW_BACKCOLOR
;
5282 if (features
& PURPLE_CONNECTION_FLAG_NO_FONTSIZE
)
5284 buttons
&= ~PIDGIN_WEBVIEW_GROW
;
5285 buttons
&= ~PIDGIN_WEBVIEW_SHRINK
;
5287 if (features
& PURPLE_CONNECTION_FLAG_NO_URLDESC
)
5288 buttons
&= ~PIDGIN_WEBVIEW_LINKDESC
5290 buttons
= PIDGIN_WEBVIEW_SMILEY
| PIDGIN_WEBVIEW_IMAGE
;
5293 if (features
& PURPLE_CONNECTION_FLAG_NO_IMAGES
)
5294 buttons
&= ~PIDGIN_WEBVIEW_IMAGE
;
5296 if (features
& PURPLE_CONNECTION_FLAG_ALLOW_CUSTOM_SMILEY
)
5297 buttons
|= PIDGIN_WEBVIEW_CUSTOM_SMILEY
;
5299 buttons
&= ~PIDGIN_WEBVIEW_CUSTOM_SMILEY
;
5301 pidgin_webview_set_format_functions(PIDGIN_WEBVIEW(gtkconv
->entry
), buttons
);
5304 /* Deal with menu items */
5305 gtk_action_set_sensitive(win
->menu
->view_log
, TRUE
);
5306 gtk_action_set_sensitive(win
->menu
->add_pounce
, TRUE
);
5307 gtk_action_set_sensitive(win
->menu
->get_info
, (PURPLE_PROTOCOL_IMPLEMENTS(protocol
, SERVER
, get_info
)));
5308 gtk_action_set_sensitive(win
->menu
->invite
, (PURPLE_PROTOCOL_IMPLEMENTS(protocol
, CHAT
, invite
)));
5309 gtk_action_set_sensitive(win
->menu
->insert_link
, (features
& PURPLE_CONNECTION_FLAG_HTML
));
5310 gtk_action_set_sensitive(win
->menu
->insert_image
, !(features
& PURPLE_CONNECTION_FLAG_NO_IMAGES
));
5312 if (PURPLE_IS_IM_CONVERSATION(conv
))
5314 gboolean can_send_file
= FALSE
;
5315 const gchar
*name
= purple_conversation_get_name(conv
);
5317 if (PURPLE_IS_PROTOCOL_XFER(protocol
) &&
5318 purple_protocol_xfer_can_receive(PURPLE_PROTOCOL_XFER(protocol
), gc
, name
)
5320 can_send_file
= TRUE
;
5323 gtk_action_set_sensitive(win
->menu
->add
, (PURPLE_PROTOCOL_IMPLEMENTS(protocol
, SERVER
, add_buddy
)));
5324 gtk_action_set_sensitive(win
->menu
->remove
, (PURPLE_PROTOCOL_IMPLEMENTS(protocol
, SERVER
, remove_buddy
)));
5325 gtk_action_set_sensitive(win
->menu
->send_file
, can_send_file
);
5326 gtk_action_set_sensitive(win
->menu
->get_attention
, (PURPLE_IS_PROTOCOL_ATTENTION(protocol
)));
5327 gtk_action_set_sensitive(win
->menu
->alias
,
5328 (account
!= NULL
) &&
5329 (purple_blist_find_buddy(account
, purple_conversation_get_name(conv
)) != NULL
));
5331 else if (PURPLE_IS_CHAT_CONVERSATION(conv
))
5333 gtk_action_set_sensitive(win
->menu
->add
, (PURPLE_PROTOCOL_IMPLEMENTS(protocol
, CHAT
, join
)));
5334 gtk_action_set_sensitive(win
->menu
->remove
, (PURPLE_PROTOCOL_IMPLEMENTS(protocol
, CHAT
, join
)));
5335 gtk_action_set_sensitive(win
->menu
->alias
,
5336 (account
!= NULL
) &&
5337 (purple_blist_find_chat(account
, purple_conversation_get_name(conv
)) != NULL
));
5341 /* Account is offline */
5342 /* Or it's a chat that we've left. */
5344 /* Then deal with menu items */
5345 gtk_action_set_sensitive(win
->menu
->view_log
, TRUE
);
5346 gtk_action_set_sensitive(win
->menu
->send_file
, FALSE
);
5347 gtk_action_set_sensitive(win
->menu
->get_attention
, FALSE
);
5348 gtk_action_set_sensitive(win
->menu
->add_pounce
, TRUE
);
5349 gtk_action_set_sensitive(win
->menu
->get_info
, FALSE
);
5350 gtk_action_set_sensitive(win
->menu
->invite
, FALSE
);
5351 gtk_action_set_sensitive(win
->menu
->alias
, FALSE
);
5352 gtk_action_set_sensitive(win
->menu
->add
, FALSE
);
5353 gtk_action_set_sensitive(win
->menu
->remove
, FALSE
);
5354 gtk_action_set_sensitive(win
->menu
->insert_link
, TRUE
);
5355 gtk_action_set_sensitive(win
->menu
->insert_image
, FALSE
);
5359 * Update the window's icon
5361 if (pidgin_conv_window_is_active_conversation(conv
))
5364 if (PURPLE_IS_IM_CONVERSATION(conv
) &&
5365 (gtkconv
->u
.im
->anim
))
5367 PurpleBuddy
*buddy
= purple_blist_find_buddy(purple_conversation_get_account(conv
), purple_conversation_get_name(conv
));
5369 gdk_pixbuf_animation_get_static_image(gtkconv
->u
.im
->anim
);
5371 if (buddy
&& !PURPLE_BUDDY_IS_ONLINE(buddy
))
5372 gdk_pixbuf_saturate_and_pixelate(window_icon
, window_icon
, 0.0, FALSE
);
5374 g_object_ref(window_icon
);
5375 l
= g_list_append(l
, window_icon
);
5377 l
= pidgin_conv_get_tab_icons(conv
);
5379 gtk_window_set_icon_list(GTK_WINDOW(win
->window
), l
);
5380 if (window_icon
!= NULL
) {
5381 g_object_unref(G_OBJECT(window_icon
));
5388 pidgin_conv_update_fields(PurpleConversation
*conv
, PidginConvFields fields
)
5390 PidginConversation
*gtkconv
;
5391 PidginConvWindow
*win
;
5393 gtkconv
= PIDGIN_CONVERSATION(conv
);
5396 win
= pidgin_conv_get_window(gtkconv
);
5400 if (fields
& PIDGIN_CONV_SET_TITLE
)
5402 purple_conversation_autoset_title(conv
);
5405 if (fields
& PIDGIN_CONV_BUDDY_ICON
)
5407 if (PURPLE_IS_IM_CONVERSATION(conv
))
5408 pidgin_conv_update_buddy_icon(PURPLE_IM_CONVERSATION(conv
));
5411 if (fields
& PIDGIN_CONV_MENU
)
5413 gray_stuff_out(PIDGIN_CONVERSATION(conv
));
5414 generate_send_to_items(win
);
5415 regenerate_plugins_items(win
);
5418 if (fields
& PIDGIN_CONV_E2EE
)
5419 generate_e2ee_controls(win
);
5421 if (fields
& PIDGIN_CONV_TAB_ICON
)
5423 update_tab_icon(conv
);
5424 generate_send_to_items(win
); /* To update the icons in SendTo menu */
5427 if ((fields
& PIDGIN_CONV_TOPIC
) &&
5428 PURPLE_IS_CHAT_CONVERSATION(conv
))
5431 PidginChatPane
*gtkchat
= gtkconv
->u
.chat
;
5433 if (gtkchat
->topic_text
!= NULL
)
5435 topic
= purple_chat_conversation_get_topic(PURPLE_CHAT_CONVERSATION(conv
));
5437 gtk_entry_set_text(GTK_ENTRY(gtkchat
->topic_text
), topic
? topic
: "");
5438 gtk_widget_set_tooltip_text(gtkchat
->topic_text
,
5439 topic
? topic
: "");
5443 if ((fields
& PIDGIN_CONV_COLORIZE_TITLE
) ||
5444 (fields
& PIDGIN_CONV_SET_TITLE
) ||
5445 (fields
& PIDGIN_CONV_TOPIC
))
5448 PurpleIMConversation
*im
= NULL
;
5449 PurpleAccount
*account
= purple_conversation_get_account(conv
);
5450 PurpleBuddy
*buddy
= NULL
;
5451 char *markup
= NULL
;
5452 AtkObject
*accessibility_obj
;
5453 /* I think this is a little longer than it needs to be but I'm lazy. */
5456 if (PURPLE_IS_IM_CONVERSATION(conv
))
5457 im
= PURPLE_IM_CONVERSATION(conv
);
5459 if ((account
== NULL
) ||
5460 !purple_account_is_connected(account
) ||
5461 (PURPLE_IS_CHAT_CONVERSATION(conv
)
5462 && purple_chat_conversation_has_left(PURPLE_CHAT_CONVERSATION(conv
))))
5463 title
= g_strdup_printf("(%s)", purple_conversation_get_title(conv
));
5465 title
= g_strdup(purple_conversation_get_title(conv
));
5467 if (PURPLE_IS_IM_CONVERSATION(conv
)) {
5468 buddy
= purple_blist_find_buddy(account
, purple_conversation_get_name(conv
));
5470 markup
= pidgin_blist_get_name_markup(buddy
, FALSE
, FALSE
);
5474 } else if (PURPLE_IS_CHAT_CONVERSATION(conv
)) {
5475 const char *topic
= gtkconv
->u
.chat
->topic_text
5476 ? gtk_entry_get_text(GTK_ENTRY(gtkconv
->u
.chat
->topic_text
))
5478 const char *title
= purple_conversation_get_title(conv
);
5479 const char *name
= purple_conversation_get_name(conv
);
5481 char *topic_esc
, *unaliased
, *unaliased_esc
, *title_esc
;
5483 topic_esc
= topic
? g_markup_escape_text(topic
, -1) : NULL
;
5484 unaliased
= g_utf8_collate(title
, name
) ? g_strdup_printf("(%s)", name
) : NULL
;
5485 unaliased_esc
= unaliased
? g_markup_escape_text(unaliased
, -1) : NULL
;
5486 title_esc
= g_markup_escape_text(title
, -1);
5488 markup
= g_strdup_printf("%s%s<span size='smaller'>%s</span>%s<span color='%s' size='smaller'>%s</span>",
5490 unaliased_esc
? " " : "",
5491 unaliased_esc
? unaliased_esc
: "",
5492 topic_esc
&& *topic_esc
? "\n" : "",
5493 pidgin_get_dim_grey_string(gtkconv
->infopane
),
5494 topic_esc
? topic_esc
: "");
5499 g_free(unaliased_esc
);
5501 gtk_list_store_set(gtkconv
->infopane_model
, &(gtkconv
->infopane_iter
),
5502 CONV_TEXT_COLUMN
, markup
, -1);
5503 /* XXX seanegan Why do I have to do this? */
5504 gtk_widget_queue_draw(gtkconv
->infopane
);
5506 if (title
!= markup
)
5509 if (!gtk_widget_get_realized(gtkconv
->tab_label
))
5510 gtk_widget_realize(gtkconv
->tab_label
);
5512 accessibility_obj
= gtk_widget_get_accessible(gtkconv
->tab_cont
);
5514 purple_im_conversation_get_typing_state(im
) == PURPLE_IM_TYPING
) {
5515 atk_object_set_description(accessibility_obj
, _("Typing"));
5516 style
= "tab-label-typing";
5517 } else if (im
!= NULL
&&
5518 purple_im_conversation_get_typing_state(im
) == PURPLE_IM_TYPED
) {
5519 atk_object_set_description(accessibility_obj
, _("Stopped Typing"));
5520 style
= "tab-label-typed";
5521 } else if (gtkconv
->unseen_state
== PIDGIN_UNSEEN_NICK
) {
5522 atk_object_set_description(accessibility_obj
, _("Nick Said"));
5523 style
= "tab-label-attention";
5524 } else if (gtkconv
->unseen_state
== PIDGIN_UNSEEN_TEXT
) {
5525 atk_object_set_description(accessibility_obj
, _("Unread Messages"));
5526 if (PURPLE_IS_CHAT_CONVERSATION(gtkconv
->active_conv
))
5527 style
= "tab-label-unreadchat";
5529 style
= "tab-label-attention";
5530 } else if (gtkconv
->unseen_state
== PIDGIN_UNSEEN_EVENT
) {
5531 atk_object_set_description(accessibility_obj
, _("New Event"));
5532 style
= "tab-label-event";
5534 style
= "tab-label";
5537 gtk_widget_set_name(gtkconv
->tab_label
, style
);
5538 gtk_label_set_text(GTK_LABEL(gtkconv
->tab_label
), title
);
5539 gtk_widget_set_state_flags(gtkconv
->tab_label
, GTK_STATE_FLAG_ACTIVE
, TRUE
);
5541 if (gtkconv
->unseen_state
== PIDGIN_UNSEEN_TEXT
||
5542 gtkconv
->unseen_state
== PIDGIN_UNSEEN_NICK
||
5543 gtkconv
->unseen_state
== PIDGIN_UNSEEN_EVENT
) {
5544 PangoAttrList
*list
= pango_attr_list_new();
5545 PangoAttribute
*attr
= pango_attr_weight_new(PANGO_WEIGHT_BOLD
);
5546 attr
->start_index
= 0;
5547 attr
->end_index
= -1;
5548 pango_attr_list_insert(list
, attr
);
5549 gtk_label_set_attributes(GTK_LABEL(gtkconv
->tab_label
), list
);
5550 pango_attr_list_unref(list
);
5552 gtk_label_set_attributes(GTK_LABEL(gtkconv
->tab_label
), NULL
);
5554 if (pidgin_conv_window_is_active_conversation(conv
))
5555 update_typing_icon(gtkconv
);
5557 gtk_label_set_text(GTK_LABEL(gtkconv
->menu_label
), title
);
5558 if (pidgin_conv_window_is_active_conversation(conv
)) {
5559 const char* current_title
= gtk_window_get_title(GTK_WINDOW(win
->window
));
5560 if (current_title
== NULL
|| !purple_strequal(current_title
, title
))
5561 gtk_window_set_title(GTK_WINDOW(win
->window
), title
);
5569 pidgin_conv_updated(PurpleConversation
*conv
, PurpleConversationUpdateType type
)
5571 PidginConvFields flags
= 0;
5573 g_return_if_fail(conv
!= NULL
);
5575 if (type
== PURPLE_CONVERSATION_UPDATE_ACCOUNT
)
5577 flags
= PIDGIN_CONV_ALL
;
5579 else if (type
== PURPLE_CONVERSATION_UPDATE_TYPING
||
5580 type
== PURPLE_CONVERSATION_UPDATE_UNSEEN
||
5581 type
== PURPLE_CONVERSATION_UPDATE_TITLE
)
5583 flags
= PIDGIN_CONV_COLORIZE_TITLE
;
5585 else if (type
== PURPLE_CONVERSATION_UPDATE_TOPIC
)
5587 flags
= PIDGIN_CONV_TOPIC
;
5589 else if (type
== PURPLE_CONVERSATION_ACCOUNT_ONLINE
||
5590 type
== PURPLE_CONVERSATION_ACCOUNT_OFFLINE
)
5592 flags
= PIDGIN_CONV_MENU
| PIDGIN_CONV_TAB_ICON
| PIDGIN_CONV_SET_TITLE
;
5594 else if (type
== PURPLE_CONVERSATION_UPDATE_AWAY
)
5596 flags
= PIDGIN_CONV_TAB_ICON
;
5598 else if (type
== PURPLE_CONVERSATION_UPDATE_ADD
||
5599 type
== PURPLE_CONVERSATION_UPDATE_REMOVE
||
5600 type
== PURPLE_CONVERSATION_UPDATE_CHATLEFT
)
5602 flags
= PIDGIN_CONV_SET_TITLE
| PIDGIN_CONV_MENU
;
5604 else if (type
== PURPLE_CONVERSATION_UPDATE_ICON
)
5606 flags
= PIDGIN_CONV_BUDDY_ICON
;
5608 else if (type
== PURPLE_CONVERSATION_UPDATE_FEATURES
)
5610 flags
= PIDGIN_CONV_MENU
;
5612 else if (type
== PURPLE_CONVERSATION_UPDATE_E2EE
)
5614 flags
= PIDGIN_CONV_E2EE
| PIDGIN_CONV_MENU
;
5617 pidgin_conv_update_fields(conv
, flags
);
5621 wrote_msg_update_unseen_cb(PurpleConversation
*conv
, PurpleMessage
*msg
,
5624 PidginConversation
*gtkconv
= conv
? PIDGIN_CONVERSATION(conv
) : NULL
;
5625 PurpleMessageFlags flags
;
5626 if (conv
== NULL
|| (gtkconv
&& gtkconv
->win
!= hidden_convwin
))
5628 flags
= purple_message_get_flags(msg
);
5629 if (flags
& (PURPLE_MESSAGE_SEND
| PURPLE_MESSAGE_RECV
)) {
5630 PidginUnseenState unseen
= PIDGIN_UNSEEN_NONE
;
5632 if ((flags
& PURPLE_MESSAGE_NICK
) == PURPLE_MESSAGE_NICK
)
5633 unseen
= PIDGIN_UNSEEN_NICK
;
5634 else if (((flags
& PURPLE_MESSAGE_SYSTEM
) == PURPLE_MESSAGE_SYSTEM
) ||
5635 ((flags
& PURPLE_MESSAGE_ERROR
) == PURPLE_MESSAGE_ERROR
))
5636 unseen
= PIDGIN_UNSEEN_EVENT
;
5637 else if ((flags
& PURPLE_MESSAGE_NO_LOG
) == PURPLE_MESSAGE_NO_LOG
)
5638 unseen
= PIDGIN_UNSEEN_NO_LOG
;
5640 unseen
= PIDGIN_UNSEEN_TEXT
;
5642 conv_set_unseen(conv
, unseen
);
5646 static PurpleConversationUiOps conversation_ui_ops
=
5649 pidgin_conv_destroy
, /* destroy_conversation */
5650 NULL
, /* write_chat */
5651 NULL
, /* write_im */
5652 pidgin_conv_write_conv
, /* write_conv */
5653 pidgin_conv_chat_add_users
, /* chat_add_users */
5654 pidgin_conv_chat_rename_user
, /* chat_rename_user */
5655 pidgin_conv_chat_remove_users
, /* chat_remove_users */
5656 pidgin_conv_chat_update_user
, /* chat_update_user */
5657 pidgin_conv_present_conversation
, /* present */
5658 pidgin_conv_has_focus
, /* has_focus */
5659 NULL
, /* send_confirm */
5666 PurpleConversationUiOps
*
5667 pidgin_conversations_get_conv_ui_ops(void)
5669 return &conversation_ui_ops
;
5672 /**************************************************************************
5673 * Public conversation utility functions
5674 **************************************************************************/
5676 pidgin_conv_update_buddy_icon(PurpleIMConversation
*im
)
5678 PidginConversation
*gtkconv
;
5679 PurpleConversation
*conv
;
5680 PidginConvWindow
*win
;
5684 PurpleImage
*custom_img
= NULL
;
5685 gconstpointer data
= NULL
;
5693 int scale_width
, scale_height
;
5696 PurpleAccount
*account
;
5698 PurpleBuddyIcon
*icon
;
5700 conv
= PURPLE_CONVERSATION(im
);
5702 g_return_if_fail(conv
!= NULL
);
5703 g_return_if_fail(PIDGIN_IS_PIDGIN_CONVERSATION(conv
));
5705 gtkconv
= PIDGIN_CONVERSATION(conv
);
5707 if (conv
!= gtkconv
->active_conv
)
5710 if (!gtkconv
->u
.im
->show_icon
)
5713 account
= purple_conversation_get_account(conv
);
5715 /* Remove the current icon stuff */
5716 children
= gtk_container_get_children(GTK_CONTAINER(gtkconv
->u
.im
->icon_container
));
5718 /* We know there's only one child here. It'd be nice to shortcut to the
5719 event box, but we can't change the PidginConversation until 3.0 */
5720 event
= (GtkWidget
*)children
->data
;
5721 gtk_container_remove(GTK_CONTAINER(gtkconv
->u
.im
->icon_container
), event
);
5722 g_list_free(children
);
5725 if (gtkconv
->u
.im
->anim
!= NULL
)
5726 g_object_unref(G_OBJECT(gtkconv
->u
.im
->anim
));
5728 gtkconv
->u
.im
->anim
= NULL
;
5730 if (gtkconv
->u
.im
->icon_timer
!= 0)
5731 g_source_remove(gtkconv
->u
.im
->icon_timer
);
5733 gtkconv
->u
.im
->icon_timer
= 0;
5735 if (gtkconv
->u
.im
->iter
!= NULL
)
5736 g_object_unref(G_OBJECT(gtkconv
->u
.im
->iter
));
5738 gtkconv
->u
.im
->iter
= NULL
;
5740 if (!purple_prefs_get_bool(PIDGIN_PREFS_ROOT
"/conversations/im/show_buddy_icons"))
5743 if (purple_conversation_get_connection(conv
) == NULL
)
5746 buddy
= purple_blist_find_buddy(account
, purple_conversation_get_name(conv
));
5749 PurpleContact
*contact
= purple_buddy_get_contact(buddy
);
5751 custom_img
= purple_buddy_icons_node_find_custom_icon((PurpleBlistNode
*)contact
);
5753 /* There is a custom icon for this user */
5754 data
= purple_image_get_data(custom_img
);
5755 len
= purple_image_get_data_size(custom_img
);
5761 icon
= purple_im_conversation_get_icon(im
);
5764 gtk_widget_set_size_request(gtkconv
->u
.im
->icon_container
,
5765 -1, BUDDYICON_SIZE_MIN
);
5769 data
= purple_buddy_icon_get_data(icon
, &len
);
5772 gtk_widget_set_size_request(gtkconv
->u
.im
->icon_container
,
5773 -1, BUDDYICON_SIZE_MIN
);
5778 gtkconv
->u
.im
->anim
= pidgin_pixbuf_anim_from_data(data
, len
);
5780 g_object_unref(custom_img
);
5782 if (!gtkconv
->u
.im
->anim
) {
5783 purple_debug_error("gtkconv", "Couldn't load icon for conv %s\n",
5784 purple_conversation_get_name(conv
));
5788 if (gdk_pixbuf_animation_is_static_image(gtkconv
->u
.im
->anim
)) {
5790 gtkconv
->u
.im
->iter
= NULL
;
5791 stat
= gdk_pixbuf_animation_get_static_image(gtkconv
->u
.im
->anim
);
5792 buf
= gdk_pixbuf_add_alpha(stat
, FALSE
, 0, 0, 0);
5795 gtkconv
->u
.im
->iter
=
5796 gdk_pixbuf_animation_get_iter(gtkconv
->u
.im
->anim
, NULL
); /* LEAK */
5797 stat
= gdk_pixbuf_animation_iter_get_pixbuf(gtkconv
->u
.im
->iter
);
5798 buf
= gdk_pixbuf_add_alpha(stat
, FALSE
, 0, 0, 0);
5799 if (gtkconv
->u
.im
->animate
)
5800 start_anim(NULL
, gtkconv
);
5803 scale_width
= gdk_pixbuf_get_width(buf
);
5804 scale_height
= gdk_pixbuf_get_height(buf
);
5806 gtk_widget_get_size_request(gtkconv
->u
.im
->icon_container
, NULL
, &size
);
5807 size
= MIN(size
, MIN(scale_width
, scale_height
));
5809 /* Some sanity checks */
5810 size
= CLAMP(size
, BUDDYICON_SIZE_MIN
, BUDDYICON_SIZE_MAX
);
5811 if (scale_width
== scale_height
) {
5812 scale_width
= scale_height
= size
;
5813 } else if (scale_height
> scale_width
) {
5814 scale_width
= size
* scale_width
/ scale_height
;
5815 scale_height
= size
;
5817 scale_height
= size
* scale_height
/ scale_width
;
5820 scale
= gdk_pixbuf_scale_simple(buf
, scale_width
, scale_height
,
5821 GDK_INTERP_BILINEAR
);
5822 g_object_unref(buf
);
5823 if (pidgin_gdk_pixbuf_is_opaque(scale
))
5824 pidgin_gdk_pixbuf_make_round(scale
);
5826 event
= gtk_event_box_new();
5827 gtk_container_add(GTK_CONTAINER(gtkconv
->u
.im
->icon_container
), event
);
5828 gtk_event_box_set_visible_window(GTK_EVENT_BOX(event
), FALSE
);
5829 gtk_widget_add_events(event
,
5830 GDK_POINTER_MOTION_MASK
| GDK_LEAVE_NOTIFY_MASK
);
5831 g_signal_connect(G_OBJECT(event
), "button-press-event",
5832 G_CALLBACK(icon_menu
), gtkconv
);
5834 pidgin_tooltip_setup_for_widget(event
, gtkconv
, pidgin_conv_create_tooltip
, NULL
);
5835 gtk_widget_show(event
);
5837 gtkconv
->u
.im
->icon
= gtk_image_new_from_pixbuf(scale
);
5838 gtk_container_add(GTK_CONTAINER(event
), gtkconv
->u
.im
->icon
);
5839 gtk_widget_show(gtkconv
->u
.im
->icon
);
5841 g_object_unref(G_OBJECT(scale
));
5843 /* The buddy icon code needs badly to be fixed. */
5844 if(pidgin_conv_window_is_active_conversation(conv
))
5846 buf
= gdk_pixbuf_animation_get_static_image(gtkconv
->u
.im
->anim
);
5847 if (buddy
&& !PURPLE_BUDDY_IS_ONLINE(buddy
))
5848 gdk_pixbuf_saturate_and_pixelate(buf
, buf
, 0.0, FALSE
);
5849 gtk_window_set_icon(GTK_WINDOW(win
->window
), buf
);
5854 pidgin_conv_update_buttons_by_protocol(PurpleConversation
*conv
)
5856 PidginConvWindow
*win
;
5858 if (!PIDGIN_IS_PIDGIN_CONVERSATION(conv
))
5861 win
= PIDGIN_CONVERSATION(conv
)->win
;
5863 if (win
!= NULL
&& pidgin_conv_window_is_active_conversation(conv
))
5864 gray_stuff_out(PIDGIN_CONVERSATION(conv
));
5868 pidgin_conv_xy_to_right_infopane(PidginConvWindow
*win
, int x
, int y
)
5870 gint pane_x
, pane_y
, x_rel
;
5871 PidginConversation
*gtkconv
;
5872 GtkAllocation allocation
;
5874 gdk_window_get_origin(gtk_widget_get_window(win
->notebook
),
5877 gtkconv
= pidgin_conv_window_get_active_gtkconv(win
);
5878 gtk_widget_get_allocation(gtkconv
->infopane
, &allocation
);
5879 return (x_rel
> allocation
.x
+ allocation
.width
/ 2);
5883 pidgin_conv_get_tab_at_xy(PidginConvWindow
*win
, int x
, int y
, gboolean
*to_right
)
5885 gint nb_x
, nb_y
, x_rel
, y_rel
;
5886 GtkNotebook
*notebook
;
5887 GtkWidget
*page
, *tab
;
5888 gint i
, page_num
= -1;
5895 notebook
= GTK_NOTEBOOK(win
->notebook
);
5897 gdk_window_get_origin(gtk_widget_get_window(win
->notebook
), &nb_x
, &nb_y
);
5901 horiz
= (gtk_notebook_get_tab_pos(notebook
) == GTK_POS_TOP
||
5902 gtk_notebook_get_tab_pos(notebook
) == GTK_POS_BOTTOM
);
5904 count
= gtk_notebook_get_n_pages(GTK_NOTEBOOK(notebook
));
5906 for (i
= 0; i
< count
; i
++) {
5907 GtkAllocation allocation
;
5909 page
= gtk_notebook_get_nth_page(GTK_NOTEBOOK(notebook
), i
);
5910 tab
= gtk_notebook_get_tab_label(GTK_NOTEBOOK(notebook
), page
);
5911 gtk_widget_get_allocation(tab
, &allocation
);
5913 /* Make sure the tab is not hidden beyond an arrow */
5914 if (!gtk_widget_is_drawable(tab
) && gtk_notebook_get_show_tabs(notebook
))
5918 if (x_rel
>= allocation
.x
- PIDGIN_HIG_BOX_SPACE
&&
5919 x_rel
<= allocation
.x
+ allocation
.width
+ PIDGIN_HIG_BOX_SPACE
) {
5922 if (to_right
&& x_rel
>= allocation
.x
+ allocation
.width
/2)
5928 if (y_rel
>= allocation
.y
- PIDGIN_HIG_BOX_SPACE
&&
5929 y_rel
<= allocation
.y
+ allocation
.height
+ PIDGIN_HIG_BOX_SPACE
) {
5932 if (to_right
&& y_rel
>= allocation
.y
+ allocation
.height
/2)
5940 if (page_num
== -1) {
5941 /* Add after the last tab */
5942 page_num
= count
- 1;
5949 close_on_tabs_pref_cb(const char *name
, PurplePrefType type
,
5950 gconstpointer value
, gpointer data
)
5953 PurpleConversation
*conv
;
5954 PidginConversation
*gtkconv
;
5956 for (l
= purple_conversations_get_all(); l
!= NULL
; l
= l
->next
) {
5957 conv
= (PurpleConversation
*)l
->data
;
5959 if (!PIDGIN_IS_PIDGIN_CONVERSATION(conv
))
5962 gtkconv
= PIDGIN_CONVERSATION(conv
);
5965 gtk_widget_show(gtkconv
->close
);
5967 gtk_widget_hide(gtkconv
->close
);
5972 spellcheck_pref_cb(const char *name
, PurplePrefType type
,
5973 gconstpointer value
, gpointer data
)
5976 PurpleConversation
*conv
;
5977 PidginConversation
*gtkconv
;
5979 for (cl
= purple_conversations_get_all(); cl
!= NULL
; cl
= cl
->next
) {
5981 conv
= (PurpleConversation
*)cl
->data
;
5983 if (!PIDGIN_IS_PIDGIN_CONVERSATION(conv
))
5986 gtkconv
= PIDGIN_CONVERSATION(conv
);
5988 # warning toggle spell checking when talkatu #60 is done.
5993 tab_side_pref_cb(const char *name
, PurplePrefType type
,
5994 gconstpointer value
, gpointer data
)
5996 GList
*gtkwins
, *gtkconvs
;
5997 GtkPositionType pos
;
5998 PidginConvWindow
*gtkwin
;
6000 pos
= GPOINTER_TO_INT(value
);
6002 for (gtkwins
= pidgin_conv_windows_get_list(); gtkwins
!= NULL
; gtkwins
= gtkwins
->next
) {
6003 gtkwin
= gtkwins
->data
;
6004 gtk_notebook_set_tab_pos(GTK_NOTEBOOK(gtkwin
->notebook
), pos
&~8);
6005 for (gtkconvs
= gtkwin
->gtkconvs
; gtkconvs
!= NULL
; gtkconvs
= gtkconvs
->next
) {
6006 pidgin_conv_tab_pack(gtkwin
, gtkconvs
->data
);
6012 show_formatting_toolbar_pref_cb(const char *name
, PurplePrefType type
,
6013 gconstpointer value
, gpointer data
)
6016 PurpleConversation
*conv
;
6017 PidginConversation
*gtkconv
;
6018 PidginConvWindow
*win
;
6019 gboolean visible
= (gboolean
)GPOINTER_TO_INT(value
);
6021 for (l
= purple_conversations_get_all(); l
!= NULL
; l
= l
->next
)
6023 conv
= (PurpleConversation
*)l
->data
;
6025 if (!PIDGIN_IS_PIDGIN_CONVERSATION(conv
))
6028 gtkconv
= PIDGIN_CONVERSATION(conv
);
6031 gtk_toggle_action_set_active(
6032 GTK_TOGGLE_ACTION(win
->menu
->show_formatting_toolbar
),
6036 talkatu_editor_set_toolbar_visible(TALKATU_EDITOR(gtkconv
->editor
), visible
);
6041 animate_buddy_icons_pref_cb(const char *name
, PurplePrefType type
,
6042 gconstpointer value
, gpointer data
)
6045 PurpleConversation
*conv
;
6046 PidginConversation
*gtkconv
;
6047 PidginConvWindow
*win
;
6049 if (!purple_prefs_get_bool(PIDGIN_PREFS_ROOT
"/conversations/im/show_buddy_icons"))
6052 /* Set the "animate" flag for each icon based on the new preference */
6053 for (l
= purple_conversations_get_ims(); l
!= NULL
; l
= l
->next
) {
6054 conv
= (PurpleConversation
*)l
->data
;
6055 gtkconv
= PIDGIN_CONVERSATION(conv
);
6057 gtkconv
->u
.im
->animate
= GPOINTER_TO_INT(value
);
6060 /* Now either stop or start animation for the active conversation in each window */
6061 for (l
= pidgin_conv_windows_get_list(); l
!= NULL
; l
= l
->next
) {
6063 conv
= pidgin_conv_window_get_active_conversation(win
);
6064 pidgin_conv_update_buddy_icon(PURPLE_IM_CONVERSATION(conv
));
6069 show_buddy_icons_pref_cb(const char *name
, PurplePrefType type
,
6070 gconstpointer value
, gpointer data
)
6074 for (l
= purple_conversations_get_all(); l
!= NULL
; l
= l
->next
) {
6075 PurpleConversation
*conv
= l
->data
;
6076 if (!PIDGIN_CONVERSATION(conv
))
6078 if (GPOINTER_TO_INT(value
))
6079 gtk_widget_show(PIDGIN_CONVERSATION(conv
)->infopane_hbox
);
6081 gtk_widget_hide(PIDGIN_CONVERSATION(conv
)->infopane_hbox
);
6083 if (PURPLE_IS_IM_CONVERSATION(conv
)) {
6084 pidgin_conv_update_buddy_icon(PURPLE_IM_CONVERSATION(conv
));
6088 /* Make the tabs show/hide correctly */
6089 for (l
= pidgin_conv_windows_get_list(); l
!= NULL
; l
= l
->next
) {
6090 PidginConvWindow
*win
= l
->data
;
6091 if (pidgin_conv_window_get_gtkconv_count(win
) == 1)
6092 gtk_notebook_set_show_tabs(GTK_NOTEBOOK(win
->notebook
),
6093 GPOINTER_TO_INT(value
) == 0);
6098 show_protocol_icons_pref_cb(const char *name
, PurplePrefType type
,
6099 gconstpointer value
, gpointer data
)
6102 for (l
= purple_conversations_get_all(); l
!= NULL
; l
= l
->next
) {
6103 PurpleConversation
*conv
= l
->data
;
6104 if (PIDGIN_CONVERSATION(conv
))
6105 update_tab_icon(conv
);
6110 conv_placement_usetabs_cb(const char *name
, PurplePrefType type
,
6111 gconstpointer value
, gpointer data
)
6113 purple_prefs_trigger_callback(PIDGIN_PREFS_ROOT
"/conversations/placement");
6117 account_status_changed_cb(PurpleAccount
*account
, PurpleStatus
*oldstatus
,
6118 PurpleStatus
*newstatus
)
6121 PurpleConversation
*conv
= NULL
;
6122 PidginConversation
*gtkconv
;
6124 if(!purple_strequal(purple_prefs_get_string(PIDGIN_PREFS_ROOT
"/conversations/im/hide_new"), "away"))
6127 if(purple_status_is_available(oldstatus
) || !purple_status_is_available(newstatus
))
6130 for (l
= hidden_convwin
->gtkconvs
; l
; ) {
6134 conv
= gtkconv
->active_conv
;
6135 if (PURPLE_IS_CHAT_CONVERSATION(conv
) ||
6136 account
!= purple_conversation_get_account(conv
))
6139 pidgin_conv_attach_to_conversation(conv
);
6141 /* TODO: do we need to do anything for any other conversations that are in the same gtkconv here?
6142 * I'm a little concerned that not doing so will cause the "pending" indicator in the gtkblist not to be cleared. -DAA*/
6143 purple_conversation_update(conv
, PURPLE_CONVERSATION_UPDATE_UNSEEN
);
6148 hide_new_pref_cb(const char *name
, PurplePrefType type
,
6149 gconstpointer value
, gpointer data
)
6152 PurpleConversation
*conv
= NULL
;
6153 PidginConversation
*gtkconv
;
6154 gboolean when_away
= FALSE
;
6159 if(purple_strequal(purple_prefs_get_string(PIDGIN_PREFS_ROOT
"/conversations/im/hide_new"), "always"))
6162 if(purple_strequal(purple_prefs_get_string(PIDGIN_PREFS_ROOT
"/conversations/im/hide_new"), "away"))
6165 for (l
= hidden_convwin
->gtkconvs
; l
; )
6170 conv
= gtkconv
->active_conv
;
6172 if (PURPLE_IS_CHAT_CONVERSATION(conv
) ||
6173 gtkconv
->unseen_count
== 0 ||
6174 (when_away
&& !purple_status_is_available(
6175 purple_account_get_active_status(
6176 purple_conversation_get_account(conv
)))))
6179 pidgin_conv_attach_to_conversation(conv
);
6185 conv_placement_pref_cb(const char *name
, PurplePrefType type
,
6186 gconstpointer value
, gpointer data
)
6188 PidginConvPlacementFunc func
;
6190 if (!purple_strequal(name
, PIDGIN_PREFS_ROOT
"/conversations/placement"))
6193 func
= pidgin_conv_placement_get_fnc(value
);
6198 pidgin_conv_placement_set_current_func(func
);
6201 static PidginConversation
*
6202 get_gtkconv_with_contact(PurpleContact
*contact
)
6204 PurpleBlistNode
*node
;
6206 node
= ((PurpleBlistNode
*)contact
)->child
;
6208 for (; node
; node
= node
->next
)
6210 PurpleBuddy
*buddy
= (PurpleBuddy
*)node
;
6211 PurpleIMConversation
*im
;
6212 im
= purple_conversations_find_im_with_account(purple_buddy_get_name(buddy
), purple_buddy_get_account(buddy
));
6214 return PIDGIN_CONVERSATION(PURPLE_CONVERSATION(im
));
6220 account_signed_off_cb(PurpleConnection
*gc
, gpointer event
)
6224 for (iter
= purple_conversations_get_all(); iter
; iter
= iter
->next
)
6226 PurpleConversation
*conv
= iter
->data
;
6228 /* This seems fine in theory, but we also need to cover the
6229 * case of this account matching one of the other buddies in
6230 * one of the contacts containing the buddy corresponding to
6231 * a conversation. It's easier to just update them all. */
6232 /* if (purple_conversation_get_account(conv) == account) */
6233 pidgin_conv_update_fields(conv
, PIDGIN_CONV_TAB_ICON
|
6234 PIDGIN_CONV_MENU
| PIDGIN_CONV_COLORIZE_TITLE
);
6236 if (PURPLE_CONNECTION_IS_CONNECTED(gc
) &&
6237 PURPLE_IS_CHAT_CONVERSATION(conv
) &&
6238 purple_conversation_get_account(conv
) == purple_connection_get_account(gc
) &&
6239 g_object_get_data(G_OBJECT(conv
), "want-to-rejoin")) {
6240 GHashTable
*comps
= NULL
;
6241 PurpleChat
*chat
= purple_blist_find_chat(purple_conversation_get_account(conv
), purple_conversation_get_name(conv
));
6243 PurpleProtocol
*protocol
= purple_connection_get_protocol(gc
);
6244 comps
= purple_protocol_chat_iface_info_defaults(protocol
, gc
, purple_conversation_get_name(conv
));
6246 comps
= purple_chat_get_components(chat
);
6248 purple_serv_join_chat(gc
, comps
);
6249 if (chat
== NULL
&& comps
!= NULL
)
6250 g_hash_table_destroy(comps
);
6256 account_signing_off(PurpleConnection
*gc
)
6258 GList
*list
= purple_conversations_get_chats();
6259 PurpleAccount
*account
= purple_connection_get_account(gc
);
6261 /* We are about to sign off. See which chats we are currently in, and mark
6262 * them for rejoin on reconnect. */
6264 PurpleConversation
*conv
= list
->data
;
6265 if (!purple_chat_conversation_has_left(PURPLE_CHAT_CONVERSATION(conv
)) &&
6266 purple_conversation_get_account(conv
) == account
) {
6267 g_object_set_data(G_OBJECT(conv
), "want-to-rejoin", GINT_TO_POINTER(TRUE
));
6268 purple_conversation_write_system_message(conv
,
6269 _("The account has disconnected and you are no "
6270 "longer in this chat. You will automatically "
6271 "rejoin the chat when the account reconnects."),
6272 PURPLE_MESSAGE_NO_LOG
);
6279 update_buddy_status_changed(PurpleBuddy
*buddy
, PurpleStatus
*old
, PurpleStatus
*newstatus
)
6281 PidginConversation
*gtkconv
;
6282 PurpleConversation
*conv
;
6284 gtkconv
= get_gtkconv_with_contact(purple_buddy_get_contact(buddy
));
6287 conv
= gtkconv
->active_conv
;
6288 pidgin_conv_update_fields(conv
, PIDGIN_CONV_TAB_ICON
6289 | PIDGIN_CONV_COLORIZE_TITLE
6290 | PIDGIN_CONV_BUDDY_ICON
);
6291 if ((purple_status_is_online(old
) ^ purple_status_is_online(newstatus
)) != 0)
6292 pidgin_conv_update_fields(conv
, PIDGIN_CONV_MENU
);
6297 update_buddy_privacy_changed(PurpleBuddy
*buddy
)
6299 PidginConversation
*gtkconv
;
6300 PurpleConversation
*conv
;
6302 gtkconv
= get_gtkconv_with_contact(purple_buddy_get_contact(buddy
));
6304 conv
= gtkconv
->active_conv
;
6305 pidgin_conv_update_fields(conv
, PIDGIN_CONV_TAB_ICON
| PIDGIN_CONV_MENU
);
6310 update_buddy_idle_changed(PurpleBuddy
*buddy
, gboolean old
, gboolean newidle
)
6312 PurpleIMConversation
*im
;
6314 im
= purple_conversations_find_im_with_account(purple_buddy_get_name(buddy
), purple_buddy_get_account(buddy
));
6316 pidgin_conv_update_fields(PURPLE_CONVERSATION(im
), PIDGIN_CONV_TAB_ICON
);
6320 update_buddy_icon(PurpleBuddy
*buddy
)
6322 PurpleIMConversation
*im
;
6324 im
= purple_conversations_find_im_with_account(purple_buddy_get_name(buddy
), purple_buddy_get_account(buddy
));
6326 pidgin_conv_update_fields(PURPLE_CONVERSATION(im
), PIDGIN_CONV_BUDDY_ICON
);
6330 update_buddy_sign(PurpleBuddy
*buddy
, const char *which
)
6332 PurplePresence
*presence
;
6333 PurpleStatus
*on
, *off
;
6335 presence
= purple_buddy_get_presence(buddy
);
6338 off
= purple_presence_get_status(presence
, "offline");
6339 on
= purple_presence_get_status(presence
, "available");
6341 if (*(which
+1) == 'f')
6342 update_buddy_status_changed(buddy
, on
, off
);
6344 update_buddy_status_changed(buddy
, off
, on
);
6348 update_conversation_switched(PurpleConversation
*conv
)
6350 pidgin_conv_update_fields(conv
, PIDGIN_CONV_TAB_ICON
|
6351 PIDGIN_CONV_SET_TITLE
| PIDGIN_CONV_MENU
|
6352 PIDGIN_CONV_BUDDY_ICON
| PIDGIN_CONV_E2EE
);
6356 update_buddy_typing(PurpleAccount
*account
, const char *who
)
6358 PurpleConversation
*conv
;
6359 PidginConversation
*gtkconv
;
6361 conv
= PURPLE_CONVERSATION(purple_conversations_find_im_with_account(who
, account
));
6365 gtkconv
= PIDGIN_CONVERSATION(conv
);
6366 if (gtkconv
&& gtkconv
->active_conv
== conv
)
6367 pidgin_conv_update_fields(conv
, PIDGIN_CONV_COLORIZE_TITLE
);
6371 update_chat(PurpleChatConversation
*chat
)
6373 pidgin_conv_update_fields(PURPLE_CONVERSATION(chat
), PIDGIN_CONV_TOPIC
|
6374 PIDGIN_CONV_MENU
| PIDGIN_CONV_SET_TITLE
);
6378 update_chat_topic(PurpleChatConversation
*chat
, const char *old
, const char *new)
6380 pidgin_conv_update_fields(PURPLE_CONVERSATION(chat
), PIDGIN_CONV_TOPIC
);
6383 /* Message history stuff */
6385 /* Compare two PurpleMessages, according to time in ascending order. */
6387 message_compare(PurpleMessage
*m1
, PurpleMessage
*m2
)
6389 guint64 t1
= purple_message_get_time(m1
), t2
= purple_message_get_time(m2
);
6390 return (t1
> t2
) - (t1
< t2
);
6393 /* Adds some message history to the gtkconv. This happens in a idle-callback. */
6395 add_message_history_to_gtkconv(gpointer data
)
6397 PidginConversation
*gtkconv
= data
;
6399 int timer
= gtkconv
->attach_timer
;
6400 time_t when
= GPOINTER_TO_INT(g_object_get_data(G_OBJECT(gtkconv
->editor
), "attach-start-time"));
6401 gboolean im
= (PURPLE_IS_IM_CONVERSATION(gtkconv
->active_conv
));
6403 gtkconv
->attach_timer
= 0;
6404 while (gtkconv
->attach_current
&& count
< ADD_MESSAGE_HISTORY_AT_ONCE
) {
6405 PurpleMessage
*msg
= gtkconv
->attach_current
->data
;
6406 if (!im
&& when
&& (guint64
)when
< purple_message_get_time(msg
)) {
6407 g_object_set_data(G_OBJECT(gtkconv
->editor
), "attach-start-time", NULL
);
6409 /* XXX: should it be gtkconv->active_conv? */
6410 pidgin_conv_write_conv(gtkconv
->active_conv
, msg
);
6412 gtkconv
->attach_current
= g_list_delete_link(gtkconv
->attach_current
, gtkconv
->attach_current
);
6414 gtkconv
->attach_current
= gtkconv
->attach_current
->prev
;
6418 gtkconv
->attach_timer
= timer
;
6419 if (gtkconv
->attach_current
)
6422 g_source_remove(gtkconv
->attach_timer
);
6423 gtkconv
->attach_timer
= 0;
6425 /* Print any message that was sent while the old history was being added back. */
6427 GList
*iter
= gtkconv
->convs
;
6428 for (; iter
; iter
= iter
->next
) {
6429 PurpleConversation
*conv
= iter
->data
;
6430 GList
*history
= purple_conversation_get_message_history(conv
);
6431 for (; history
; history
= history
->next
) {
6432 PurpleMessage
*msg
= history
->data
;
6433 if (purple_message_get_time(msg
) > (guint64
)when
)
6434 msgs
= g_list_prepend(msgs
, msg
);
6437 msgs
= g_list_sort(msgs
, (GCompareFunc
)message_compare
);
6438 for (; msgs
; msgs
= g_list_delete_link(msgs
, msgs
)) {
6439 PurpleMessage
*msg
= msgs
->data
;
6440 /* XXX: see above - should it be active_conv? */
6441 pidgin_conv_write_conv(gtkconv
->active_conv
, msg
);
6443 g_object_set_data(G_OBJECT(gtkconv
->editor
), "attach-start-time", NULL
);
6446 g_object_set_data(G_OBJECT(gtkconv
->editor
), "attach-start-time", NULL
);
6447 purple_signal_emit(pidgin_conversations_get_handle(),
6448 "conversation-displayed", gtkconv
);
6453 pidgin_conv_attach(PurpleConversation
*conv
)
6456 g_object_set_data(G_OBJECT(conv
), "unseen-count", NULL
);
6457 g_object_set_data(G_OBJECT(conv
), "unseen-state", NULL
);
6458 purple_conversation_set_ui_ops(conv
, pidgin_conversations_get_conv_ui_ops());
6459 if (!PIDGIN_CONVERSATION(conv
))
6460 private_gtkconv_new(conv
, FALSE
);
6461 timer
= GPOINTER_TO_INT(g_object_get_data(G_OBJECT(conv
), "close-timer"));
6463 g_source_remove(timer
);
6464 g_object_set_data(G_OBJECT(conv
), "close-timer", NULL
);
6468 gboolean
pidgin_conv_attach_to_conversation(PurpleConversation
*conv
)
6471 PidginConversation
*gtkconv
;
6473 if (PIDGIN_IS_PIDGIN_CONVERSATION(conv
)) {
6474 /* This is pretty much always the case now. */
6475 gtkconv
= PIDGIN_CONVERSATION(conv
);
6476 if (gtkconv
->win
!= hidden_convwin
)
6478 pidgin_conv_window_remove_gtkconv(hidden_convwin
, gtkconv
);
6479 pidgin_conv_placement_place(gtkconv
);
6480 purple_signal_emit(pidgin_conversations_get_handle(),
6481 "conversation-displayed", gtkconv
);
6482 list
= gtkconv
->convs
;
6484 pidgin_conv_attach(list
->data
);
6490 pidgin_conv_attach(conv
);
6491 gtkconv
= PIDGIN_CONVERSATION(conv
);
6493 list
= purple_conversation_get_message_history(conv
);
6495 if (PURPLE_IS_IM_CONVERSATION(conv
)) {
6497 list
= g_list_copy(list
);
6498 for (convs
= purple_conversations_get_ims(); convs
; convs
= convs
->next
)
6499 if (convs
->data
!= conv
&&
6500 pidgin_conv_find_gtkconv(convs
->data
) == gtkconv
) {
6501 pidgin_conv_attach(convs
->data
);
6502 list
= g_list_concat(list
, g_list_copy(purple_conversation_get_message_history(convs
->data
)));
6504 list
= g_list_sort(list
, (GCompareFunc
)message_compare
);
6505 gtkconv
->attach_current
= list
;
6506 list
= g_list_last(list
);
6507 } else if (PURPLE_IS_CHAT_CONVERSATION(conv
)) {
6508 gtkconv
->attach_current
= g_list_last(list
);
6511 g_object_set_data(G_OBJECT(gtkconv
->editor
), "attach-start-time",
6512 GINT_TO_POINTER(purple_message_get_time(list
->data
)));
6513 gtkconv
->attach_timer
= g_idle_add(add_message_history_to_gtkconv
, gtkconv
);
6515 purple_signal_emit(pidgin_conversations_get_handle(),
6516 "conversation-displayed", gtkconv
);
6519 if (PURPLE_IS_CHAT_CONVERSATION(conv
)) {
6521 PurpleChatConversation
*chat
= PURPLE_CHAT_CONVERSATION(conv
);
6522 pidgin_conv_update_fields(conv
, PIDGIN_CONV_TOPIC
);
6523 users
= purple_chat_conversation_get_users(chat
);
6524 pidgin_conv_chat_add_users(chat
, users
, TRUE
);
6532 pidgin_conversations_get_handle(void)
6540 pidgin_conversations_pre_uninit(void);
6543 pidgin_conversations_init(void)
6545 void *handle
= pidgin_conversations_get_handle();
6546 void *blist_handle
= purple_blist_get_handle();
6548 e2ee_stock
= g_hash_table_new_full(g_str_hash
, g_str_equal
,
6549 g_free
, g_object_unref
);
6552 purple_prefs_add_none(PIDGIN_PREFS_ROOT
"/conversations");
6553 purple_prefs_add_bool(PIDGIN_PREFS_ROOT
"/conversations/use_smooth_scrolling", TRUE
);
6554 purple_prefs_add_bool(PIDGIN_PREFS_ROOT
"/conversations/close_on_tabs", TRUE
);
6555 purple_prefs_add_bool(PIDGIN_PREFS_ROOT
"/conversations/send_bold", FALSE
);
6556 purple_prefs_add_bool(PIDGIN_PREFS_ROOT
"/conversations/send_italic", FALSE
);
6557 purple_prefs_add_bool(PIDGIN_PREFS_ROOT
"/conversations/send_underline", FALSE
);
6558 purple_prefs_add_bool(PIDGIN_PREFS_ROOT
"/conversations/send_strike", FALSE
);
6559 purple_prefs_add_bool(PIDGIN_PREFS_ROOT
"/conversations/spellcheck", TRUE
);
6560 purple_prefs_add_bool(PIDGIN_PREFS_ROOT
"/conversations/show_incoming_formatting", TRUE
);
6561 /* TODO: it's about *remote* smileys, not local ones */
6562 purple_prefs_add_bool(PIDGIN_PREFS_ROOT
"/conversations/resize_custom_smileys", TRUE
);
6563 purple_prefs_add_int(PIDGIN_PREFS_ROOT
"/conversations/custom_smileys_size", 96);
6564 purple_prefs_add_int(PIDGIN_PREFS_ROOT
"/conversations/minimum_entry_lines", 2);
6566 purple_prefs_add_bool(PIDGIN_PREFS_ROOT
"/conversations/show_formatting_toolbar", TRUE
);
6568 purple_prefs_add_string(PIDGIN_PREFS_ROOT
"/conversations/placement", "last");
6569 purple_prefs_add_int(PIDGIN_PREFS_ROOT
"/conversations/placement_number", 1);
6570 purple_prefs_add_string(PIDGIN_PREFS_ROOT
"/conversations/bgcolor", "");
6571 purple_prefs_add_string(PIDGIN_PREFS_ROOT
"/conversations/fgcolor", "");
6572 purple_prefs_add_string(PIDGIN_PREFS_ROOT
"/conversations/font_face", "");
6573 purple_prefs_add_int(PIDGIN_PREFS_ROOT
"/conversations/font_size", 3);
6574 purple_prefs_add_bool(PIDGIN_PREFS_ROOT
"/conversations/tabs", TRUE
);
6575 purple_prefs_add_int(PIDGIN_PREFS_ROOT
"/conversations/tab_side", GTK_POS_TOP
);
6576 purple_prefs_add_int(PIDGIN_PREFS_ROOT
"/conversations/scrollback_lines", 4000);
6579 purple_prefs_add_bool(PIDGIN_PREFS_ROOT
"/conversations/use_theme_font", TRUE
);
6580 purple_prefs_add_string(PIDGIN_PREFS_ROOT
"/conversations/custom_font", "");
6583 /* Conversations -> Chat */
6584 purple_prefs_add_none(PIDGIN_PREFS_ROOT
"/conversations/chat");
6585 purple_prefs_add_int(PIDGIN_PREFS_ROOT
"/conversations/chat/entry_height", 54);
6586 purple_prefs_add_int(PIDGIN_PREFS_ROOT
"/conversations/chat/userlist_width", 80);
6587 purple_prefs_add_int(PIDGIN_PREFS_ROOT
"/conversations/chat/x", 0);
6588 purple_prefs_add_int(PIDGIN_PREFS_ROOT
"/conversations/chat/y", 0);
6589 purple_prefs_add_int(PIDGIN_PREFS_ROOT
"/conversations/chat/width", 340);
6590 purple_prefs_add_int(PIDGIN_PREFS_ROOT
"/conversations/chat/height", 390);
6592 /* Conversations -> IM */
6593 purple_prefs_add_none(PIDGIN_PREFS_ROOT
"/conversations/im");
6594 purple_prefs_add_int(PIDGIN_PREFS_ROOT
"/conversations/im/x", 0);
6595 purple_prefs_add_int(PIDGIN_PREFS_ROOT
"/conversations/im/y", 0);
6596 purple_prefs_add_int(PIDGIN_PREFS_ROOT
"/conversations/im/width", 340);
6597 purple_prefs_add_int(PIDGIN_PREFS_ROOT
"/conversations/im/height", 390);
6599 purple_prefs_add_bool(PIDGIN_PREFS_ROOT
"/conversations/im/animate_buddy_icons", TRUE
);
6601 purple_prefs_add_int(PIDGIN_PREFS_ROOT
"/conversations/im/entry_height", 54);
6602 purple_prefs_add_bool(PIDGIN_PREFS_ROOT
"/conversations/im/show_buddy_icons", TRUE
);
6604 purple_prefs_add_string(PIDGIN_PREFS_ROOT
"/conversations/im/hide_new", "never");
6605 purple_prefs_add_bool(PIDGIN_PREFS_ROOT
"/conversations/im/close_immediately", TRUE
);
6608 purple_prefs_add_bool(PIDGIN_PREFS_ROOT
"/win32/minimize_new_convs", FALSE
);
6611 /* Connect callbacks. */
6612 purple_prefs_connect_callback(handle
, PIDGIN_PREFS_ROOT
"/conversations/close_on_tabs",
6613 close_on_tabs_pref_cb
, NULL
);
6614 purple_prefs_connect_callback(handle
, PIDGIN_PREFS_ROOT
"/conversations/show_formatting_toolbar",
6615 show_formatting_toolbar_pref_cb
, NULL
);
6616 purple_prefs_connect_callback(handle
, PIDGIN_PREFS_ROOT
"/conversations/spellcheck",
6617 spellcheck_pref_cb
, NULL
);
6618 purple_prefs_connect_callback(handle
, PIDGIN_PREFS_ROOT
"/conversations/tab_side",
6619 tab_side_pref_cb
, NULL
);
6621 purple_prefs_connect_callback(handle
, PIDGIN_PREFS_ROOT
"/conversations/tabs",
6622 conv_placement_usetabs_cb
, NULL
);
6624 purple_prefs_connect_callback(handle
, PIDGIN_PREFS_ROOT
"/conversations/placement",
6625 conv_placement_pref_cb
, NULL
);
6626 purple_prefs_trigger_callback(PIDGIN_PREFS_ROOT
"/conversations/placement");
6628 purple_prefs_connect_callback(handle
, PIDGIN_PREFS_ROOT
"/conversations/minimum_entry_lines",
6629 minimum_entry_lines_pref_cb
, NULL
);
6632 purple_prefs_connect_callback(handle
, PIDGIN_PREFS_ROOT
"/conversations/im/animate_buddy_icons",
6633 animate_buddy_icons_pref_cb
, NULL
);
6634 purple_prefs_connect_callback(handle
, PIDGIN_PREFS_ROOT
"/conversations/im/show_buddy_icons",
6635 show_buddy_icons_pref_cb
, NULL
);
6636 purple_prefs_connect_callback(handle
, PIDGIN_PREFS_ROOT
"/blist/show_protocol_icons",
6637 show_protocol_icons_pref_cb
, NULL
);
6638 purple_prefs_connect_callback(handle
, PIDGIN_PREFS_ROOT
"/conversations/im/hide_new",
6639 hide_new_pref_cb
, NULL
);
6641 /**********************************************************************
6643 **********************************************************************/
6644 purple_signal_register(handle
, "conversation-dragging",
6645 purple_marshal_VOID__POINTER_POINTER
, G_TYPE_NONE
, 2,
6646 G_TYPE_POINTER
, /* pointer to a (PidginConvWindow *) */
6647 G_TYPE_POINTER
); /* pointer to a (PidginConvWindow *) */
6649 purple_signal_register(handle
, "conversation-timestamp",
6650 #if SIZEOF_TIME_T == 4
6651 purple_marshal_POINTER__POINTER_INT_BOOLEAN
,
6652 #elif SIZEOF_TIME_T == 8
6653 purple_marshal_POINTER__POINTER_INT64_BOOLEAN
,
6655 #error Unkown size of time_t
6657 G_TYPE_STRING
, 3, PURPLE_TYPE_CONVERSATION
,
6658 #if SIZEOF_TIME_T == 4
6660 #elif SIZEOF_TIME_T == 8
6663 # error Unknown size of time_t
6667 purple_signal_register(handle
, "displaying-im-msg",
6668 purple_marshal_BOOLEAN__POINTER_POINTER
,
6669 G_TYPE_BOOLEAN
, 2, PURPLE_TYPE_CONVERSATION
, PURPLE_TYPE_MESSAGE
);
6671 purple_signal_register(handle
, "displayed-im-msg",
6672 purple_marshal_VOID__POINTER_POINTER
, G_TYPE_NONE
, 2,
6673 PURPLE_TYPE_CONVERSATION
, PURPLE_TYPE_MESSAGE
);
6675 purple_signal_register(handle
, "displaying-chat-msg",
6676 purple_marshal_BOOLEAN__POINTER_POINTER
,
6677 G_TYPE_BOOLEAN
, 2, PURPLE_TYPE_CONVERSATION
, PURPLE_TYPE_MESSAGE
);
6679 purple_signal_register(handle
, "displayed-chat-msg",
6680 purple_marshal_VOID__POINTER_POINTER
, G_TYPE_NONE
, 2,
6681 PURPLE_TYPE_CONVERSATION
, PURPLE_TYPE_MESSAGE
);
6683 purple_signal_register(handle
, "conversation-switched",
6684 purple_marshal_VOID__POINTER
, G_TYPE_NONE
, 1,
6685 PURPLE_TYPE_CONVERSATION
);
6687 purple_signal_register(handle
, "conversation-hiding",
6688 purple_marshal_VOID__POINTER
, G_TYPE_NONE
, 1,
6689 G_TYPE_POINTER
); /* (PidginConversation *) */
6691 purple_signal_register(handle
, "conversation-displayed",
6692 purple_marshal_VOID__POINTER
, G_TYPE_NONE
, 1,
6693 G_TYPE_POINTER
); /* (PidginConversation *) */
6695 purple_signal_register(handle
, "chat-nick-autocomplete",
6696 purple_marshal_BOOLEAN__POINTER_BOOLEAN
,
6697 G_TYPE_BOOLEAN
, 1, PURPLE_TYPE_CONVERSATION
);
6699 purple_signal_register(handle
, "chat-nick-clicked",
6700 purple_marshal_BOOLEAN__POINTER_POINTER_UINT
,
6701 G_TYPE_BOOLEAN
, 3, PURPLE_TYPE_CONVERSATION
,
6702 G_TYPE_STRING
, G_TYPE_UINT
);
6704 purple_signal_register(handle
, "conversation-window-created",
6705 purple_marshal_VOID__POINTER
, G_TYPE_NONE
, 1,
6706 G_TYPE_POINTER
); /* (PidginConvWindow *) */
6709 /**********************************************************************
6711 **********************************************************************/
6712 purple_cmd_register("say", "S", PURPLE_CMD_P_DEFAULT
,
6713 PURPLE_CMD_FLAG_CHAT
| PURPLE_CMD_FLAG_IM
, NULL
,
6714 say_command_cb
, _("say <message>: Send a message normally as if you weren't using a command."), NULL
);
6715 purple_cmd_register("me", "S", PURPLE_CMD_P_DEFAULT
,
6716 PURPLE_CMD_FLAG_CHAT
| PURPLE_CMD_FLAG_IM
, NULL
,
6717 me_command_cb
, _("me <action>: Send an IRC style action to a buddy or chat."), NULL
);
6718 purple_cmd_register("debug", "w", PURPLE_CMD_P_DEFAULT
,
6719 PURPLE_CMD_FLAG_CHAT
| PURPLE_CMD_FLAG_IM
, NULL
,
6720 debug_command_cb
, _("debug <option>: Send various debug information to the current conversation."), NULL
);
6721 purple_cmd_register("clear", "", PURPLE_CMD_P_DEFAULT
,
6722 PURPLE_CMD_FLAG_CHAT
| PURPLE_CMD_FLAG_IM
, NULL
,
6723 clear_command_cb
, _("clear: Clears the conversation scrollback."), NULL
);
6724 purple_cmd_register("clearall", "", PURPLE_CMD_P_DEFAULT
,
6725 PURPLE_CMD_FLAG_CHAT
| PURPLE_CMD_FLAG_IM
, NULL
,
6726 clearall_command_cb
, _("clear: Clears all conversation scrollbacks."), NULL
);
6727 purple_cmd_register("help", "w", PURPLE_CMD_P_DEFAULT
,
6728 PURPLE_CMD_FLAG_CHAT
| PURPLE_CMD_FLAG_IM
| PURPLE_CMD_FLAG_ALLOW_WRONG_ARGS
, NULL
,
6729 help_command_cb
, _("help <command>: Help on a specific command."), NULL
);
6731 /**********************************************************************
6733 **********************************************************************/
6735 purple_signal_connect(purple_connections_get_handle(), "signed-on", handle
,
6736 G_CALLBACK(account_signed_off_cb
),
6737 GINT_TO_POINTER(PURPLE_CONVERSATION_ACCOUNT_ONLINE
));
6738 purple_signal_connect(purple_connections_get_handle(), "signed-off", handle
,
6739 G_CALLBACK(account_signed_off_cb
),
6740 GINT_TO_POINTER(PURPLE_CONVERSATION_ACCOUNT_OFFLINE
));
6741 purple_signal_connect(purple_connections_get_handle(), "signing-off", handle
,
6742 G_CALLBACK(account_signing_off
), NULL
);
6744 purple_signal_connect(purple_conversations_get_handle(), "writing-im-msg",
6745 handle
, G_CALLBACK(writing_msg
), NULL
);
6746 purple_signal_connect(purple_conversations_get_handle(), "writing-chat-msg",
6747 handle
, G_CALLBACK(writing_msg
), NULL
);
6748 purple_signal_connect(purple_conversations_get_handle(), "received-im-msg",
6749 handle
, G_CALLBACK(received_im_msg_cb
), NULL
);
6750 purple_signal_connect(purple_conversations_get_handle(), "cleared-message-history",
6751 handle
, G_CALLBACK(clear_conversation_scrollback_cb
), NULL
);
6753 purple_signal_connect(purple_conversations_get_handle(), "deleting-chat-user",
6754 handle
, G_CALLBACK(deleting_chat_user_cb
), NULL
);
6756 purple_conversations_set_ui_ops(&conversation_ui_ops
);
6758 hidden_convwin
= pidgin_conv_window_new();
6759 window_list
= g_list_remove(window_list
, hidden_convwin
);
6761 purple_signal_connect(purple_accounts_get_handle(), "account-status-changed",
6762 handle
, PURPLE_CALLBACK(account_status_changed_cb
), NULL
);
6764 purple_signal_connect_priority(purple_get_core(), "quitting", handle
,
6765 PURPLE_CALLBACK(pidgin_conversations_pre_uninit
), NULL
, PURPLE_SIGNAL_PRIORITY_HIGHEST
);
6767 /* Callbacks to update a conversation */
6768 purple_signal_connect(blist_handle
, "blist-node-added", handle
,
6769 G_CALLBACK(buddy_update_cb
), NULL
);
6770 purple_signal_connect(blist_handle
, "blist-node-removed", handle
,
6771 G_CALLBACK(buddy_update_cb
), NULL
);
6772 purple_signal_connect(blist_handle
, "buddy-signed-on",
6773 handle
, PURPLE_CALLBACK(update_buddy_sign
), "on");
6774 purple_signal_connect(blist_handle
, "buddy-signed-off",
6775 handle
, PURPLE_CALLBACK(update_buddy_sign
), "off");
6776 purple_signal_connect(blist_handle
, "buddy-status-changed",
6777 handle
, PURPLE_CALLBACK(update_buddy_status_changed
), NULL
);
6778 purple_signal_connect(blist_handle
, "buddy-privacy-changed",
6779 handle
, PURPLE_CALLBACK(update_buddy_privacy_changed
), NULL
);
6780 purple_signal_connect(blist_handle
, "buddy-idle-changed",
6781 handle
, PURPLE_CALLBACK(update_buddy_idle_changed
), NULL
);
6782 purple_signal_connect(blist_handle
, "buddy-icon-changed",
6783 handle
, PURPLE_CALLBACK(update_buddy_icon
), NULL
);
6784 purple_signal_connect(purple_conversations_get_handle(), "buddy-typing",
6785 handle
, PURPLE_CALLBACK(update_buddy_typing
), NULL
);
6786 purple_signal_connect(purple_conversations_get_handle(), "buddy-typing-stopped",
6787 handle
, PURPLE_CALLBACK(update_buddy_typing
), NULL
);
6788 purple_signal_connect(pidgin_conversations_get_handle(), "conversation-switched",
6789 handle
, PURPLE_CALLBACK(update_conversation_switched
), NULL
);
6790 purple_signal_connect(purple_conversations_get_handle(), "chat-left", handle
,
6791 PURPLE_CALLBACK(update_chat
), NULL
);
6792 purple_signal_connect(purple_conversations_get_handle(), "chat-joined", handle
,
6793 PURPLE_CALLBACK(update_chat
), NULL
);
6794 purple_signal_connect(purple_conversations_get_handle(), "chat-topic-changed", handle
,
6795 PURPLE_CALLBACK(update_chat_topic
), NULL
);
6796 purple_signal_connect_priority(purple_conversations_get_handle(), "conversation-updated", handle
,
6797 PURPLE_CALLBACK(pidgin_conv_updated
), NULL
,
6798 PURPLE_SIGNAL_PRIORITY_LOWEST
);
6799 purple_signal_connect(purple_conversations_get_handle(), "wrote-im-msg", handle
,
6800 PURPLE_CALLBACK(wrote_msg_update_unseen_cb
), NULL
);
6801 purple_signal_connect(purple_conversations_get_handle(), "wrote-chat-msg", handle
,
6802 PURPLE_CALLBACK(wrote_msg_update_unseen_cb
), NULL
);
6806 pidgin_conversations_pre_uninit(void)
6808 g_hash_table_destroy(e2ee_stock
);
6812 /* Invalidate the first tab color set */
6813 static gboolean tab_color_fuse
= TRUE
;
6816 pidgin_conversations_set_tab_colors(void)
6818 /* Set default tab colors */
6819 GString
*str
= g_string_new(NULL
);
6820 GtkSettings
*settings
= gtk_settings_get_default();
6821 GtkStyle
*parent
= gtk_rc_get_style_by_paths(settings
, "tab-container.tab-label*", NULL
, G_TYPE_NONE
), *now
;
6823 const char *stylename
;
6824 const char *labelname
;
6827 {"pidgin_tab_label_typing_default", "tab-label-typing", "#4e9a06"},
6828 {"pidgin_tab_label_typed_default", "tab-label-typed", "#c4a000"},
6829 {"pidgin_tab_label_attention_default", "tab-label-attention", "#006aff"},
6830 {"pidgin_tab_label_unreadchat_default", "tab-label-unreadchat", "#cc0000"},
6831 {"pidgin_tab_label_event_default", "tab-label-event", "#888a85"},
6836 if(tab_color_fuse
) {
6837 tab_color_fuse
= FALSE
;
6841 for (iter
= 0; styles
[iter
].stylename
; iter
++) {
6842 now
= gtk_rc_get_style_by_paths(settings
, styles
[iter
].labelname
, NULL
, G_TYPE_NONE
);
6843 if (parent
== now
||
6844 (parent
&& now
&& parent
->rc_style
== now
->rc_style
)) {
6848 gdk_rgba_parse(&color
, styles
[iter
].color
);
6849 pidgin_style_adjust_contrast(gtk_widget_get_default_style(), &color
);
6851 color_str
= gdk_rgba_to_string(&color
);
6852 g_string_append_printf(str
, "style \"%s\" {\n"
6853 "fg[ACTIVE] = \"%s\"\n"
6855 "widget \"*%s\" style \"%s\"\n",
6856 styles
[iter
].stylename
,
6858 styles
[iter
].labelname
, styles
[iter
].stylename
);
6862 gtk_rc_parse_string(str
->str
);
6863 g_string_free(str
, TRUE
);
6864 gtk_rc_reset_styles(settings
);
6868 pidgin_conversations_uninit(void)
6870 purple_prefs_disconnect_by_handle(pidgin_conversations_get_handle());
6871 purple_signals_disconnect_by_handle(pidgin_conversations_get_handle());
6872 purple_signals_unregister_by_instance(pidgin_conversations_get_handle());
6875 /**************************************************************************
6876 * PidginConversation GBoxed code
6877 **************************************************************************/
6878 static PidginConversation
*
6879 pidgin_conversation_ref(PidginConversation
*gtkconv
)
6881 g_return_val_if_fail(gtkconv
!= NULL
, NULL
);
6883 gtkconv
->box_count
++;
6889 pidgin_conversation_unref(PidginConversation
*gtkconv
)
6891 g_return_if_fail(gtkconv
!= NULL
);
6892 g_return_if_fail(gtkconv
->box_count
>= 0);
6894 if (!gtkconv
->box_count
--)
6895 pidgin_conv_destroy(gtkconv
->active_conv
);
6899 pidgin_conversation_get_type(void)
6901 static GType type
= 0;
6904 type
= g_boxed_type_register_static("PidginConversation",
6905 (GBoxedCopyFunc
)pidgin_conversation_ref
,
6906 (GBoxedFreeFunc
)pidgin_conversation_unref
);
6927 /* down here is where gtkconvwin.c ought to start. except they share like every freaking function,
6928 * and touch each others' private members all day long */
6932 * Pidgin is the legal property of its developers, whose names are too numerous
6933 * to list here. Please refer to the COPYRIGHT file distributed with this
6934 * source distribution.
6936 * This program is free software; you can redistribute it and/or modify
6937 * it under the terms of the GNU General Public License as published by
6938 * the Free Software Foundation; either version 2 of the License, or
6939 * (at your option) any later version.
6941 * This program is distributed in the hope that it will be useful,
6942 * but WITHOUT ANY WARRANTY; without even the implied warranty of
6943 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
6944 * GNU General Public License for more details.
6946 * You should have received a copy of the GNU General Public License
6947 * along with this program; if not, write to the Free Software
6948 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA
6951 #include "internal.h"
6955 #include <gdk/gdkkeysyms.h>
6957 #include "account.h"
6962 #include "protocol.h"
6963 #include "request.h"
6966 #include "gtkdnd-hints.h"
6967 #include "gtkblist.h"
6968 #include "gtkconv.h"
6969 #include "gtkdialogs.h"
6970 #include "gtkmenutray.h"
6971 #include "gtkpounce.h"
6972 #include "gtkprefs.h"
6973 #include "gtkprivacy.h"
6974 #include "gtkutils.h"
6975 #include "pidginstock.h"
6978 do_close(GtkWidget
*w
, int resp
, PidginConvWindow
*win
)
6980 gtk_widget_destroy(warn_close_dialog
);
6981 warn_close_dialog
= NULL
;
6983 if (resp
== GTK_RESPONSE_OK
)
6984 pidgin_conv_window_destroy(win
);
6988 build_warn_close_dialog(PidginConvWindow
*gtkwin
)
6990 GtkWidget
*label
, *vbox
, *hbox
, *img
;
6992 g_return_if_fail(warn_close_dialog
== NULL
);
6994 warn_close_dialog
= gtk_dialog_new_with_buttons(_("Confirm close"),
6995 GTK_WINDOW(gtkwin
->window
), GTK_DIALOG_MODAL
,
6996 GTK_STOCK_CANCEL
, GTK_RESPONSE_CANCEL
,
6997 GTK_STOCK_CLOSE
, GTK_RESPONSE_OK
, NULL
);
6999 gtk_dialog_set_default_response(GTK_DIALOG(warn_close_dialog
),
7002 gtk_container_set_border_width(GTK_CONTAINER(warn_close_dialog
),
7004 gtk_window_set_resizable(GTK_WINDOW(warn_close_dialog
), FALSE
);
7006 /* Setup the outside spacing. */
7007 vbox
= gtk_dialog_get_content_area(GTK_DIALOG(warn_close_dialog
));
7009 gtk_box_set_spacing(GTK_BOX(vbox
), 12);
7010 gtk_container_set_border_width(GTK_CONTAINER(vbox
), 6);
7012 img
= gtk_image_new_from_icon_name("dialog-warning",
7013 GTK_ICON_SIZE_DIALOG
);
7015 /* Setup the inner hbox and put the dialog's icon in it. */
7016 hbox
= gtk_box_new(GTK_ORIENTATION_HORIZONTAL
, 12);
7017 gtk_container_add(GTK_CONTAINER(vbox
), hbox
);
7018 gtk_box_pack_start(GTK_BOX(hbox
), img
, FALSE
, FALSE
, 0);
7019 gtk_widget_set_halign(img
, GTK_ALIGN_START
);
7020 gtk_widget_set_valign(img
, GTK_ALIGN_START
);
7022 /* Setup the right vbox. */
7023 vbox
= gtk_box_new(GTK_ORIENTATION_VERTICAL
, 12);
7024 gtk_container_add(GTK_CONTAINER(hbox
), vbox
);
7026 label
= gtk_label_new(_("You have unread messages. Are you sure you want to close the window?"));
7027 gtk_widget_set_size_request(label
, 350, -1);
7028 gtk_label_set_line_wrap(GTK_LABEL(label
), TRUE
);
7029 gtk_label_set_xalign(GTK_LABEL(label
), 0);
7030 gtk_label_set_yalign(GTK_LABEL(label
), 0);
7031 gtk_box_pack_start(GTK_BOX(vbox
), label
, FALSE
, FALSE
, 0);
7033 /* Connect the signals. */
7034 g_signal_connect(G_OBJECT(warn_close_dialog
), "response",
7035 G_CALLBACK(do_close
), gtkwin
);
7039 /**************************************************************************
7041 **************************************************************************/
7044 close_win_cb(GtkWidget
*w
, GdkEventAny
*e
, gpointer d
)
7046 PidginConvWindow
*win
= d
;
7049 /* If there are unread messages then show a warning dialog */
7050 for (l
= pidgin_conv_window_get_gtkconvs(win
);
7051 l
!= NULL
; l
= l
->next
)
7053 PidginConversation
*gtkconv
= l
->data
;
7054 if (PURPLE_IS_IM_CONVERSATION(gtkconv
->active_conv
) &&
7055 gtkconv
->unseen_state
>= PIDGIN_UNSEEN_TEXT
)
7057 build_warn_close_dialog(win
);
7058 gtk_widget_show_all(warn_close_dialog
);
7064 pidgin_conv_window_destroy(win
);
7070 conv_set_unseen(PurpleConversation
*conv
, PidginUnseenState state
)
7072 int unseen_count
= 0;
7073 PidginUnseenState unseen_state
= PIDGIN_UNSEEN_NONE
;
7075 if(g_object_get_data(G_OBJECT(conv
), "unseen-count"))
7076 unseen_count
= GPOINTER_TO_INT(g_object_get_data(G_OBJECT(conv
), "unseen-count"));
7078 if(g_object_get_data(G_OBJECT(conv
), "unseen-state"))
7079 unseen_state
= GPOINTER_TO_INT(g_object_get_data(G_OBJECT(conv
), "unseen-state"));
7081 if (state
== PIDGIN_UNSEEN_NONE
)
7084 unseen_state
= PIDGIN_UNSEEN_NONE
;
7088 if (state
>= PIDGIN_UNSEEN_TEXT
)
7091 if (state
> unseen_state
)
7092 unseen_state
= state
;
7095 g_object_set_data(G_OBJECT(conv
), "unseen-count", GINT_TO_POINTER(unseen_count
));
7096 g_object_set_data(G_OBJECT(conv
), "unseen-state", GINT_TO_POINTER(unseen_state
));
7098 purple_conversation_update(conv
, PURPLE_CONVERSATION_UPDATE_UNSEEN
);
7102 gtkconv_set_unseen(PidginConversation
*gtkconv
, PidginUnseenState state
)
7104 if (state
== PIDGIN_UNSEEN_NONE
)
7106 gtkconv
->unseen_count
= 0;
7107 gtkconv
->unseen_state
= PIDGIN_UNSEEN_NONE
;
7111 if (state
>= PIDGIN_UNSEEN_TEXT
)
7112 gtkconv
->unseen_count
++;
7114 if (state
> gtkconv
->unseen_state
)
7115 gtkconv
->unseen_state
= state
;
7118 g_object_set_data(G_OBJECT(gtkconv
->active_conv
), "unseen-count", GINT_TO_POINTER(gtkconv
->unseen_count
));
7119 g_object_set_data(G_OBJECT(gtkconv
->active_conv
), "unseen-state", GINT_TO_POINTER(gtkconv
->unseen_state
));
7121 purple_conversation_update(gtkconv
->active_conv
, PURPLE_CONVERSATION_UPDATE_UNSEEN
);
7125 * When a conversation window is focused, we know the user
7126 * has looked at it so we know there are no longer unseen
7130 focus_win_cb(GtkWidget
*w
, GdkEventFocus
*e
, gpointer d
)
7132 PidginConvWindow
*win
= d
;
7133 PidginConversation
*gtkconv
= pidgin_conv_window_get_active_gtkconv(win
);
7136 gtkconv_set_unseen(gtkconv
, PIDGIN_UNSEEN_NONE
);
7142 notebook_init_grab(PidginConvWindow
*gtkwin
, GtkWidget
*widget
, GdkEvent
*event
)
7144 static GdkCursor
*cursor
= NULL
;
7147 gtkwin
->in_drag
= TRUE
;
7149 if (gtkwin
->drag_leave_signal
) {
7150 g_signal_handler_disconnect(G_OBJECT(widget
),
7151 gtkwin
->drag_leave_signal
);
7152 gtkwin
->drag_leave_signal
= 0;
7155 if (cursor
== NULL
) {
7156 GdkDisplay
*display
= gtk_widget_get_display(gtkwin
->notebook
);
7157 cursor
= gdk_cursor_new_for_display(display
, GDK_FLEUR
);
7160 /* Grab the pointer */
7161 gtk_grab_add(gtkwin
->notebook
);
7162 device
= gdk_event_get_device(event
);
7163 if (!gdk_display_device_is_grabbed(gdk_device_get_display(device
), device
))
7164 gdk_device_grab(device
, gtk_widget_get_window(gtkwin
->notebook
),
7165 GDK_OWNERSHIP_WINDOW
, FALSE
,
7166 GDK_BUTTON1_MOTION_MASK
| GDK_BUTTON_RELEASE_MASK
,
7167 cursor
, gdk_event_get_time(event
));
7171 notebook_motion_cb(GtkWidget
*widget
, GdkEventButton
*e
, PidginConvWindow
*win
)
7175 * Make sure the user moved the mouse far enough for the
7176 * drag to be initiated.
7178 if (win
->in_predrag
) {
7179 if (e
->x_root
< win
->drag_min_x
||
7180 e
->x_root
>= win
->drag_max_x
||
7181 e
->y_root
< win
->drag_min_y
||
7182 e
->y_root
>= win
->drag_max_y
) {
7184 win
->in_predrag
= FALSE
;
7185 notebook_init_grab(win
, widget
, (GdkEvent
*)e
);
7188 else { /* Otherwise, draw the arrows. */
7189 PidginConvWindow
*dest_win
;
7190 GtkNotebook
*dest_notebook
;
7193 gboolean horiz_tabs
= FALSE
;
7194 gboolean to_right
= FALSE
;
7196 /* Get the window that the cursor is over. */
7197 dest_win
= pidgin_conv_window_get_at_event((GdkEvent
*)e
);
7199 if (dest_win
== NULL
) {
7200 pidgin_dnd_hints_hide_all();
7205 dest_notebook
= GTK_NOTEBOOK(dest_win
->notebook
);
7207 if (gtk_notebook_get_show_tabs(dest_notebook
)) {
7208 page_num
= pidgin_conv_get_tab_at_xy(dest_win
,
7209 e
->x_root
, e
->y_root
, &to_right
);
7210 to_right
= to_right
&& (win
!= dest_win
);
7211 tab
= pidgin_conv_window_get_gtkconv_at_index(dest_win
, page_num
)->tabby
;
7214 to_right
= pidgin_conv_xy_to_right_infopane(dest_win
, e
->x_root
, e
->y_root
);
7215 tab
= pidgin_conv_window_get_gtkconv_at_index(dest_win
, page_num
)->infopane_hbox
;
7218 if (gtk_notebook_get_tab_pos(dest_notebook
) == GTK_POS_TOP
||
7219 gtk_notebook_get_tab_pos(dest_notebook
) == GTK_POS_BOTTOM
) {
7223 if (gtk_notebook_get_show_tabs(dest_notebook
) == FALSE
&& win
== dest_win
)
7225 /* dragging a tab from a single-tabbed window over its own window */
7226 pidgin_dnd_hints_hide_all();
7228 } else if (horiz_tabs
) {
7229 if (((gpointer
)win
== (gpointer
)dest_win
&& win
->drag_tab
< page_num
) || to_right
) {
7230 pidgin_dnd_hints_show_relative(HINT_ARROW_DOWN
, tab
, HINT_POSITION_RIGHT
, HINT_POSITION_TOP
);
7231 pidgin_dnd_hints_show_relative(HINT_ARROW_UP
, tab
, HINT_POSITION_RIGHT
, HINT_POSITION_BOTTOM
);
7233 pidgin_dnd_hints_show_relative(HINT_ARROW_DOWN
, tab
, HINT_POSITION_LEFT
, HINT_POSITION_TOP
);
7234 pidgin_dnd_hints_show_relative(HINT_ARROW_UP
, tab
, HINT_POSITION_LEFT
, HINT_POSITION_BOTTOM
);
7237 if (((gpointer
)win
== (gpointer
)dest_win
&& win
->drag_tab
< page_num
) || to_right
) {
7238 pidgin_dnd_hints_show_relative(HINT_ARROW_RIGHT
, tab
, HINT_POSITION_LEFT
, HINT_POSITION_BOTTOM
);
7239 pidgin_dnd_hints_show_relative(HINT_ARROW_LEFT
, tab
, HINT_POSITION_RIGHT
, HINT_POSITION_BOTTOM
);
7241 pidgin_dnd_hints_show_relative(HINT_ARROW_RIGHT
, tab
, HINT_POSITION_LEFT
, HINT_POSITION_TOP
);
7242 pidgin_dnd_hints_show_relative(HINT_ARROW_LEFT
, tab
, HINT_POSITION_RIGHT
, HINT_POSITION_TOP
);
7251 notebook_leave_cb(GtkWidget
*widget
, GdkEventCrossing
*e
, PidginConvWindow
*win
)
7256 if (e
->x_root
< win
->drag_min_x
||
7257 e
->x_root
>= win
->drag_max_x
||
7258 e
->y_root
< win
->drag_min_y
||
7259 e
->y_root
>= win
->drag_max_y
) {
7261 win
->in_predrag
= FALSE
;
7262 notebook_init_grab(win
, widget
, (GdkEvent
*)e
);
7273 infopane_press_cb(GtkWidget
*widget
, GdkEventButton
*e
, PidginConversation
*gtkconv
)
7275 if (e
->type
== GDK_2BUTTON_PRESS
&& e
->button
== GDK_BUTTON_PRIMARY
) {
7276 if (infopane_entry_activate(gtkconv
))
7280 if (e
->type
!= GDK_BUTTON_PRESS
)
7283 if (e
->button
== GDK_BUTTON_PRIMARY
) {
7285 GtkAllocation allocation
;
7287 gtk_widget_get_allocation(gtkconv
->infopane_hbox
, &allocation
);
7289 if (gtkconv
->win
->in_drag
)
7292 gtkconv
->win
->in_predrag
= TRUE
;
7293 gtkconv
->win
->drag_tab
= gtk_notebook_page_num(GTK_NOTEBOOK(gtkconv
->win
->notebook
), gtkconv
->tab_cont
);
7295 gdk_window_get_origin(gtk_widget_get_window(gtkconv
->infopane_hbox
), &nb_x
, &nb_y
);
7297 gtkconv
->win
->drag_min_x
= allocation
.x
+ nb_x
;
7298 gtkconv
->win
->drag_min_y
= allocation
.y
+ nb_y
;
7299 gtkconv
->win
->drag_max_x
= allocation
.width
+ gtkconv
->win
->drag_min_x
;
7300 gtkconv
->win
->drag_max_y
= allocation
.height
+ gtkconv
->win
->drag_min_y
;
7302 gtkconv
->win
->drag_motion_signal
= g_signal_connect(G_OBJECT(gtkconv
->win
->notebook
), "motion_notify_event",
7303 G_CALLBACK(notebook_motion_cb
), gtkconv
->win
);
7304 gtkconv
->win
->drag_leave_signal
= g_signal_connect(G_OBJECT(gtkconv
->win
->notebook
), "leave_notify_event",
7305 G_CALLBACK(notebook_leave_cb
), gtkconv
->win
);
7309 if (gdk_event_triggers_context_menu((GdkEvent
*)e
)) {
7310 /* Right click was pressed. Popup the context menu. */
7311 GtkWidget
*menu
= gtk_menu_new(), *sub
;
7312 gboolean populated
= populate_menu_with_options(menu
, gtkconv
, TRUE
);
7314 sub
= gtk_menu_item_get_submenu(GTK_MENU_ITEM(gtkconv
->win
->menu
->send_to
));
7315 if (sub
&& gtk_widget_is_sensitive(gtkconv
->win
->menu
->send_to
)) {
7316 GtkWidget
*item
= gtk_menu_item_new_with_mnemonic(_("S_end To"));
7318 pidgin_separator(menu
);
7319 gtk_menu_shell_append(GTK_MENU_SHELL(menu
), item
);
7320 gtk_menu_item_set_submenu(GTK_MENU_ITEM(item
), sub
);
7321 gtk_widget_show(item
);
7322 gtk_widget_show_all(sub
);
7323 } else if (!populated
) {
7324 gtk_widget_destroy(menu
);
7328 gtk_widget_show_all(menu
);
7329 gtk_menu_popup_at_pointer(GTK_MENU(menu
), (GdkEvent
*)e
);
7336 notebook_press_cb(GtkWidget
*widget
, GdkEventButton
*e
, PidginConvWindow
*win
)
7342 GtkAllocation allocation
;
7344 if (e
->button
== GDK_BUTTON_MIDDLE
&& e
->type
== GDK_BUTTON_PRESS
) {
7345 PidginConversation
*gtkconv
;
7346 tab_clicked
= pidgin_conv_get_tab_at_xy(win
, e
->x_root
, e
->y_root
, NULL
);
7348 if (tab_clicked
== -1)
7351 gtkconv
= pidgin_conv_window_get_gtkconv_at_index(win
, tab_clicked
);
7352 close_conv_cb(NULL
, gtkconv
);
7357 if (e
->button
!= GDK_BUTTON_PRIMARY
|| e
->type
!= GDK_BUTTON_PRESS
)
7362 purple_debug(PURPLE_DEBUG_WARNING
, "gtkconv",
7363 "Already in the middle of a window drag at tab_press_cb\n");
7368 * Make sure a tab was actually clicked. The arrow buttons
7371 tab_clicked
= pidgin_conv_get_tab_at_xy(win
, e
->x_root
, e
->y_root
, NULL
);
7373 if (tab_clicked
== -1)
7377 * Get the relative position of the press event, with regards to
7378 * the position of the notebook.
7380 gdk_window_get_origin(gtk_widget_get_window(win
->notebook
), &nb_x
, &nb_y
);
7382 /* Reset the min/max x/y */
7383 win
->drag_min_x
= 0;
7384 win
->drag_min_y
= 0;
7385 win
->drag_max_x
= 0;
7386 win
->drag_max_y
= 0;
7388 /* Find out which tab was dragged. */
7389 page
= gtk_notebook_get_nth_page(GTK_NOTEBOOK(win
->notebook
), tab_clicked
);
7390 tab
= gtk_notebook_get_tab_label(GTK_NOTEBOOK(win
->notebook
), page
);
7392 gtk_widget_get_allocation(tab
, &allocation
);
7394 win
->drag_min_x
= allocation
.x
+ nb_x
;
7395 win
->drag_min_y
= allocation
.y
+ nb_y
;
7396 win
->drag_max_x
= allocation
.width
+ win
->drag_min_x
;
7397 win
->drag_max_y
= allocation
.height
+ win
->drag_min_y
;
7399 /* Make sure the click occurred in the tab. */
7400 if (e
->x_root
< win
->drag_min_x
||
7401 e
->x_root
>= win
->drag_max_x
||
7402 e
->y_root
< win
->drag_min_y
||
7403 e
->y_root
>= win
->drag_max_y
) {
7408 win
->in_predrag
= TRUE
;
7409 win
->drag_tab
= tab_clicked
;
7411 /* Connect the new motion signals. */
7412 win
->drag_motion_signal
=
7413 g_signal_connect(G_OBJECT(widget
), "motion_notify_event",
7414 G_CALLBACK(notebook_motion_cb
), win
);
7416 win
->drag_leave_signal
=
7417 g_signal_connect(G_OBJECT(widget
), "leave_notify_event",
7418 G_CALLBACK(notebook_leave_cb
), win
);
7424 notebook_release_cb(GtkWidget
*widget
, GdkEventButton
*e
, PidginConvWindow
*win
)
7426 PidginConvWindow
*dest_win
;
7427 GtkNotebook
*dest_notebook
;
7428 PidginConversation
*active_gtkconv
;
7429 PidginConversation
*gtkconv
;
7430 gint dest_page_num
= 0;
7431 gboolean new_window
= FALSE
;
7432 gboolean to_right
= FALSE
;
7436 * Don't check to make sure that the event's window matches the
7437 * widget's, because we may be getting an event passed on from the
7440 if (e
->button
!= GDK_BUTTON_PRIMARY
&& e
->type
!= GDK_BUTTON_RELEASE
)
7443 device
= gdk_event_get_device((GdkEvent
*)e
);
7444 if (gdk_display_device_is_grabbed(gdk_device_get_display(device
), device
)) {
7445 gdk_device_ungrab(device
, gdk_event_get_time((GdkEvent
*)e
));
7446 gtk_grab_remove(widget
);
7449 if (!win
->in_predrag
&& !win
->in_drag
)
7452 /* Disconnect the motion signal. */
7453 if (win
->drag_motion_signal
) {
7454 g_signal_handler_disconnect(G_OBJECT(widget
),
7455 win
->drag_motion_signal
);
7457 win
->drag_motion_signal
= 0;
7461 * If we're in a pre-drag, we'll also need to disconnect the leave
7464 if (win
->in_predrag
) {
7465 win
->in_predrag
= FALSE
;
7467 if (win
->drag_leave_signal
) {
7468 g_signal_handler_disconnect(G_OBJECT(widget
),
7469 win
->drag_leave_signal
);
7471 win
->drag_leave_signal
= 0;
7475 /* If we're not in drag... */
7476 /* We're perfectly normal people! */
7480 win
->in_drag
= FALSE
;
7482 pidgin_dnd_hints_hide_all();
7484 dest_win
= pidgin_conv_window_get_at_event((GdkEvent
*)e
);
7486 active_gtkconv
= pidgin_conv_window_get_active_gtkconv(win
);
7488 if (dest_win
== NULL
) {
7489 /* If the current window doesn't have any other conversations,
7490 * there isn't much point transferring the conv to a new window. */
7491 if (pidgin_conv_window_get_gtkconv_count(win
) > 1) {
7492 /* Make a new window to stick this to. */
7493 dest_win
= pidgin_conv_window_new();
7498 if (dest_win
== NULL
)
7501 purple_signal_emit(pidgin_conversations_get_handle(),
7502 "conversation-dragging", win
, dest_win
);
7504 /* Get the destination page number. */
7506 dest_notebook
= GTK_NOTEBOOK(dest_win
->notebook
);
7507 if (gtk_notebook_get_show_tabs(dest_notebook
)) {
7508 dest_page_num
= pidgin_conv_get_tab_at_xy(dest_win
,
7509 e
->x_root
, e
->y_root
, &to_right
);
7512 to_right
= pidgin_conv_xy_to_right_infopane(dest_win
, e
->x_root
, e
->y_root
);
7516 gtkconv
= pidgin_conv_window_get_gtkconv_at_index(win
, win
->drag_tab
);
7518 if (win
== dest_win
) {
7519 gtk_notebook_reorder_child(GTK_NOTEBOOK(win
->notebook
), gtkconv
->tab_cont
, dest_page_num
);
7521 pidgin_conv_window_remove_gtkconv(win
, gtkconv
);
7522 pidgin_conv_window_add_gtkconv(dest_win
, gtkconv
);
7523 gtk_notebook_reorder_child(GTK_NOTEBOOK(dest_win
->notebook
), gtkconv
->tab_cont
, dest_page_num
+ to_right
);
7524 pidgin_conv_window_switch_gtkconv(dest_win
, gtkconv
);
7526 gint win_width
, win_height
;
7528 gtk_window_get_size(GTK_WINDOW(dest_win
->window
),
7529 &win_width
, &win_height
);
7530 #ifdef _WIN32 /* only override window manager placement on Windows */
7531 gtk_window_move(GTK_WINDOW(dest_win
->window
),
7532 e
->x_root
- (win_width
/ 2),
7533 e
->y_root
- (win_height
/ 2));
7536 pidgin_conv_window_show(dest_win
);
7540 gtk_widget_grab_focus(active_gtkconv
->editor
);
7547 before_switch_conv_cb(GtkNotebook
*notebook
, GtkWidget
*page
, gint page_num
,
7550 PidginConvWindow
*win
;
7551 PurpleConversation
*conv
;
7552 PidginConversation
*gtkconv
;
7555 conv
= pidgin_conv_window_get_active_conversation(win
);
7557 g_return_if_fail(conv
!= NULL
);
7559 if (!PURPLE_IS_IM_CONVERSATION(conv
))
7562 gtkconv
= PIDGIN_CONVERSATION(conv
);
7564 if (gtkconv
->u
.im
->typing_timer
!= 0) {
7565 g_source_remove(gtkconv
->u
.im
->typing_timer
);
7566 gtkconv
->u
.im
->typing_timer
= 0;
7569 stop_anim(NULL
, gtkconv
);
7573 close_window(GtkWidget
*w
, PidginConvWindow
*win
)
7575 close_win_cb(w
, NULL
, win
);
7579 detach_tab_cb(GtkWidget
*w
, PidginConvWindow
*win
)
7581 PidginConvWindow
*new_window
;
7582 PidginConversation
*gtkconv
;
7584 gtkconv
= win
->clicked_tab
;
7589 /* Nothing to do if there's only one tab in the window */
7590 if (pidgin_conv_window_get_gtkconv_count(win
) == 1)
7593 pidgin_conv_window_remove_gtkconv(win
, gtkconv
);
7595 new_window
= pidgin_conv_window_new();
7596 pidgin_conv_window_add_gtkconv(new_window
, gtkconv
);
7597 pidgin_conv_window_show(new_window
);
7601 close_others_cb(GtkWidget
*w
, PidginConvWindow
*win
)
7604 PidginConversation
*gtkconv
;
7606 gtkconv
= win
->clicked_tab
;
7611 for (iter
= pidgin_conv_window_get_gtkconvs(win
); iter
; )
7613 PidginConversation
*gconv
= iter
->data
;
7616 if (gconv
!= gtkconv
)
7618 close_conv_cb(NULL
, gconv
);
7624 close_tab_cb(GtkWidget
*w
, PidginConvWindow
*win
)
7626 PidginConversation
*gtkconv
;
7628 gtkconv
= win
->clicked_tab
;
7631 close_conv_cb(NULL
, gtkconv
);
7635 notebook_menu_switch_cb(GtkWidget
*item
, GtkWidget
*child
)
7637 GtkNotebook
*notebook
;
7640 notebook
= GTK_NOTEBOOK(gtk_widget_get_parent(child
));
7641 index
= gtk_notebook_page_num(notebook
, child
);
7642 gtk_notebook_set_current_page(notebook
, index
);
7646 notebook_menu_update_label_cb(GtkWidget
*child
, GParamSpec
*pspec
,
7647 GtkNotebook
*notebook
)
7652 item
= g_object_get_data(G_OBJECT(child
), "popup-menu-item");
7653 label
= gtk_bin_get_child(GTK_BIN(item
));
7655 gtk_container_remove(GTK_CONTAINER(item
), label
);
7657 label
= gtk_notebook_get_menu_label(notebook
, child
);
7659 gtk_widget_show(label
);
7660 gtk_container_add(GTK_CONTAINER(item
), label
);
7661 gtk_widget_show(item
);
7663 gtk_widget_hide(item
);
7668 notebook_add_tab_to_menu_cb(GtkNotebook
*notebook
, GtkWidget
*child
,
7669 guint page_num
, PidginConvWindow
*win
)
7674 item
= gtk_menu_item_new();
7675 label
= gtk_notebook_get_menu_label(notebook
, child
);
7677 gtk_widget_show(label
);
7678 gtk_container_add(GTK_CONTAINER(item
), label
);
7679 gtk_widget_show(item
);
7682 g_signal_connect(child
, "child-notify::menu-label",
7683 G_CALLBACK(notebook_menu_update_label_cb
), notebook
);
7684 g_signal_connect(item
, "activate",
7685 G_CALLBACK(notebook_menu_switch_cb
), child
);
7686 g_object_set_data(G_OBJECT(child
), "popup-menu-item", item
);
7688 gtk_menu_shell_insert(GTK_MENU_SHELL(win
->notebook_menu
), item
, page_num
);
7692 notebook_remove_tab_from_menu_cb(GtkNotebook
*notebook
, GtkWidget
*child
,
7693 guint page_num
, PidginConvWindow
*win
)
7697 /* Disconnecting the "child-notify::menu-label" signal. */
7698 g_signal_handlers_disconnect_by_data(child
, notebook
);
7700 item
= g_object_get_data(G_OBJECT(child
), "popup-menu-item");
7701 gtk_container_remove(GTK_CONTAINER(win
->notebook_menu
), item
);
7706 notebook_reorder_tab_in_menu_cb(GtkNotebook
*notebook
, GtkWidget
*child
,
7707 guint page_num
, PidginConvWindow
*win
)
7711 item
= g_object_get_data(G_OBJECT(child
), "popup-menu-item");
7712 gtk_menu_reorder_child(GTK_MENU(win
->notebook_menu
), item
, page_num
);
7716 notebook_right_click_menu_cb(GtkNotebook
*notebook
, GdkEventButton
*event
,
7717 PidginConvWindow
*win
)
7720 PidginConversation
*gtkconv
;
7722 if (!gdk_event_triggers_context_menu((GdkEvent
*)event
))
7725 gtkconv
= pidgin_conv_window_get_gtkconv_at_index(win
,
7726 pidgin_conv_get_tab_at_xy(win
, event
->x_root
, event
->y_root
, NULL
));
7728 win
->clicked_tab
= gtkconv
;
7730 menu
= win
->notebook_menu
;
7732 gtk_menu_popup_at_pointer(GTK_MENU(menu
), (GdkEvent
*)event
);
7738 remove_edit_entry(PidginConversation
*gtkconv
, GtkWidget
*entry
)
7740 g_signal_handlers_disconnect_matched(G_OBJECT(entry
), G_SIGNAL_MATCH_DATA
,
7741 0, 0, NULL
, NULL
, gtkconv
);
7742 gtk_widget_show(gtkconv
->infopane
);
7743 gtk_widget_grab_focus(gtkconv
->editor
);
7744 gtk_widget_destroy(entry
);
7748 alias_focus_cb(GtkWidget
*widget
, GdkEventFocus
*event
, gpointer user_data
)
7750 remove_edit_entry(user_data
, widget
);
7755 alias_key_press_cb(GtkWidget
*widget
, GdkEventKey
*event
, gpointer user_data
)
7757 if (event
->keyval
== GDK_KEY_Escape
) {
7758 remove_edit_entry(user_data
, widget
);
7765 alias_cb(GtkEntry
*entry
, gpointer user_data
)
7767 PidginConversation
*gtkconv
;
7768 PurpleConversation
*conv
;
7769 PurpleAccount
*account
;
7772 gtkconv
= (PidginConversation
*)user_data
;
7773 if (gtkconv
== NULL
) {
7776 conv
= gtkconv
->active_conv
;
7777 account
= purple_conversation_get_account(conv
);
7778 name
= purple_conversation_get_name(conv
);
7780 if (PURPLE_IS_IM_CONVERSATION(conv
)) {
7782 buddy
= purple_blist_find_buddy(account
, name
);
7783 if (buddy
!= NULL
) {
7784 purple_buddy_set_local_alias(buddy
, gtk_entry_get_text(entry
));
7786 purple_serv_alias_buddy(buddy
);
7787 } else if (PURPLE_IS_CHAT_CONVERSATION(conv
)) {
7788 gtk_entry_set_text(GTK_ENTRY(gtkconv
->u
.chat
->topic_text
), gtk_entry_get_text(entry
));
7789 topic_callback(NULL
, gtkconv
);
7791 remove_edit_entry(user_data
, GTK_WIDGET(entry
));
7795 infopane_entry_activate(PidginConversation
*gtkconv
)
7797 GtkWidget
*entry
= NULL
;
7798 PurpleConversation
*conv
= gtkconv
->active_conv
;
7799 const char *text
= NULL
;
7801 if (!gtk_widget_get_visible(gtkconv
->infopane
)) {
7802 /* There's already an entry for alias. Let's not create another one. */
7806 if (!purple_account_is_connected(purple_conversation_get_account(gtkconv
->active_conv
))) {
7807 /* Do not allow aliasing someone on a disconnected account. */
7811 if (PURPLE_IS_IM_CONVERSATION(conv
)) {
7812 PurpleBuddy
*buddy
= purple_blist_find_buddy(purple_conversation_get_account(gtkconv
->active_conv
), purple_conversation_get_name(gtkconv
->active_conv
));
7814 /* This buddy isn't in your buddy list, so we can't alias him */
7817 text
= purple_buddy_get_contact_alias(buddy
);
7818 } else if (PURPLE_IS_CHAT_CONVERSATION(conv
)) {
7819 PurpleConnection
*gc
;
7820 PurpleProtocol
*protocol
= NULL
;
7822 gc
= purple_conversation_get_connection(conv
);
7824 protocol
= purple_connection_get_protocol(gc
);
7825 if (protocol
&& !PURPLE_PROTOCOL_IMPLEMENTS(protocol
, CHAT
, set_topic
))
7826 /* This protocol doesn't support setting the chat room topic */
7829 text
= purple_chat_conversation_get_topic(PURPLE_CHAT_CONVERSATION(conv
));
7833 entry
= gtk_entry_new();
7834 gtk_entry_set_has_frame(GTK_ENTRY(entry
), FALSE
);
7835 gtk_entry_set_width_chars(GTK_ENTRY(entry
), 10);
7836 gtk_entry_set_alignment(GTK_ENTRY(entry
), 0.5);
7838 gtk_box_pack_start(GTK_BOX(gtkconv
->infopane_hbox
), entry
, TRUE
, TRUE
, 0);
7839 /* after the tab label */
7840 gtk_box_reorder_child(GTK_BOX(gtkconv
->infopane_hbox
), entry
, 0);
7842 g_signal_connect(G_OBJECT(entry
), "activate", G_CALLBACK(alias_cb
), gtkconv
);
7843 g_signal_connect(G_OBJECT(entry
), "focus-out-event", G_CALLBACK(alias_focus_cb
), gtkconv
);
7844 g_signal_connect(G_OBJECT(entry
), "key-press-event", G_CALLBACK(alias_key_press_cb
), gtkconv
);
7847 gtk_entry_set_text(GTK_ENTRY(entry
), text
);
7848 gtk_widget_show(entry
);
7849 gtk_widget_hide(gtkconv
->infopane
);
7850 gtk_widget_grab_focus(entry
);
7856 window_keypress_cb(GtkWidget
*widget
, GdkEventKey
*event
, PidginConvWindow
*win
)
7858 PidginConversation
*gtkconv
= pidgin_conv_window_get_active_gtkconv(win
);
7860 return conv_keypress_common(gtkconv
, event
);
7864 switch_conv_cb(GtkNotebook
*notebook
, GtkWidget
*page
, gint page_num
,
7867 PidginConvWindow
*win
;
7868 PurpleConversation
*conv
;
7869 PidginConversation
*gtkconv
;
7870 const char *sound_method
;
7873 gtkconv
= pidgin_conv_window_get_gtkconv_at_index(win
, page_num
);
7874 conv
= gtkconv
->active_conv
;
7876 g_return_if_fail(conv
!= NULL
);
7878 /* clear unseen flag if conversation is not hidden */
7879 if(!pidgin_conv_is_hidden(gtkconv
)) {
7880 gtkconv_set_unseen(gtkconv
, PIDGIN_UNSEEN_NONE
);
7883 /* Update the menubar */
7885 gtk_toggle_action_set_active(GTK_TOGGLE_ACTION(gtkconv
->win
->menu
->logging
),
7886 purple_conversation_is_logging(conv
));
7888 generate_send_to_items(win
);
7889 generate_e2ee_controls(win
);
7890 regenerate_options_items(win
);
7891 regenerate_plugins_items(win
);
7893 pidgin_conv_switch_active_conversation(conv
);
7895 sound_method
= purple_prefs_get_string(PIDGIN_PREFS_ROOT
"/sound/method");
7896 if (!purple_strequal(sound_method
, "none"))
7897 gtk_toggle_action_set_active(GTK_TOGGLE_ACTION(win
->menu
->sounds
),
7898 gtkconv
->make_sound
);
7900 gtk_toggle_action_set_active(GTK_TOGGLE_ACTION(win
->menu
->show_formatting_toolbar
),
7901 purple_prefs_get_bool(PIDGIN_PREFS_ROOT
"/conversations/show_formatting_toolbar"));
7904 * We pause icons when they are not visible. If this icon should
7905 * be animated then start it back up again.
7907 if (PURPLE_IS_IM_CONVERSATION(conv
) &&
7908 (gtkconv
->u
.im
->animate
))
7909 start_anim(NULL
, gtkconv
);
7911 purple_signal_emit(pidgin_conversations_get_handle(), "conversation-switched", conv
);
7914 /**************************************************************************
7916 **************************************************************************/
7919 pidgin_conv_windows_get_list()
7925 make_status_icon_list(const char *stock
, GtkWidget
*w
)
7928 l
= g_list_append(l
,
7929 gtk_widget_render_icon(w
, stock
,
7930 gtk_icon_size_from_name(PIDGIN_ICON_SIZE_TANGO_EXTRA_SMALL
), "GtkWindow"));
7931 l
= g_list_append(l
,
7932 gtk_widget_render_icon(w
, stock
,
7933 gtk_icon_size_from_name(PIDGIN_ICON_SIZE_TANGO_SMALL
), "GtkWindow"));
7934 l
= g_list_append(l
,
7935 gtk_widget_render_icon(w
, stock
,
7936 gtk_icon_size_from_name(PIDGIN_ICON_SIZE_TANGO_MEDIUM
), "GtkWindow"));
7937 l
= g_list_append(l
,
7938 gtk_widget_render_icon(w
, stock
,
7939 gtk_icon_size_from_name(PIDGIN_ICON_SIZE_TANGO_LARGE
), "GtkWindow"));
7944 create_icon_lists(GtkWidget
*w
)
7946 available_list
= make_status_icon_list(PIDGIN_STOCK_STATUS_AVAILABLE
, w
);
7947 busy_list
= make_status_icon_list(PIDGIN_STOCK_STATUS_BUSY
, w
);
7948 xa_list
= make_status_icon_list(PIDGIN_STOCK_STATUS_XA
, w
);
7949 offline_list
= make_status_icon_list(PIDGIN_STOCK_STATUS_OFFLINE
, w
);
7950 away_list
= make_status_icon_list(PIDGIN_STOCK_STATUS_AWAY
, w
);
7951 protocol_lists
= g_hash_table_new(g_str_hash
, g_str_equal
);
7955 plugin_changed_cb(PurplePlugin
*p
, gpointer data
)
7957 regenerate_plugins_items(data
);
7960 static gboolean
gtk_conv_configure_cb(GtkWidget
*w
, GdkEventConfigure
*event
, gpointer data
) {
7963 if (gtk_widget_get_visible(w
))
7964 gtk_window_get_position(GTK_WINDOW(w
), &x
, &y
);
7966 return FALSE
; /* carry on normally */
7968 /* Workaround for GTK+ bug # 169811 - "configure_event" is fired
7969 * when the window is being maximized */
7970 if (gdk_window_get_state(gtk_widget_get_window(w
)) & GDK_WINDOW_STATE_MAXIMIZED
)
7973 /* don't save off-screen positioning */
7974 if (x
+ event
->width
< 0 ||
7975 y
+ event
->height
< 0 ||
7976 x
> gdk_screen_width() ||
7977 y
> gdk_screen_height())
7978 return FALSE
; /* carry on normally */
7980 /* store the position */
7981 purple_prefs_set_int(PIDGIN_PREFS_ROOT
"/conversations/im/x", x
);
7982 purple_prefs_set_int(PIDGIN_PREFS_ROOT
"/conversations/im/y", y
);
7983 purple_prefs_set_int(PIDGIN_PREFS_ROOT
"/conversations/im/width", event
->width
);
7984 purple_prefs_set_int(PIDGIN_PREFS_ROOT
"/conversations/im/height", event
->height
);
7986 /* continue to handle event normally */
7992 pidgin_conv_set_position_size(PidginConvWindow
*win
, int conv_x
, int conv_y
,
7993 int conv_width
, int conv_height
)
7995 /* if the window exists, is hidden, we're saving positions, and the
7996 * position is sane... */
7997 if (win
&& win
->window
&&
7998 !gtk_widget_get_visible(win
->window
) && conv_width
!= 0) {
8000 #ifdef _WIN32 /* only override window manager placement on Windows */
8001 /* ...check position is on screen... */
8002 if (conv_x
>= gdk_screen_width())
8003 conv_x
= gdk_screen_width() - 100;
8004 else if (conv_x
+ conv_width
< 0)
8007 if (conv_y
>= gdk_screen_height())
8008 conv_y
= gdk_screen_height() - 100;
8009 else if (conv_y
+ conv_height
< 0)
8012 /* ...and move it back. */
8013 gtk_window_move(GTK_WINDOW(win
->window
), conv_x
, conv_y
);
8015 gtk_window_resize(GTK_WINDOW(win
->window
), conv_width
, conv_height
);
8020 pidgin_conv_restore_position(PidginConvWindow
*win
) {
8021 pidgin_conv_set_position_size(win
,
8022 purple_prefs_get_int(PIDGIN_PREFS_ROOT
"/conversations/im/x"),
8023 purple_prefs_get_int(PIDGIN_PREFS_ROOT
"/conversations/im/y"),
8024 purple_prefs_get_int(PIDGIN_PREFS_ROOT
"/conversations/im/width"),
8025 purple_prefs_get_int(PIDGIN_PREFS_ROOT
"/conversations/im/height"));
8029 pidgin_conv_window_new()
8031 PidginConvWindow
*win
;
8032 GtkPositionType pos
;
8033 GtkWidget
*testidea
;
8037 GdkModifierType state
;
8039 win
= g_malloc0(sizeof(PidginConvWindow
));
8040 win
->menu
= g_malloc0(sizeof(PidginConvWindowMenu
));
8042 window_list
= g_list_append(window_list
, win
);
8044 /* Create the window. */
8045 win
->window
= pidgin_create_window(NULL
, 0, "conversation", TRUE
);
8046 /*_pidgin_widget_set_accessible_name(win->window, "Conversations");*/
8047 if (!gtk_get_current_event_state(&state
))
8048 gtk_window_set_focus_on_map(GTK_WINDOW(win
->window
), FALSE
);
8050 /* Etan: I really think this entire function call should happen only
8051 * when we are on Windows but I was informed that back before we used
8052 * to save the window position we stored the window size, so I'm
8053 * leaving it for now. */
8054 #if TRUE || defined(_WIN32)
8055 pidgin_conv_restore_position(win
);
8058 if (available_list
== NULL
) {
8059 create_icon_lists(win
->window
);
8062 g_signal_connect(G_OBJECT(win
->window
), "delete_event",
8063 G_CALLBACK(close_win_cb
), win
);
8064 g_signal_connect(G_OBJECT(win
->window
), "focus_in_event",
8065 G_CALLBACK(focus_win_cb
), win
);
8067 /* Intercept keystrokes from the menu items */
8068 g_signal_connect(G_OBJECT(win
->window
), "key_press_event",
8069 G_CALLBACK(window_keypress_cb
), win
);
8072 /* Create the notebook. */
8073 win
->notebook
= gtk_notebook_new();
8075 pos
= purple_prefs_get_int(PIDGIN_PREFS_ROOT
"/conversations/tab_side");
8077 gtk_notebook_set_tab_pos(GTK_NOTEBOOK(win
->notebook
), pos
);
8078 gtk_notebook_set_scrollable(GTK_NOTEBOOK(win
->notebook
), TRUE
);
8079 gtk_notebook_set_show_tabs(GTK_NOTEBOOK(win
->notebook
), FALSE
);
8080 gtk_notebook_set_show_border(GTK_NOTEBOOK(win
->notebook
), TRUE
);
8082 menu
= win
->notebook_menu
= gtk_menu_new();
8084 pidgin_separator(GTK_WIDGET(menu
));
8086 item
= gtk_menu_item_new_with_label(_("Close other tabs"));
8087 gtk_widget_show(item
);
8088 gtk_menu_shell_append(GTK_MENU_SHELL(menu
), item
);
8089 g_signal_connect(G_OBJECT(item
), "activate",
8090 G_CALLBACK(close_others_cb
), win
);
8092 item
= gtk_menu_item_new_with_label(_("Close all tabs"));
8093 gtk_widget_show(item
);
8094 gtk_menu_shell_append(GTK_MENU_SHELL(menu
), item
);
8095 g_signal_connect(G_OBJECT(item
), "activate",
8096 G_CALLBACK(close_window
), win
);
8098 pidgin_separator(menu
);
8100 item
= gtk_menu_item_new_with_label(_("Detach this tab"));
8101 gtk_widget_show(item
);
8102 gtk_menu_shell_append(GTK_MENU_SHELL(menu
), item
);
8103 g_signal_connect(G_OBJECT(item
), "activate",
8104 G_CALLBACK(detach_tab_cb
), win
);
8106 item
= gtk_menu_item_new_with_label(_("Close this tab"));
8107 gtk_widget_show(item
);
8108 gtk_menu_shell_append(GTK_MENU_SHELL(menu
), item
);
8109 g_signal_connect(G_OBJECT(item
), "activate",
8110 G_CALLBACK(close_tab_cb
), win
);
8112 g_signal_connect(G_OBJECT(win
->notebook
), "page-added",
8113 G_CALLBACK(notebook_add_tab_to_menu_cb
), win
);
8114 g_signal_connect(G_OBJECT(win
->notebook
), "page-removed",
8115 G_CALLBACK(notebook_remove_tab_from_menu_cb
), win
);
8116 g_signal_connect(G_OBJECT(win
->notebook
), "page-reordered",
8117 G_CALLBACK(notebook_reorder_tab_in_menu_cb
), win
);
8119 g_signal_connect(G_OBJECT(win
->notebook
), "button-press-event",
8120 G_CALLBACK(notebook_right_click_menu_cb
), win
);
8122 gtk_widget_show(win
->notebook
);
8124 g_signal_connect(G_OBJECT(win
->notebook
), "switch_page",
8125 G_CALLBACK(before_switch_conv_cb
), win
);
8126 g_signal_connect_after(G_OBJECT(win
->notebook
), "switch_page",
8127 G_CALLBACK(switch_conv_cb
), win
);
8129 /* Setup the tab drag and drop signals. */
8130 gtk_widget_add_events(win
->notebook
,
8131 GDK_BUTTON1_MOTION_MASK
| GDK_LEAVE_NOTIFY_MASK
);
8132 g_signal_connect(G_OBJECT(win
->notebook
), "button_press_event",
8133 G_CALLBACK(notebook_press_cb
), win
);
8134 g_signal_connect(G_OBJECT(win
->notebook
), "button_release_event",
8135 G_CALLBACK(notebook_release_cb
), win
);
8137 testidea
= gtk_box_new(GTK_ORIENTATION_VERTICAL
, 0);
8139 /* Setup the menubar. */
8140 menubar
= setup_menubar(win
);
8141 gtk_box_pack_start(GTK_BOX(testidea
), menubar
, FALSE
, TRUE
, 0);
8143 gtk_box_pack_start(GTK_BOX(testidea
), win
->notebook
, TRUE
, TRUE
, 0);
8145 gtk_container_add(GTK_CONTAINER(win
->window
), testidea
);
8147 gtk_widget_show(testidea
);
8149 /* Update the plugin actions when plugins are (un)loaded */
8150 purple_signal_connect(purple_plugins_get_handle(), "plugin-load",
8151 win
, PURPLE_CALLBACK(plugin_changed_cb
), win
);
8152 purple_signal_connect(purple_plugins_get_handle(), "plugin-unload",
8153 win
, PURPLE_CALLBACK(plugin_changed_cb
), win
);
8157 g_signal_connect(G_OBJECT(win
->window
), "show",
8158 G_CALLBACK(winpidgin_ensure_onscreen
), win
->window
);
8160 if (purple_prefs_get_bool(PIDGIN_PREFS_ROOT
"/win32/minimize_new_convs")
8161 && !gtk_get_current_event_state(&state
))
8162 gtk_window_iconify(GTK_WINDOW(win
->window
));
8165 purple_signal_emit(pidgin_conversations_get_handle(),
8166 "conversation-window-created", win
);
8169 pidgin_conversations_set_tab_colors();
8175 pidgin_conv_window_destroy(PidginConvWindow
*win
)
8177 if (win
->gtkconvs
) {
8178 GList
*iter
= win
->gtkconvs
;
8181 PidginConversation
*gtkconv
= iter
->data
;
8183 close_conv_cb(NULL
, gtkconv
);
8188 purple_prefs_disconnect_by_handle(win
);
8189 window_list
= g_list_remove(window_list
, win
);
8191 gtk_widget_destroy(win
->notebook_menu
);
8192 gtk_widget_destroy(win
->window
);
8194 g_object_unref(G_OBJECT(win
->menu
->ui
));
8196 purple_notify_close_with_handle(win
);
8197 purple_signals_disconnect_by_handle(win
);
8204 pidgin_conv_window_show(PidginConvWindow
*win
)
8206 gtk_widget_show(win
->window
);
8210 pidgin_conv_window_hide(PidginConvWindow
*win
)
8212 gtk_widget_hide(win
->window
);
8216 pidgin_conv_window_raise(PidginConvWindow
*win
)
8218 gdk_window_raise(GDK_WINDOW(gtk_widget_get_window(win
->window
)));
8222 pidgin_conv_window_switch_gtkconv(PidginConvWindow
*win
, PidginConversation
*gtkconv
)
8224 gtk_notebook_set_current_page(GTK_NOTEBOOK(win
->notebook
),
8225 gtk_notebook_page_num(GTK_NOTEBOOK(win
->notebook
),
8226 gtkconv
->tab_cont
));
8230 gtkconv_tab_set_tip(GtkWidget
*widget
, GdkEventCrossing
*event
, PidginConversation
*gtkconv
)
8232 /* PANGO_VERSION_CHECK macro was introduced in 1.15. So we need this double check. */
8233 #ifndef PANGO_VERSION_CHECK
8234 #define pango_layout_is_ellipsized(l) TRUE
8235 #elif !PANGO_VERSION_CHECK(1,16,0)
8236 #define pango_layout_is_ellipsized(l) TRUE
8238 PangoLayout
*layout
;
8240 layout
= gtk_label_get_layout(GTK_LABEL(gtkconv
->tab_label
));
8241 if (pango_layout_is_ellipsized(layout
))
8242 gtk_widget_set_tooltip_text(widget
, gtk_label_get_text(GTK_LABEL(gtkconv
->tab_label
)));
8244 gtk_widget_set_tooltip_text(widget
, NULL
);
8250 set_default_tab_colors(GtkWidget
*widget
)
8253 GtkCssProvider
*provider
;
8254 GError
*error
= NULL
;
8258 const char *labelname
;
8261 {"tab-label-typing", "#4e9a06"},
8262 {"tab-label-typed", "#c4a000"},
8263 {"tab-label-attention", "#006aff"},
8264 {"tab-label-unreadchat", "#cc0000"},
8265 {"tab-label-event", "#888a85"},
8269 str
= g_string_new(NULL
);
8271 for (iter
= 0; styles
[iter
].labelname
; iter
++) {
8272 g_string_append_printf(str
,
8276 styles
[iter
].labelname
,
8277 styles
[iter
].color
);
8280 provider
= gtk_css_provider_new();
8282 gtk_css_provider_load_from_data(provider
, str
->str
, str
->len
, &error
);
8284 gtk_style_context_add_provider(gtk_widget_get_style_context(widget
),
8285 GTK_STYLE_PROVIDER(provider
),
8286 GTK_STYLE_PROVIDER_PRIORITY_APPLICATION
);
8289 g_error_free(error
);
8290 g_string_free(str
, TRUE
);
8294 pidgin_conv_window_add_gtkconv(PidginConvWindow
*win
, PidginConversation
*gtkconv
)
8296 PurpleConversation
*conv
= gtkconv
->active_conv
;
8297 PidginConversation
*focus_gtkconv
;
8298 GtkWidget
*tab_cont
= gtkconv
->tab_cont
;
8299 const gchar
*tmp_lab
;
8301 win
->gtkconvs
= g_list_append(win
->gtkconvs
, gtkconv
);
8304 if (win
->gtkconvs
&& win
->gtkconvs
->next
&& win
->gtkconvs
->next
->next
== NULL
)
8305 pidgin_conv_tab_pack(win
, ((PidginConversation
*)win
->gtkconvs
->data
));
8309 gtkconv
->close
= pidgin_create_small_button(gtk_label_new("×"));
8310 gtk_widget_set_tooltip_text(gtkconv
->close
, _("Close conversation"));
8312 g_signal_connect(gtkconv
->close
, "clicked", G_CALLBACK (close_conv_cb
), gtkconv
);
8315 gtkconv
->icon
= gtk_image_new();
8316 gtkconv
->menu_icon
= gtk_image_new();
8317 g_object_set(G_OBJECT(gtkconv
->icon
),
8318 "icon-size", gtk_icon_size_from_name(PIDGIN_ICON_SIZE_TANGO_MICROSCOPIC
),
8320 g_object_set(G_OBJECT(gtkconv
->menu_icon
),
8321 "icon-size", gtk_icon_size_from_name(PIDGIN_ICON_SIZE_TANGO_MICROSCOPIC
),
8323 gtk_widget_show(gtkconv
->icon
);
8324 update_tab_icon(conv
);
8327 gtkconv
->tab_label
= gtk_label_new(tmp_lab
= purple_conversation_get_title(conv
));
8328 set_default_tab_colors(gtkconv
->tab_label
);
8329 gtk_widget_set_name(gtkconv
->tab_label
, "tab-label");
8331 gtkconv
->menu_tabby
= gtk_box_new(GTK_ORIENTATION_HORIZONTAL
, PIDGIN_HIG_BOX_SPACE
);
8332 gtkconv
->menu_label
= gtk_label_new(tmp_lab
);
8333 gtk_box_pack_start(GTK_BOX(gtkconv
->menu_tabby
), gtkconv
->menu_icon
, FALSE
, FALSE
, 0);
8335 gtk_widget_show_all(gtkconv
->menu_icon
);
8337 gtk_box_pack_start(GTK_BOX(gtkconv
->menu_tabby
), gtkconv
->menu_label
, TRUE
, TRUE
, 0);
8338 gtk_widget_show(gtkconv
->menu_label
);
8339 gtk_label_set_xalign(GTK_LABEL(gtkconv
->menu_label
), 0);
8340 gtk_label_set_yalign(GTK_LABEL(gtkconv
->menu_label
), 0);
8342 gtk_widget_show(gtkconv
->menu_tabby
);
8344 if (PURPLE_IS_IM_CONVERSATION(conv
))
8345 pidgin_conv_update_buddy_icon(PURPLE_IM_CONVERSATION(conv
));
8347 /* Build and set conversations tab */
8348 pidgin_conv_tab_pack(win
, gtkconv
);
8350 gtk_notebook_set_menu_label(GTK_NOTEBOOK(win
->notebook
), tab_cont
, gtkconv
->menu_tabby
);
8352 gtk_widget_show(tab_cont
);
8354 if (pidgin_conv_window_get_gtkconv_count(win
) == 1) {
8355 /* Er, bug in notebooks? Switch to the page manually. */
8356 gtk_notebook_set_current_page(GTK_NOTEBOOK(win
->notebook
), 0);
8358 gtk_notebook_set_show_tabs(GTK_NOTEBOOK(win
->notebook
), TRUE
);
8361 focus_gtkconv
= g_list_nth_data(pidgin_conv_window_get_gtkconvs(win
),
8362 gtk_notebook_get_current_page(GTK_NOTEBOOK(win
->notebook
)));
8363 gtk_widget_grab_focus(focus_gtkconv
->editor
);
8365 if (pidgin_conv_window_get_gtkconv_count(win
) == 1)
8366 update_send_to_selection(win
);
8370 pidgin_conv_tab_pack(PidginConvWindow
*win
, PidginConversation
*gtkconv
)
8372 gboolean tabs_side
= FALSE
;
8374 GtkWidget
*first
, *third
, *ebox
, *parent
;
8376 if (purple_prefs_get_int(PIDGIN_PREFS_ROOT
"/conversations/tab_side") == GTK_POS_LEFT
||
8377 purple_prefs_get_int(PIDGIN_PREFS_ROOT
"/conversations/tab_side") == GTK_POS_RIGHT
)
8379 else if (purple_prefs_get_int(PIDGIN_PREFS_ROOT
"/conversations/tab_side") == (GTK_POS_LEFT
|8))
8381 else if (purple_prefs_get_int(PIDGIN_PREFS_ROOT
"/conversations/tab_side") == (GTK_POS_RIGHT
|8))
8385 g_object_set(G_OBJECT(gtkconv
->tab_label
), "ellipsize", PANGO_ELLIPSIZE_END
, NULL
);
8386 gtk_label_set_width_chars(GTK_LABEL(gtkconv
->tab_label
), 4);
8388 g_object_set(G_OBJECT(gtkconv
->tab_label
), "ellipsize", PANGO_ELLIPSIZE_NONE
, NULL
);
8389 gtk_label_set_width_chars(GTK_LABEL(gtkconv
->tab_label
), -1);
8393 gtk_label_set_width_chars(
8394 GTK_LABEL(gtkconv
->tab_label
),
8395 MIN(g_utf8_strlen(gtk_label_get_text(GTK_LABEL(gtkconv
->tab_label
)), -1), 12)
8399 gtk_label_set_angle(GTK_LABEL(gtkconv
->tab_label
), angle
);
8402 gtkconv
->tabby
= gtk_box_new(GTK_ORIENTATION_VERTICAL
, PIDGIN_HIG_BOX_SPACE
);
8404 gtkconv
->tabby
= gtk_box_new(GTK_ORIENTATION_HORIZONTAL
, PIDGIN_HIG_BOX_SPACE
);
8405 gtk_widget_set_name(gtkconv
->tabby
, "tab-container");
8407 /* select the correct ordering for verticle tabs */
8409 first
= gtkconv
->close
;
8410 third
= gtkconv
->icon
;
8412 first
= gtkconv
->icon
;
8413 third
= gtkconv
->close
;
8416 ebox
= gtk_event_box_new();
8417 gtk_event_box_set_visible_window(GTK_EVENT_BOX(ebox
), FALSE
);
8418 gtk_container_add(GTK_CONTAINER(ebox
), gtkconv
->tabby
);
8419 g_signal_connect(G_OBJECT(ebox
), "enter-notify-event",
8420 G_CALLBACK(gtkconv_tab_set_tip
), gtkconv
);
8422 parent
= gtk_widget_get_parent(gtkconv
->tab_label
);
8423 if (parent
!= NULL
) {
8424 /* reparent old widgets on preference changes */
8425 g_object_ref(first
);
8426 g_object_ref(gtkconv
->tab_label
);
8427 g_object_ref(third
);
8428 gtk_container_remove(GTK_CONTAINER(parent
), first
);
8429 gtk_container_remove(GTK_CONTAINER(parent
), gtkconv
->tab_label
);
8430 gtk_container_remove(GTK_CONTAINER(parent
), third
);
8433 gtk_box_pack_start(GTK_BOX(gtkconv
->tabby
), first
, FALSE
, FALSE
, 0);
8434 gtk_box_pack_start(GTK_BOX(gtkconv
->tabby
), gtkconv
->tab_label
, TRUE
, TRUE
, 0);
8435 gtk_box_pack_start(GTK_BOX(gtkconv
->tabby
), third
, FALSE
, FALSE
, 0);
8437 if (parent
== NULL
) {
8438 /* Add this pane to the conversation's notebook. */
8439 gtk_notebook_append_page(GTK_NOTEBOOK(win
->notebook
), gtkconv
->tab_cont
, ebox
);
8441 /* reparent old widgets on preference changes */
8442 g_object_unref(first
);
8443 g_object_unref(gtkconv
->tab_label
);
8444 g_object_unref(third
);
8446 /* Reset the tabs label to the new version */
8447 gtk_notebook_set_tab_label(GTK_NOTEBOOK(win
->notebook
), gtkconv
->tab_cont
, ebox
);
8450 gtk_container_child_set(GTK_CONTAINER(win
->notebook
), gtkconv
->tab_cont
,
8451 "tab-expand", !tabs_side
&& !angle
,
8452 "tab-fill", TRUE
, NULL
);
8454 if (pidgin_conv_window_get_gtkconv_count(win
) == 1)
8455 gtk_notebook_set_show_tabs(GTK_NOTEBOOK(win
->notebook
),
8456 purple_prefs_get_bool(PIDGIN_PREFS_ROOT
"/conversations/tabs") &&
8457 (!purple_prefs_get_bool(PIDGIN_PREFS_ROOT
"/conversations/im/show_buddy_icons") ||
8458 purple_prefs_get_int(PIDGIN_PREFS_ROOT
"/conversations/tab_side") != GTK_POS_TOP
));
8460 /* show the widgets */
8461 /* gtk_widget_show(gtkconv->icon); */
8462 gtk_widget_show(gtkconv
->tab_label
);
8463 if (purple_prefs_get_bool(PIDGIN_PREFS_ROOT
"/conversations/close_on_tabs"))
8464 gtk_widget_show(gtkconv
->close
);
8465 gtk_widget_show(gtkconv
->tabby
);
8466 gtk_widget_show(ebox
);
8470 pidgin_conv_window_remove_gtkconv(PidginConvWindow
*win
, PidginConversation
*gtkconv
)
8474 index
= gtk_notebook_page_num(GTK_NOTEBOOK(win
->notebook
), gtkconv
->tab_cont
);
8476 g_object_ref_sink(G_OBJECT(gtkconv
->tab_cont
));
8478 gtk_notebook_remove_page(GTK_NOTEBOOK(win
->notebook
), index
);
8480 win
->gtkconvs
= g_list_remove(win
->gtkconvs
, gtkconv
);
8482 g_signal_handlers_disconnect_matched(win
->window
, G_SIGNAL_MATCH_DATA
,
8483 0, 0, NULL
, NULL
, gtkconv
);
8485 if (win
->gtkconvs
&& win
->gtkconvs
->next
== NULL
)
8486 pidgin_conv_tab_pack(win
, win
->gtkconvs
->data
);
8488 if (!win
->gtkconvs
&& win
!= hidden_convwin
)
8489 pidgin_conv_window_destroy(win
);
8492 PidginConversation
*
8493 pidgin_conv_window_get_gtkconv_at_index(const PidginConvWindow
*win
, int index
)
8495 GtkWidget
*tab_cont
;
8499 tab_cont
= gtk_notebook_get_nth_page(GTK_NOTEBOOK(win
->notebook
), index
);
8500 return tab_cont
? g_object_get_data(G_OBJECT(tab_cont
), "PidginConversation") : NULL
;
8503 PidginConversation
*
8504 pidgin_conv_window_get_active_gtkconv(const PidginConvWindow
*win
)
8507 GtkWidget
*tab_cont
;
8509 index
= gtk_notebook_get_current_page(GTK_NOTEBOOK(win
->notebook
));
8512 tab_cont
= gtk_notebook_get_nth_page(GTK_NOTEBOOK(win
->notebook
), index
);
8515 return g_object_get_data(G_OBJECT(tab_cont
), "PidginConversation");
8519 PurpleConversation
*
8520 pidgin_conv_window_get_active_conversation(const PidginConvWindow
*win
)
8522 PidginConversation
*gtkconv
;
8524 gtkconv
= pidgin_conv_window_get_active_gtkconv(win
);
8525 return gtkconv
? gtkconv
->active_conv
: NULL
;
8529 pidgin_conv_window_is_active_conversation(const PurpleConversation
*conv
)
8531 return conv
== pidgin_conv_window_get_active_conversation(PIDGIN_CONVERSATION(conv
)->win
);
8535 pidgin_conv_window_has_focus(PidginConvWindow
*win
)
8537 gboolean has_focus
= FALSE
;
8539 g_object_get(G_OBJECT(win
->window
), "has-toplevel-focus", &has_focus
, NULL
);
8545 pidgin_conv_window_get_at_event(GdkEvent
*event
)
8547 PidginConvWindow
*win
;
8552 gdkwin
= gdk_device_get_window_at_position(gdk_event_get_device(event
),
8556 gdkwin
= gdk_window_get_toplevel(gdkwin
);
8558 for (l
= pidgin_conv_windows_get_list(); l
!= NULL
; l
= l
->next
) {
8561 if (gdkwin
== gtk_widget_get_window(win
->window
))
8569 pidgin_conv_window_get_gtkconvs(PidginConvWindow
*win
)
8571 return win
->gtkconvs
;
8575 pidgin_conv_window_get_gtkconv_count(PidginConvWindow
*win
)
8577 return g_list_length(win
->gtkconvs
);
8581 pidgin_conv_window_first_im(void)
8583 GList
*wins
, *convs
;
8584 PidginConvWindow
*win
;
8585 PidginConversation
*conv
;
8587 for (wins
= pidgin_conv_windows_get_list(); wins
!= NULL
; wins
= wins
->next
) {
8590 for (convs
= win
->gtkconvs
;
8592 convs
= convs
->next
) {
8596 if (PURPLE_IS_IM_CONVERSATION(conv
->active_conv
))
8605 pidgin_conv_window_last_im(void)
8607 GList
*wins
, *convs
;
8608 PidginConvWindow
*win
;
8609 PidginConversation
*conv
;
8611 for (wins
= g_list_last(pidgin_conv_windows_get_list());
8613 wins
= wins
->prev
) {
8617 for (convs
= win
->gtkconvs
;
8619 convs
= convs
->next
) {
8623 if (PURPLE_IS_IM_CONVERSATION(conv
->active_conv
))
8632 pidgin_conv_window_first_chat(void)
8634 GList
*wins
, *convs
;
8635 PidginConvWindow
*win
;
8636 PidginConversation
*conv
;
8638 for (wins
= pidgin_conv_windows_get_list(); wins
!= NULL
; wins
= wins
->next
) {
8641 for (convs
= win
->gtkconvs
;
8643 convs
= convs
->next
) {
8647 if (PURPLE_IS_CHAT_CONVERSATION(conv
->active_conv
))
8656 pidgin_conv_window_last_chat(void)
8658 GList
*wins
, *convs
;
8659 PidginConvWindow
*win
;
8660 PidginConversation
*conv
;
8662 for (wins
= g_list_last(pidgin_conv_windows_get_list());
8664 wins
= wins
->prev
) {
8668 for (convs
= win
->gtkconvs
;
8670 convs
= convs
->next
) {
8674 if (PURPLE_IS_CHAT_CONVERSATION(conv
->active_conv
))
8683 /**************************************************************************
8684 * Conversation placement functions
8685 **************************************************************************/
8690 PidginConvPlacementFunc fnc
;
8692 } ConvPlacementData
;
8694 static GList
*conv_placement_fncs
= NULL
;
8695 static PidginConvPlacementFunc place_conv
= NULL
;
8697 /* This one places conversations in the last made window. */
8699 conv_placement_last_created_win(PidginConversation
*conv
)
8701 PidginConvWindow
*win
;
8703 GList
*l
= g_list_last(pidgin_conv_windows_get_list());
8704 win
= l
? l
->data
: NULL
;;
8707 win
= pidgin_conv_window_new();
8709 g_signal_connect(G_OBJECT(win
->window
), "configure_event",
8710 G_CALLBACK(gtk_conv_configure_cb
), NULL
);
8712 pidgin_conv_window_add_gtkconv(win
, conv
);
8713 pidgin_conv_window_show(win
);
8715 pidgin_conv_window_add_gtkconv(win
, conv
);
8719 /* This one places conversations in the last made window of the same type. */
8721 conv_placement_last_created_win_type_configured_cb(GtkWidget
*w
,
8722 GdkEventConfigure
*event
, PidginConversation
*conv
)
8727 if (gtk_widget_get_visible(w
))
8728 gtk_window_get_position(GTK_WINDOW(w
), &x
, &y
);
8730 return FALSE
; /* carry on normally */
8732 /* Workaround for GTK+ bug # 169811 - "configure_event" is fired
8733 * when the window is being maximized */
8734 if (gdk_window_get_state(gtk_widget_get_window(w
)) & GDK_WINDOW_STATE_MAXIMIZED
)
8737 /* don't save off-screen positioning */
8738 if (x
+ event
->width
< 0 ||
8739 y
+ event
->height
< 0 ||
8740 x
> gdk_screen_width() ||
8741 y
> gdk_screen_height())
8742 return FALSE
; /* carry on normally */
8744 for (all
= conv
->convs
; all
!= NULL
; all
= all
->next
) {
8745 if (PURPLE_IS_IM_CONVERSATION(conv
->active_conv
) != PURPLE_IS_IM_CONVERSATION(all
->data
)) {
8746 /* this window has different types of conversation, don't save */
8751 if (PURPLE_IS_IM_CONVERSATION(conv
->active_conv
)) {
8752 purple_prefs_set_int(PIDGIN_PREFS_ROOT
"/conversations/im/x", x
);
8753 purple_prefs_set_int(PIDGIN_PREFS_ROOT
"/conversations/im/y", y
);
8754 purple_prefs_set_int(PIDGIN_PREFS_ROOT
"/conversations/im/width", event
->width
);
8755 purple_prefs_set_int(PIDGIN_PREFS_ROOT
"/conversations/im/height", event
->height
);
8756 } else if (PURPLE_IS_CHAT_CONVERSATION(conv
->active_conv
)) {
8757 purple_prefs_set_int(PIDGIN_PREFS_ROOT
"/conversations/chat/x", x
);
8758 purple_prefs_set_int(PIDGIN_PREFS_ROOT
"/conversations/chat/y", y
);
8759 purple_prefs_set_int(PIDGIN_PREFS_ROOT
"/conversations/chat/width", event
->width
);
8760 purple_prefs_set_int(PIDGIN_PREFS_ROOT
"/conversations/chat/height", event
->height
);
8767 conv_placement_last_created_win_type(PidginConversation
*conv
)
8769 PidginConvWindow
*win
;
8771 if (PURPLE_IS_IM_CONVERSATION(conv
->active_conv
))
8772 win
= pidgin_conv_window_last_im();
8774 win
= pidgin_conv_window_last_chat();
8777 win
= pidgin_conv_window_new();
8779 if (PURPLE_IS_IM_CONVERSATION(conv
->active_conv
) ||
8780 purple_prefs_get_int(PIDGIN_PREFS_ROOT
"/conversations/chat/width") == 0) {
8781 pidgin_conv_set_position_size(win
,
8782 purple_prefs_get_int(PIDGIN_PREFS_ROOT
"/conversations/im/x"),
8783 purple_prefs_get_int(PIDGIN_PREFS_ROOT
"/conversations/im/y"),
8784 purple_prefs_get_int(PIDGIN_PREFS_ROOT
"/conversations/im/width"),
8785 purple_prefs_get_int(PIDGIN_PREFS_ROOT
"/conversations/im/height"));
8786 } else if (PURPLE_IS_CHAT_CONVERSATION(conv
->active_conv
)) {
8787 pidgin_conv_set_position_size(win
,
8788 purple_prefs_get_int(PIDGIN_PREFS_ROOT
"/conversations/chat/x"),
8789 purple_prefs_get_int(PIDGIN_PREFS_ROOT
"/conversations/chat/y"),
8790 purple_prefs_get_int(PIDGIN_PREFS_ROOT
"/conversations/chat/width"),
8791 purple_prefs_get_int(PIDGIN_PREFS_ROOT
"/conversations/chat/height"));
8794 pidgin_conv_window_add_gtkconv(win
, conv
);
8795 pidgin_conv_window_show(win
);
8797 g_signal_connect(G_OBJECT(win
->window
), "configure_event",
8798 G_CALLBACK(conv_placement_last_created_win_type_configured_cb
), conv
);
8800 pidgin_conv_window_add_gtkconv(win
, conv
);
8803 /* This one places each conversation in its own window. */
8805 conv_placement_new_window(PidginConversation
*conv
)
8807 PidginConvWindow
*win
;
8809 win
= pidgin_conv_window_new();
8811 g_signal_connect(G_OBJECT(win
->window
), "configure_event",
8812 G_CALLBACK(gtk_conv_configure_cb
), NULL
);
8814 pidgin_conv_window_add_gtkconv(win
, conv
);
8816 pidgin_conv_window_show(win
);
8819 static PurpleGroup
*
8820 conv_get_group(PidginConversation
*conv
)
8822 PurpleGroup
*group
= NULL
;
8824 if (PURPLE_IS_IM_CONVERSATION(conv
->active_conv
)) {
8827 buddy
= purple_blist_find_buddy(purple_conversation_get_account(conv
->active_conv
),
8828 purple_conversation_get_name(conv
->active_conv
));
8831 group
= purple_buddy_get_group(buddy
);
8833 } else if (PURPLE_IS_CHAT_CONVERSATION(conv
->active_conv
)) {
8836 chat
= purple_blist_find_chat(purple_conversation_get_account(conv
->active_conv
),
8837 purple_conversation_get_name(conv
->active_conv
));
8840 group
= purple_chat_get_group(chat
);
8847 * This groups things by, well, group. Buddies from groups will always be
8848 * grouped together, and a buddy from a group not belonging to any currently
8849 * open windows will get a new window.
8852 conv_placement_by_group(PidginConversation
*conv
)
8854 PurpleGroup
*group
= NULL
;
8857 group
= conv_get_group(conv
);
8859 /* Go through the list of IMs and find one with this group. */
8860 for (wl
= pidgin_conv_windows_get_list(); wl
!= NULL
; wl
= wl
->next
) {
8861 PidginConvWindow
*win2
;
8862 PidginConversation
*conv2
;
8863 PurpleGroup
*group2
= NULL
;
8867 for (cl
= win2
->gtkconvs
;
8872 group2
= conv_get_group(conv2
);
8874 if (group
== group2
) {
8875 pidgin_conv_window_add_gtkconv(win2
, conv
);
8882 /* Make a new window. */
8883 conv_placement_new_window(conv
);
8886 /* This groups things by account. Otherwise, the same semantics as above */
8888 conv_placement_by_account(PidginConversation
*conv
)
8890 GList
*wins
, *convs
;
8891 PurpleAccount
*account
;
8893 account
= purple_conversation_get_account(conv
->active_conv
);
8895 /* Go through the list of IMs and find one with this group. */
8896 for (wins
= pidgin_conv_windows_get_list(); wins
!= NULL
; wins
= wins
->next
) {
8897 PidginConvWindow
*win2
;
8898 PidginConversation
*conv2
;
8902 for (convs
= win2
->gtkconvs
;
8904 convs
= convs
->next
) {
8905 conv2
= convs
->data
;
8907 if (account
== purple_conversation_get_account(conv2
->active_conv
)) {
8908 pidgin_conv_window_add_gtkconv(win2
, conv
);
8914 /* Make a new window. */
8915 conv_placement_new_window(conv
);
8918 static ConvPlacementData
*
8919 get_conv_placement_data(const char *id
)
8921 ConvPlacementData
*data
= NULL
;
8924 for (n
= conv_placement_fncs
; n
; n
= n
->next
) {
8926 if (purple_strequal(data
->id
, id
))
8934 add_conv_placement_fnc(const char *id
, const char *name
,
8935 PidginConvPlacementFunc fnc
)
8937 ConvPlacementData
*data
;
8939 data
= g_new(ConvPlacementData
, 1);
8941 data
->id
= g_strdup(id
);
8942 data
->name
= g_strdup(name
);
8945 conv_placement_fncs
= g_list_append(conv_placement_fncs
, data
);
8949 ensure_default_funcs(void)
8951 if (conv_placement_fncs
== NULL
) {
8952 add_conv_placement_fnc("last", _("Last created window"),
8953 conv_placement_last_created_win
);
8954 add_conv_placement_fnc("im_chat", _("Separate IM and Chat windows"),
8955 conv_placement_last_created_win_type
);
8956 add_conv_placement_fnc("new", _("New window"),
8957 conv_placement_new_window
);
8958 add_conv_placement_fnc("group", _("By group"),
8959 conv_placement_by_group
);
8960 add_conv_placement_fnc("account", _("By account"),
8961 conv_placement_by_account
);
8966 pidgin_conv_placement_get_options(void)
8968 GList
*n
, *list
= NULL
;
8969 ConvPlacementData
*data
;
8971 ensure_default_funcs();
8973 for (n
= conv_placement_fncs
; n
; n
= n
->next
) {
8975 list
= g_list_append(list
, data
->name
);
8976 list
= g_list_append(list
, data
->id
);
8984 pidgin_conv_placement_add_fnc(const char *id
, const char *name
,
8985 PidginConvPlacementFunc fnc
)
8987 g_return_if_fail(id
!= NULL
);
8988 g_return_if_fail(name
!= NULL
);
8989 g_return_if_fail(fnc
!= NULL
);
8991 ensure_default_funcs();
8993 add_conv_placement_fnc(id
, name
, fnc
);
8997 pidgin_conv_placement_remove_fnc(const char *id
)
8999 ConvPlacementData
*data
= get_conv_placement_data(id
);
9004 conv_placement_fncs
= g_list_remove(conv_placement_fncs
, data
);
9012 pidgin_conv_placement_get_name(const char *id
)
9014 ConvPlacementData
*data
;
9016 ensure_default_funcs();
9018 data
= get_conv_placement_data(id
);
9026 PidginConvPlacementFunc
9027 pidgin_conv_placement_get_fnc(const char *id
)
9029 ConvPlacementData
*data
;
9031 ensure_default_funcs();
9033 data
= get_conv_placement_data(id
);
9042 pidgin_conv_placement_set_current_func(PidginConvPlacementFunc func
)
9044 g_return_if_fail(func
!= NULL
);
9046 /* If tabs are enabled, set the function, otherwise, NULL it out. */
9047 if (purple_prefs_get_bool(PIDGIN_PREFS_ROOT
"/conversations/tabs"))
9053 PidginConvPlacementFunc
9054 pidgin_conv_placement_get_current_func(void)
9060 pidgin_conv_placement_place(PidginConversation
*gtkconv
)
9063 place_conv(gtkconv
);
9065 conv_placement_new_window(gtkconv
);
9069 pidgin_conv_is_hidden(PidginConversation
*gtkconv
)
9071 g_return_val_if_fail(gtkconv
!= NULL
, FALSE
);
9073 return (gtkconv
->win
== hidden_convwin
);
9077 gdouble
luminance(GdkRGBA color
)
9081 gdouble cutoff
= 0.03928, scale
= 12.92;
9082 gdouble a
= 0.055, d
= 1.055, p
= 2.2;
9088 r
= (rr
> cutoff
) ? pow((rr
+a
)/d
, p
) : rr
/scale
;
9089 g
= (gg
> cutoff
) ? pow((gg
+a
)/d
, p
) : gg
/scale
;
9090 b
= (bb
> cutoff
) ? pow((bb
+a
)/d
, p
) : bb
/scale
;
9092 return (r
*0.2126 + g
*0.7152 + b
*0.0722);
9095 /* Algorithm from https://www.w3.org/TR/2008/REC-WCAG20-20081211/relative-luminance.xml */
9097 color_is_visible(GdkRGBA foreground
, GdkRGBA background
, gdouble min_contrast_ratio
)
9099 gdouble lfg
, lbg
, lmin
, lmax
;
9100 gdouble luminosity_ratio
;
9103 lfg
= luminance(foreground
);
9104 lbg
= luminance(background
);
9107 lmax
= lfg
, lmin
= lbg
;
9109 lmax
= lbg
, lmin
= lfg
;
9111 nr
= lmax
+ 0.05, dr
= lmin
- 0.05;
9112 if (dr
< 0.005 && dr
> -0.005)
9115 luminosity_ratio
= nr
/dr
;
9116 if ( luminosity_ratio
< 0)
9117 luminosity_ratio
*= -1.0;
9118 return (luminosity_ratio
> min_contrast_ratio
);
9123 generate_nick_colors(guint numcolors
, GdkRGBA background
)
9126 GArray
*colors
= g_array_new(FALSE
, FALSE
, sizeof(GdkRGBA
));
9127 GdkRGBA nick_highlight
;
9129 time_t breakout_time
;
9131 gdk_rgba_parse(&nick_highlight
, DEFAULT_HIGHLIGHT_COLOR
);
9132 gdk_rgba_parse(&send_color
, DEFAULT_SEND_COLOR
);
9134 pidgin_style_adjust_contrast(NULL
, &nick_highlight
);
9135 pidgin_style_adjust_contrast(NULL
, &send_color
);
9137 srand(background
.red
* 65535 + background
.green
* 65535 + background
.blue
* 65535 + 1);
9139 breakout_time
= time(NULL
) + 3;
9141 /* first we look through the list of "good" colors: colors that differ from every other color in the
9142 * list. only some of them will differ from the background color though. lets see if we can find
9143 * numcolors of them that do
9145 while (i
< numcolors
&& j
< PIDGIN_NUM_NICK_SEED_COLORS
&& time(NULL
) < breakout_time
)
9147 GdkRGBA color
= nick_seed_colors
[j
];
9149 if (color_is_visible(color
, background
, MIN_LUMINANCE_CONTRAST_RATIO
) &&
9150 color_is_visible(color
, nick_highlight
, MIN_LUMINANCE_CONTRAST_RATIO
) &&
9151 color_is_visible(color
, send_color
, MIN_LUMINANCE_CONTRAST_RATIO
))
9153 g_array_append_val(colors
, color
);
9159 /* we might not have found numcolors in the last loop. if we did, we'll never enter this one.
9160 * if we did not, lets just find some colors that don't conflict with the background. its
9161 * expensive to find colors that not only don't conflict with the background, but also do not
9162 * conflict with each other.
9164 while(i
< numcolors
&& time(NULL
) < breakout_time
)
9166 GdkRGBA color
= {g_random_double_range(0, 1), g_random_double_range(0, 1), g_random_double_range(0, 1), 1};
9168 if (color_is_visible(color
, background
, MIN_LUMINANCE_CONTRAST_RATIO
) &&
9169 color_is_visible(color
, nick_highlight
, MIN_LUMINANCE_CONTRAST_RATIO
) &&
9170 color_is_visible(color
, send_color
, MIN_LUMINANCE_CONTRAST_RATIO
))
9172 g_array_append_val(colors
, color
);
9177 if (i
< numcolors
) {
9178 purple_debug_warning("gtkconv", "Unable to generate enough random colors before timeout. %u colors found.\n", i
);
9182 /* To remove errors caused by an empty array. */
9183 GdkRGBA color
= {0.5, 0.5, 0.5, 1.0};
9184 g_array_append_val(colors
, color
);
9190 /**************************************************************************
9191 * PidginConvWindow GBoxed code
9192 **************************************************************************/
9193 static PidginConvWindow
*
9194 pidgin_conv_window_ref(PidginConvWindow
*win
)
9196 g_return_val_if_fail(win
!= NULL
, NULL
);
9204 pidgin_conv_window_unref(PidginConvWindow
*win
)
9206 g_return_if_fail(win
!= NULL
);
9207 g_return_if_fail(win
->box_count
>= 0);
9209 if (!win
->box_count
--)
9210 pidgin_conv_window_destroy(win
);
9214 pidgin_conv_window_get_type(void)
9216 static GType type
= 0;
9219 type
= g_boxed_type_register_static("PidginConvWindow",
9220 (GBoxedCopyFunc
)pidgin_conv_window_ref
,
9221 (GBoxedFreeFunc
)pidgin_conv_window_unref
);