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_free_full(list
, g_object_unref
);
244 close_conv_cb(GtkButton
*button
, PidginConversation
*gtkconv
)
246 /* We are going to destroy the conversations immediately only if the 'close immediately'
247 * preference is selected. Otherwise, close the conversation after a reasonable timeout
248 * (I am going to consider 10 minutes as a 'reasonable timeout' here.
249 * For chats, close immediately if the chat is not in the buddylist, or if the chat is
250 * not marked 'Persistent' */
251 PurpleConversation
*conv
= gtkconv
->active_conv
;
252 PurpleAccount
*account
= purple_conversation_get_account(conv
);
253 const char *name
= purple_conversation_get_name(conv
);
255 if (PURPLE_IS_IM_CONVERSATION(conv
)) {
256 if (purple_prefs_get_bool(PIDGIN_PREFS_ROOT
"/conversations/im/close_immediately"))
257 close_this_sucker(gtkconv
);
259 hide_conv(gtkconv
, TRUE
);
260 } else if (PURPLE_IS_CHAT_CONVERSATION(conv
)) {
261 PurpleChat
*chat
= purple_blist_find_chat(account
, name
);
263 !purple_blist_node_get_bool(&chat
->node
, "gtk-persistent"))
264 close_this_sucker(gtkconv
);
266 hide_conv(gtkconv
, FALSE
);
273 lbox_size_allocate_cb(GtkWidget
*w
, GtkAllocation
*allocation
, gpointer data
)
275 purple_prefs_set_int(PIDGIN_PREFS_ROOT
"/conversations/chat/userlist_width", allocation
->width
== 1 ? 0 : allocation
->width
);
281 pidgin_get_cmd_prefix(void)
287 say_command_cb(PurpleConversation
*conv
,
288 const char *cmd
, char **args
, char **error
, void *data
)
290 purple_conversation_send(conv
, args
[0]);
292 return PURPLE_CMD_RET_OK
;
296 me_command_cb(PurpleConversation
*conv
,
297 const char *cmd
, char **args
, char **error
, void *data
)
301 tmp
= g_strdup_printf("/me %s", args
[0]);
302 purple_conversation_send(conv
, tmp
);
305 return PURPLE_CMD_RET_OK
;
309 debug_command_cb(PurpleConversation
*conv
,
310 const char *cmd
, char **args
, char **error
, void *data
)
314 if (!g_ascii_strcasecmp(args
[0], "version")) {
315 tmp
= g_strdup_printf("Using Pidgin v%s with libpurple v%s.",
316 DISPLAY_VERSION
, purple_core_get_version());
317 } else if (!g_ascii_strcasecmp(args
[0], "plugins")) {
318 /* Show all the loaded plugins, including plugins marked internal.
319 * This is intentional, since third party protocols are often sources of bugs, and some
320 * plugin loaders can also be buggy.
322 GString
*str
= g_string_new("Loaded Plugins: ");
323 const GList
*plugins
= purple_plugins_get_loaded();
325 for (; plugins
; plugins
= plugins
->next
) {
326 GPluginPluginInfo
*info
= GPLUGIN_PLUGIN_INFO(
327 purple_plugin_get_info(
328 PURPLE_PLUGIN(plugins
->data
)));
329 str
= g_string_append(
331 gplugin_plugin_info_get_name(info
));
334 str
= g_string_append(str
, ", ");
337 str
= g_string_append(str
, "(none)");
340 tmp
= g_string_free(str
, FALSE
);
341 } else if (!g_ascii_strcasecmp(args
[0], "unsafe")) {
342 if (purple_debug_is_unsafe()) {
343 purple_debug_set_unsafe(FALSE
);
344 purple_conversation_write_system_message(conv
,
345 _("Unsafe debugging is now disabled."),
346 PURPLE_MESSAGE_NO_LOG
);
348 purple_debug_set_unsafe(TRUE
);
349 purple_conversation_write_system_message(conv
,
350 _("Unsafe debugging is now enabled."),
351 PURPLE_MESSAGE_NO_LOG
);
354 return PURPLE_CMD_RET_OK
;
355 } else if (!g_ascii_strcasecmp(args
[0], "verbose")) {
356 if (purple_debug_is_verbose()) {
357 purple_debug_set_verbose(FALSE
);
358 purple_conversation_write_system_message(conv
,
359 _("Verbose debugging is now disabled."),
360 PURPLE_MESSAGE_NO_LOG
);
362 purple_debug_set_verbose(TRUE
);
363 purple_conversation_write_system_message(conv
,
364 _("Verbose debugging is now enabled."),
365 PURPLE_MESSAGE_NO_LOG
);
368 return PURPLE_CMD_RET_OK
;
370 purple_conversation_write_system_message(conv
,
371 _("Supported debug options are: plugins, version, unsafe, verbose"),
372 PURPLE_MESSAGE_NO_LOG
);
373 return PURPLE_CMD_RET_OK
;
376 markup
= g_markup_escape_text(tmp
, -1);
377 purple_conversation_send(conv
, markup
);
381 return PURPLE_CMD_RET_OK
;
384 static void clear_conversation_scrollback_cb(PurpleConversation
*conv
,
387 PidginConversation
*gtkconv
= PIDGIN_CONVERSATION(conv
);
389 if (PIDGIN_CONVERSATION(conv
)) {
390 gtkconv
->last_flags
= 0;
395 clear_command_cb(PurpleConversation
*conv
,
396 const char *cmd
, char **args
, char **error
, void *data
)
398 purple_conversation_clear_message_history(conv
);
399 return PURPLE_CMD_RET_OK
;
403 clearall_command_cb(PurpleConversation
*conv
,
404 const char *cmd
, char **args
, char **error
, void *data
)
407 for (l
= purple_conversations_get_all(); l
!= NULL
; l
= l
->next
)
408 purple_conversation_clear_message_history(PURPLE_CONVERSATION(l
->data
));
410 return PURPLE_CMD_RET_OK
;
414 help_command_cb(PurpleConversation
*conv
,
415 const char *cmd
, char **args
, char **error
, void *data
)
420 if (args
[0] != NULL
) {
421 s
= g_string_new("");
422 text
= purple_cmd_help(conv
, args
[0]);
425 for (l
= text
; l
; l
= l
->next
)
427 g_string_append_printf(s
, "%s\n", (char *)l
->data
);
429 g_string_append_printf(s
, "%s", (char *)l
->data
);
431 g_string_append(s
, _("No such command (in this context)."));
434 s
= g_string_new(_("Use \"/help <command>\" for help with a "
435 "specific command.<br/>The following commands are available "
436 "in this context:<br/>"));
438 text
= purple_cmd_list(conv
);
439 for (l
= text
; l
; l
= l
->next
)
441 g_string_append_printf(s
, "%s, ", (char *)l
->data
);
443 g_string_append_printf(s
, "%s.", (char *)l
->data
);
447 purple_conversation_write_system_message(conv
, s
->str
, PURPLE_MESSAGE_NO_LOG
);
448 g_string_free(s
, TRUE
);
450 return PURPLE_CMD_RET_OK
;
454 send_history_add(PidginConversation
*gtkconv
, const char *message
)
458 first
= g_list_first(gtkconv
->send_history
);
460 first
->data
= g_strdup(message
);
461 gtkconv
->send_history
= g_list_prepend(first
, NULL
);
465 check_for_and_do_command(PurpleConversation
*conv
)
467 PidginConversation
*gtkconv
;
468 GtkWidget
*view
= NULL
;
469 GtkTextBuffer
*buffer
= NULL
;
472 gboolean retval
= FALSE
;
474 gtkconv
= PIDGIN_CONVERSATION(conv
);
475 prefix
= pidgin_get_cmd_prefix();
477 view
= talkatu_editor_get_view(TALKATU_EDITOR(gtkconv
->editor
));
478 buffer
= gtk_text_view_get_buffer(GTK_TEXT_VIEW(view
));
480 cmd
= talkatu_buffer_get_plain_text(TALKATU_BUFFER(buffer
));
482 if (cmd
&& purple_str_has_prefix(cmd
, prefix
)) {
483 PurpleCmdStatus status
;
484 char *error
, *cmdline
, *markup
, *send_history
;
486 send_history
= talkatu_markup_get_html(buffer
, NULL
);
487 send_history_add(gtkconv
, send_history
);
488 g_free(send_history
);
490 cmdline
= cmd
+ strlen(prefix
);
492 if (purple_strequal(cmdline
, "xyzzy")) {
493 purple_conversation_write_system_message(conv
,
494 "Nothing happens", PURPLE_MESSAGE_NO_LOG
);
499 /* Docs are unclear on whether or not prefix should be removed from
500 * the markup so, ignoring for now. Notably if the markup is
501 * `<b>/foo arg1</b>` we now have to move the bold tag around?
503 markup
= talkatu_markup_get_html(buffer
, NULL
);
504 status
= purple_cmd_do_command(conv
, cmdline
, markup
, &error
);
508 case PURPLE_CMD_STATUS_OK
:
511 case PURPLE_CMD_STATUS_NOT_FOUND
:
513 PurpleProtocol
*protocol
= NULL
;
514 PurpleConnection
*gc
;
516 if ((gc
= purple_conversation_get_connection(conv
)))
517 protocol
= purple_connection_get_protocol(gc
);
519 if ((protocol
!= NULL
) && (purple_protocol_get_options(protocol
) & OPT_PROTO_SLASH_COMMANDS_NATIVE
)) {
522 /* If the first word in the entered text has a '/' in it, then the user
523 * probably didn't mean it as a command. So send the text as message. */
524 spaceslash
= cmdline
;
525 while (*spaceslash
&& *spaceslash
!= ' ' && *spaceslash
!= '/')
528 if (*spaceslash
!= '/') {
529 purple_conversation_write_system_message(conv
,
530 _("Unknown command."), PURPLE_MESSAGE_NO_LOG
);
536 case PURPLE_CMD_STATUS_WRONG_ARGS
:
537 purple_conversation_write_system_message(conv
,
538 _("Syntax Error: You typed the wrong "
539 "number of arguments to that command."),
540 PURPLE_MESSAGE_NO_LOG
);
543 case PURPLE_CMD_STATUS_FAILED
:
544 purple_conversation_write_system_message(conv
,
545 error
? error
: _("Your command failed for an unknown reason."),
546 PURPLE_MESSAGE_NO_LOG
);
550 case PURPLE_CMD_STATUS_WRONG_TYPE
:
551 if(PURPLE_IS_IM_CONVERSATION(conv
))
552 purple_conversation_write_system_message(conv
,
553 _("That command only works in chats, not IMs."),
554 PURPLE_MESSAGE_NO_LOG
);
556 purple_conversation_write_system_message(conv
,
557 _("That command only works in IMs, not chats."),
558 PURPLE_MESSAGE_NO_LOG
);
561 case PURPLE_CMD_STATUS_WRONG_PROTOCOL
:
562 purple_conversation_write_system_message(conv
,
563 _("That command doesn't work on this protocol."),
564 PURPLE_MESSAGE_NO_LOG
);
576 send_cb(GtkWidget
*widget
, PidginConversation
*gtkconv
)
578 PurpleConversation
*conv
= gtkconv
->active_conv
;
579 PurpleAccount
*account
;
580 PurpleMessageFlags flags
= 0;
581 GtkTextBuffer
*buffer
= NULL
;
584 account
= purple_conversation_get_account(conv
);
586 buffer
= talkatu_editor_get_buffer(TALKATU_EDITOR(gtkconv
->editor
));
588 if (check_for_and_do_command(conv
)) {
589 talkatu_buffer_clear(TALKATU_BUFFER(buffer
));
593 if (PURPLE_IS_CHAT_CONVERSATION(conv
) &&
594 purple_chat_conversation_has_left(PURPLE_CHAT_CONVERSATION(conv
))) {
598 if (!purple_account_is_connected(account
)) {
602 content
= talkatu_markup_get_html(buffer
, NULL
);
603 if (purple_strequal(content
, "")) {
610 /* XXX: is there a better way to tell if the message has images? */
611 // if (strstr(buf, "<img ") != NULL)
612 // flags |= PURPLE_MESSAGE_IMAGES;
614 purple_conversation_send_with_flags(conv
, content
, flags
);
618 talkatu_buffer_clear(TALKATU_BUFFER(buffer
));
619 gtkconv_set_unseen(gtkconv
, PIDGIN_UNSEEN_NONE
);
623 add_remove_cb(GtkWidget
*widget
, PidginConversation
*gtkconv
)
625 PurpleAccount
*account
;
627 PurpleConversation
*conv
= gtkconv
->active_conv
;
629 account
= purple_conversation_get_account(conv
);
630 name
= purple_conversation_get_name(conv
);
632 if (PURPLE_IS_IM_CONVERSATION(conv
)) {
635 b
= purple_blist_find_buddy(account
, name
);
637 pidgin_dialogs_remove_buddy(b
);
638 else if (account
!= NULL
&& purple_account_is_connected(account
))
639 purple_blist_request_add_buddy(account
, (char *)name
, NULL
, NULL
);
640 } else if (PURPLE_IS_CHAT_CONVERSATION(conv
)) {
643 c
= purple_blist_find_chat(account
, name
);
645 pidgin_dialogs_remove_chat(c
);
646 else if (account
!= NULL
&& purple_account_is_connected(account
))
647 purple_blist_request_add_chat(account
, NULL
, NULL
, name
);
651 static void chat_do_info(PidginConversation
*gtkconv
, const char *who
)
653 PurpleChatConversation
*chat
= PURPLE_CHAT_CONVERSATION(gtkconv
->active_conv
);
654 PurpleConnection
*gc
;
656 if ((gc
= purple_conversation_get_connection(gtkconv
->active_conv
))) {
657 pidgin_retrieve_user_info_in_chat(gc
, who
, purple_chat_conversation_get_id(chat
));
663 info_cb(GtkWidget
*widget
, PidginConversation
*gtkconv
)
665 PurpleConversation
*conv
= gtkconv
->active_conv
;
667 if (PURPLE_IS_IM_CONVERSATION(conv
)) {
668 pidgin_retrieve_user_info(purple_conversation_get_connection(conv
),
669 purple_conversation_get_name(conv
));
670 } else if (PURPLE_IS_CHAT_CONVERSATION(conv
)) {
671 /* Get info of the person currently selected in the GtkTreeView */
672 PidginChatPane
*gtkchat
;
675 GtkTreeSelection
*sel
;
678 gtkchat
= gtkconv
->u
.chat
;
680 model
= gtk_tree_view_get_model(GTK_TREE_VIEW(gtkchat
->list
));
681 sel
= gtk_tree_view_get_selection(GTK_TREE_VIEW(gtkchat
->list
));
683 if (gtk_tree_selection_get_selected(sel
, NULL
, &iter
))
684 gtk_tree_model_get(GTK_TREE_MODEL(model
), &iter
, CHAT_USERS_NAME_COLUMN
, &name
, -1);
688 chat_do_info(gtkconv
, name
);
694 block_cb(GtkWidget
*widget
, PidginConversation
*gtkconv
)
696 PurpleConversation
*conv
= gtkconv
->active_conv
;
697 PurpleAccount
*account
;
699 account
= purple_conversation_get_account(conv
);
701 if (account
!= NULL
&& purple_account_is_connected(account
))
702 pidgin_request_add_block(account
, purple_conversation_get_name(conv
));
706 unblock_cb(GtkWidget
*widget
, PidginConversation
*gtkconv
)
708 PurpleConversation
*conv
= gtkconv
->active_conv
;
709 PurpleAccount
*account
;
711 account
= purple_conversation_get_account(conv
);
713 if (account
!= NULL
&& purple_account_is_connected(account
))
714 pidgin_request_add_permit(account
, purple_conversation_get_name(conv
));
718 do_invite(GtkWidget
*w
, int resp
, gpointer data
)
720 PidginInviteDialog
*dialog
= PIDGIN_INVITE_DIALOG(w
);
721 PurpleChatConversation
*chat
= pidgin_invite_dialog_get_conversation(dialog
);
722 const gchar
*contact
, *message
;
724 if (resp
== GTK_RESPONSE_ACCEPT
) {
725 contact
= pidgin_invite_dialog_get_contact(dialog
);
726 if (!g_ascii_strcasecmp(contact
, ""))
729 message
= pidgin_invite_dialog_get_message(dialog
);
731 purple_serv_chat_invite(purple_conversation_get_connection(PURPLE_CONVERSATION(chat
)),
732 purple_chat_conversation_get_id(chat
),
736 g_clear_pointer(&invite_dialog
, gtk_widget_destroy
);
740 invite_cb(GtkWidget
*widget
, PidginConversation
*gtkconv
)
742 PurpleChatConversation
*chat
= PURPLE_CHAT_CONVERSATION(gtkconv
->active_conv
);
744 if (invite_dialog
== NULL
) {
745 invite_dialog
= pidgin_invite_dialog_new(chat
);
747 /* Connect the signals. */
748 g_signal_connect(G_OBJECT(invite_dialog
), "response",
749 G_CALLBACK(do_invite
), NULL
);
752 gtk_widget_show_all(invite_dialog
);
756 menu_new_conv_cb(GtkAction
*action
, gpointer data
)
762 menu_join_chat_cb(GtkAction
*action
, gpointer data
)
764 pidgin_blist_joinchat_show();
768 savelog_writefile_cb(void *user_data
, const char *filename
)
770 PurpleConversation
*conv
= (PurpleConversation
*)user_data
;
771 PidginConversation
*gtkconv
= PIDGIN_CONVERSATION(conv
);
772 GtkTextBuffer
*buffer
= NULL
;
777 if ((fp
= g_fopen(filename
, "w+")) == NULL
) {
778 purple_notify_error(PIDGIN_CONVERSATION(conv
), NULL
,
779 _("Unable to open file."), NULL
,
780 purple_request_cpar_from_conversation(conv
));
784 name
= purple_conversation_get_name(conv
);
786 fprintf(fp
, "<html>\n");
787 fprintf(fp
, "<head>\n");
788 fprintf(fp
, "<meta http-equiv=\"content-type\" content=\"text/html; charset=UTF-8\">\n");
789 fprintf(fp
, "<title>%s</title>\n", name
);
790 fprintf(fp
, "</head>\n");
792 fprintf(fp
, "<body>\n");
793 fprintf(fp
, _("<h1>Conversation with %s</h1>\n"), name
);
794 buffer
= gtk_text_view_get_buffer(GTK_TEXT_VIEW(gtkconv
->history
));
795 text
= talkatu_markup_get_html(buffer
, NULL
);
796 fprintf(fp
, "%s", text
);
798 fprintf(fp
, "\n</body>\n");
800 fprintf(fp
, "</html>\n");
805 * It would be kinda cool if this gave the option of saving a
806 * plaintext v. HTML file.
809 menu_save_as_cb(GtkAction
*action
, gpointer data
)
811 PidginConvWindow
*win
= data
;
812 PurpleConversation
*conv
= pidgin_conv_window_get_active_conversation(win
);
813 PurpleAccount
*account
= purple_conversation_get_account(conv
);
814 PurpleBuddy
*buddy
= purple_blist_find_buddy(account
, purple_conversation_get_name(conv
));
820 name
= purple_buddy_get_contact_alias(buddy
);
822 name
= purple_normalize(account
, purple_conversation_get_name(conv
));
824 buf
= g_strdup_printf("%s.html", name
);
825 for (c
= buf
; *c
; c
++)
827 if (*c
== '/' || *c
== '\\')
830 purple_request_file(PIDGIN_CONVERSATION(conv
), _("Save Conversation"),
831 buf
, TRUE
, G_CALLBACK(savelog_writefile_cb
), NULL
,
832 purple_request_cpar_from_conversation(conv
), conv
);
838 menu_view_log_cb(GtkAction
*action
, gpointer data
)
840 PidginConvWindow
*win
= data
;
841 PurpleConversation
*conv
;
843 PidginBuddyList
*gtkblist
;
845 PurpleAccount
*account
;
849 conv
= pidgin_conv_window_get_active_conversation(win
);
851 if (PURPLE_IS_IM_CONVERSATION(conv
))
852 type
= PURPLE_LOG_IM
;
853 else if (PURPLE_IS_CHAT_CONVERSATION(conv
))
854 type
= PURPLE_LOG_CHAT
;
858 gtkblist
= pidgin_blist_get_default_gtk_blist();
860 pidgin_set_cursor(gtkblist
->window
, GDK_WATCH
);
861 pidgin_set_cursor(win
->window
, GDK_WATCH
);
863 name
= purple_conversation_get_name(conv
);
864 account
= purple_conversation_get_account(conv
);
866 buddies
= purple_blist_find_buddies(account
, name
);
867 for (cur
= buddies
; cur
!= NULL
; cur
= cur
->next
)
869 PurpleBlistNode
*node
= cur
->data
;
870 if ((node
!= NULL
) && ((node
->prev
!= NULL
) || (node
->next
!= NULL
)))
872 pidgin_log_show_contact((PurpleContact
*)node
->parent
);
873 g_slist_free(buddies
);
874 pidgin_clear_cursor(gtkblist
->window
);
875 pidgin_clear_cursor(win
->window
);
879 g_slist_free(buddies
);
881 pidgin_log_show(type
, name
, account
);
883 pidgin_clear_cursor(gtkblist
->window
);
884 pidgin_clear_cursor(win
->window
);
888 menu_clear_cb(GtkAction
*action
, gpointer data
)
890 PidginConvWindow
*win
= data
;
891 PurpleConversation
*conv
;
893 conv
= pidgin_conv_window_get_active_conversation(win
);
894 purple_conversation_clear_message_history(conv
);
898 menu_find_cb(GtkAction
*action
, gpointer data
)
900 PidginConvWindow
*gtkwin
= data
;
901 PidginConversation
*gtkconv
= pidgin_conv_window_get_active_gtkconv(gtkwin
);
902 gtk_widget_show_all(gtkconv
->quickfind_container
);
903 gtk_widget_grab_focus(gtkconv
->quickfind_entry
);
908 menu_initiate_media_call_cb(GtkAction
*action
, gpointer data
)
910 PidginConvWindow
*win
= (PidginConvWindow
*)data
;
911 PurpleConversation
*conv
= pidgin_conv_window_get_active_conversation(win
);
912 PurpleAccount
*account
= purple_conversation_get_account(conv
);
914 purple_protocol_initiate_media(account
,
915 purple_conversation_get_name(conv
),
916 action
== win
->menu
->audio_call
? PURPLE_MEDIA_AUDIO
:
917 action
== win
->menu
->video_call
? PURPLE_MEDIA_VIDEO
:
918 action
== win
->menu
->audio_video_call
? PURPLE_MEDIA_AUDIO
|
919 PURPLE_MEDIA_VIDEO
: PURPLE_MEDIA_NONE
);
924 menu_send_file_cb(GtkAction
*action
, gpointer data
)
926 PidginConvWindow
*win
= data
;
927 PurpleConversation
*conv
= pidgin_conv_window_get_active_conversation(win
);
929 if (PURPLE_IS_IM_CONVERSATION(conv
)) {
930 purple_serv_send_file(purple_conversation_get_connection(conv
), purple_conversation_get_name(conv
), NULL
);
936 menu_get_attention_cb(GObject
*obj
, gpointer data
)
938 PidginConvWindow
*win
= data
;
939 PurpleConversation
*conv
= pidgin_conv_window_get_active_conversation(win
);
941 if (PURPLE_IS_IM_CONVERSATION(conv
)) {
943 if ((GtkAction
*)obj
== win
->menu
->get_attention
)
946 index
= GPOINTER_TO_INT(g_object_get_data(G_OBJECT(obj
), "index"));
947 purple_protocol_send_attention(purple_conversation_get_connection(conv
),
948 purple_conversation_get_name(conv
), index
);
953 menu_add_pounce_cb(GtkAction
*action
, gpointer data
)
955 PidginConvWindow
*win
= data
;
956 PurpleConversation
*conv
;
958 conv
= pidgin_conv_window_get_active_gtkconv(win
)->active_conv
;
960 pidgin_pounce_editor_show(purple_conversation_get_account(conv
),
961 purple_conversation_get_name(conv
), NULL
);
965 menu_alias_cb(GtkAction
*action
, gpointer data
)
967 PidginConvWindow
*win
= data
;
968 PurpleConversation
*conv
;
969 PurpleAccount
*account
;
972 conv
= pidgin_conv_window_get_active_conversation(win
);
973 account
= purple_conversation_get_account(conv
);
974 name
= purple_conversation_get_name(conv
);
976 if (PURPLE_IS_IM_CONVERSATION(conv
)) {
979 b
= purple_blist_find_buddy(account
, name
);
981 pidgin_dialogs_alias_buddy(b
);
982 } else if (PURPLE_IS_CHAT_CONVERSATION(conv
)) {
985 c
= purple_blist_find_chat(account
, name
);
987 pidgin_dialogs_alias_chat(c
);
992 menu_get_info_cb(GtkAction
*action
, gpointer data
)
994 PidginConvWindow
*win
= data
;
995 PurpleConversation
*conv
;
997 conv
= pidgin_conv_window_get_active_conversation(win
);
999 info_cb(NULL
, PIDGIN_CONVERSATION(conv
));
1003 menu_invite_cb(GtkAction
*action
, gpointer data
)
1005 PidginConvWindow
*win
= data
;
1006 PurpleConversation
*conv
;
1008 conv
= pidgin_conv_window_get_active_conversation(win
);
1010 invite_cb(NULL
, PIDGIN_CONVERSATION(conv
));
1014 menu_block_cb(GtkAction
*action
, gpointer data
)
1016 PidginConvWindow
*win
= data
;
1017 PurpleConversation
*conv
;
1019 conv
= pidgin_conv_window_get_active_conversation(win
);
1021 block_cb(NULL
, PIDGIN_CONVERSATION(conv
));
1025 menu_unblock_cb(GtkAction
*action
, gpointer data
)
1027 PidginConvWindow
*win
= data
;
1028 PurpleConversation
*conv
;
1030 conv
= pidgin_conv_window_get_active_conversation(win
);
1032 unblock_cb(NULL
, PIDGIN_CONVERSATION(conv
));
1036 menu_add_remove_cb(GtkAction
*action
, gpointer data
)
1038 PidginConvWindow
*win
= data
;
1039 PurpleConversation
*conv
;
1041 conv
= pidgin_conv_window_get_active_conversation(win
);
1043 add_remove_cb(NULL
, PIDGIN_CONVERSATION(conv
));
1047 close_already(gpointer data
)
1049 g_object_unref(data
);
1054 hide_conv(PidginConversation
*gtkconv
, gboolean closetimer
)
1058 purple_signal_emit(pidgin_conversations_get_handle(),
1059 "conversation-hiding", gtkconv
);
1061 for (list
= g_list_copy(gtkconv
->convs
); list
; list
= g_list_delete_link(list
, list
)) {
1062 PurpleConversation
*conv
= list
->data
;
1064 guint timer
= GPOINTER_TO_INT(g_object_get_data(G_OBJECT(conv
), "close-timer"));
1066 g_source_remove(timer
);
1067 timer
= g_timeout_add_seconds(CLOSE_CONV_TIMEOUT_SECS
, close_already
, conv
);
1068 g_object_set_data(G_OBJECT(conv
), "close-timer", GINT_TO_POINTER(timer
));
1070 pidgin_conv_window_remove_gtkconv(gtkconv
->win
, gtkconv
);
1071 pidgin_conv_window_add_gtkconv(hidden_convwin
, gtkconv
);
1076 menu_close_conv_cb(GtkAction
*action
, gpointer data
)
1078 PidginConvWindow
*win
= data
;
1080 close_conv_cb(NULL
, PIDGIN_CONVERSATION(pidgin_conv_window_get_active_conversation(win
)));
1084 menu_logging_cb(GtkAction
*action
, gpointer data
)
1086 PidginConvWindow
*win
= data
;
1087 PurpleConversation
*conv
;
1089 PurpleBlistNode
*node
;
1091 conv
= pidgin_conv_window_get_active_conversation(win
);
1096 logging
= gtk_toggle_action_get_active(GTK_TOGGLE_ACTION(action
));
1098 if (logging
== purple_conversation_is_logging(conv
))
1101 node
= get_conversation_blist_node(conv
);
1105 /* Enable logging first so the message below can be logged. */
1106 purple_conversation_set_logging(conv
, TRUE
);
1108 purple_conversation_write_system_message(conv
,
1109 _("Logging started. Future messages in this conversation will be logged."), 0);
1113 purple_conversation_write_system_message(conv
,
1114 _("Logging stopped. Future messages in this conversation will not be logged."), 0);
1116 /* Disable the logging second, so that the above message can be logged. */
1117 purple_conversation_set_logging(conv
, FALSE
);
1120 /* Save the setting IFF it's different than the pref. */
1121 if (PURPLE_IS_IM_CONVERSATION(conv
)) {
1122 if (logging
== purple_prefs_get_bool("/purple/logging/log_ims"))
1123 purple_blist_node_remove_setting(node
, "enable-logging");
1125 purple_blist_node_set_bool(node
, "enable-logging", logging
);
1126 } else if (PURPLE_IS_CHAT_CONVERSATION(conv
)) {
1127 if (logging
== purple_prefs_get_bool("/purple/logging/log_chats"))
1128 purple_blist_node_remove_setting(node
, "enable-logging");
1130 purple_blist_node_set_bool(node
, "enable-logging", logging
);
1135 menu_toolbar_cb(GtkAction
*action
, gpointer data
)
1137 purple_prefs_set_bool(PIDGIN_PREFS_ROOT
"/conversations/show_formatting_toolbar",
1138 gtk_toggle_action_get_active(GTK_TOGGLE_ACTION(action
)));
1142 menu_sounds_cb(GtkAction
*action
, gpointer data
)
1144 PidginConvWindow
*win
= data
;
1145 PurpleConversation
*conv
;
1146 PidginConversation
*gtkconv
;
1147 PurpleBlistNode
*node
;
1149 conv
= pidgin_conv_window_get_active_conversation(win
);
1154 gtkconv
= PIDGIN_CONVERSATION(conv
);
1156 gtkconv
->make_sound
=
1157 gtk_toggle_action_get_active(GTK_TOGGLE_ACTION(action
));
1158 node
= get_conversation_blist_node(conv
);
1160 purple_blist_node_set_bool(node
, "gtk-mute-sound", !gtkconv
->make_sound
);
1164 chat_do_im(PidginConversation
*gtkconv
, const char *who
)
1166 PurpleConversation
*conv
= gtkconv
->active_conv
;
1167 PurpleAccount
*account
;
1168 PurpleConnection
*gc
;
1169 PurpleProtocol
*protocol
= NULL
;
1170 gchar
*real_who
= NULL
;
1172 account
= purple_conversation_get_account(conv
);
1173 g_return_if_fail(account
!= NULL
);
1175 gc
= purple_account_get_connection(account
);
1176 g_return_if_fail(gc
!= NULL
);
1178 protocol
= purple_connection_get_protocol(gc
);
1181 real_who
= purple_protocol_chat_iface_get_user_real_name(protocol
, gc
,
1182 purple_chat_conversation_get_id(PURPLE_CHAT_CONVERSATION(conv
)), who
);
1184 if(!who
&& !real_who
)
1187 pidgin_dialogs_im_with_user(account
, real_who
? real_who
: who
);
1192 static void pidgin_conv_chat_update_user(PurpleChatUser
*chatuser
);
1195 ignore_cb(GtkWidget
*w
, PidginConversation
*gtkconv
)
1197 PurpleChatConversation
*chat
= PURPLE_CHAT_CONVERSATION(gtkconv
->active_conv
);
1200 name
= g_object_get_data(G_OBJECT(w
), "user_data");
1205 if (purple_chat_conversation_is_ignored_user(chat
, name
))
1206 purple_chat_conversation_unignore(chat
, name
);
1208 purple_chat_conversation_ignore(chat
, name
);
1210 pidgin_conv_chat_update_user(purple_chat_conversation_find_user(chat
, name
));
1214 menu_chat_im_cb(GtkWidget
*w
, PidginConversation
*gtkconv
)
1216 const char *who
= g_object_get_data(G_OBJECT(w
), "user_data");
1218 chat_do_im(gtkconv
, who
);
1222 menu_chat_send_file_cb(GtkWidget
*w
, PidginConversation
*gtkconv
)
1224 PurpleProtocol
*protocol
;
1225 PurpleConversation
*conv
= gtkconv
->active_conv
;
1226 const char *who
= g_object_get_data(G_OBJECT(w
), "user_data");
1227 PurpleConnection
*gc
= purple_conversation_get_connection(conv
);
1228 gchar
*real_who
= NULL
;
1230 g_return_if_fail(gc
!= NULL
);
1232 protocol
= purple_connection_get_protocol(gc
);
1235 real_who
= purple_protocol_chat_iface_get_user_real_name(protocol
, gc
,
1236 purple_chat_conversation_get_id(PURPLE_CHAT_CONVERSATION(conv
)), who
);
1238 purple_serv_send_file(gc
, real_who
? real_who
: who
, NULL
);
1243 menu_chat_info_cb(GtkWidget
*w
, PidginConversation
*gtkconv
)
1247 who
= g_object_get_data(G_OBJECT(w
), "user_data");
1249 chat_do_info(gtkconv
, who
);
1253 menu_chat_add_remove_cb(GtkWidget
*w
, PidginConversation
*gtkconv
)
1255 PurpleConversation
*conv
= gtkconv
->active_conv
;
1256 PurpleAccount
*account
;
1260 account
= purple_conversation_get_account(conv
);
1261 name
= g_object_get_data(G_OBJECT(w
), "user_data");
1262 b
= purple_blist_find_buddy(account
, name
);
1265 pidgin_dialogs_remove_buddy(b
);
1266 else if (account
!= NULL
&& purple_account_is_connected(account
))
1267 purple_blist_request_add_buddy(account
, name
, NULL
, NULL
);
1269 gtk_widget_grab_focus(PIDGIN_CONVERSATION(conv
)->entry
);
1273 create_chat_menu(PurpleChatConversation
*chat
, const char *who
, PurpleConnection
*gc
)
1275 static GtkWidget
*menu
= NULL
;
1276 PurpleProtocol
*protocol
= NULL
;
1277 PurpleConversation
*conv
= PURPLE_CONVERSATION(chat
);
1278 PurpleAccount
*account
= purple_conversation_get_account(conv
);
1279 gboolean is_me
= FALSE
;
1281 PurpleBuddy
*buddy
= NULL
;
1284 protocol
= purple_connection_get_protocol(gc
);
1287 * If a menu already exists, destroy it before creating a new one,
1288 * thus freeing-up the memory it occupied.
1291 gtk_widget_destroy(menu
);
1293 if (purple_strequal(purple_chat_conversation_get_nick(chat
), purple_normalize(account
, who
)))
1296 menu
= gtk_menu_new();
1299 button
= pidgin_new_menu_item(menu
, _("IM"),
1300 PIDGIN_STOCK_TOOLBAR_MESSAGE_NEW
,
1301 G_CALLBACK(menu_chat_im_cb
),
1302 PIDGIN_CONVERSATION(conv
));
1305 gtk_widget_set_sensitive(button
, FALSE
);
1307 g_object_set_data_full(G_OBJECT(button
), "user_data", g_strdup(who
), g_free
);
1310 if (protocol
&& PURPLE_IS_PROTOCOL_XFER(protocol
))
1312 gboolean can_receive_file
= TRUE
;
1314 button
= pidgin_new_menu_item(menu
, _("Send File"),
1315 PIDGIN_STOCK_TOOLBAR_SEND_FILE
, G_CALLBACK(menu_chat_send_file_cb
),
1316 PIDGIN_CONVERSATION(conv
));
1319 can_receive_file
= FALSE
;
1321 gchar
*real_who
= NULL
;
1322 real_who
= purple_protocol_chat_iface_get_user_real_name(protocol
, gc
,
1323 purple_chat_conversation_get_id(chat
), who
);
1325 if (!purple_protocol_xfer_can_receive(
1326 PURPLE_PROTOCOL_XFER(protocol
),
1327 gc
, real_who
? real_who
: who
)) {
1328 can_receive_file
= FALSE
;
1334 if (!can_receive_file
)
1335 gtk_widget_set_sensitive(button
, FALSE
);
1337 g_object_set_data_full(G_OBJECT(button
), "user_data", g_strdup(who
), g_free
);
1341 if (purple_chat_conversation_is_ignored_user(chat
, who
))
1342 button
= pidgin_new_menu_item(menu
, _("Un-Ignore"),
1343 PIDGIN_STOCK_IGNORE
, G_CALLBACK(ignore_cb
),
1344 PIDGIN_CONVERSATION(conv
));
1346 button
= pidgin_new_menu_item(menu
, _("Ignore"),
1347 PIDGIN_STOCK_IGNORE
, G_CALLBACK(ignore_cb
),
1348 PIDGIN_CONVERSATION(conv
));
1351 gtk_widget_set_sensitive(button
, FALSE
);
1353 g_object_set_data_full(G_OBJECT(button
), "user_data", g_strdup(who
), g_free
);
1356 if (protocol
&& PURPLE_PROTOCOL_IMPLEMENTS(protocol
, SERVER
, get_info
)) {
1357 button
= pidgin_new_menu_item(menu
, _("Info"),
1358 PIDGIN_STOCK_TOOLBAR_USER_INFO
,
1359 G_CALLBACK(menu_chat_info_cb
),
1360 PIDGIN_CONVERSATION(conv
));
1363 gtk_widget_set_sensitive(button
, FALSE
);
1365 g_object_set_data_full(G_OBJECT(button
), "user_data", g_strdup(who
), g_free
);
1368 if (!is_me
&& protocol
&& !(purple_protocol_get_options(protocol
) & OPT_PROTO_UNIQUE_CHATNAME
) && PURPLE_PROTOCOL_IMPLEMENTS(protocol
, SERVER
, add_buddy
)) {
1369 if ((buddy
= purple_blist_find_buddy(account
, who
)) != NULL
)
1370 button
= pidgin_new_menu_item(menu
, _("Remove"),
1372 G_CALLBACK(menu_chat_add_remove_cb
),
1373 PIDGIN_CONVERSATION(conv
));
1375 button
= pidgin_new_menu_item(menu
, _("Add"),
1377 G_CALLBACK(menu_chat_add_remove_cb
),
1378 PIDGIN_CONVERSATION(conv
));
1381 gtk_widget_set_sensitive(button
, FALSE
);
1383 g_object_set_data_full(G_OBJECT(button
), "user_data", g_strdup(who
), g_free
);
1388 if (purple_account_is_connected(account
))
1389 pidgin_append_blist_node_proto_menu(menu
, purple_account_get_connection(account
),
1390 (PurpleBlistNode
*)buddy
);
1391 pidgin_append_blist_node_extended_menu(menu
, (PurpleBlistNode
*)buddy
);
1392 gtk_widget_show_all(menu
);
1400 gtkconv_chat_popup_menu_cb(GtkWidget
*widget
, PidginConversation
*gtkconv
)
1402 PurpleConversation
*conv
= gtkconv
->active_conv
;
1403 PidginChatPane
*gtkchat
;
1404 PurpleConnection
*gc
;
1405 PurpleAccount
*account
;
1406 GtkTreeSelection
*sel
;
1408 GtkTreeModel
*model
;
1412 gtkconv
= PIDGIN_CONVERSATION(conv
);
1413 gtkchat
= gtkconv
->u
.chat
;
1414 account
= purple_conversation_get_account(conv
);
1415 gc
= purple_account_get_connection(account
);
1417 model
= gtk_tree_view_get_model(GTK_TREE_VIEW(gtkchat
->list
));
1419 sel
= gtk_tree_view_get_selection(GTK_TREE_VIEW(gtkchat
->list
));
1420 if(!gtk_tree_selection_get_selected(sel
, NULL
, &iter
))
1423 gtk_tree_model_get(GTK_TREE_MODEL(model
), &iter
, CHAT_USERS_NAME_COLUMN
, &who
, -1);
1424 menu
= create_chat_menu (PURPLE_CHAT_CONVERSATION(conv
), who
, gc
);
1425 pidgin_menu_popup_at_treeview_selection(menu
, widget
);
1433 right_click_chat_cb(GtkWidget
*widget
, GdkEventButton
*event
,
1434 PidginConversation
*gtkconv
)
1436 PurpleConversation
*conv
= gtkconv
->active_conv
;
1437 PidginChatPane
*gtkchat
;
1438 PurpleConnection
*gc
;
1439 PurpleAccount
*account
;
1442 GtkTreeModel
*model
;
1443 GtkTreeViewColumn
*column
;
1447 gtkchat
= gtkconv
->u
.chat
;
1448 account
= purple_conversation_get_account(conv
);
1449 gc
= purple_account_get_connection(account
);
1451 model
= gtk_tree_view_get_model(GTK_TREE_VIEW(gtkchat
->list
));
1453 gtk_tree_view_get_path_at_pos(GTK_TREE_VIEW(gtkchat
->list
),
1454 event
->x
, event
->y
, &path
, &column
, &x
, &y
);
1459 gtk_tree_selection_select_path(GTK_TREE_SELECTION(
1460 gtk_tree_view_get_selection(GTK_TREE_VIEW(gtkchat
->list
))), path
);
1461 gtk_tree_view_set_cursor(GTK_TREE_VIEW(gtkchat
->list
),
1463 gtk_widget_grab_focus(GTK_WIDGET(gtkchat
->list
));
1465 gtk_tree_model_get_iter(GTK_TREE_MODEL(model
), &iter
, path
);
1466 gtk_tree_model_get(GTK_TREE_MODEL(model
), &iter
, CHAT_USERS_NAME_COLUMN
, &who
, -1);
1468 /* emit chat-nick-clicked signal */
1469 if (event
->type
== GDK_BUTTON_PRESS
) {
1470 gint plugin_return
= GPOINTER_TO_INT(purple_signal_emit_return_1(
1471 pidgin_conversations_get_handle(), "chat-nick-clicked",
1472 conv
, who
, event
->button
));
1477 if (event
->button
== GDK_BUTTON_PRIMARY
&& event
->type
== GDK_2BUTTON_PRESS
) {
1478 chat_do_im(gtkconv
, who
);
1479 } else if (gdk_event_triggers_context_menu((GdkEvent
*)event
)) {
1480 GtkWidget
*menu
= create_chat_menu (PURPLE_CHAT_CONVERSATION(conv
), who
, gc
);
1481 gtk_menu_popup_at_pointer(GTK_MENU(menu
), (GdkEvent
*)event
);
1486 gtk_tree_path_free(path
);
1492 activate_list_cb(GtkTreeView
*list
, GtkTreePath
*path
, GtkTreeViewColumn
*column
, PidginConversation
*gtkconv
)
1495 GtkTreeModel
*model
;
1498 model
= gtk_tree_view_get_model(GTK_TREE_VIEW(list
));
1500 gtk_tree_model_get_iter(GTK_TREE_MODEL(model
), &iter
, path
);
1501 gtk_tree_model_get(GTK_TREE_MODEL(model
), &iter
, CHAT_USERS_NAME_COLUMN
, &who
, -1);
1502 chat_do_im(gtkconv
, who
);
1508 move_to_next_unread_tab(PidginConversation
*gtkconv
, gboolean forward
)
1510 PidginConversation
*next_gtkconv
= NULL
, *most_active
= NULL
;
1511 PidginUnseenState unseen_state
= PIDGIN_UNSEEN_NONE
;
1512 PidginConvWindow
*win
;
1513 int initial
, i
, total
, diff
;
1516 initial
= gtk_notebook_page_num(GTK_NOTEBOOK(win
->notebook
),
1518 total
= pidgin_conv_window_get_gtkconv_count(win
);
1519 /* By adding total here, the moduli calculated later will always have two
1520 * positive arguments. x % y where x < 0 is not guaranteed to return a
1523 diff
= (forward
? 1 : -1) + total
;
1525 for (i
= (initial
+ diff
) % total
; i
!= initial
; i
= (i
+ diff
) % total
) {
1526 next_gtkconv
= pidgin_conv_window_get_gtkconv_at_index(win
, i
);
1527 if (next_gtkconv
->unseen_state
> unseen_state
) {
1528 most_active
= next_gtkconv
;
1529 unseen_state
= most_active
->unseen_state
;
1530 if(PIDGIN_UNSEEN_NICK
== unseen_state
) /* highest possible state */
1535 if (most_active
== NULL
) { /* no new messages */
1536 i
= (i
+ diff
) % total
;
1537 most_active
= pidgin_conv_window_get_gtkconv_at_index(win
, i
);
1540 if (most_active
!= NULL
&& most_active
!= gtkconv
)
1541 pidgin_conv_window_switch_gtkconv(win
, most_active
);
1545 gtkconv_cycle_focus(PidginConversation
*gtkconv
, GtkDirectionType dir
)
1547 PurpleConversation
*conv
= gtkconv
->active_conv
;
1548 gboolean chat
= PURPLE_IS_CHAT_CONVERSATION(conv
);
1549 GtkWidget
*next
= NULL
;
1554 {gtkconv
->entry
, gtkconv
->history
},
1555 {gtkconv
->history
, chat
? gtkconv
->u
.chat
->list
: gtkconv
->entry
},
1556 {chat
? gtkconv
->u
.chat
->list
: NULL
, gtkconv
->entry
},
1560 for (ptr
= transitions
; !next
&& ptr
->from
; ptr
++) {
1561 GtkWidget
*from
, *to
;
1562 if (dir
== GTK_DIR_TAB_FORWARD
) {
1569 if (gtk_widget_is_focus(from
))
1574 gtk_widget_grab_focus(next
);
1579 update_typing_inserting(PidginConversation
*gtkconv
)
1581 GtkTextBuffer
*buffer
= NULL
;
1582 gboolean is_empty
= FALSE
;
1584 g_return_if_fail(gtkconv
!= NULL
);
1586 buffer
= talkatu_editor_get_buffer(TALKATU_EDITOR(gtkconv
->editor
));
1587 is_empty
= talkatu_buffer_get_is_empty(TALKATU_BUFFER(buffer
));
1589 got_typing_keypress(gtkconv
, is_empty
);
1593 update_typing_deleting_cb(PidginConversation
*gtkconv
)
1595 PurpleIMConversation
*im
= PURPLE_IM_CONVERSATION(gtkconv
->active_conv
);
1596 GtkTextBuffer
*buffer
= NULL
;
1598 buffer
= talkatu_editor_get_buffer(TALKATU_EDITOR(gtkconv
->editor
));
1600 if (!talkatu_buffer_get_is_empty(TALKATU_BUFFER(buffer
))) {
1601 /* We deleted all the text, so turn off typing. */
1602 purple_im_conversation_stop_send_typed_timeout(im
);
1604 purple_serv_send_typing(purple_conversation_get_connection(gtkconv
->active_conv
),
1605 purple_conversation_get_name(gtkconv
->active_conv
),
1606 PURPLE_IM_NOT_TYPING
);
1608 /* We're deleting, but not all of it, so it counts as typing. */
1609 got_typing_keypress(gtkconv
, FALSE
);
1616 update_typing_deleting(PidginConversation
*gtkconv
)
1618 GtkTextBuffer
*buffer
= NULL
;
1620 g_return_if_fail(gtkconv
!= NULL
);
1622 buffer
= talkatu_editor_get_buffer(TALKATU_EDITOR(gtkconv
->editor
));
1624 if (!talkatu_buffer_get_is_empty(TALKATU_BUFFER(buffer
))) {
1625 g_timeout_add(0, (GSourceFunc
)update_typing_deleting_cb
, gtkconv
);
1630 conv_keypress_common(PidginConversation
*gtkconv
, GdkEventKey
*event
)
1632 PidginConvWindow
*win
;
1636 curconv
= gtk_notebook_get_current_page(GTK_NOTEBOOK(win
->notebook
));
1638 /* clear any tooltips */
1639 pidgin_tooltip_destroy();
1641 /* If CTRL was held down... */
1642 if (event
->state
& GDK_CONTROL_MASK
) {
1643 switch (event
->keyval
) {
1644 case GDK_KEY_Page_Down
:
1645 case GDK_KEY_KP_Page_Down
:
1647 if (!pidgin_conv_window_get_gtkconv_at_index(win
, curconv
+ 1))
1648 gtk_notebook_set_current_page(GTK_NOTEBOOK(win
->notebook
), 0);
1650 gtk_notebook_set_current_page(GTK_NOTEBOOK(win
->notebook
), curconv
+ 1);
1654 case GDK_KEY_Page_Up
:
1655 case GDK_KEY_KP_Page_Up
:
1657 if (!pidgin_conv_window_get_gtkconv_at_index(win
, curconv
- 1))
1658 gtk_notebook_set_current_page(GTK_NOTEBOOK(win
->notebook
), -1);
1660 gtk_notebook_set_current_page(GTK_NOTEBOOK(win
->notebook
), curconv
- 1);
1665 case GDK_KEY_KP_Tab
:
1666 case GDK_KEY_ISO_Left_Tab
:
1667 if (event
->state
& GDK_SHIFT_MASK
) {
1668 move_to_next_unread_tab(gtkconv
, FALSE
);
1670 move_to_next_unread_tab(gtkconv
, TRUE
);
1677 gtk_notebook_reorder_child(GTK_NOTEBOOK(win
->notebook
),
1678 gtk_notebook_get_nth_page(GTK_NOTEBOOK(win
->notebook
), curconv
),
1683 case GDK_KEY_period
:
1684 gtk_notebook_reorder_child(GTK_NOTEBOOK(win
->notebook
),
1685 gtk_notebook_get_nth_page(GTK_NOTEBOOK(win
->notebook
), curconv
),
1686 (curconv
+ 1) % gtk_notebook_get_n_pages(GTK_NOTEBOOK(win
->notebook
)));
1690 if (gtkconv_cycle_focus(gtkconv
, event
->state
& GDK_SHIFT_MASK
? GTK_DIR_TAB_BACKWARD
: GTK_DIR_TAB_FORWARD
))
1693 } /* End of switch */
1696 /* If ALT (or whatever) was held down... */
1697 else if (event
->state
& GDK_MOD1_MASK
)
1699 if (event
->keyval
> '0' && event
->keyval
<= '9')
1701 guint switchto
= event
->keyval
- '1';
1702 if (switchto
< pidgin_conv_window_get_gtkconv_count(win
))
1703 gtk_notebook_set_current_page(GTK_NOTEBOOK(win
->notebook
), switchto
);
1709 /* If neither CTRL nor ALT were held down... */
1712 switch (event
->keyval
) {
1714 if (gtk_widget_is_focus(GTK_WIDGET(win
->notebook
))) {
1715 infopane_entry_activate(gtkconv
);
1720 if (gtkconv_cycle_focus(gtkconv
, event
->state
& GDK_SHIFT_MASK
? GTK_DIR_TAB_BACKWARD
: GTK_DIR_TAB_FORWARD
))
1729 entry_key_press_cb(GtkWidget
*entry
, GdkEventKey
*event
, gpointer data
)
1731 PurpleConversation
*conv
;
1732 PidginConversation
*gtkconv
;
1734 gtkconv
= (PidginConversation
*)data
;
1735 conv
= gtkconv
->active_conv
;
1737 if (conv_keypress_common(gtkconv
, event
))
1740 /* If CTRL was held down... */
1741 if (event
->state
& GDK_CONTROL_MASK
) {
1743 /* If ALT (or whatever) was held down... */
1744 else if (event
->state
& GDK_MOD1_MASK
) {
1747 /* If neither CTRL nor ALT were held down... */
1749 switch (event
->keyval
) {
1751 case GDK_KEY_KP_Tab
:
1752 case GDK_KEY_ISO_Left_Tab
:
1753 if (gtkconv
->entry
!= entry
)
1757 plugin_return
= GPOINTER_TO_INT(purple_signal_emit_return_1(
1758 pidgin_conversations_get_handle(), "chat-nick-autocomplete",
1759 conv
, event
->state
& GDK_SHIFT_MASK
));
1760 return plugin_return
;
1764 case GDK_KEY_Page_Up
:
1765 case GDK_KEY_KP_Page_Up
:
1766 talkatu_history_page_up(TALKATU_HISTORY(gtkconv
->history
));
1770 case GDK_KEY_Page_Down
:
1771 case GDK_KEY_KP_Page_Down
:
1772 talkatu_history_page_down(TALKATU_HISTORY(gtkconv
->history
));
1776 case GDK_KEY_KP_Enter
:
1777 case GDK_KEY_Return
:
1778 send_cb(entry
, gtkconv
);
1785 if (PURPLE_IS_IM_CONVERSATION(conv
) &&
1786 purple_prefs_get_bool("/purple/conversations/im/send_typing")) {
1788 switch (event
->keyval
) {
1789 case GDK_KEY_BackSpace
:
1790 case GDK_KEY_Delete
:
1791 case GDK_KEY_KP_Delete
:
1792 update_typing_deleting(gtkconv
);
1795 update_typing_inserting(gtkconv
);
1803 * If someone tries to type into the conversation backlog of a
1804 * conversation window then we yank focus from the conversation backlog
1805 * and give it to the text entry box so that people can type
1806 * all the live long day and it will get entered into the entry box.
1809 refocus_entry_cb(GtkWidget
*widget
, GdkEventKey
*event
, gpointer data
)
1811 GtkWidget
*view
= NULL
;
1812 PidginConversation
*gtkconv
= data
;
1814 /* If we have a valid key for the conversation display, then exit */
1815 if ((event
->state
& GDK_CONTROL_MASK
) ||
1816 (event
->keyval
== GDK_KEY_F6
) ||
1817 (event
->keyval
== GDK_KEY_F10
) ||
1818 (event
->keyval
== GDK_KEY_Menu
) ||
1819 (event
->keyval
== GDK_KEY_Shift_L
) ||
1820 (event
->keyval
== GDK_KEY_Shift_R
) ||
1821 (event
->keyval
== GDK_KEY_Control_L
) ||
1822 (event
->keyval
== GDK_KEY_Control_R
) ||
1823 (event
->keyval
== GDK_KEY_Escape
) ||
1824 (event
->keyval
== GDK_KEY_Up
) ||
1825 (event
->keyval
== GDK_KEY_Down
) ||
1826 (event
->keyval
== GDK_KEY_Left
) ||
1827 (event
->keyval
== GDK_KEY_Right
) ||
1828 (event
->keyval
== GDK_KEY_Page_Up
) ||
1829 (event
->keyval
== GDK_KEY_KP_Page_Up
) ||
1830 (event
->keyval
== GDK_KEY_Page_Down
) ||
1831 (event
->keyval
== GDK_KEY_KP_Page_Down
) ||
1832 (event
->keyval
== GDK_KEY_Home
) ||
1833 (event
->keyval
== GDK_KEY_End
) ||
1834 (event
->keyval
== GDK_KEY_Tab
) ||
1835 (event
->keyval
== GDK_KEY_KP_Tab
) ||
1836 (event
->keyval
== GDK_KEY_ISO_Left_Tab
))
1838 if (event
->type
== GDK_KEY_PRESS
)
1839 return conv_keypress_common(gtkconv
, event
);
1843 view
= talkatu_editor_get_view(TALKATU_EDITOR(gtkconv
->editor
));
1844 gtk_widget_grab_focus(view
);
1845 gtk_widget_event(view
, (GdkEvent
*)event
);
1851 regenerate_options_items(PidginConvWindow
*win
);
1854 pidgin_conv_switch_active_conversation(PurpleConversation
*conv
)
1856 PidginConversation
*gtkconv
;
1857 PurpleConversation
*old_conv
;
1858 PurpleConnectionFlags features
;
1860 g_return_if_fail(conv
!= NULL
);
1862 gtkconv
= PIDGIN_CONVERSATION(conv
);
1863 old_conv
= gtkconv
->active_conv
;
1865 purple_debug_info("gtkconv", "setting active conversation on toolbar %p\n",
1868 if (old_conv
== conv
)
1871 purple_conversation_close_logs(old_conv
);
1872 gtkconv
->active_conv
= conv
;
1874 purple_conversation_set_logging(conv
,
1875 gtk_toggle_action_get_active(GTK_TOGGLE_ACTION(gtkconv
->win
->menu
->logging
)));
1877 purple_signal_emit(pidgin_conversations_get_handle(), "conversation-switched", conv
);
1879 gray_stuff_out(gtkconv
);
1880 update_typing_icon(gtkconv
);
1881 g_object_set_data(G_OBJECT(gtkconv
->entry
), "transient_buddy", NULL
);
1882 regenerate_options_items(gtkconv
->win
);
1884 gtk_window_set_title(GTK_WINDOW(gtkconv
->win
->window
),
1885 gtk_label_get_text(GTK_LABEL(gtkconv
->tab_label
)));
1889 menu_conv_sel_send_cb(GObject
*m
, gpointer data
)
1891 PurpleAccount
*account
= g_object_get_data(m
, "purple_account");
1892 gchar
*name
= g_object_get_data(m
, "purple_buddy_name");
1893 PurpleIMConversation
*im
;
1895 if (gtk_check_menu_item_get_active((GtkCheckMenuItem
*) m
) == FALSE
)
1898 im
= purple_im_conversation_new(account
, name
);
1899 pidgin_conv_switch_active_conversation(PURPLE_CONVERSATION(im
));
1902 /**************************************************************************
1903 * A bunch of buddy icon functions
1904 **************************************************************************/
1906 static GList
*get_protocol_icon_list(PurpleAccount
*account
)
1909 PurpleProtocol
*protocol
=
1910 purple_protocols_find(purple_account_get_protocol_id(account
));
1911 const char *protoname
= purple_protocol_class_list_icon(protocol
, account
, NULL
);
1912 l
= g_hash_table_lookup(protocol_lists
, protoname
);
1916 l
= g_list_prepend(l
, pidgin_create_protocol_icon(account
, PIDGIN_PROTOCOL_ICON_LARGE
));
1917 l
= g_list_prepend(l
, pidgin_create_protocol_icon(account
, PIDGIN_PROTOCOL_ICON_MEDIUM
));
1918 l
= g_list_prepend(l
, pidgin_create_protocol_icon(account
, PIDGIN_PROTOCOL_ICON_SMALL
));
1920 g_hash_table_insert(protocol_lists
, g_strdup(protoname
), l
);
1925 pidgin_conv_get_tab_icons(PurpleConversation
*conv
)
1927 PurpleAccount
*account
= NULL
;
1928 const char *name
= NULL
;
1930 g_return_val_if_fail(conv
!= NULL
, NULL
);
1932 account
= purple_conversation_get_account(conv
);
1933 name
= purple_conversation_get_name(conv
);
1935 g_return_val_if_fail(account
!= NULL
, NULL
);
1936 g_return_val_if_fail(name
!= NULL
, NULL
);
1938 /* Use the buddy icon, if possible */
1939 if (PURPLE_IS_IM_CONVERSATION(conv
)) {
1940 PurpleBuddy
*b
= purple_blist_find_buddy(account
, name
);
1943 p
= purple_buddy_get_presence(b
);
1944 if (purple_presence_is_status_primitive_active(p
, PURPLE_STATUS_AWAY
))
1946 if (purple_presence_is_status_primitive_active(p
, PURPLE_STATUS_UNAVAILABLE
))
1948 if (purple_presence_is_status_primitive_active(p
, PURPLE_STATUS_EXTENDED_AWAY
))
1950 if (purple_presence_is_status_primitive_active(p
, PURPLE_STATUS_OFFLINE
))
1951 return offline_list
;
1953 return available_list
;
1957 return get_protocol_icon_list(account
);
1961 pidgin_conv_get_icon_stock(PurpleConversation
*conv
)
1963 PurpleAccount
*account
= NULL
;
1964 const char *stock
= NULL
;
1966 g_return_val_if_fail(conv
!= NULL
, NULL
);
1968 account
= purple_conversation_get_account(conv
);
1969 g_return_val_if_fail(account
!= NULL
, NULL
);
1971 /* Use the buddy icon, if possible */
1972 if (PURPLE_IS_IM_CONVERSATION(conv
)) {
1973 const char *name
= NULL
;
1975 name
= purple_conversation_get_name(conv
);
1976 b
= purple_blist_find_buddy(account
, name
);
1978 PurplePresence
*p
= purple_buddy_get_presence(b
);
1979 PurpleStatus
*active
= purple_presence_get_active_status(p
);
1980 PurpleStatusType
*type
= purple_status_get_status_type(active
);
1981 PurpleStatusPrimitive prim
= purple_status_type_get_primitive(type
);
1982 stock
= pidgin_stock_id_from_status_primitive(prim
);
1984 stock
= PIDGIN_STOCK_STATUS_PERSON
;
1987 stock
= PIDGIN_STOCK_STATUS_CHAT
;
1994 pidgin_conv_get_icon(PurpleConversation
*conv
, GtkWidget
*parent
, const char *icon_size
)
1996 PurpleAccount
*account
= NULL
;
1997 const char *name
= NULL
;
1998 const char *stock
= NULL
;
1999 GdkPixbuf
*status
= NULL
;
2002 g_return_val_if_fail(conv
!= NULL
, NULL
);
2004 account
= purple_conversation_get_account(conv
);
2005 name
= purple_conversation_get_name(conv
);
2007 g_return_val_if_fail(account
!= NULL
, NULL
);
2008 g_return_val_if_fail(name
!= NULL
, NULL
);
2010 /* Use the buddy icon, if possible */
2011 if (PURPLE_IS_IM_CONVERSATION(conv
)) {
2012 PurpleBuddy
*b
= purple_blist_find_buddy(account
, name
);
2014 /* I hate this hack. It fixes a bug where the pending message icon
2015 * displays in the conv tab even though it shouldn't.
2016 * A better solution would be great. */
2017 purple_blist_update_node(NULL
, PURPLE_BLIST_NODE(b
));
2021 stock
= pidgin_conv_get_icon_stock(conv
);
2022 size
= gtk_icon_size_from_name(icon_size
);
2023 status
= gtk_widget_render_icon (parent
, stock
, size
, "GtkWidget");
2028 pidgin_conv_get_tab_icon(PurpleConversation
*conv
, gboolean small_icon
)
2030 const char *icon_size
= small_icon
? PIDGIN_ICON_SIZE_TANGO_MICROSCOPIC
: PIDGIN_ICON_SIZE_TANGO_EXTRA_SMALL
;
2031 return pidgin_conv_get_icon(conv
, PIDGIN_CONVERSATION(conv
)->icon
, icon_size
);
2036 update_tab_icon(PurpleConversation
*conv
)
2038 PidginConversation
*gtkconv
;
2039 PidginConvWindow
*win
;
2041 GdkPixbuf
*emblem
= NULL
;
2042 const char *status
= NULL
;
2043 const char *infopane_status
= NULL
;
2045 g_return_if_fail(conv
!= NULL
);
2047 gtkconv
= PIDGIN_CONVERSATION(conv
);
2049 if (conv
!= gtkconv
->active_conv
)
2052 status
= infopane_status
= pidgin_conv_get_icon_stock(conv
);
2054 if (PURPLE_IS_IM_CONVERSATION(conv
)) {
2055 PurpleBuddy
*b
= purple_blist_find_buddy(purple_conversation_get_account(conv
), purple_conversation_get_name(conv
));
2057 emblem
= pidgin_blist_get_emblem((PurpleBlistNode
*)b
);
2060 g_return_if_fail(status
!= NULL
);
2062 g_object_set(G_OBJECT(gtkconv
->icon
), "stock", status
, NULL
);
2063 g_object_set(G_OBJECT(gtkconv
->menu_icon
), "stock", status
, NULL
);
2065 gtk_list_store_set(GTK_LIST_STORE(gtkconv
->infopane_model
),
2066 &(gtkconv
->infopane_iter
),
2067 CONV_ICON_COLUMN
, infopane_status
, -1);
2069 gtk_list_store_set(GTK_LIST_STORE(gtkconv
->infopane_model
),
2070 &(gtkconv
->infopane_iter
),
2071 CONV_EMBLEM_COLUMN
, emblem
, -1);
2073 g_object_unref(emblem
);
2075 if (purple_prefs_get_bool(PIDGIN_PREFS_ROOT
"/blist/show_protocol_icons")) {
2076 emblem
= pidgin_create_protocol_icon(purple_conversation_get_account(gtkconv
->active_conv
), PIDGIN_PROTOCOL_ICON_SMALL
);
2081 gtk_list_store_set(GTK_LIST_STORE(gtkconv
->infopane_model
),
2082 &(gtkconv
->infopane_iter
),
2083 CONV_PROTOCOL_ICON_COLUMN
, emblem
, -1);
2085 g_object_unref(emblem
);
2087 /* XXX seanegan Why do I have to do this? */
2088 gtk_widget_queue_resize(gtkconv
->infopane
);
2089 gtk_widget_queue_draw(gtkconv
->infopane
);
2091 if (pidgin_conv_window_is_active_conversation(conv
) &&
2092 (!PURPLE_IS_IM_CONVERSATION(conv
) || gtkconv
->u
.im
->anim
== NULL
))
2094 l
= pidgin_conv_get_tab_icons(conv
);
2096 gtk_window_set_icon_list(GTK_WINDOW(win
->window
), l
);
2101 redraw_icon(gpointer data
)
2103 PidginConversation
*gtkconv
= (PidginConversation
*)data
;
2104 PurpleConversation
*conv
= gtkconv
->active_conv
;
2105 PurpleAccount
*account
;
2110 int scale_width
, scale_height
;
2113 gtkconv
= PIDGIN_CONVERSATION(conv
);
2114 account
= purple_conversation_get_account(conv
);
2116 if (!(account
&& purple_account_get_connection(account
))) {
2117 gtkconv
->u
.im
->icon_timer
= 0;
2121 gdk_pixbuf_animation_iter_advance(gtkconv
->u
.im
->iter
, NULL
);
2122 buf
= gdk_pixbuf_animation_iter_get_pixbuf(gtkconv
->u
.im
->iter
);
2124 scale_width
= gdk_pixbuf_get_width(buf
);
2125 scale_height
= gdk_pixbuf_get_height(buf
);
2127 gtk_widget_get_size_request(gtkconv
->u
.im
->icon_container
, NULL
, &size
);
2128 size
= MIN(size
, MIN(scale_width
, scale_height
));
2129 size
= CLAMP(size
, BUDDYICON_SIZE_MIN
, BUDDYICON_SIZE_MAX
);
2131 if (scale_width
== scale_height
) {
2132 scale_width
= scale_height
= size
;
2133 } else if (scale_height
> scale_width
) {
2134 scale_width
= size
* scale_width
/ scale_height
;
2135 scale_height
= size
;
2137 scale_height
= size
* scale_height
/ scale_width
;
2141 scale
= gdk_pixbuf_scale_simple(buf
, scale_width
, scale_height
,
2142 GDK_INTERP_BILINEAR
);
2143 if (pidgin_gdk_pixbuf_is_opaque(scale
))
2144 pidgin_gdk_pixbuf_make_round(scale
);
2146 gtk_image_set_from_pixbuf(GTK_IMAGE(gtkconv
->u
.im
->icon
), scale
);
2147 g_object_unref(G_OBJECT(scale
));
2148 gtk_widget_queue_draw(gtkconv
->u
.im
->icon
);
2150 delay
= gdk_pixbuf_animation_iter_get_delay_time(gtkconv
->u
.im
->iter
);
2155 gtkconv
->u
.im
->icon_timer
= g_timeout_add(delay
, redraw_icon
, gtkconv
);
2161 start_anim(GtkWidget
*widget
, PidginConversation
*gtkconv
)
2165 if (gtkconv
->u
.im
->anim
== NULL
)
2168 if (gtkconv
->u
.im
->icon_timer
!= 0)
2171 if (gdk_pixbuf_animation_is_static_image(gtkconv
->u
.im
->anim
))
2174 delay
= gdk_pixbuf_animation_iter_get_delay_time(gtkconv
->u
.im
->iter
);
2179 gtkconv
->u
.im
->icon_timer
= g_timeout_add(delay
, redraw_icon
, gtkconv
);
2183 remove_icon(GtkWidget
*widget
, PidginConversation
*gtkconv
)
2187 PurpleConversation
*conv
= gtkconv
->active_conv
;
2189 g_return_if_fail(conv
!= NULL
);
2191 gtk_widget_set_size_request(gtkconv
->u
.im
->icon_container
, -1, BUDDYICON_SIZE_MIN
);
2192 children
= gtk_container_get_children(GTK_CONTAINER(gtkconv
->u
.im
->icon_container
));
2194 /* We know there's only one child here. It'd be nice to shortcut to the
2195 event box, but we can't change the PidginConversation until 3.0 */
2196 event
= (GtkWidget
*)children
->data
;
2197 gtk_container_remove(GTK_CONTAINER(gtkconv
->u
.im
->icon_container
), event
);
2198 g_list_free(children
);
2201 if (gtkconv
->u
.im
->anim
!= NULL
)
2202 g_object_unref(G_OBJECT(gtkconv
->u
.im
->anim
));
2204 if (gtkconv
->u
.im
->icon_timer
!= 0)
2205 g_source_remove(gtkconv
->u
.im
->icon_timer
);
2207 if (gtkconv
->u
.im
->iter
!= NULL
)
2208 g_object_unref(G_OBJECT(gtkconv
->u
.im
->iter
));
2210 gtkconv
->u
.im
->icon_timer
= 0;
2211 gtkconv
->u
.im
->icon
= NULL
;
2212 gtkconv
->u
.im
->anim
= NULL
;
2213 gtkconv
->u
.im
->iter
= NULL
;
2214 gtkconv
->u
.im
->show_icon
= FALSE
;
2218 saveicon_writefile_cb(void *user_data
, const char *filename
)
2220 PidginConversation
*gtkconv
= (PidginConversation
*)user_data
;
2221 PurpleIMConversation
*im
= PURPLE_IM_CONVERSATION(gtkconv
->active_conv
);
2222 PurpleBuddyIcon
*icon
;
2226 icon
= purple_im_conversation_get_icon(im
);
2227 data
= purple_buddy_icon_get_data(icon
, &len
);
2229 if ((len
<= 0) || (data
== NULL
) || !purple_util_write_data_to_file_absolute(filename
, data
, len
)) {
2230 purple_notify_error(gtkconv
, NULL
, _("Unable to save icon file to disk."), NULL
, NULL
);
2235 custom_icon_sel_cb(const char *filename
, gpointer data
)
2238 PurpleContact
*contact
= data
;
2240 purple_buddy_icons_node_set_custom_icon_from_file((PurpleBlistNode
*)contact
, filename
);
2242 g_object_set_data(G_OBJECT(data
), "buddy-icon-chooser", NULL
);
2246 set_custom_icon_cb(GtkWidget
*widget
, PurpleContact
*contact
)
2248 GtkFileChooserNative
*win
= NULL
;
2250 /* Should not happen as menu item should be disabled. */
2251 g_return_if_fail(contact
!= NULL
);
2253 win
= g_object_get_data(G_OBJECT(contact
), "buddy-icon-chooser");
2255 GtkMenu
*menu
= GTK_MENU(gtk_widget_get_parent(widget
));
2256 GtkWidget
*toplevel
=
2257 gtk_widget_get_toplevel(gtk_menu_get_attach_widget(menu
));
2258 win
= pidgin_buddy_icon_chooser_new(GTK_WINDOW(toplevel
),
2259 custom_icon_sel_cb
, contact
);
2260 g_object_set_data_full(G_OBJECT(contact
), "buddy-icon-chooser", win
,
2261 (GDestroyNotify
)g_object_unref
);
2263 gtk_native_dialog_show(GTK_NATIVE_DIALOG(win
));
2267 change_size_cb(GtkWidget
*widget
, PidginConversation
*gtkconv
)
2270 PurpleConversation
*conv
= gtkconv
->active_conv
;
2273 gtk_widget_get_size_request(gtkconv
->u
.im
->icon_container
, NULL
, &size
);
2275 if (size
== BUDDYICON_SIZE_MAX
) {
2276 size
= BUDDYICON_SIZE_MIN
;
2278 size
= BUDDYICON_SIZE_MAX
;
2281 gtk_widget_set_size_request(gtkconv
->u
.im
->icon_container
, -1, size
);
2282 pidgin_conv_update_buddy_icon(PURPLE_IM_CONVERSATION(conv
));
2284 buddies
= purple_blist_find_buddies(purple_conversation_get_account(conv
),
2285 purple_conversation_get_name(conv
));
2286 for (; buddies
; buddies
= g_slist_delete_link(buddies
, buddies
)) {
2287 PurpleBuddy
*buddy
= buddies
->data
;
2288 PurpleContact
*contact
= purple_buddy_get_contact(buddy
);
2289 purple_blist_node_set_int((PurpleBlistNode
*)contact
, "pidgin-infopane-iconsize", size
);
2294 remove_custom_icon_cb(GtkWidget
*widget
, PidginConversation
*gtkconv
)
2298 PurpleAccount
*account
;
2299 PurpleContact
*contact
;
2300 PurpleConversation
*conv
= gtkconv
->active_conv
;
2302 account
= purple_conversation_get_account(conv
);
2303 name
= purple_conversation_get_name(conv
);
2304 buddy
= purple_blist_find_buddy(account
, name
);
2308 contact
= purple_buddy_get_contact(buddy
);
2310 purple_buddy_icons_node_set_custom_icon_from_file((PurpleBlistNode
*)contact
, NULL
);
2314 icon_menu_save_cb(GtkWidget
*widget
, PidginConversation
*gtkconv
)
2316 PurpleConversation
*conv
= gtkconv
->active_conv
;
2320 g_return_if_fail(conv
!= NULL
);
2322 ext
= purple_buddy_icon_get_extension(purple_im_conversation_get_icon(PURPLE_IM_CONVERSATION(conv
)));
2324 buf
= g_strdup_printf("%s.%s", purple_normalize(purple_conversation_get_account(conv
), purple_conversation_get_name(conv
)), ext
);
2326 purple_request_file(gtkconv
, _("Save Icon"), buf
, TRUE
,
2327 G_CALLBACK(saveicon_writefile_cb
), NULL
,
2328 purple_request_cpar_from_conversation(conv
), gtkconv
);
2334 stop_anim(GtkWidget
*widget
, PidginConversation
*gtkconv
)
2336 if (gtkconv
->u
.im
->icon_timer
!= 0)
2337 g_source_remove(gtkconv
->u
.im
->icon_timer
);
2339 gtkconv
->u
.im
->icon_timer
= 0;
2344 toggle_icon_animate_cb(GtkWidget
*w
, PidginConversation
*gtkconv
)
2346 gtkconv
->u
.im
->animate
=
2347 gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(w
));
2349 if (gtkconv
->u
.im
->animate
)
2350 start_anim(NULL
, gtkconv
);
2352 stop_anim(NULL
, gtkconv
);
2356 icon_menu(GtkWidget
*widget
, GdkEventButton
*e
, PidginConversation
*gtkconv
)
2358 GtkWidget
*menu
= NULL
;
2359 GList
*old_menus
= NULL
;
2360 PurpleConversation
*conv
;
2363 if (e
->button
== GDK_BUTTON_PRIMARY
&& e
->type
== GDK_BUTTON_PRESS
) {
2364 change_size_cb(NULL
, gtkconv
);
2368 if (!gdk_event_triggers_context_menu((GdkEvent
*)e
)) {
2373 * If a menu already exists, destroy it before creating a new one,
2374 * thus freeing-up the memory it occupied.
2376 while ((old_menus
= gtk_menu_get_for_attach_widget(widget
)) != NULL
) {
2377 menu
= old_menus
->data
;
2378 gtk_menu_detach(GTK_MENU(menu
));
2379 gtk_widget_destroy(menu
);
2382 menu
= gtk_menu_new();
2383 gtk_menu_attach_to_widget(GTK_MENU(menu
), widget
, NULL
);
2385 if (gtkconv
->u
.im
->anim
&&
2386 !(gdk_pixbuf_animation_is_static_image(gtkconv
->u
.im
->anim
)))
2388 pidgin_new_check_item(menu
, _("Animate"),
2389 G_CALLBACK(toggle_icon_animate_cb
), gtkconv
,
2390 gtkconv
->u
.im
->icon_timer
);
2393 conv
= gtkconv
->active_conv
;
2394 buddy
= purple_blist_find_buddy(purple_conversation_get_account(conv
),
2395 purple_conversation_get_name(conv
));
2397 pidgin_new_menu_item(menu
, _("Hide Icon"), NULL
,
2398 G_CALLBACK(remove_icon
), gtkconv
);
2400 pidgin_new_menu_item(menu
, _("Save Icon As..."), GTK_STOCK_SAVE_AS
,
2401 G_CALLBACK(icon_menu_save_cb
), gtkconv
);
2404 PurpleContact
*contact
= purple_buddy_get_contact(buddy
);
2405 pidgin_new_menu_item(menu
, _("Set Custom Icon..."), NULL
,
2406 G_CALLBACK(set_custom_icon_cb
), contact
);
2409 pidgin_new_menu_item(menu
, _("Set Custom Icon..."), NULL
,
2410 G_CALLBACK(set_custom_icon_cb
), NULL
);
2411 gtk_widget_set_sensitive(item
, FALSE
);
2414 pidgin_new_menu_item(menu
, _("Change Size"), NULL
,
2415 G_CALLBACK(change_size_cb
), gtkconv
);
2417 /* Is there a custom icon for this person? */
2420 PurpleContact
*contact
= purple_buddy_get_contact(buddy
);
2421 if (contact
&& purple_buddy_icons_node_has_custom_icon((PurpleBlistNode
*)contact
))
2423 pidgin_new_menu_item(menu
, _("Remove Custom Icon"),
2424 NULL
, G_CALLBACK(remove_custom_icon_cb
),
2429 gtk_menu_popup_at_pointer(GTK_MENU(menu
), (GdkEvent
*)e
);
2434 /**************************************************************************
2435 * End of the bunch of buddy icon functions
2436 **************************************************************************/
2438 pidgin_conv_present_conversation(PurpleConversation
*conv
)
2440 PidginConversation
*gtkconv
;
2441 GdkModifierType state
;
2443 pidgin_conv_attach_to_conversation(conv
);
2444 gtkconv
= PIDGIN_CONVERSATION(conv
);
2446 pidgin_conv_switch_active_conversation(conv
);
2447 /* Switch the tab only if the user initiated the event by pressing
2448 * a button or hitting a key. */
2449 if (gtk_get_current_event_state(&state
))
2450 pidgin_conv_window_switch_gtkconv(gtkconv
->win
, gtkconv
);
2451 gtk_window_present(GTK_WINDOW(gtkconv
->win
->window
));
2455 pidgin_conversations_get_unseen(GList
*l
,
2456 PidginUnseenState min_state
,
2457 gboolean hidden_only
,
2463 for (; l
!= NULL
&& (max_count
== 0 || c
< max_count
); l
= l
->next
) {
2464 PurpleConversation
*conv
= (PurpleConversation
*)l
->data
;
2465 PidginConversation
*gtkconv
= PIDGIN_CONVERSATION(conv
);
2467 if(gtkconv
== NULL
|| gtkconv
->active_conv
!= conv
)
2470 if (gtkconv
->unseen_state
>= min_state
&&
2471 (!hidden_only
|| gtkconv
->win
== hidden_convwin
)) {
2473 r
= g_list_prepend(r
, conv
);
2482 pidgin_conversations_get_unseen_all(PidginUnseenState min_state
,
2483 gboolean hidden_only
,
2486 return pidgin_conversations_get_unseen(purple_conversations_get_all(),
2487 min_state
, hidden_only
, max_count
);
2491 pidgin_conversations_get_unseen_ims(PidginUnseenState min_state
,
2492 gboolean hidden_only
,
2495 return pidgin_conversations_get_unseen(purple_conversations_get_ims(),
2496 min_state
, hidden_only
, max_count
);
2500 pidgin_conversations_get_unseen_chats(PidginUnseenState min_state
,
2501 gboolean hidden_only
,
2504 return pidgin_conversations_get_unseen(purple_conversations_get_chats(),
2505 min_state
, hidden_only
, max_count
);
2509 unseen_conv_menu_cb(GtkMenuItem
*item
, PurpleConversation
*conv
)
2511 g_return_if_fail(conv
!= NULL
);
2512 pidgin_conv_present_conversation(conv
);
2516 unseen_all_conv_menu_cb(GtkMenuItem
*item
, GList
*list
)
2518 g_return_if_fail(list
!= NULL
);
2519 /* Do not free the list from here. It will be freed from the
2520 * 'destroy' callback on the menuitem. */
2522 pidgin_conv_present_conversation(list
->data
);
2528 pidgin_conversations_fill_menu(GtkWidget
*menu
, GList
*convs
)
2533 g_return_val_if_fail(menu
!= NULL
, 0);
2534 g_return_val_if_fail(convs
!= NULL
, 0);
2536 for (l
= convs
; l
!= NULL
; l
= l
->next
) {
2537 PurpleConversation
*conv
= (PurpleConversation
*)l
->data
;
2538 PidginConversation
*gtkconv
= PIDGIN_CONVERSATION(conv
);
2540 GtkWidget
*icon
= gtk_image_new_from_stock(pidgin_conv_get_icon_stock(conv
),
2541 gtk_icon_size_from_name(PIDGIN_ICON_SIZE_TANGO_MICROSCOPIC
));
2543 gchar
*text
= g_strdup_printf("%s (%d)",
2544 gtk_label_get_text(GTK_LABEL(gtkconv
->tab_label
)),
2545 gtkconv
->unseen_count
);
2547 item
= gtk_image_menu_item_new_with_label(text
);
2548 gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(item
), icon
);
2549 g_signal_connect(G_OBJECT(item
), "activate", G_CALLBACK(unseen_conv_menu_cb
), conv
);
2550 gtk_menu_shell_append(GTK_MENU_SHELL(menu
), item
);
2556 /* There are more than one conversation. Add an option to show all conversations. */
2558 GList
*list
= g_list_copy(convs
);
2560 pidgin_separator(menu
);
2562 item
= gtk_menu_item_new_with_label(_("Show All"));
2563 g_signal_connect(G_OBJECT(item
), "activate", G_CALLBACK(unseen_all_conv_menu_cb
), list
);
2564 g_signal_connect_swapped(G_OBJECT(item
), "destroy", G_CALLBACK(g_list_free
), list
);
2565 gtk_menu_shell_append(GTK_MENU_SHELL(menu
), item
);
2572 pidgin_conv_get_window(PidginConversation
*gtkconv
)
2574 g_return_val_if_fail(gtkconv
!= NULL
, NULL
);
2575 return gtkconv
->win
;
2578 static GtkActionEntry menu_entries
[] =
2579 /* TODO: fill out tooltips... */
2581 /* Conversation menu */
2582 { "ConversationMenu", NULL
, N_("_Conversation"), NULL
, NULL
, NULL
},
2583 { "NewInstantMessage", PIDGIN_STOCK_TOOLBAR_MESSAGE_NEW
, N_("New Instant _Message..."), "<control>M", NULL
, G_CALLBACK(menu_new_conv_cb
) },
2584 { "JoinAChat", PIDGIN_STOCK_CHAT
, N_("Join a _Chat..."), NULL
, NULL
, G_CALLBACK(menu_join_chat_cb
) },
2585 { "Find", GTK_STOCK_FIND
, N_("_Find..."), NULL
, NULL
, G_CALLBACK(menu_find_cb
) },
2586 { "ViewLog", NULL
, N_("View _Log"), NULL
, NULL
, G_CALLBACK(menu_view_log_cb
) },
2587 { "SaveAs", GTK_STOCK_SAVE_AS
, N_("_Save As..."), NULL
, NULL
, G_CALLBACK(menu_save_as_cb
) },
2588 { "ClearScrollback", GTK_STOCK_CLEAR
, N_("Clea_r Scrollback"), "<control>L", NULL
, G_CALLBACK(menu_clear_cb
) },
2591 { "MediaMenu", NULL
, N_("M_edia"), NULL
, NULL
, NULL
},
2592 { "AudioCall", PIDGIN_STOCK_TOOLBAR_AUDIO_CALL
, N_("_Audio Call"), NULL
, NULL
, G_CALLBACK(menu_initiate_media_call_cb
) },
2593 { "VideoCall", PIDGIN_STOCK_TOOLBAR_VIDEO_CALL
, N_("_Video Call"), NULL
, NULL
, G_CALLBACK(menu_initiate_media_call_cb
) },
2594 { "AudioVideoCall", PIDGIN_STOCK_TOOLBAR_VIDEO_CALL
, N_("Audio/Video _Call"), NULL
, NULL
, G_CALLBACK(menu_initiate_media_call_cb
) },
2597 { "SendFile", PIDGIN_STOCK_TOOLBAR_SEND_FILE
, N_("Se_nd File..."), NULL
, NULL
, G_CALLBACK(menu_send_file_cb
) },
2598 { "GetAttention", PIDGIN_STOCK_TOOLBAR_SEND_ATTENTION
, N_("Get _Attention"), NULL
, NULL
, G_CALLBACK(menu_get_attention_cb
) },
2599 { "AddBuddyPounce", NULL
, N_("Add Buddy _Pounce..."), NULL
, NULL
, G_CALLBACK(menu_add_pounce_cb
) },
2600 { "GetInfo", PIDGIN_STOCK_TOOLBAR_USER_INFO
, N_("_Get Info"), "<control>O", NULL
, G_CALLBACK(menu_get_info_cb
) },
2601 { "Invite", NULL
, N_("In_vite..."), NULL
, NULL
, G_CALLBACK(menu_invite_cb
) },
2602 { "MoreMenu", NULL
, N_("M_ore"), NULL
, NULL
, NULL
},
2603 { "Alias", NULL
, N_("Al_ias..."), NULL
, NULL
, G_CALLBACK(menu_alias_cb
) },
2604 { "Block", PIDGIN_STOCK_TOOLBAR_BLOCK
, N_("_Block..."), NULL
, NULL
, G_CALLBACK(menu_block_cb
) },
2605 { "Unblock", PIDGIN_STOCK_TOOLBAR_UNBLOCK
, N_("_Unblock..."), NULL
, NULL
, G_CALLBACK(menu_unblock_cb
) },
2606 { "Add", GTK_STOCK_ADD
, N_("_Add..."), NULL
, NULL
, G_CALLBACK(menu_add_remove_cb
) },
2607 { "Remove", GTK_STOCK_REMOVE
, N_("_Remove..."), NULL
, NULL
, G_CALLBACK(menu_add_remove_cb
) },
2608 { "InsertLink", PIDGIN_STOCK_TOOLBAR_INSERT_LINK
, N_("Insert Lin_k..."), NULL
, NULL
, NULL
},
2609 { "InsertImage", PIDGIN_STOCK_TOOLBAR_INSERT_IMAGE
, N_("Insert Imag_e..."), NULL
, NULL
, NULL
},
2610 { "Close", GTK_STOCK_CLOSE
, N_("_Close"), "<control>W", NULL
, G_CALLBACK(menu_close_conv_cb
) },
2613 { "OptionsMenu", NULL
, N_("_Options"), NULL
, NULL
, NULL
},
2617 static const GtkToggleActionEntry menu_toggle_entries
[] = {
2618 { "EnableLogging", NULL
, N_("Enable _Logging"), NULL
, NULL
, G_CALLBACK(menu_logging_cb
), FALSE
},
2619 { "EnableSounds", NULL
, N_("Enable _Sounds"), NULL
, NULL
, G_CALLBACK(menu_sounds_cb
), FALSE
},
2620 { "ShowFormattingToolbars", NULL
, N_("Show Formatting _Toolbars"), NULL
, NULL
, G_CALLBACK(menu_toolbar_cb
), FALSE
},
2623 static const char *conversation_menu
=
2625 "<menubar name='Conversation'>"
2626 "<menu action='ConversationMenu'>"
2627 "<menuitem action='NewInstantMessage'/>"
2628 "<menuitem action='JoinAChat'/>"
2630 "<menuitem action='Find'/>"
2631 "<menuitem action='ViewLog'/>"
2632 "<menuitem action='SaveAs'/>"
2633 "<menuitem action='ClearScrollback'/>"
2636 "<menu action='MediaMenu'>"
2637 "<menuitem action='AudioCall'/>"
2638 "<menuitem action='VideoCall'/>"
2639 "<menuitem action='AudioVideoCall'/>"
2642 "<menuitem action='SendFile'/>"
2643 "<menuitem action='GetAttention'/>"
2644 "<menuitem action='AddBuddyPounce'/>"
2645 "<menuitem action='GetInfo'/>"
2646 "<menuitem action='Invite'/>"
2647 "<menu action='MoreMenu'/>"
2649 "<menuitem action='Alias'/>"
2650 "<menuitem action='Block'/>"
2651 "<menuitem action='Unblock'/>"
2652 "<menuitem action='Add'/>"
2653 "<menuitem action='Remove'/>"
2655 "<menuitem action='InsertLink'/>"
2656 "<menuitem action='InsertImage'/>"
2658 "<menuitem action='Close'/>"
2660 "<menu action='OptionsMenu'>"
2661 "<menuitem action='EnableLogging'/>"
2662 "<menuitem action='EnableSounds'/>"
2664 "<menuitem action='ShowFormattingToolbars'/>"
2670 sound_method_pref_changed_cb(const char *name
, PurplePrefType type
,
2671 gconstpointer value
, gpointer data
)
2673 PidginConvWindow
*win
= data
;
2674 const char *method
= value
;
2676 if (purple_strequal(method
, "none"))
2678 gtk_toggle_action_set_active(GTK_TOGGLE_ACTION(win
->menu
->sounds
),
2680 gtk_action_set_sensitive(win
->menu
->sounds
, FALSE
);
2684 PidginConversation
*gtkconv
= pidgin_conv_window_get_active_gtkconv(win
);
2686 if (gtkconv
!= NULL
)
2687 gtk_toggle_action_set_active(GTK_TOGGLE_ACTION(win
->menu
->sounds
),
2688 gtkconv
->make_sound
);
2689 gtk_action_set_sensitive(win
->menu
->sounds
, TRUE
);
2693 /* Returns TRUE if some items were added to the menu, FALSE otherwise */
2695 populate_menu_with_options(GtkWidget
*menu
, PidginConversation
*gtkconv
, gboolean all
)
2698 PurpleConversation
*conv
;
2699 PurpleAccount
*account
;
2700 PurpleBlistNode
*node
= NULL
;
2701 PurpleChat
*chat
= NULL
;
2702 PurpleBuddy
*buddy
= NULL
;
2705 conv
= gtkconv
->active_conv
;
2706 account
= purple_conversation_get_account(conv
);
2708 if (PURPLE_IS_CHAT_CONVERSATION(conv
)) {
2709 chat
= purple_blist_find_chat(account
, purple_conversation_get_name(conv
));
2711 if ((chat
== NULL
) && (gtkconv
->history
!= NULL
)) {
2712 chat
= g_object_get_data(G_OBJECT(gtkconv
->history
), "transient_chat");
2715 if ((chat
== NULL
) && (gtkconv
->history
!= NULL
)) {
2716 GHashTable
*components
;
2717 PurpleAccount
*account
= purple_conversation_get_account(conv
);
2718 PurpleProtocol
*protocol
=
2719 purple_protocols_find(purple_account_get_protocol_id(account
));
2720 if (purple_account_get_connection(account
) != NULL
&&
2721 PURPLE_PROTOCOL_IMPLEMENTS(protocol
, CHAT
, info_defaults
)) {
2722 components
= purple_protocol_chat_iface_info_defaults(protocol
, purple_account_get_connection(account
),
2723 purple_conversation_get_name(conv
));
2725 components
= g_hash_table_new_full(g_str_hash
, g_str_equal
,
2727 g_hash_table_replace(components
, g_strdup("channel"),
2728 g_strdup(purple_conversation_get_name(conv
)));
2730 chat
= purple_chat_new(account
, NULL
, components
);
2731 purple_blist_node_set_transient((PurpleBlistNode
*)chat
, TRUE
);
2732 g_object_set_data_full(G_OBJECT(gtkconv
->history
), "transient_chat",
2733 chat
, (GDestroyNotify
)purple_blist_remove_chat
);
2736 if (!purple_account_is_connected(account
))
2739 buddy
= purple_blist_find_buddy(account
, purple_conversation_get_name(conv
));
2740 if (!buddy
&& gtkconv
->history
) {
2741 buddy
= g_object_get_data(G_OBJECT(gtkconv
->history
), "transient_buddy");
2744 buddy
= purple_buddy_new(account
, purple_conversation_get_name(conv
), NULL
);
2745 purple_blist_node_set_transient((PurpleBlistNode
*)buddy
, TRUE
);
2746 g_object_set_data_full(G_OBJECT(gtkconv
->history
), "transient_buddy",
2747 buddy
, (GDestroyNotify
)g_object_unref
);
2753 node
= (PurpleBlistNode
*)chat
;
2755 node
= (PurpleBlistNode
*)buddy
;
2757 /* Now add the stuff */
2760 pidgin_blist_make_buddy_menu(menu
, buddy
, TRUE
);
2765 if (purple_account_is_connected(account
))
2766 pidgin_append_blist_node_proto_menu(menu
, purple_account_get_connection(account
), node
);
2767 pidgin_append_blist_node_extended_menu(menu
, node
);
2770 if ((list
= gtk_container_get_children(GTK_CONTAINER(menu
))) == NULL
) {
2780 regenerate_media_items(PidginConvWindow
*win
)
2783 PurpleAccount
*account
;
2784 PurpleConversation
*conv
;
2786 conv
= pidgin_conv_window_get_active_conversation(win
);
2789 purple_debug_error("gtkconv", "couldn't get active conversation"
2790 " when regenerating media items\n");
2794 account
= purple_conversation_get_account(conv
);
2796 if (account
== NULL
) {
2797 purple_debug_error("gtkconv", "couldn't get account when"
2798 " regenerating media items\n");
2803 * Check if account support voice and/or calls, and
2804 * if the current buddy supports it.
2806 if (PURPLE_IS_IM_CONVERSATION(conv
)) {
2807 PurpleMediaCaps caps
=
2808 purple_protocol_get_media_caps(account
,
2809 purple_conversation_get_name(conv
));
2811 gtk_action_set_sensitive(win
->menu
->audio_call
,
2812 caps
& PURPLE_MEDIA_CAPS_AUDIO
2814 gtk_action_set_sensitive(win
->menu
->video_call
,
2815 caps
& PURPLE_MEDIA_CAPS_VIDEO
2817 gtk_action_set_sensitive(win
->menu
->audio_video_call
,
2818 caps
& PURPLE_MEDIA_CAPS_AUDIO_VIDEO
2820 } else if (PURPLE_IS_CHAT_CONVERSATION(conv
)) {
2821 /* for now, don't care about chats... */
2822 gtk_action_set_sensitive(win
->menu
->audio_call
, FALSE
);
2823 gtk_action_set_sensitive(win
->menu
->video_call
, FALSE
);
2824 gtk_action_set_sensitive(win
->menu
->audio_video_call
, FALSE
);
2826 gtk_action_set_sensitive(win
->menu
->audio_call
, FALSE
);
2827 gtk_action_set_sensitive(win
->menu
->video_call
, FALSE
);
2828 gtk_action_set_sensitive(win
->menu
->audio_video_call
, FALSE
);
2834 regenerate_attention_items(PidginConvWindow
*win
)
2836 GtkWidget
*attention
;
2838 PurpleConversation
*conv
;
2839 PurpleConnection
*pc
;
2840 PurpleProtocol
*protocol
= NULL
;
2843 conv
= pidgin_conv_window_get_active_conversation(win
);
2847 attention
= gtk_ui_manager_get_widget(win
->menu
->ui
,
2848 "/Conversation/ConversationMenu/GetAttention");
2850 /* Remove the previous entries */
2851 gtk_menu_item_set_submenu(GTK_MENU_ITEM(attention
), NULL
);
2853 pc
= purple_conversation_get_connection(conv
);
2855 protocol
= purple_connection_get_protocol(pc
);
2857 if (protocol
&& PURPLE_IS_PROTOCOL_ATTENTION(protocol
)) {
2858 list
= purple_protocol_attention_get_types(PURPLE_PROTOCOL_ATTENTION(protocol
), purple_connection_get_account(pc
));
2860 /* Multiple attention types */
2861 if (list
&& list
->next
) {
2864 menu
= gtk_menu_new();
2866 PurpleAttentionType
*type
;
2867 GtkWidget
*menuitem
;
2871 menuitem
= gtk_menu_item_new_with_label(purple_attention_type_get_name(type
));
2872 g_object_set_data(G_OBJECT(menuitem
), "index", GINT_TO_POINTER(index
));
2873 g_signal_connect(G_OBJECT(menuitem
), "activate",
2874 G_CALLBACK(menu_get_attention_cb
),
2876 gtk_menu_shell_append(GTK_MENU_SHELL(menu
), menuitem
);
2879 list
= g_list_delete_link(list
, list
);
2882 gtk_menu_item_set_submenu(GTK_MENU_ITEM(attention
), menu
);
2883 gtk_widget_show_all(menu
);
2889 regenerate_options_items(PidginConvWindow
*win
)
2892 PidginConversation
*gtkconv
;
2894 GtkWidget
*more_menu
;
2896 gtkconv
= pidgin_conv_window_get_active_gtkconv(win
);
2897 more_menu
= gtk_ui_manager_get_widget(win
->menu
->ui
,
2898 "/Conversation/ConversationMenu/MoreMenu");
2899 gtk_widget_show(more_menu
);
2900 menu
= gtk_menu_item_get_submenu(GTK_MENU_ITEM(more_menu
));
2902 /* Remove the previous entries */
2903 list
= gtk_container_get_children(GTK_CONTAINER(menu
));
2904 g_list_free_full(list
, (GDestroyNotify
)gtk_widget_destroy
);
2906 if (!populate_menu_with_options(menu
, gtkconv
, FALSE
))
2908 GtkWidget
*item
= gtk_menu_item_new_with_label(_("No actions available"));
2909 gtk_menu_shell_append(GTK_MENU_SHELL(menu
), item
);
2910 gtk_widget_set_sensitive(item
, FALSE
);
2913 gtk_widget_show_all(menu
);
2917 remove_from_list(GtkWidget
*widget
, PidginConvWindow
*win
)
2919 GList
*list
= g_object_get_data(G_OBJECT(win
->window
), "plugin-actions");
2920 list
= g_list_remove(list
, widget
);
2921 g_object_set_data(G_OBJECT(win
->window
), "plugin-actions", list
);
2925 regenerate_plugins_items(PidginConvWindow
*win
)
2927 GList
*action_items
;
2930 PidginConversation
*gtkconv
;
2931 PurpleConversation
*conv
;
2934 if (win
->window
== NULL
|| win
== hidden_convwin
)
2937 gtkconv
= pidgin_conv_window_get_active_gtkconv(win
);
2938 if (gtkconv
== NULL
)
2941 conv
= gtkconv
->active_conv
;
2942 action_items
= g_object_get_data(G_OBJECT(win
->window
), "plugin-actions");
2944 /* Remove the old menuitems */
2945 while (action_items
) {
2946 g_signal_handlers_disconnect_by_func(G_OBJECT(action_items
->data
),
2947 G_CALLBACK(remove_from_list
), win
);
2948 gtk_widget_destroy(action_items
->data
);
2949 action_items
= g_list_delete_link(action_items
, action_items
);
2952 item
= gtk_ui_manager_get_widget(win
->menu
->ui
, "/Conversation/OptionsMenu");
2953 menu
= gtk_menu_item_get_submenu(GTK_MENU_ITEM(item
));
2955 list
= purple_conversation_get_extended_menu(conv
);
2957 action_items
= g_list_prepend(NULL
, (item
= pidgin_separator(menu
)));
2958 g_signal_connect(G_OBJECT(item
), "destroy", G_CALLBACK(remove_from_list
), win
);
2961 for(; list
; list
= g_list_delete_link(list
, list
)) {
2962 PurpleActionMenu
*act
= (PurpleActionMenu
*) list
->data
;
2963 item
= pidgin_append_menu_action(menu
, act
, conv
);
2964 action_items
= g_list_prepend(action_items
, item
);
2965 gtk_widget_show_all(item
);
2966 g_signal_connect(G_OBJECT(item
), "destroy", G_CALLBACK(remove_from_list
), win
);
2968 g_object_set_data(G_OBJECT(win
->window
), "plugin-actions", action_items
);
2971 static void menubar_activated(GtkWidget
*item
, gpointer data
)
2973 PidginConvWindow
*win
= data
;
2974 regenerate_media_items(win
);
2975 regenerate_options_items(win
);
2976 regenerate_plugins_items(win
);
2977 regenerate_attention_items(win
);
2979 /* The following are to make sure the 'More' submenu is not regenerated every time
2980 * the focus shifts from 'Conversations' to some other menu and back. */
2981 g_signal_handlers_block_by_func(G_OBJECT(item
), G_CALLBACK(menubar_activated
), data
);
2982 g_signal_connect(G_OBJECT(win
->menu
->menubar
), "deactivate", G_CALLBACK(focus_out_from_menubar
), data
);
2986 focus_out_from_menubar(GtkWidget
*wid
, PidginConvWindow
*win
)
2988 /* The menubar has been deactivated. Make sure the 'More' submenu is regenerated next time
2989 * the 'Conversation' menu pops up. */
2990 GtkWidget
*menuitem
= gtk_ui_manager_get_widget(win
->menu
->ui
, "/Conversation/ConversationMenu");
2991 g_signal_handlers_unblock_by_func(G_OBJECT(menuitem
), G_CALLBACK(menubar_activated
), win
);
2992 g_signal_handlers_disconnect_by_func(G_OBJECT(win
->menu
->menubar
),
2993 G_CALLBACK(focus_out_from_menubar
), win
);
2997 setup_menubar(PidginConvWindow
*win
)
2999 GtkAccelGroup
*accel_group
;
3001 GtkActionGroup
*action_group
;
3003 GtkWidget
*menuitem
;
3005 action_group
= gtk_action_group_new("ConversationActions");
3006 gtk_action_group_set_translation_domain(action_group
, PACKAGE
);
3007 gtk_action_group_add_actions(action_group
,
3009 G_N_ELEMENTS(menu_entries
),
3011 gtk_action_group_add_toggle_actions(action_group
,
3012 menu_toggle_entries
,
3013 G_N_ELEMENTS(menu_toggle_entries
),
3016 win
->menu
->ui
= gtk_ui_manager_new();
3017 gtk_ui_manager_insert_action_group(win
->menu
->ui
, action_group
, 0);
3019 accel_group
= gtk_ui_manager_get_accel_group(win
->menu
->ui
);
3020 gtk_window_add_accel_group(GTK_WINDOW(win
->window
), accel_group
);
3021 g_signal_connect(G_OBJECT(accel_group
), "accel-changed",
3022 G_CALLBACK(pidgin_save_accels_cb
), NULL
);
3025 if (!gtk_ui_manager_add_ui_from_string(win
->menu
->ui
, conversation_menu
, -1, &error
))
3027 g_message("building menus failed: %s", error
->message
);
3028 g_error_free(error
);
3032 win
->menu
->menubar
=
3033 gtk_ui_manager_get_widget(win
->menu
->ui
, "/Conversation");
3035 /* Make sure the 'Conversation ⇨ More' menuitems are regenerated whenever
3036 * the 'Conversation' menu pops up because the entries can change after the
3037 * conversation is created. */
3038 menuitem
= gtk_ui_manager_get_widget(win
->menu
->ui
, "/Conversation/ConversationMenu");
3039 g_signal_connect(G_OBJECT(menuitem
), "activate", G_CALLBACK(menubar_activated
), win
);
3041 win
->menu
->view_log
=
3042 gtk_ui_manager_get_action(win
->menu
->ui
,
3043 "/Conversation/ConversationMenu/ViewLog");
3046 win
->menu
->audio_call
=
3047 gtk_ui_manager_get_action(win
->menu
->ui
,
3048 "/Conversation/ConversationMenu/MediaMenu/AudioCall");
3049 win
->menu
->video_call
=
3050 gtk_ui_manager_get_action(win
->menu
->ui
,
3051 "/Conversation/ConversationMenu/MediaMenu/VideoCall");
3052 win
->menu
->audio_video_call
=
3053 gtk_ui_manager_get_action(win
->menu
->ui
,
3054 "/Conversation/ConversationMenu/MediaMenu/AudioVideoCall");
3059 win
->menu
->send_file
=
3060 gtk_ui_manager_get_action(win
->menu
->ui
,
3061 "/Conversation/ConversationMenu/SendFile");
3063 win
->menu
->get_attention
=
3064 gtk_ui_manager_get_action(win
->menu
->ui
,
3065 "/Conversation/ConversationMenu/GetAttention");
3067 win
->menu
->add_pounce
=
3068 gtk_ui_manager_get_action(win
->menu
->ui
,
3069 "/Conversation/ConversationMenu/AddBuddyPounce");
3073 win
->menu
->get_info
=
3074 gtk_ui_manager_get_action(win
->menu
->ui
,
3075 "/Conversation/ConversationMenu/GetInfo");
3078 gtk_ui_manager_get_action(win
->menu
->ui
,
3079 "/Conversation/ConversationMenu/Invite");
3084 gtk_ui_manager_get_action(win
->menu
->ui
,
3085 "/Conversation/ConversationMenu/Alias");
3088 gtk_ui_manager_get_action(win
->menu
->ui
,
3089 "/Conversation/ConversationMenu/Block");
3091 win
->menu
->unblock
=
3092 gtk_ui_manager_get_action(win
->menu
->ui
,
3093 "/Conversation/ConversationMenu/Unblock");
3096 gtk_ui_manager_get_action(win
->menu
->ui
,
3097 "/Conversation/ConversationMenu/Add");
3100 gtk_ui_manager_get_action(win
->menu
->ui
,
3101 "/Conversation/ConversationMenu/Remove");
3105 win
->menu
->insert_link
=
3106 gtk_ui_manager_get_action(win
->menu
->ui
,
3107 "/Conversation/ConversationMenu/InsertLink");
3109 win
->menu
->insert_image
=
3110 gtk_ui_manager_get_action(win
->menu
->ui
,
3111 "/Conversation/ConversationMenu/InsertImage");
3115 win
->menu
->logging
=
3116 gtk_ui_manager_get_action(win
->menu
->ui
,
3117 "/Conversation/OptionsMenu/EnableLogging");
3119 gtk_ui_manager_get_action(win
->menu
->ui
,
3120 "/Conversation/OptionsMenu/EnableSounds");
3121 method
= purple_prefs_get_string(PIDGIN_PREFS_ROOT
"/sound/method");
3122 if (purple_strequal(method
, "none"))
3124 gtk_toggle_action_set_active(GTK_TOGGLE_ACTION(win
->menu
->sounds
),
3126 gtk_action_set_sensitive(win
->menu
->sounds
, FALSE
);
3128 purple_prefs_connect_callback(win
, PIDGIN_PREFS_ROOT
"/sound/method",
3129 sound_method_pref_changed_cb
, win
);
3131 win
->menu
->show_formatting_toolbar
=
3132 gtk_ui_manager_get_action(win
->menu
->ui
,
3133 "/Conversation/OptionsMenu/ShowFormattingToolbars");
3135 win
->menu
->tray
= pidgin_menu_tray_new();
3136 gtk_menu_shell_append(GTK_MENU_SHELL(win
->menu
->menubar
),
3138 gtk_widget_show(win
->menu
->tray
);
3140 gtk_widget_show(win
->menu
->menubar
);
3142 return win
->menu
->menubar
;
3146 /**************************************************************************
3148 **************************************************************************/
3151 got_typing_keypress(PidginConversation
*gtkconv
, gboolean first
)
3153 PurpleConversation
*conv
= gtkconv
->active_conv
;
3154 PurpleIMConversation
*im
;
3157 * We know we got something, so we at least have to make sure we don't
3158 * send PURPLE_IM_TYPED any time soon.
3161 im
= PURPLE_IM_CONVERSATION(conv
);
3163 purple_im_conversation_stop_send_typed_timeout(im
);
3164 purple_im_conversation_start_send_typed_timeout(im
);
3166 /* Check if we need to send another PURPLE_IM_TYPING message */
3167 if (first
|| (purple_im_conversation_get_type_again(im
) != 0 &&
3168 time(NULL
) > purple_im_conversation_get_type_again(im
)))
3170 unsigned int timeout
;
3171 timeout
= purple_serv_send_typing(purple_conversation_get_connection(conv
),
3172 purple_conversation_get_name(conv
),
3174 purple_im_conversation_set_type_again(im
, timeout
);
3180 typing_animation(gpointer data
) {
3181 PidginConversation
*gtkconv
= data
;
3182 PidginConvWindow
*gtkwin
= gtkconv
->win
;
3183 const char *stock_id
= NULL
;
3185 if(gtkconv
!= pidgin_conv_window_get_active_gtkconv(gtkwin
)) {
3189 switch (rand() % 5) {
3191 stock_id
= PIDGIN_STOCK_ANIMATION_TYPING0
;
3194 stock_id
= PIDGIN_STOCK_ANIMATION_TYPING1
;
3197 stock_id
= PIDGIN_STOCK_ANIMATION_TYPING2
;
3200 stock_id
= PIDGIN_STOCK_ANIMATION_TYPING3
;
3203 stock_id
= PIDGIN_STOCK_ANIMATION_TYPING4
;
3206 if (gtkwin
->menu
->typing_icon
== NULL
) {
3207 gtkwin
->menu
->typing_icon
= gtk_image_new_from_stock(stock_id
, GTK_ICON_SIZE_MENU
);
3208 pidgin_menu_tray_append(PIDGIN_MENU_TRAY(gtkwin
->menu
->tray
),
3209 gtkwin
->menu
->typing_icon
,
3210 _("User is typing..."));
3212 gtk_image_set_from_stock(GTK_IMAGE(gtkwin
->menu
->typing_icon
), stock_id
, GTK_ICON_SIZE_MENU
);
3214 gtk_widget_show(gtkwin
->menu
->typing_icon
);
3220 update_typing_message(PidginConversation
*gtkconv
, const char *message
)
3222 /* TODO WEBKIT: this is not handled at all */
3224 GtkTextBuffer
*buffer
;
3225 GtkTextMark
*stmark
, *enmark
;
3227 if (g_object_get_data(G_OBJECT(gtkconv
->imhtml
), "disable-typing-notification"))
3230 buffer
= gtk_text_view_get_buffer(GTK_TEXT_VIEW(gtkconv
->imhtml
));
3231 stmark
= gtk_text_buffer_get_mark(buffer
, "typing-notification-start");
3232 enmark
= gtk_text_buffer_get_mark(buffer
, "typing-notification-end");
3233 if (stmark
&& enmark
) {
3234 GtkTextIter start
, end
;
3235 gtk_text_buffer_get_iter_at_mark(buffer
, &start
, stmark
);
3236 gtk_text_buffer_get_iter_at_mark(buffer
, &end
, enmark
);
3237 gtk_text_buffer_delete_mark(buffer
, stmark
);
3238 gtk_text_buffer_delete_mark(buffer
, enmark
);
3239 gtk_text_buffer_delete(buffer
, &start
, &end
);
3240 } else if (message
&& *message
== '\n' && message
[1] == ' ' && message
[2] == '\0')
3245 message
= "\n "; /* The blank space is required to avoid a GTK+/Pango bug */
3250 gtk_text_buffer_get_end_iter(buffer
, &iter
);
3251 gtk_text_buffer_create_mark(buffer
, "typing-notification-start", &iter
, TRUE
);
3252 gtk_text_buffer_insert_with_tags_by_name(buffer
, &iter
, message
, -1, "TYPING-NOTIFICATION", NULL
);
3253 gtk_text_buffer_get_end_iter(buffer
, &iter
);
3254 gtk_text_buffer_create_mark(buffer
, "typing-notification-end", &iter
, TRUE
);
3260 update_typing_icon(PidginConversation
*gtkconv
)
3262 PurpleIMConversation
*im
;
3263 char *message
= NULL
;
3265 if (!PURPLE_IS_IM_CONVERSATION(gtkconv
->active_conv
))
3268 im
= PURPLE_IM_CONVERSATION(gtkconv
->active_conv
);
3270 if (purple_im_conversation_get_typing_state(im
) == PURPLE_IM_NOT_TYPING
) {
3272 update_typing_message(gtkconv
, NULL
);
3274 update_typing_message(gtkconv
, "\n ");
3279 if (purple_im_conversation_get_typing_state(im
) == PURPLE_IM_TYPING
) {
3280 message
= g_strdup_printf(_("\n%s is typing..."), purple_conversation_get_title(PURPLE_CONVERSATION(im
)));
3282 message
= g_strdup_printf(_("\n%s has stopped typing"), purple_conversation_get_title(PURPLE_CONVERSATION(im
)));
3285 update_typing_message(gtkconv
, message
);
3290 update_send_to_selection(PidginConvWindow
*win
)
3292 PurpleAccount
*account
;
3293 PurpleConversation
*conv
;
3298 conv
= pidgin_conv_window_get_active_conversation(win
);
3303 account
= purple_conversation_get_account(conv
);
3305 if (account
== NULL
)
3308 if (win
->menu
->send_to
== NULL
)
3311 if (!(b
= purple_blist_find_buddy(account
, purple_conversation_get_name(conv
))))
3314 gtk_widget_show(win
->menu
->send_to
);
3316 menu
= gtk_menu_item_get_submenu(GTK_MENU_ITEM(win
->menu
->send_to
));
3318 for (child
= gtk_container_get_children(GTK_CONTAINER(menu
));
3320 child
= g_list_delete_link(child
, child
)) {
3322 GtkWidget
*item
= child
->data
;
3323 PurpleBuddy
*item_buddy
;
3324 PurpleAccount
*item_account
= g_object_get_data(G_OBJECT(item
), "purple_account");
3325 gchar
*buddy_name
= g_object_get_data(G_OBJECT(item
),
3326 "purple_buddy_name");
3327 item_buddy
= purple_blist_find_buddy(item_account
, buddy_name
);
3329 if (b
== item_buddy
) {
3330 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(item
), TRUE
);
3340 send_to_item_enter_notify_cb(GtkWidget
*menuitem
, GdkEventCrossing
*event
, GtkWidget
*label
)
3342 gtk_widget_set_sensitive(GTK_WIDGET(label
), TRUE
);
3347 send_to_item_leave_notify_cb(GtkWidget
*menuitem
, GdkEventCrossing
*event
, GtkWidget
*label
)
3349 gtk_widget_set_sensitive(GTK_WIDGET(label
), FALSE
);
3354 e2ee_state_to_gtkimage(PurpleE2eeState
*state
)
3358 img
= _pidgin_e2ee_stock_icon_get(
3359 purple_e2ee_state_get_stock_icon(state
));
3363 return gtk_image_new_from_pixbuf(pidgin_pixbuf_from_image(img
));
3367 create_sendto_item(GtkWidget
*menu
, GtkSizeGroup
*sg
, GSList
**group
,
3368 PurpleBuddy
*buddy
, PurpleAccount
*account
, const char *name
,
3369 gboolean e2ee_enabled
)
3374 GtkWidget
*e2ee_image
= NULL
;
3375 GtkWidget
*menuitem
;
3379 /* Create a pixmap for the protocol icon. */
3380 pixbuf
= pidgin_create_protocol_icon(account
, PIDGIN_PROTOCOL_ICON_SMALL
);
3382 /* Now convert it to GtkImage */
3384 image
= gtk_image_new();
3387 image
= gtk_image_new_from_pixbuf(pixbuf
);
3388 g_object_unref(G_OBJECT(pixbuf
));
3392 PurpleIMConversation
*im
;
3393 PurpleE2eeState
*state
= NULL
;
3395 im
= purple_conversations_find_im_with_account(
3396 purple_buddy_get_name(buddy
), purple_buddy_get_account(buddy
));
3398 state
= purple_conversation_get_e2ee_state(PURPLE_CONVERSATION(im
));
3400 e2ee_image
= e2ee_state_to_gtkimage(state
);
3402 e2ee_image
= gtk_image_new();
3405 gtk_size_group_add_widget(sg
, image
);
3407 /* Make our menu item */
3408 text
= g_strdup_printf("%s (%s)", name
, purple_account_get_name_for_display(account
));
3409 menuitem
= gtk_radio_menu_item_new_with_label(*group
, text
);
3411 *group
= gtk_radio_menu_item_get_group(GTK_RADIO_MENU_ITEM(menuitem
));
3413 /* Do some evil, see some evil, speak some evil. */
3414 box
= gtk_box_new(GTK_ORIENTATION_HORIZONTAL
, 0);
3416 label
= gtk_bin_get_child(GTK_BIN(menuitem
));
3417 g_object_ref(label
);
3418 gtk_container_remove(GTK_CONTAINER(menuitem
), label
);
3420 gtk_box_pack_start(GTK_BOX(box
), image
, FALSE
, FALSE
, 0);
3422 gtk_box_pack_start(GTK_BOX(box
), label
, TRUE
, TRUE
, 4);
3424 gtk_box_pack_start(GTK_BOX(box
), e2ee_image
, FALSE
, FALSE
, 0);
3426 if (buddy
!= NULL
&&
3427 !purple_presence_is_online(purple_buddy_get_presence(buddy
)))
3429 gtk_widget_set_sensitive(label
, FALSE
);
3431 /* Set the label sensitive when the menuitem is highlighted and
3432 * insensitive again when the mouse leaves it. This way, it
3433 * doesn't appear weird from the highlighting of the embossed
3434 * (insensitive style) text.*/
3435 g_signal_connect(menuitem
, "enter-notify-event",
3436 G_CALLBACK(send_to_item_enter_notify_cb
), label
);
3437 g_signal_connect(menuitem
, "leave-notify-event",
3438 G_CALLBACK(send_to_item_leave_notify_cb
), label
);
3441 g_object_unref(label
);
3443 gtk_container_add(GTK_CONTAINER(menuitem
), box
);
3445 gtk_widget_show(label
);
3446 gtk_widget_show(image
);
3448 gtk_widget_show(e2ee_image
);
3449 gtk_widget_show(box
);
3451 /* Set our data and callbacks. */
3452 g_object_set_data(G_OBJECT(menuitem
), "purple_account", account
);
3453 g_object_set_data_full(G_OBJECT(menuitem
), "purple_buddy_name", g_strdup(name
), g_free
);
3455 g_signal_connect(G_OBJECT(menuitem
), "activate",
3456 G_CALLBACK(menu_conv_sel_send_cb
), NULL
);
3458 gtk_widget_show(menuitem
);
3459 gtk_menu_shell_append(GTK_MENU_SHELL(menu
), menuitem
);
3463 compare_buddy_presence(PurplePresence
*p1
, PurplePresence
*p2
)
3465 /* This is necessary because multiple PurpleBuddy's don't share the same
3466 * PurplePresence anymore.
3468 PurpleBuddy
*b1
= purple_buddy_presence_get_buddy(PURPLE_BUDDY_PRESENCE(p1
));
3469 PurpleBuddy
*b2
= purple_buddy_presence_get_buddy(PURPLE_BUDDY_PRESENCE(p2
));
3470 if (purple_buddy_get_account(b1
) == purple_buddy_get_account(b2
) &&
3471 purple_strequal(purple_buddy_get_name(b1
), purple_buddy_get_name(b2
)))
3477 generate_send_to_items(PidginConvWindow
*win
)
3480 GSList
*group
= NULL
;
3481 GtkSizeGroup
*sg
= gtk_size_group_new(GTK_SIZE_GROUP_HORIZONTAL
);
3482 PidginConversation
*gtkconv
;
3485 g_return_if_fail(win
!= NULL
);
3487 gtkconv
= pidgin_conv_window_get_active_gtkconv(win
);
3489 g_return_if_fail(gtkconv
!= NULL
);
3491 if (win
->menu
->send_to
!= NULL
)
3492 gtk_widget_destroy(win
->menu
->send_to
);
3494 /* Build the Send To menu */
3495 win
->menu
->send_to
= gtk_menu_item_new_with_mnemonic(_("S_end To"));
3496 gtk_widget_show(win
->menu
->send_to
);
3498 menu
= gtk_menu_new();
3499 gtk_menu_shell_insert(GTK_MENU_SHELL(win
->menu
->menubar
),
3500 win
->menu
->send_to
, 2);
3501 gtk_menu_item_set_submenu(GTK_MENU_ITEM(win
->menu
->send_to
), menu
);
3503 gtk_widget_show(menu
);
3505 if (PURPLE_IS_IM_CONVERSATION(gtkconv
->active_conv
)) {
3506 buds
= purple_blist_find_buddies(purple_conversation_get_account(gtkconv
->active_conv
), purple_conversation_get_name(gtkconv
->active_conv
));
3510 /* The user isn't on the buddy list. So we don't create any sendto menu. */
3514 gboolean e2ee_enabled
= FALSE
;
3515 GList
*list
= NULL
, *iter
;
3516 for (l
= buds
; l
!= NULL
; l
= l
->next
)
3518 PurpleBlistNode
*node
;
3520 node
= PURPLE_BLIST_NODE(purple_buddy_get_contact(PURPLE_BUDDY(l
->data
)));
3522 for (node
= node
->child
; node
!= NULL
; node
= node
->next
)
3524 PurpleBuddy
*buddy
= (PurpleBuddy
*)node
;
3525 PurpleAccount
*account
;
3526 PurpleIMConversation
*im
;
3528 if (!PURPLE_IS_BUDDY(node
))
3531 im
= purple_conversations_find_im_with_account(purple_buddy_get_name(buddy
), purple_buddy_get_account(buddy
));
3532 if (im
&& purple_conversation_get_e2ee_state(PURPLE_CONVERSATION(im
)) != NULL
)
3533 e2ee_enabled
= TRUE
;
3535 account
= purple_buddy_get_account(buddy
);
3536 /* TODO WEBKIT: (I'm not actually sure if this is webkit-related --Mark Doliner) */
3537 if (purple_account_is_connected(account
) /*|| account == purple_conversation_get_account(gtkconv->active_conv)*/)
3539 /* Use the PurplePresence to get unique buddies. */
3540 PurplePresence
*presence
= purple_buddy_get_presence(buddy
);
3541 if (!g_list_find_custom(list
, presence
, (GCompareFunc
)compare_buddy_presence
))
3542 list
= g_list_prepend(list
, presence
);
3547 /* Create the sendto menu only if it has more than one item to show */
3548 if (list
&& list
->next
) {
3549 /* Loop over the list backwards so we get the items in the right order,
3550 * since we did a g_list_prepend() earlier. */
3551 for (iter
= g_list_last(list
); iter
!= NULL
; iter
= iter
->prev
) {
3552 PurplePresence
*pre
= iter
->data
;
3553 PurpleBuddy
*buddy
= purple_buddy_presence_get_buddy(PURPLE_BUDDY_PRESENCE(pre
));
3554 create_sendto_item(menu
, sg
, &group
, buddy
,
3555 purple_buddy_get_account(buddy
), purple_buddy_get_name(buddy
), e2ee_enabled
);
3565 gtk_widget_show(win
->menu
->send_to
);
3566 /* TODO: This should never be insensitive. Possibly hidden or not. */
3568 gtk_widget_set_sensitive(win
->menu
->send_to
, FALSE
);
3569 update_send_to_selection(win
);
3573 _pidgin_e2ee_stock_icon_get(const gchar
*stock_name
)
3575 gchar filename
[100], *path
;
3578 /* core is quitting */
3579 if (e2ee_stock
== NULL
)
3582 if (g_hash_table_lookup_extended(e2ee_stock
, stock_name
, NULL
, (gpointer
*)&image
))
3585 g_snprintf(filename
, sizeof(filename
), "e2ee-%s.png", stock_name
);
3586 path
= g_build_filename(PURPLE_DATADIR
, "pidgin", "icons",
3587 "hicolor", "16x16", "status", filename
, NULL
);
3588 image
= purple_image_new_from_file(path
, NULL
);
3591 g_hash_table_insert(e2ee_stock
, g_strdup(stock_name
), image
);
3596 generate_e2ee_controls(PidginConvWindow
*win
)
3598 PidginConversation
*gtkconv
;
3599 PurpleConversation
*conv
;
3600 PurpleE2eeState
*state
;
3601 PurpleE2eeProvider
*provider
;
3603 GList
*menu_actions
, *it
;
3604 GtkWidget
*e2ee_image
;
3606 gtkconv
= pidgin_conv_window_get_active_gtkconv(win
);
3607 g_return_if_fail(gtkconv
!= NULL
);
3609 conv
= gtkconv
->active_conv
;
3610 g_return_if_fail(conv
!= NULL
);
3612 if (win
->menu
->e2ee
!= NULL
) {
3613 gtk_widget_destroy(win
->menu
->e2ee
);
3614 win
->menu
->e2ee
= NULL
;
3617 provider
= purple_e2ee_provider_get_main();
3618 state
= purple_conversation_get_e2ee_state(conv
);
3619 if (state
== NULL
|| provider
== NULL
)
3621 if (purple_e2ee_state_get_provider(state
) != provider
)
3624 win
->menu
->e2ee
= gtk_image_menu_item_new_with_label(
3625 purple_e2ee_provider_get_name(provider
));
3627 menu
= gtk_menu_new();
3628 gtk_menu_shell_insert(GTK_MENU_SHELL(win
->menu
->menubar
),
3629 win
->menu
->e2ee
, 3);
3630 gtk_menu_item_set_submenu(GTK_MENU_ITEM(win
->menu
->e2ee
), menu
);
3632 e2ee_image
= e2ee_state_to_gtkimage(state
);
3634 gtk_image_menu_item_set_image(
3635 GTK_IMAGE_MENU_ITEM(win
->menu
->e2ee
), e2ee_image
);
3638 gtk_widget_set_tooltip_text(win
->menu
->e2ee
,
3639 purple_e2ee_state_get_name(state
));
3641 menu_actions
= purple_e2ee_provider_get_conv_menu_actions(provider
, conv
);
3642 for (it
= menu_actions
; it
; it
= g_list_next(it
)) {
3643 PurpleActionMenu
*action
= it
->data
;
3645 gtk_widget_show_all(
3646 pidgin_append_menu_action(menu
, action
, conv
));
3648 g_list_free(menu_actions
);
3650 gtk_widget_show(win
->menu
->e2ee
);
3651 gtk_widget_show(menu
);
3655 get_chat_user_status_icon(PurpleChatConversation
*chat
, const char *name
, PurpleChatUserFlags flags
)
3657 const char *image
= NULL
;
3659 if (flags
& PURPLE_CHAT_USER_FOUNDER
) {
3660 image
= PIDGIN_STOCK_STATUS_FOUNDER
;
3661 } else if (flags
& PURPLE_CHAT_USER_OP
) {
3662 image
= PIDGIN_STOCK_STATUS_OPERATOR
;
3663 } else if (flags
& PURPLE_CHAT_USER_HALFOP
) {
3664 image
= PIDGIN_STOCK_STATUS_HALFOP
;
3665 } else if (flags
& PURPLE_CHAT_USER_VOICE
) {
3666 image
= PIDGIN_STOCK_STATUS_VOICE
;
3667 } else if ((!flags
) && purple_chat_conversation_is_ignored_user(chat
, name
)) {
3668 image
= PIDGIN_STOCK_STATUS_IGNORED
;
3676 deleting_chat_user_cb(PurpleChatUser
*cb
)
3678 GtkTreeRowReference
*ref
= purple_chat_user_get_ui_data(cb
);
3681 gtk_tree_row_reference_free(ref
);
3682 purple_chat_user_set_ui_data(cb
, NULL
);
3687 add_chat_user_common(PurpleChatConversation
*chat
, PurpleChatUser
*cb
, const char *old_name
)
3689 PidginConversation
*gtkconv
;
3690 PurpleConversation
*conv
;
3691 PidginChatPane
*gtkchat
;
3692 PurpleConnection
*gc
;
3693 PurpleProtocol
*protocol
;
3696 GtkTreePath
*newpath
;
3699 gboolean is_me
= FALSE
;
3701 const gchar
*name
, *alias
;
3702 gchar
*tmp
, *alias_key
;
3703 PurpleChatUserFlags flags
;
3704 GdkRGBA
*color
= NULL
;
3706 alias
= purple_chat_user_get_alias(cb
);
3707 name
= purple_chat_user_get_name(cb
);
3708 flags
= purple_chat_user_get_flags(cb
);
3710 conv
= PURPLE_CONVERSATION(chat
);
3711 gtkconv
= PIDGIN_CONVERSATION(conv
);
3712 gtkchat
= gtkconv
->u
.chat
;
3713 gc
= purple_conversation_get_connection(conv
);
3715 if (!gc
|| !(protocol
= purple_connection_get_protocol(gc
)))
3718 tm
= gtk_tree_view_get_model(GTK_TREE_VIEW(gtkchat
->list
));
3719 ls
= GTK_LIST_STORE(tm
);
3721 stock
= get_chat_user_status_icon(chat
, name
, flags
);
3723 if (purple_strequal(purple_chat_conversation_get_nick(chat
), purple_normalize(purple_conversation_get_account(conv
), old_name
!= NULL
? old_name
: name
)))
3726 is_buddy
= purple_chat_user_is_buddy(cb
);
3728 tmp
= g_utf8_casefold(alias
, -1);
3729 alias_key
= g_utf8_collate_key(tmp
, -1);
3734 /* TODO WEBKIT: No tags in webkit stuff, yet. */
3735 GtkTextTag
*tag
= gtk_text_tag_table_lookup(
3736 gtk_text_buffer_get_tag_table(GTK_IMHTML(gtkconv
->webview
)->text_buffer
),
3738 g_object_get(tag
, "foreground-rgba", &color
, NULL
);
3742 if ((tag
= get_buddy_tag(chat
, name
, 0, FALSE
)))
3743 g_object_set(G_OBJECT(tag
), "style", PANGO_STYLE_NORMAL
, NULL
);
3744 if ((tag
= get_buddy_tag(chat
, name
, PURPLE_MESSAGE_NICK
, FALSE
)))
3745 g_object_set(G_OBJECT(tag
), "style", PANGO_STYLE_NORMAL
, NULL
);
3746 color
= (GdkRGBA
*)get_nick_color(gtkconv
, name
);
3749 gtk_list_store_insert_with_values(ls
, &iter
,
3751 * The GTK docs are mute about the effects of the "row" value for performance.
3752 * X-Chat hardcodes their value to 0 (prepend) and -1 (append), so we will too.
3753 * It *might* be faster to search the gtk_list_store and set row accurately,
3754 * but no one in #gtk+ seems to know anything about it either.
3755 * Inserting in the "wrong" location has no visible ill effects. - F.P.
3758 CHAT_USERS_ICON_STOCK_COLUMN
, stock
,
3759 CHAT_USERS_ALIAS_COLUMN
, alias
,
3760 CHAT_USERS_ALIAS_KEY_COLUMN
, alias_key
,
3761 CHAT_USERS_NAME_COLUMN
, name
,
3762 CHAT_USERS_FLAGS_COLUMN
, flags
,
3763 CHAT_USERS_COLOR_COLUMN
, color
,
3764 CHAT_USERS_WEIGHT_COLUMN
, is_buddy
? PANGO_WEIGHT_BOLD
: PANGO_WEIGHT_NORMAL
,
3767 if (purple_chat_user_get_ui_data(cb
)) {
3768 GtkTreeRowReference
*ref
= purple_chat_user_get_ui_data(cb
);
3769 gtk_tree_row_reference_free(ref
);
3772 newpath
= gtk_tree_model_get_path(tm
, &iter
);
3773 purple_chat_user_set_ui_data(cb
, gtk_tree_row_reference_new(tm
, newpath
));
3774 gtk_tree_path_free(newpath
);
3778 gdk_rgba_free(color
);
3783 static void topic_callback(GtkWidget
*w
, PidginConversation
*gtkconv
)
3785 PurpleProtocol
*protocol
= NULL
;
3786 PurpleConnection
*gc
;
3787 PurpleConversation
*conv
= gtkconv
->active_conv
;
3788 PidginChatPane
*gtkchat
;
3790 const char *current_topic
;
3792 gc
= purple_conversation_get_connection(conv
);
3794 if(!gc
|| !(protocol
= purple_connection_get_protocol(gc
)))
3797 if(!PURPLE_PROTOCOL_IMPLEMENTS(protocol
, CHAT
, set_topic
))
3800 gtkconv
= PIDGIN_CONVERSATION(conv
);
3801 gtkchat
= gtkconv
->u
.chat
;
3802 new_topic
= g_strdup(gtk_entry_get_text(GTK_ENTRY(gtkchat
->topic_text
)));
3803 current_topic
= purple_chat_conversation_get_topic(PURPLE_CHAT_CONVERSATION(conv
));
3805 if(current_topic
&& !g_utf8_collate(new_topic
, current_topic
)){
3811 gtk_entry_set_text(GTK_ENTRY(gtkchat
->topic_text
), current_topic
);
3813 gtk_entry_set_text(GTK_ENTRY(gtkchat
->topic_text
), "");
3815 purple_protocol_chat_iface_set_topic(protocol
, gc
, purple_chat_conversation_get_id(PURPLE_CHAT_CONVERSATION(conv
)),
3822 sort_chat_users(GtkTreeModel
*model
, GtkTreeIter
*a
, GtkTreeIter
*b
, gpointer userdata
)
3824 PurpleChatUserFlags f1
= 0, f2
= 0;
3825 char *user1
= NULL
, *user2
= NULL
;
3826 gboolean buddy1
= FALSE
, buddy2
= FALSE
;
3829 gtk_tree_model_get(model
, a
,
3830 CHAT_USERS_ALIAS_KEY_COLUMN
, &user1
,
3831 CHAT_USERS_FLAGS_COLUMN
, &f1
,
3832 CHAT_USERS_WEIGHT_COLUMN
, &buddy1
,
3834 gtk_tree_model_get(model
, b
,
3835 CHAT_USERS_ALIAS_KEY_COLUMN
, &user2
,
3836 CHAT_USERS_FLAGS_COLUMN
, &f2
,
3837 CHAT_USERS_WEIGHT_COLUMN
, &buddy2
,
3840 /* Only sort by membership levels */
3841 f1
&= PURPLE_CHAT_USER_VOICE
| PURPLE_CHAT_USER_HALFOP
| PURPLE_CHAT_USER_OP
|
3842 PURPLE_CHAT_USER_FOUNDER
;
3843 f2
&= PURPLE_CHAT_USER_VOICE
| PURPLE_CHAT_USER_HALFOP
| PURPLE_CHAT_USER_OP
|
3844 PURPLE_CHAT_USER_FOUNDER
;
3846 ret
= g_strcmp0(user1
, user2
);
3848 if (user1
!= NULL
&& user2
!= NULL
) {
3850 /* sort more important users first */
3851 ret
= (f1
> f2
) ? -1 : 1;
3852 } else if (buddy1
!= buddy2
) {
3853 ret
= (buddy1
> buddy2
) ? -1 : 1;
3864 update_chat_alias(PurpleBuddy
*buddy
, PurpleChatConversation
*chat
, PurpleConnection
*gc
, PurpleProtocol
*protocol
)
3866 PidginConversation
*gtkconv
= PIDGIN_CONVERSATION(PURPLE_CONVERSATION(chat
));
3867 PurpleAccount
*account
= purple_conversation_get_account(PURPLE_CONVERSATION(chat
));
3868 GtkTreeModel
*model
;
3869 char *normalized_name
;
3873 g_return_if_fail(buddy
!= NULL
);
3874 g_return_if_fail(chat
!= NULL
);
3876 /* This is safe because this callback is only used in chats, not IMs. */
3877 model
= gtk_tree_view_get_model(GTK_TREE_VIEW(gtkconv
->u
.chat
->list
));
3879 if (!gtk_tree_model_get_iter_first(GTK_TREE_MODEL(model
), &iter
))
3882 normalized_name
= g_strdup(purple_normalize(account
, purple_buddy_get_name(buddy
)));
3887 gtk_tree_model_get(model
, &iter
, CHAT_USERS_NAME_COLUMN
, &name
, -1);
3889 if (purple_strequal(normalized_name
, purple_normalize(account
, name
))) {
3890 const char *alias
= name
;
3892 char *alias_key
= NULL
;
3893 PurpleBuddy
*buddy2
;
3895 if (!purple_strequal(purple_chat_conversation_get_nick(chat
), purple_normalize(account
, name
))) {
3896 /* This user is not me, so look into updating the alias. */
3898 if ((buddy2
= purple_blist_find_buddy(account
, name
)) != NULL
) {
3899 alias
= purple_buddy_get_contact_alias(buddy2
);
3902 tmp
= g_utf8_casefold(alias
, -1);
3903 alias_key
= g_utf8_collate_key(tmp
, -1);
3906 gtk_list_store_set(GTK_LIST_STORE(model
), &iter
,
3907 CHAT_USERS_ALIAS_COLUMN
, alias
,
3908 CHAT_USERS_ALIAS_KEY_COLUMN
, alias_key
,
3916 f
= gtk_tree_model_iter_next(model
, &iter
);
3921 g_free(normalized_name
);
3925 blist_node_aliased_cb(PurpleBlistNode
*node
, const char *old_alias
, PurpleChatConversation
*chat
)
3927 PurpleConnection
*gc
;
3928 PurpleProtocol
*protocol
;
3929 PurpleConversation
*conv
= PURPLE_CONVERSATION(chat
);
3931 g_return_if_fail(node
!= NULL
);
3932 g_return_if_fail(conv
!= NULL
);
3934 gc
= purple_conversation_get_connection(conv
);
3935 g_return_if_fail(gc
!= NULL
);
3936 g_return_if_fail(purple_connection_get_protocol(gc
) != NULL
);
3937 protocol
= purple_connection_get_protocol(gc
);
3939 if (purple_protocol_get_options(protocol
) & OPT_PROTO_UNIQUE_CHATNAME
)
3942 if (PURPLE_IS_CONTACT(node
))
3944 PurpleBlistNode
*bnode
;
3946 for(bnode
= node
->child
; bnode
; bnode
= bnode
->next
) {
3948 if(!PURPLE_IS_BUDDY(bnode
))
3951 update_chat_alias((PurpleBuddy
*)bnode
, chat
, gc
, protocol
);
3954 else if (PURPLE_IS_BUDDY(node
))
3955 update_chat_alias((PurpleBuddy
*)node
, chat
, gc
, protocol
);
3956 else if (PURPLE_IS_CHAT(node
) &&
3957 purple_conversation_get_account(conv
) == purple_chat_get_account((PurpleChat
*)node
))
3959 if (old_alias
== NULL
|| g_utf8_collate(old_alias
, purple_conversation_get_title(conv
)) == 0)
3960 pidgin_conv_update_fields(conv
, PIDGIN_CONV_SET_TITLE
);
3965 buddy_cb_common(PurpleBuddy
*buddy
, PurpleChatConversation
*chat
, gboolean is_buddy
)
3967 GtkTreeModel
*model
;
3968 char *normalized_name
;
3970 GtkTextTag
*texttag
;
3971 PurpleConversation
*conv
= PURPLE_CONVERSATION(chat
);
3974 g_return_if_fail(buddy
!= NULL
);
3975 g_return_if_fail(conv
!= NULL
);
3977 /* Do nothing if the buddy does not belong to the conv's account */
3978 if (purple_buddy_get_account(buddy
) != purple_conversation_get_account(conv
))
3981 /* This is safe because this callback is only used in chats, not IMs. */
3982 model
= gtk_tree_view_get_model(GTK_TREE_VIEW(PIDGIN_CONVERSATION(conv
)->u
.chat
->list
));
3984 if (!gtk_tree_model_get_iter_first(GTK_TREE_MODEL(model
), &iter
))
3987 normalized_name
= g_strdup(purple_normalize(purple_conversation_get_account(conv
), purple_buddy_get_name(buddy
)));
3992 gtk_tree_model_get(model
, &iter
, CHAT_USERS_NAME_COLUMN
, &name
, -1);
3994 if (purple_strequal(normalized_name
, purple_normalize(purple_conversation_get_account(conv
), name
))) {
3995 gtk_list_store_set(GTK_LIST_STORE(model
), &iter
,
3996 CHAT_USERS_WEIGHT_COLUMN
, is_buddy
? PANGO_WEIGHT_BOLD
: PANGO_WEIGHT_NORMAL
, -1);
4001 f
= gtk_tree_model_iter_next(model
, &iter
);
4006 g_free(normalized_name
);
4008 blist_node_aliased_cb((PurpleBlistNode
*)buddy
, NULL
, chat
);
4010 texttag
= get_buddy_tag(chat
, purple_buddy_get_name(buddy
), 0, FALSE
); /* XXX: do we want the normalized name? */
4012 g_object_set(texttag
, "weight", is_buddy
? PANGO_WEIGHT_BOLD
: PANGO_WEIGHT_NORMAL
, NULL
);
4017 buddy_added_cb(PurpleBlistNode
*node
, PurpleChatConversation
*chat
)
4019 if (!PURPLE_IS_BUDDY(node
))
4022 buddy_cb_common(PURPLE_BUDDY(node
), chat
, TRUE
);
4026 buddy_removed_cb(PurpleBlistNode
*node
, PurpleChatConversation
*chat
)
4028 if (!PURPLE_IS_BUDDY(node
))
4031 /* If there's another buddy for the same "dude" on the list, do nothing. */
4032 if (purple_blist_find_buddy(purple_buddy_get_account(PURPLE_BUDDY(node
)),
4033 purple_buddy_get_name(PURPLE_BUDDY(node
))) != NULL
)
4036 buddy_cb_common(PURPLE_BUDDY(node
), chat
, FALSE
);
4040 minimum_entry_lines_pref_cb(const char *name
,
4041 PurplePrefType type
,
4042 gconstpointer value
,
4046 GList
*l
= purple_conversations_get_all();
4047 PurpleConversation
*conv
;
4050 conv
= (PurpleConversation
*)l
->data
;
4052 if (PIDGIN_IS_PIDGIN_CONVERSATION(conv
))
4053 resize_webview_cb(PIDGIN_CONVERSATION(conv
));
4060 setup_chat_topic(PidginConversation
*gtkconv
, GtkWidget
*vbox
)
4062 PurpleConversation
*conv
= gtkconv
->active_conv
;
4063 PurpleConnection
*gc
= purple_conversation_get_connection(conv
);
4064 PurpleProtocol
*protocol
= purple_connection_get_protocol(gc
);
4065 if (purple_protocol_get_options(protocol
) & OPT_PROTO_CHAT_TOPIC
)
4067 GtkWidget
*hbox
, *label
;
4068 PidginChatPane
*gtkchat
= gtkconv
->u
.chat
;
4070 hbox
= gtk_box_new(GTK_ORIENTATION_HORIZONTAL
, PIDGIN_HIG_BOX_SPACE
);
4071 gtk_box_pack_start(GTK_BOX(vbox
), hbox
, FALSE
, FALSE
, 0);
4073 label
= gtk_label_new(_("Topic:"));
4074 gtk_box_pack_start(GTK_BOX(hbox
), label
, FALSE
, FALSE
, 0);
4076 gtkchat
->topic_text
= gtk_entry_new();
4077 gtk_widget_set_size_request(gtkchat
->topic_text
, -1, BUDDYICON_SIZE_MIN
);
4079 if(!PURPLE_PROTOCOL_IMPLEMENTS(protocol
, CHAT
, set_topic
)) {
4080 gtk_editable_set_editable(GTK_EDITABLE(gtkchat
->topic_text
), FALSE
);
4082 g_signal_connect(G_OBJECT(gtkchat
->topic_text
), "activate",
4083 G_CALLBACK(topic_callback
), gtkconv
);
4086 gtk_box_pack_start(GTK_BOX(hbox
), gtkchat
->topic_text
, TRUE
, TRUE
, 0);
4087 g_signal_connect(G_OBJECT(gtkchat
->topic_text
), "key_press_event",
4088 G_CALLBACK(entry_key_press_cb
), gtkconv
);
4093 pidgin_conv_userlist_create_tooltip(GtkWidget
*tipwindow
, GtkTreePath
*path
,
4094 gpointer userdata
, int *w
, int *h
)
4096 PidginConversation
*gtkconv
= userdata
;
4098 GtkTreeModel
*model
= gtk_tree_view_get_model(GTK_TREE_VIEW(gtkconv
->u
.chat
->list
));
4099 PurpleConversation
*conv
= gtkconv
->active_conv
;
4100 PurpleBlistNode
*node
;
4101 PurpleProtocol
*protocol
;
4102 PurpleAccount
*account
= purple_conversation_get_account(conv
);
4105 if (purple_account_get_connection(account
) == NULL
)
4108 if (!gtk_tree_model_get_iter(GTK_TREE_MODEL(model
), &iter
, path
))
4111 gtk_tree_model_get(GTK_TREE_MODEL(model
), &iter
, CHAT_USERS_NAME_COLUMN
, &who
, -1);
4113 protocol
= purple_connection_get_protocol(purple_account_get_connection(account
));
4114 node
= (PurpleBlistNode
*)(purple_blist_find_buddy(purple_conversation_get_account(conv
), who
));
4115 if (node
&& protocol
&& (purple_protocol_get_options(protocol
) & OPT_PROTO_UNIQUE_CHATNAME
))
4116 pidgin_blist_draw_tooltip(node
, gtkconv
->infopane
);
4123 setup_chat_userlist(PidginConversation
*gtkconv
, GtkWidget
*hpaned
)
4125 PidginChatPane
*gtkchat
= gtkconv
->u
.chat
;
4126 GtkWidget
*lbox
, *list
;
4128 GtkCellRenderer
*rend
;
4129 GtkTreeViewColumn
*col
;
4131 void *blist_handle
= purple_blist_get_handle();
4132 PurpleConversation
*conv
= gtkconv
->active_conv
;
4134 /* Build the right pane. */
4135 lbox
= gtk_box_new(GTK_ORIENTATION_VERTICAL
, PIDGIN_HIG_BOX_SPACE
);
4136 gtk_paned_pack2(GTK_PANED(hpaned
), lbox
, FALSE
, TRUE
);
4137 gtk_widget_show(lbox
);
4139 /* Setup the label telling how many people are in the room. */
4140 gtkchat
->count
= gtk_label_new(_("0 people in room"));
4141 gtk_label_set_ellipsize(GTK_LABEL(gtkchat
->count
), PANGO_ELLIPSIZE_END
);
4142 gtk_box_pack_start(GTK_BOX(lbox
), gtkchat
->count
, FALSE
, FALSE
, 0);
4143 gtk_widget_show(gtkchat
->count
);
4145 /* Setup the list of users. */
4147 ls
= gtk_list_store_new(CHAT_USERS_COLUMNS
, GDK_TYPE_PIXBUF
, G_TYPE_STRING
,
4148 G_TYPE_STRING
, G_TYPE_STRING
, G_TYPE_INT
,
4149 GDK_TYPE_RGBA
, G_TYPE_INT
, G_TYPE_STRING
);
4150 gtk_tree_sortable_set_sort_func(GTK_TREE_SORTABLE(ls
), CHAT_USERS_ALIAS_KEY_COLUMN
,
4151 sort_chat_users
, NULL
, NULL
);
4153 list
= gtk_tree_view_new_with_model(GTK_TREE_MODEL(ls
));
4155 /* Allow a user to specify gtkrc settings for the chat userlist only */
4156 gtk_widget_set_name(list
, "pidgin_conv_userlist");
4158 rend
= gtk_cell_renderer_pixbuf_new();
4159 g_object_set(G_OBJECT(rend
),
4160 "stock-size", gtk_icon_size_from_name(PIDGIN_ICON_SIZE_TANGO_EXTRA_SMALL
),
4162 col
= gtk_tree_view_column_new_with_attributes(NULL
, rend
,
4163 "stock-id", CHAT_USERS_ICON_STOCK_COLUMN
, NULL
);
4164 gtk_tree_view_column_set_sizing(col
, GTK_TREE_VIEW_COLUMN_AUTOSIZE
);
4165 gtk_tree_view_append_column(GTK_TREE_VIEW(list
), col
);
4166 ul_width
= purple_prefs_get_int(PIDGIN_PREFS_ROOT
"/conversations/chat/userlist_width");
4167 gtk_widget_set_size_request(lbox
, ul_width
, -1);
4169 /* Hack to prevent completely collapsed userlist coming back with a 1 pixel width.
4170 * I would have liked to use the GtkPaned "max-position", but for some reason that didn't work */
4172 gtk_paned_set_position(GTK_PANED(hpaned
), 999999);
4174 g_signal_connect(G_OBJECT(list
), "button_press_event",
4175 G_CALLBACK(right_click_chat_cb
), gtkconv
);
4176 g_signal_connect(G_OBJECT(list
), "row-activated",
4177 G_CALLBACK(activate_list_cb
), gtkconv
);
4178 g_signal_connect(G_OBJECT(list
), "popup-menu",
4179 G_CALLBACK(gtkconv_chat_popup_menu_cb
), gtkconv
);
4180 g_signal_connect(G_OBJECT(lbox
), "size-allocate", G_CALLBACK(lbox_size_allocate_cb
), gtkconv
);
4182 pidgin_tooltip_setup_for_treeview(list
, gtkconv
,
4183 pidgin_conv_userlist_create_tooltip
, NULL
);
4185 rend
= gtk_cell_renderer_text_new();
4187 "foreground-set", TRUE
,
4190 g_object_set(G_OBJECT(rend
), "editable", TRUE
, NULL
);
4192 col
= gtk_tree_view_column_new_with_attributes(NULL
, rend
,
4193 "text", CHAT_USERS_ALIAS_COLUMN
,
4194 "foreground-rgba", CHAT_USERS_COLOR_COLUMN
,
4195 "weight", CHAT_USERS_WEIGHT_COLUMN
,
4198 purple_signal_connect(blist_handle
, "blist-node-added",
4199 gtkchat
, PURPLE_CALLBACK(buddy_added_cb
), conv
);
4200 purple_signal_connect(blist_handle
, "blist-node-removed",
4201 gtkchat
, PURPLE_CALLBACK(buddy_removed_cb
), conv
);
4202 purple_signal_connect(blist_handle
, "blist-node-aliased",
4203 gtkchat
, PURPLE_CALLBACK(blist_node_aliased_cb
), conv
);
4205 gtk_tree_view_column_set_expand(col
, TRUE
);
4206 g_object_set(rend
, "ellipsize", PANGO_ELLIPSIZE_END
, NULL
);
4208 gtk_tree_view_append_column(GTK_TREE_VIEW(list
), col
);
4210 gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(list
), FALSE
);
4211 gtk_widget_show(list
);
4213 gtkchat
->list
= list
;
4215 gtk_box_pack_start(GTK_BOX(lbox
),
4216 pidgin_make_scrollable(list
, GTK_POLICY_AUTOMATIC
, GTK_POLICY_AUTOMATIC
, GTK_SHADOW_IN
, -1, -1),
4221 pidgin_conv_create_tooltip(GtkWidget
*tipwindow
, gpointer userdata
, int *w
, int *h
)
4223 PurpleBlistNode
*node
= NULL
;
4224 PurpleConversation
*conv
;
4225 PidginConversation
*gtkconv
= userdata
;
4227 conv
= gtkconv
->active_conv
;
4228 if (PURPLE_IS_CHAT_CONVERSATION(conv
)) {
4229 node
= (PurpleBlistNode
*)(purple_blist_find_chat(purple_conversation_get_account(conv
), purple_conversation_get_name(conv
)));
4231 node
= g_object_get_data(G_OBJECT(gtkconv
->history
), "transient_chat");
4233 node
= (PurpleBlistNode
*)(purple_blist_find_buddy(purple_conversation_get_account(conv
), purple_conversation_get_name(conv
)));
4235 /* Using the transient blist nodes to show the tooltip doesn't quite work yet. */
4237 node
= g_object_get_data(G_OBJECT(gtkconv
->webview
), "transient_buddy");
4242 pidgin_blist_draw_tooltip(node
, gtkconv
->infopane
);
4247 setup_common_pane(PidginConversation
*gtkconv
)
4249 GtkWidget
*vbox
, *sw
, *event_box
, *view
;
4250 GtkCellRenderer
*rend
;
4252 PurpleConversation
*conv
= gtkconv
->active_conv
;
4254 gboolean chat
= PURPLE_IS_CHAT_CONVERSATION(conv
);
4255 int buddyicon_size
= 0;
4257 /* Setup the top part of the pane */
4258 vbox
= gtk_box_new(GTK_ORIENTATION_VERTICAL
, PIDGIN_HIG_BOX_SPACE
);
4259 gtk_widget_show(vbox
);
4261 /* Setup the info pane */
4262 event_box
= gtk_event_box_new();
4263 gtk_event_box_set_visible_window(GTK_EVENT_BOX(event_box
), FALSE
);
4264 gtk_widget_show(event_box
);
4265 gtkconv
->infopane_hbox
= gtk_box_new(GTK_ORIENTATION_HORIZONTAL
, 0);
4266 gtk_box_pack_start(GTK_BOX(vbox
), event_box
, FALSE
, FALSE
, 0);
4267 gtk_container_add(GTK_CONTAINER(event_box
), gtkconv
->infopane_hbox
);
4268 gtk_widget_show(gtkconv
->infopane_hbox
);
4269 gtk_widget_add_events(event_box
,
4270 GDK_POINTER_MOTION_MASK
| GDK_LEAVE_NOTIFY_MASK
);
4271 g_signal_connect(G_OBJECT(event_box
), "button-press-event",
4272 G_CALLBACK(infopane_press_cb
), gtkconv
);
4274 pidgin_tooltip_setup_for_widget(event_box
, gtkconv
,
4275 pidgin_conv_create_tooltip
, NULL
);
4277 gtkconv
->infopane
= gtk_cell_view_new();
4278 gtkconv
->infopane_model
= gtk_list_store_new(CONV_NUM_COLUMNS
, G_TYPE_STRING
, G_TYPE_STRING
, GDK_TYPE_PIXBUF
, GDK_TYPE_PIXBUF
);
4279 gtk_cell_view_set_model(GTK_CELL_VIEW(gtkconv
->infopane
),
4280 GTK_TREE_MODEL(gtkconv
->infopane_model
));
4281 g_object_unref(gtkconv
->infopane_model
);
4282 gtk_list_store_append(gtkconv
->infopane_model
, &(gtkconv
->infopane_iter
));
4283 gtk_box_pack_start(GTK_BOX(gtkconv
->infopane_hbox
), gtkconv
->infopane
, TRUE
, TRUE
, 0);
4284 path
= gtk_tree_path_new_from_string("0");
4285 gtk_cell_view_set_displayed_row(GTK_CELL_VIEW(gtkconv
->infopane
), path
);
4286 gtk_tree_path_free(path
);
4289 /* This empty widget is used to ensure that the infopane is consistently
4290 sized for chat windows. The correct fix is to put an icon in the chat
4291 window as well, because that would make "Set Custom Icon" consistent
4292 for both the buddy list and the chat window, but PidginConversation
4293 is pretty much stuck until 3.0. */
4294 GtkWidget
*sizing_vbox
;
4295 sizing_vbox
= gtk_box_new(GTK_ORIENTATION_VERTICAL
, 0);
4296 gtk_widget_set_size_request(sizing_vbox
, -1, BUDDYICON_SIZE_MIN
);
4297 gtk_box_pack_start(GTK_BOX(gtkconv
->infopane_hbox
), sizing_vbox
, FALSE
, FALSE
, 0);
4298 gtk_widget_show(sizing_vbox
);
4301 gtkconv
->u
.im
->icon_container
= gtk_box_new(GTK_ORIENTATION_VERTICAL
, 0);
4303 if ((buddy
= purple_blist_find_buddy(purple_conversation_get_account(conv
),
4304 purple_conversation_get_name(conv
))) != NULL
) {
4305 PurpleContact
*contact
= purple_buddy_get_contact(buddy
);
4307 buddyicon_size
= purple_blist_node_get_int((PurpleBlistNode
*)contact
, "pidgin-infopane-iconsize");
4310 buddyicon_size
= CLAMP(buddyicon_size
, BUDDYICON_SIZE_MIN
, BUDDYICON_SIZE_MAX
);
4311 gtk_widget_set_size_request(gtkconv
->u
.im
->icon_container
, -1, buddyicon_size
);
4313 gtk_box_pack_start(GTK_BOX(gtkconv
->infopane_hbox
),
4314 gtkconv
->u
.im
->icon_container
, FALSE
, FALSE
, 0);
4316 gtk_widget_show(gtkconv
->u
.im
->icon_container
);
4319 gtk_widget_show(gtkconv
->infopane
);
4321 rend
= gtk_cell_renderer_pixbuf_new();
4322 gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(gtkconv
->infopane
), rend
, FALSE
);
4323 gtk_cell_layout_set_attributes(GTK_CELL_LAYOUT(gtkconv
->infopane
), rend
, "stock-id", CONV_ICON_COLUMN
, NULL
);
4324 g_object_set(rend
, "xalign", 0.0, "xpad", 6, "ypad", 0,
4325 "stock-size", gtk_icon_size_from_name(PIDGIN_ICON_SIZE_TANGO_EXTRA_SMALL
),
4328 rend
= gtk_cell_renderer_text_new();
4329 gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(gtkconv
->infopane
), rend
, TRUE
);
4330 gtk_cell_layout_set_attributes(GTK_CELL_LAYOUT(gtkconv
->infopane
), rend
, "markup", CONV_TEXT_COLUMN
, NULL
);
4331 g_object_set(rend
, "ypad", 0, "yalign", 0.5, NULL
);
4333 g_object_set(rend
, "ellipsize", PANGO_ELLIPSIZE_END
, NULL
);
4335 rend
= gtk_cell_renderer_pixbuf_new();
4336 gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(gtkconv
->infopane
), rend
, FALSE
);
4337 gtk_cell_layout_set_attributes(GTK_CELL_LAYOUT(gtkconv
->infopane
), rend
, "pixbuf", CONV_PROTOCOL_ICON_COLUMN
, NULL
);
4338 g_object_set(rend
, "xalign", 0.0, "xpad", 3, "ypad", 0, NULL
);
4340 rend
= gtk_cell_renderer_pixbuf_new();
4341 gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(gtkconv
->infopane
), rend
, FALSE
);
4342 gtk_cell_layout_set_attributes(GTK_CELL_LAYOUT(gtkconv
->infopane
), rend
, "pixbuf", CONV_EMBLEM_COLUMN
, NULL
);
4343 g_object_set(rend
, "xalign", 0.0, "xpad", 6, "ypad", 0, NULL
);
4345 /* Setup the history widget */
4346 sw
= gtk_scrolled_window_new(NULL
, NULL
);
4347 gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(sw
), GTK_SHADOW_IN
);
4348 gtk_scrolled_window_set_policy(
4349 GTK_SCROLLED_WINDOW(sw
),
4354 gtkconv
->history_buffer
= talkatu_history_buffer_new();
4355 gtkconv
->history
= talkatu_history_new();
4356 gtk_text_view_set_buffer(GTK_TEXT_VIEW(gtkconv
->history
), gtkconv
->history_buffer
);
4357 gtk_text_view_set_wrap_mode(GTK_TEXT_VIEW(gtkconv
->history
), GTK_WRAP_WORD
);
4358 gtk_container_add(GTK_CONTAINER(sw
), gtkconv
->history
);
4364 setup_chat_topic(gtkconv
, vbox
);
4366 /* Add the talkatu history */
4367 hpaned
= gtk_paned_new(GTK_ORIENTATION_HORIZONTAL
);
4368 gtk_box_pack_start(GTK_BOX(vbox
), hpaned
, TRUE
, TRUE
, 0);
4369 gtk_widget_show(hpaned
);
4370 gtk_paned_pack1(GTK_PANED(hpaned
), sw
, TRUE
, TRUE
);
4372 /* Now add the userlist */
4373 setup_chat_userlist(gtkconv
, hpaned
);
4375 gtk_box_pack_start(GTK_BOX(vbox
), sw
, TRUE
, TRUE
, 0);
4377 gtk_widget_show_all(sw
);
4379 g_object_set_data(G_OBJECT(gtkconv
->history
), "gtkconv", gtkconv
);
4381 g_signal_connect(G_OBJECT(gtkconv
->history
), "key_press_event",
4382 G_CALLBACK(refocus_entry_cb
), gtkconv
);
4383 g_signal_connect(G_OBJECT(gtkconv
->history
), "key_release_event",
4384 G_CALLBACK(refocus_entry_cb
), gtkconv
);
4386 gtkconv
->lower_hbox
= gtk_box_new(GTK_ORIENTATION_HORIZONTAL
, PIDGIN_HIG_BOX_SPACE
);
4387 gtk_box_pack_start(GTK_BOX(vbox
), gtkconv
->lower_hbox
, FALSE
, FALSE
, 0);
4388 gtk_widget_show(gtkconv
->lower_hbox
);
4390 /* Setup the entry widget and all signals */
4391 gtkconv
->editor
= talkatu_editor_new();
4392 talkatu_editor_set_buffer(TALKATU_EDITOR(gtkconv
->editor
), talkatu_html_buffer_new());
4393 gtk_box_pack_start(GTK_BOX(gtkconv
->lower_hbox
), gtkconv
->editor
, TRUE
, TRUE
, 0);
4395 view
= talkatu_editor_get_view(TALKATU_EDITOR(gtkconv
->editor
));
4396 gtk_widget_set_name(view
, "pidgin_conv_entry");
4397 talkatu_view_set_send_binding(TALKATU_VIEW(view
), TALKATU_VIEW_SEND_BINDING_RETURN
| TALKATU_VIEW_SEND_BINDING_KP_ENTER
);
4401 G_CALLBACK(send_cb
),
4406 /* For sending typing notifications for IMs */
4407 gtkconv
->u
.im
->typing_timer
= 0;
4408 gtkconv
->u
.im
->animate
= purple_prefs_get_bool(PIDGIN_PREFS_ROOT
"/conversations/im/animate_buddy_icons");
4409 gtkconv
->u
.im
->show_icon
= TRUE
;
4415 static PidginConversation
*
4416 pidgin_conv_find_gtkconv(PurpleConversation
* conv
)
4418 PurpleBuddy
*bud
= purple_blist_find_buddy(purple_conversation_get_account(conv
), purple_conversation_get_name(conv
));
4420 PurpleBlistNode
*cn
, *bn
;
4425 if (!(c
= purple_buddy_get_contact(bud
)))
4428 cn
= PURPLE_BLIST_NODE(c
);
4429 for (bn
= purple_blist_node_get_first_child(cn
); bn
; bn
= purple_blist_node_get_sibling_next(bn
)) {
4430 PurpleBuddy
*b
= PURPLE_BUDDY(bn
);
4431 PurpleIMConversation
*im
;
4432 if ((im
= purple_conversations_find_im_with_account(purple_buddy_get_name(b
), purple_buddy_get_account(b
)))) {
4433 if (PIDGIN_CONVERSATION(PURPLE_CONVERSATION(im
)))
4434 return PIDGIN_CONVERSATION(PURPLE_CONVERSATION(im
));
4442 buddy_update_cb(PurpleBlistNode
*bnode
, gpointer null
)
4446 g_return_if_fail(bnode
);
4447 if (!PURPLE_IS_BUDDY(bnode
))
4450 for (list
= pidgin_conv_windows_get_list(); list
; list
= list
->next
)
4452 PidginConvWindow
*win
= list
->data
;
4453 PurpleConversation
*conv
= pidgin_conv_window_get_active_conversation(win
);
4455 if (!PURPLE_IS_IM_CONVERSATION(conv
))
4458 pidgin_conv_update_fields(conv
, PIDGIN_CONV_MENU
);
4463 ignore_middle_click(GtkWidget
*widget
, GdkEventButton
*e
, gpointer null
)
4465 /* A click on the pane is propagated to the notebook containing the pane.
4466 * So if Stu accidentally aims high and middle clicks on the pane-handle,
4467 * it causes a conversation tab to close. Let's stop that from happening.
4469 if (e
->button
== GDK_BUTTON_MIDDLE
&& e
->type
== GDK_BUTTON_PRESS
)
4474 /**************************************************************************
4475 * Conversation UI operations
4476 **************************************************************************/
4478 private_gtkconv_new(PurpleConversation
*conv
, gboolean hidden
)
4480 PidginConversation
*gtkconv
;
4481 GtkWidget
*pane
= NULL
;
4482 GtkWidget
*tab_cont
;
4483 PurpleBlistNode
*convnode
;
4485 if (PURPLE_IS_IM_CONVERSATION(conv
) && (gtkconv
= pidgin_conv_find_gtkconv(conv
))) {
4486 purple_conversation_set_ui_data(conv
, gtkconv
);
4487 if (!g_list_find(gtkconv
->convs
, conv
))
4488 gtkconv
->convs
= g_list_prepend(gtkconv
->convs
, conv
);
4489 pidgin_conv_switch_active_conversation(conv
);
4493 gtkconv
= g_new0(PidginConversation
, 1);
4494 purple_conversation_set_ui_data(conv
, gtkconv
);
4495 gtkconv
->active_conv
= conv
;
4496 gtkconv
->convs
= g_list_prepend(gtkconv
->convs
, conv
);
4497 gtkconv
->send_history
= g_list_append(NULL
, NULL
);
4499 /* Setup some initial variables. */
4500 gtkconv
->unseen_state
= PIDGIN_UNSEEN_NONE
;
4501 gtkconv
->unseen_count
= 0;
4502 gtkconv
->last_flags
= 0;
4504 if (PURPLE_IS_IM_CONVERSATION(conv
)) {
4505 gtkconv
->u
.im
= g_malloc0(sizeof(PidginImPane
));
4506 } else if (PURPLE_IS_CHAT_CONVERSATION(conv
)) {
4507 gtkconv
->u
.chat
= g_malloc0(sizeof(PidginChatPane
));
4509 pane
= setup_common_pane(gtkconv
);
4512 if (PURPLE_IS_CHAT_CONVERSATION(conv
))
4513 g_free(gtkconv
->u
.chat
);
4514 else if (PURPLE_IS_IM_CONVERSATION(conv
))
4515 g_free(gtkconv
->u
.im
);
4518 purple_conversation_set_ui_data(conv
, NULL
);
4522 g_signal_connect(G_OBJECT(pane
), "button_press_event",
4523 G_CALLBACK(ignore_middle_click
), NULL
);
4525 /* Setup the container for the tab. */
4526 gtkconv
->tab_cont
= tab_cont
= gtk_box_new(GTK_ORIENTATION_VERTICAL
, PIDGIN_HIG_BOX_SPACE
);
4527 g_object_set_data(G_OBJECT(tab_cont
), "PidginConversation", gtkconv
);
4528 gtk_container_set_border_width(GTK_CONTAINER(tab_cont
), PIDGIN_HIG_BOX_SPACE
);
4529 gtk_box_pack_start(GTK_BOX(tab_cont
), pane
, TRUE
, TRUE
, 0);
4530 gtk_widget_show(pane
);
4532 convnode
= get_conversation_blist_node(conv
);
4533 if (convnode
== NULL
|| !purple_blist_node_get_bool(convnode
, "gtk-mute-sound"))
4534 gtkconv
->make_sound
= TRUE
;
4536 if (convnode
!= NULL
&& purple_blist_node_has_setting(convnode
, "enable-logging")) {
4537 gboolean logging
= purple_blist_node_get_bool(convnode
, "enable-logging");
4538 purple_conversation_set_logging(conv
, logging
);
4541 talkatu_editor_set_toolbar_visible(
4542 TALKATU_EDITOR(gtkconv
->editor
),
4543 purple_prefs_get_bool(PIDGIN_PREFS_ROOT
"/conversations/show_formatting_toolbar")
4546 if (purple_prefs_get_bool(PIDGIN_PREFS_ROOT
"/conversations/im/show_buddy_icons"))
4547 gtk_widget_show(gtkconv
->infopane_hbox
);
4549 gtk_widget_hide(gtkconv
->infopane_hbox
);
4552 g_signal_connect_swapped(G_OBJECT(pane
), "focus",
4553 G_CALLBACK(gtk_widget_grab_focus
),
4557 pidgin_conv_window_add_gtkconv(hidden_convwin
, gtkconv
);
4559 pidgin_conv_placement_place(gtkconv
);
4561 if (generated_nick_colors
== NULL
) {
4564 color
= gtk_widget_get_style(gtkconv
->history
)->base
[GTK_STATE_NORMAL
];
4565 rgba
.red
= color
.red
/ 65535.0;
4566 rgba
.green
= color
.green
/ 65535.0;
4567 rgba
.blue
= color
.blue
/ 65535.0;
4569 generated_nick_colors
= generate_nick_colors(NICK_COLOR_GENERATE_COUNT
, rgba
);
4572 gtkconv
->nick_colors
= g_array_ref(generated_nick_colors
);
4576 pidgin_conv_new_hidden(PurpleConversation
*conv
)
4578 private_gtkconv_new(conv
, TRUE
);
4582 pidgin_conv_new(PurpleConversation
*conv
)
4584 private_gtkconv_new(conv
, FALSE
);
4585 if (PIDGIN_IS_PIDGIN_CONVERSATION(conv
))
4586 purple_signal_emit(pidgin_conversations_get_handle(),
4587 "conversation-displayed", PIDGIN_CONVERSATION(conv
));
4591 received_im_msg_cb(PurpleAccount
*account
, char *sender
, char *message
,
4592 PurpleConversation
*conv
, PurpleMessageFlags flags
)
4594 PurpleConversationUiOps
*ui_ops
= pidgin_conversations_get_conv_ui_ops();
4595 gboolean hide
= FALSE
;
4598 /* create hidden conv if hide_new pref is always */
4599 if (purple_strequal(purple_prefs_get_string(PIDGIN_PREFS_ROOT
"/conversations/im/hide_new"), "always"))
4602 /* create hidden conv if hide_new pref is away and account is away */
4603 if (purple_strequal(purple_prefs_get_string(PIDGIN_PREFS_ROOT
"/conversations/im/hide_new"), "away") &&
4604 !purple_status_is_available(purple_account_get_active_status(account
)))
4607 if (conv
&& PIDGIN_IS_PIDGIN_CONVERSATION(conv
) && !hide
) {
4608 PidginConversation
*gtkconv
= PIDGIN_CONVERSATION(conv
);
4609 if (gtkconv
->win
== hidden_convwin
) {
4610 pidgin_conv_attach_to_conversation(gtkconv
->active_conv
);
4616 ui_ops
->create_conversation
= pidgin_conv_new_hidden
;
4617 purple_im_conversation_new(account
, sender
);
4618 ui_ops
->create_conversation
= pidgin_conv_new
;
4621 /* Somebody wants to keep this conversation around, so don't time it out */
4623 timer
= GPOINTER_TO_INT(g_object_get_data(G_OBJECT(conv
), "close-timer"));
4625 g_source_remove(timer
);
4626 g_object_set_data(G_OBJECT(conv
), "close-timer", GINT_TO_POINTER(0));
4632 pidgin_conv_destroy(PurpleConversation
*conv
)
4634 PidginConversation
*gtkconv
= PIDGIN_CONVERSATION(conv
);
4636 gtkconv
->convs
= g_list_remove(gtkconv
->convs
, conv
);
4637 /* Don't destroy ourselves until all our convos are gone */
4638 if (gtkconv
->convs
) {
4639 /* Make sure the destroyed conversation is not the active one */
4640 if (gtkconv
->active_conv
== conv
) {
4641 gtkconv
->active_conv
= gtkconv
->convs
->data
;
4642 purple_conversation_update(gtkconv
->active_conv
, PURPLE_CONVERSATION_UPDATE_FEATURES
);
4647 pidgin_conv_window_remove_gtkconv(gtkconv
->win
, gtkconv
);
4649 /* If the "Save Conversation" or "Save Icon" dialogs are open then close them */
4650 purple_request_close_with_handle(gtkconv
);
4651 purple_notify_close_with_handle(gtkconv
);
4653 gtk_widget_destroy(gtkconv
->tab_cont
);
4654 g_object_unref(gtkconv
->tab_cont
);
4656 if (PURPLE_IS_IM_CONVERSATION(conv
)) {
4657 if (gtkconv
->u
.im
->icon_timer
!= 0)
4658 g_source_remove(gtkconv
->u
.im
->icon_timer
);
4660 if (gtkconv
->u
.im
->anim
!= NULL
)
4661 g_object_unref(G_OBJECT(gtkconv
->u
.im
->anim
));
4663 if (gtkconv
->u
.im
->typing_timer
!= 0)
4664 g_source_remove(gtkconv
->u
.im
->typing_timer
);
4666 g_free(gtkconv
->u
.im
);
4667 } else if (PURPLE_IS_CHAT_CONVERSATION(conv
)) {
4668 purple_signals_disconnect_by_handle(gtkconv
->u
.chat
);
4669 g_free(gtkconv
->u
.chat
);
4672 gtkconv
->send_history
= g_list_first(gtkconv
->send_history
);
4673 g_list_free_full(gtkconv
->send_history
, g_free
);
4675 if (gtkconv
->attach_timer
) {
4676 g_source_remove(gtkconv
->attach_timer
);
4679 g_array_unref(gtkconv
->nick_colors
);
4686 get_text_tag_color(GtkTextTag
*tag
)
4688 GdkRGBA
*color
= NULL
;
4689 gboolean set
= FALSE
;
4690 static char colcode
[] = "#XXXXXX";
4692 g_object_get(G_OBJECT(tag
), "foreground-set", &set
, "foreground-rgba", &color
, NULL
);
4694 g_snprintf(colcode
, sizeof(colcode
), "#%02x%02x%02x",
4695 (unsigned int)(color
->red
* 255),
4696 (unsigned int)(color
->green
* 255),
4697 (unsigned int)(color
->blue
* 255));
4701 gdk_rgba_free(color
);
4705 /* The callback for an event on a link tag. */
4706 static gboolean
buddytag_event(GtkTextTag
*tag
, GObject
*imhtml
,
4707 GdkEvent
*event
, GtkTextIter
*arg2
, gpointer data
)
4709 if (event
->type
== GDK_BUTTON_PRESS
4710 || event
->type
== GDK_2BUTTON_PRESS
) {
4711 GdkEventButton
*btn_event
= (GdkEventButton
*) event
;
4712 PurpleConversation
*conv
= data
;
4716 g_object_get(G_OBJECT(tag
), "name", &name
, NULL
);
4718 /* strlen("BUDDY " or "HILIT ") == 6 */
4719 g_return_val_if_fail((name
!= NULL
) && (strlen(name
) > 6), FALSE
);
4721 buddyname
= name
+ 6;
4723 /* emit chat-nick-clicked signal */
4724 if (event
->type
== GDK_BUTTON_PRESS
) {
4725 gint plugin_return
= GPOINTER_TO_INT(purple_signal_emit_return_1(
4726 pidgin_conversations_get_handle(), "chat-nick-clicked",
4727 data
, buddyname
, btn_event
->button
));
4728 if (plugin_return
) {
4734 if (btn_event
->button
== GDK_BUTTON_PRIMARY
&& event
->type
== GDK_2BUTTON_PRESS
) {
4735 chat_do_im(PIDGIN_CONVERSATION(conv
), buddyname
);
4739 } else if (btn_event
->button
== GDK_BUTTON_MIDDLE
&& event
->type
== GDK_2BUTTON_PRESS
) {
4740 chat_do_info(PIDGIN_CONVERSATION(conv
), buddyname
);
4744 } else if (gdk_event_triggers_context_menu(event
)) {
4745 GtkTextIter start
, end
;
4747 /* we shouldn't display the popup
4748 * if the user has selected something: */
4749 if (!gtk_text_buffer_get_selection_bounds(
4750 gtk_text_iter_get_buffer(arg2
),
4752 GtkWidget
*menu
= NULL
;
4753 PurpleConnection
*gc
=
4754 purple_conversation_get_connection(conv
);
4756 menu
= create_chat_menu(conv
, buddyname
, gc
);
4757 gtk_menu_popup_at_pointer(GTK_MENU(menu
), event
);
4761 /* Don't propagate the event any further */
4773 static GtkTextTag
*get_buddy_tag(PurpleChatConversation
*chat
, const char *who
, PurpleMessageFlags flag
,
4778 PidginConversation
*gtkconv
= PIDGIN_CONVERSATION(conv
);
4779 GtkTextTag
*buddytag
;
4781 gboolean highlight
= (flag
& PURPLE_MESSAGE_NICK
);
4782 GtkTextBuffer
*buffer
= GTK_IMHTML(gtkconv
->imhtml
)->text_buffer
;
4784 str
= g_strdup_printf(highlight
? "HILIT %s" : "BUDDY %s", who
);
4786 buddytag
= gtk_text_tag_table_lookup(
4787 gtk_text_buffer_get_tag_table(buffer
), str
);
4789 if (buddytag
== NULL
&& create
) {
4791 buddytag
= gtk_text_buffer_create_tag(buffer
, str
,
4792 "foreground", get_text_tag_color(gtk_text_tag_table_lookup(
4793 gtk_text_buffer_get_tag_table(buffer
), "highlight-name")),
4794 "weight", PANGO_WEIGHT_BOLD
,
4797 buddytag
= gtk_text_buffer_create_tag(
4799 "foreground-rgba", get_nick_color(gtkconv
, who
),
4800 "weight", purple_blist_find_buddy(purple_conversation_get_account(conv
), who
) ? PANGO_WEIGHT_BOLD
: PANGO_WEIGHT_NORMAL
,
4803 g_object_set_data(G_OBJECT(buddytag
), "cursor", "");
4804 g_signal_connect(G_OBJECT(buddytag
), "event",
4805 G_CALLBACK(buddytag_event
), conv
);
4816 writing_msg(PurpleConversation
*conv
, PurpleMessage
*msg
, gpointer _unused
)
4818 PidginConversation
*gtkconv
;
4820 g_return_val_if_fail(msg
!= NULL
, FALSE
);
4822 if (!(purple_message_get_flags(msg
) & PURPLE_MESSAGE_ACTIVE_ONLY
))
4825 g_return_val_if_fail(conv
!= NULL
, FALSE
);
4826 gtkconv
= PIDGIN_CONVERSATION(conv
);
4827 g_return_val_if_fail(gtkconv
!= NULL
, FALSE
);
4829 if (conv
== gtkconv
->active_conv
)
4832 purple_debug_info("gtkconv",
4833 "Suppressing message for an inactive conversation");
4839 pidgin_conv_write_conv(PurpleConversation
*conv
, PurpleMessage
*pmsg
)
4841 PidginMessage
*pidgin_msg
= NULL
;
4842 PurpleMessageFlags flags
;
4843 PidginConversation
*gtkconv
;
4844 PurpleConnection
*gc
;
4845 PurpleAccount
*account
;
4846 gboolean plugin_return
;
4848 g_return_if_fail(conv
!= NULL
);
4849 gtkconv
= PIDGIN_CONVERSATION(conv
);
4850 g_return_if_fail(gtkconv
!= NULL
);
4851 flags
= purple_message_get_flags(pmsg
);
4854 if (gtkconv
->attach_timer
) {
4855 /* We are currently in the process of filling up the buffer with the message
4856 * history of the conversation. So we do not need to add the message here.
4857 * Instead, this message will be added to the message-list, which in turn will
4858 * be processed and displayed by the attach-callback.
4863 if (conv
!= gtkconv
->active_conv
)
4865 /* Set the active conversation to the one that just messaged us. */
4866 /* TODO: consider not doing this if the account is offline or something */
4867 if (flags
& (PURPLE_MESSAGE_SEND
| PURPLE_MESSAGE_RECV
))
4868 pidgin_conv_switch_active_conversation(conv
);
4872 account
= purple_conversation_get_account(conv
);
4873 g_return_if_fail(account
!= NULL
);
4874 gc
= purple_account_get_connection(account
);
4875 g_return_if_fail(gc
!= NULL
|| !(flags
& (PURPLE_MESSAGE_SEND
| PURPLE_MESSAGE_RECV
)));
4877 plugin_return
= GPOINTER_TO_INT(purple_signal_emit_return_1(
4878 pidgin_conversations_get_handle(),
4879 (PURPLE_IS_IM_CONVERSATION(conv
) ? "displaying-im-msg" : "displaying-chat-msg"),
4886 pidgin_msg
= pidgin_message_new(pmsg
);
4887 talkatu_history_buffer_write_message(
4888 TALKATU_HISTORY_BUFFER(gtkconv
->history_buffer
),
4889 TALKATU_MESSAGE(pidgin_msg
)
4892 /* Tab highlighting stuff */
4893 if (!(flags
& PURPLE_MESSAGE_SEND
) && !pidgin_conv_has_focus(conv
))
4895 PidginUnseenState unseen
= PIDGIN_UNSEEN_NONE
;
4897 if ((flags
& PURPLE_MESSAGE_NICK
) == PURPLE_MESSAGE_NICK
)
4898 unseen
= PIDGIN_UNSEEN_NICK
;
4899 else if (((flags
& PURPLE_MESSAGE_SYSTEM
) == PURPLE_MESSAGE_SYSTEM
) ||
4900 ((flags
& PURPLE_MESSAGE_ERROR
) == PURPLE_MESSAGE_ERROR
))
4901 unseen
= PIDGIN_UNSEEN_EVENT
;
4902 else if ((flags
& PURPLE_MESSAGE_NO_LOG
) == PURPLE_MESSAGE_NO_LOG
)
4903 unseen
= PIDGIN_UNSEEN_NO_LOG
;
4905 unseen
= PIDGIN_UNSEEN_TEXT
;
4907 gtkconv_set_unseen(gtkconv
, unseen
);
4910 /* on rejoin only request message history from after this message */
4911 if (flags
& (PURPLE_MESSAGE_SEND
| PURPLE_MESSAGE_RECV
) &&
4912 PURPLE_IS_CHAT_CONVERSATION(conv
)) {
4913 PurpleChat
*chat
= purple_blist_find_chat(
4914 purple_conversation_get_account(conv
),
4915 purple_conversation_get_name(conv
));
4917 GHashTable
*comps
= purple_chat_get_components(chat
);
4918 time_t now
, history_since
, prev_history_since
= 0;
4919 struct tm
*history_since_tm
;
4920 const char *history_since_s
, *prev_history_since_s
;
4922 history_since
= purple_message_get_time(pmsg
) + 1;
4924 prev_history_since_s
= g_hash_table_lookup(comps
,
4926 if (prev_history_since_s
!= NULL
)
4927 prev_history_since
= purple_str_to_time(
4928 prev_history_since_s
, TRUE
, NULL
, NULL
,
4932 /* in case of incorrectly stored timestamps */
4933 if (prev_history_since
> now
)
4934 prev_history_since
= now
;
4935 /* in case of delayed messages */
4936 if (history_since
< prev_history_since
)
4937 history_since
= prev_history_since
;
4939 history_since_tm
= gmtime(&history_since
);
4940 history_since_s
= purple_utf8_strftime(
4941 "%Y-%m-%dT%H:%M:%SZ", history_since_tm
);
4942 if (!purple_strequal(prev_history_since_s
,
4944 g_hash_table_replace(comps
,
4945 g_strdup("history_since"),
4946 g_strdup(history_since_s
));
4950 purple_signal_emit(pidgin_conversations_get_handle(),
4951 (PURPLE_IS_IM_CONVERSATION(conv
) ? "displayed-im-msg" : "displayed-chat-msg"),
4953 update_typing_message(gtkconv
, NULL
);
4956 static gboolean
get_iter_from_chatuser(PurpleChatUser
*cb
, GtkTreeIter
*iter
)
4958 GtkTreeRowReference
*ref
;
4960 GtkTreeModel
*model
;
4962 g_return_val_if_fail(cb
!= NULL
, FALSE
);
4964 ref
= purple_chat_user_get_ui_data(cb
);
4968 if ((path
= gtk_tree_row_reference_get_path(ref
)) == NULL
)
4971 model
= gtk_tree_row_reference_get_model(ref
);
4972 if (!gtk_tree_model_get_iter(GTK_TREE_MODEL(model
), iter
, path
)) {
4973 gtk_tree_path_free(path
);
4977 gtk_tree_path_free(path
);
4982 pidgin_conv_chat_add_users(PurpleChatConversation
*chat
, GList
*cbuddies
, gboolean new_arrivals
)
4984 PidginConversation
*gtkconv
;
4985 PidginChatPane
*gtkchat
;
4992 gtkconv
= PIDGIN_CONVERSATION(PURPLE_CONVERSATION(chat
));
4993 gtkchat
= gtkconv
->u
.chat
;
4995 num_users
= purple_chat_conversation_get_users_count(chat
);
4997 g_snprintf(tmp
, sizeof(tmp
),
4998 ngettext("%d person in room", "%d people in room",
5002 gtk_label_set_text(GTK_LABEL(gtkchat
->count
), tmp
);
5004 ls
= GTK_LIST_STORE(gtk_tree_view_get_model(GTK_TREE_VIEW(gtkchat
->list
)));
5006 gtk_tree_sortable_set_sort_column_id(GTK_TREE_SORTABLE(ls
), GTK_TREE_SORTABLE_UNSORTED_SORT_COLUMN_ID
,
5007 GTK_TREE_SORTABLE_UNSORTED_SORT_COLUMN_ID
);
5011 add_chat_user_common(chat
, (PurpleChatUser
*)l
->data
, NULL
);
5015 /* Currently GTK+ maintains our sorted list after it's in the tree.
5016 * This may change if it turns out we can manage it faster ourselves.
5018 gtk_tree_sortable_set_sort_column_id(GTK_TREE_SORTABLE(ls
), CHAT_USERS_ALIAS_KEY_COLUMN
,
5019 GTK_SORT_ASCENDING
);
5023 pidgin_conv_chat_rename_user(PurpleChatConversation
*chat
, const char *old_name
,
5024 const char *new_name
, const char *new_alias
)
5026 PidginConversation
*gtkconv
;
5027 PidginChatPane
*gtkchat
;
5028 PurpleChatUser
*old_chatuser
, *new_chatuser
;
5030 GtkTreeModel
*model
;
5033 gtkconv
= PIDGIN_CONVERSATION(PURPLE_CONVERSATION(chat
));
5034 gtkchat
= gtkconv
->u
.chat
;
5036 model
= gtk_tree_view_get_model(GTK_TREE_VIEW(gtkchat
->list
));
5038 if (!gtk_tree_model_get_iter_first(GTK_TREE_MODEL(model
), &iter
))
5041 if ((tag
= get_buddy_tag(chat
, old_name
, 0, FALSE
)))
5042 g_object_set(G_OBJECT(tag
), "style", PANGO_STYLE_ITALIC
, NULL
);
5043 if ((tag
= get_buddy_tag(chat
, old_name
, PURPLE_MESSAGE_NICK
, FALSE
)))
5044 g_object_set(G_OBJECT(tag
), "style", PANGO_STYLE_ITALIC
, NULL
);
5046 old_chatuser
= purple_chat_conversation_find_user(chat
, old_name
);
5050 if (get_iter_from_chatuser(old_chatuser
, &iter
)) {
5051 GtkTreeRowReference
*ref
= purple_chat_user_get_ui_data(old_chatuser
);
5053 gtk_list_store_remove(GTK_LIST_STORE(model
), &iter
);
5054 gtk_tree_row_reference_free(ref
);
5055 purple_chat_user_set_ui_data(old_chatuser
, NULL
);
5058 g_return_if_fail(new_alias
!= NULL
);
5060 new_chatuser
= purple_chat_conversation_find_user(chat
, new_name
);
5062 add_chat_user_common(chat
, new_chatuser
, old_name
);
5066 pidgin_conv_chat_remove_users(PurpleChatConversation
*chat
, GList
*users
)
5068 PidginConversation
*gtkconv
;
5069 PidginChatPane
*gtkchat
;
5071 GtkTreeModel
*model
;
5078 gtkconv
= PIDGIN_CONVERSATION(PURPLE_CONVERSATION(chat
));
5079 gtkchat
= gtkconv
->u
.chat
;
5081 num_users
= purple_chat_conversation_get_users_count(chat
);
5083 for (l
= users
; l
!= NULL
; l
= l
->next
) {
5084 model
= gtk_tree_view_get_model(GTK_TREE_VIEW(gtkchat
->list
));
5086 if (!gtk_tree_model_get_iter_first(GTK_TREE_MODEL(model
), &iter
))
5093 gtk_tree_model_get(GTK_TREE_MODEL(model
), &iter
,
5094 CHAT_USERS_NAME_COLUMN
, &val
, -1);
5096 if (!purple_utf8_strcasecmp((char *)l
->data
, val
)) {
5097 f
= gtk_list_store_remove(GTK_LIST_STORE(model
), &iter
);
5100 f
= gtk_tree_model_iter_next(GTK_TREE_MODEL(model
), &iter
);
5105 if ((tag
= get_buddy_tag(chat
, l
->data
, 0, FALSE
)))
5106 g_object_set(G_OBJECT(tag
), "style", PANGO_STYLE_ITALIC
, NULL
);
5107 if ((tag
= get_buddy_tag(chat
, l
->data
, PURPLE_MESSAGE_NICK
, FALSE
)))
5108 g_object_set(G_OBJECT(tag
), "style", PANGO_STYLE_ITALIC
, NULL
);
5111 g_snprintf(tmp
, sizeof(tmp
),
5112 ngettext("%d person in room", "%d people in room",
5113 num_users
), num_users
);
5115 gtk_label_set_text(GTK_LABEL(gtkchat
->count
), tmp
);
5119 pidgin_conv_chat_update_user(PurpleChatUser
*chatuser
)
5121 PurpleChatConversation
*chat
;
5122 PidginConversation
*gtkconv
;
5123 PidginChatPane
*gtkchat
;
5125 GtkTreeModel
*model
;
5130 chat
= purple_chat_user_get_chat(chatuser
);
5131 gtkconv
= PIDGIN_CONVERSATION(PURPLE_CONVERSATION(chat
));
5132 gtkchat
= gtkconv
->u
.chat
;
5134 model
= gtk_tree_view_get_model(GTK_TREE_VIEW(gtkchat
->list
));
5136 if (!gtk_tree_model_get_iter_first(GTK_TREE_MODEL(model
), &iter
))
5139 if (get_iter_from_chatuser(chatuser
, &iter
)) {
5140 GtkTreeRowReference
*ref
= purple_chat_user_get_ui_data(chatuser
);
5141 gtk_list_store_remove(GTK_LIST_STORE(model
), &iter
);
5142 gtk_tree_row_reference_free(ref
);
5143 purple_chat_user_set_ui_data(chatuser
, NULL
);
5146 add_chat_user_common(chat
, chatuser
, NULL
);
5150 pidgin_conv_has_focus(PurpleConversation
*conv
)
5152 PidginConversation
*gtkconv
= PIDGIN_CONVERSATION(conv
);
5153 PidginConvWindow
*win
;
5158 g_object_get(G_OBJECT(win
->window
), "has-toplevel-focus", &has_focus
, NULL
);
5160 if (has_focus
&& pidgin_conv_window_is_active_conversation(conv
))
5167 * Makes sure all the menu items and all the buttons are hidden/shown and
5168 * sensitive/insensitive. This is called after changing tabs and when an
5169 * account signs on or off.
5172 gray_stuff_out(PidginConversation
*gtkconv
)
5174 PidginConvWindow
*win
;
5175 PurpleConversation
*conv
= gtkconv
->active_conv
;
5176 PurpleConnection
*gc
;
5177 PurpleProtocol
*protocol
= NULL
;
5178 GdkPixbuf
*window_icon
= NULL
;
5179 // PidginWebViewButtons buttons;
5180 PurpleAccount
*account
;
5182 win
= pidgin_conv_get_window(gtkconv
);
5183 gc
= purple_conversation_get_connection(conv
);
5184 account
= purple_conversation_get_account(conv
);
5187 protocol
= purple_connection_get_protocol(gc
);
5189 if (win
->menu
->send_to
!= NULL
)
5190 update_send_to_selection(win
);
5193 * Handle hiding and showing stuff based on what type of conv this is.
5194 * Stuff that Purple IMs support in general should be shown for IM
5195 * conversations. Stuff that Purple chats support in general should be
5196 * shown for chat conversations. It doesn't matter whether the protocol
5197 * supports it or not--that only affects if the button or menu item
5198 * is sensitive or not.
5200 if (PURPLE_IS_IM_CONVERSATION(conv
)) {
5201 /* Show stuff that applies to IMs, hide stuff that applies to chats */
5203 /* Deal with menu items */
5204 gtk_action_set_visible(win
->menu
->view_log
, TRUE
);
5205 gtk_action_set_visible(win
->menu
->send_file
, TRUE
);
5206 gtk_action_set_visible(win
->menu
->get_attention
, TRUE
);
5207 gtk_action_set_visible(win
->menu
->add_pounce
, TRUE
);
5208 gtk_action_set_visible(win
->menu
->get_info
, TRUE
);
5209 gtk_action_set_visible(win
->menu
->invite
, FALSE
);
5210 gtk_action_set_visible(win
->menu
->alias
, TRUE
);
5211 if (purple_account_privacy_check(account
, purple_conversation_get_name(conv
))) {
5212 gtk_action_set_visible(win
->menu
->unblock
, FALSE
);
5213 gtk_action_set_visible(win
->menu
->block
, TRUE
);
5215 gtk_action_set_visible(win
->menu
->block
, FALSE
);
5216 gtk_action_set_visible(win
->menu
->unblock
, TRUE
);
5219 if (purple_blist_find_buddy(account
, purple_conversation_get_name(conv
)) == NULL
) {
5220 gtk_action_set_visible(win
->menu
->add
, TRUE
);
5221 gtk_action_set_visible(win
->menu
->remove
, FALSE
);
5223 gtk_action_set_visible(win
->menu
->remove
, TRUE
);
5224 gtk_action_set_visible(win
->menu
->add
, FALSE
);
5227 gtk_action_set_visible(win
->menu
->insert_link
, TRUE
);
5228 gtk_action_set_visible(win
->menu
->insert_image
, TRUE
);
5229 } else if (PURPLE_IS_CHAT_CONVERSATION(conv
)) {
5230 /* Show stuff that applies to Chats, hide stuff that applies to IMs */
5232 /* Deal with menu items */
5233 gtk_action_set_visible(win
->menu
->view_log
, TRUE
);
5234 gtk_action_set_visible(win
->menu
->send_file
, FALSE
);
5235 gtk_action_set_visible(win
->menu
->get_attention
, FALSE
);
5236 gtk_action_set_visible(win
->menu
->add_pounce
, FALSE
);
5237 gtk_action_set_visible(win
->menu
->get_info
, FALSE
);
5238 gtk_action_set_visible(win
->menu
->invite
, TRUE
);
5239 gtk_action_set_visible(win
->menu
->alias
, TRUE
);
5240 gtk_action_set_visible(win
->menu
->block
, FALSE
);
5241 gtk_action_set_visible(win
->menu
->unblock
, FALSE
);
5243 if ((account
== NULL
) || purple_blist_find_chat(account
, purple_conversation_get_name(conv
)) == NULL
) {
5244 /* If the chat is NOT in the buddy list */
5245 gtk_action_set_visible(win
->menu
->add
, TRUE
);
5246 gtk_action_set_visible(win
->menu
->remove
, FALSE
);
5248 /* If the chat IS in the buddy list */
5249 gtk_action_set_visible(win
->menu
->add
, FALSE
);
5250 gtk_action_set_visible(win
->menu
->remove
, TRUE
);
5253 gtk_action_set_visible(win
->menu
->insert_link
, TRUE
);
5254 gtk_action_set_visible(win
->menu
->insert_image
, TRUE
);
5258 * Handle graying stuff out based on whether an account is connected
5259 * and what features that account supports.
5262 (!PURPLE_IS_CHAT_CONVERSATION(conv
) ||
5263 !purple_chat_conversation_has_left(PURPLE_CHAT_CONVERSATION(conv
)) ))
5265 PurpleConnectionFlags features
= purple_conversation_get_features(conv
);
5266 /* Account is online */
5267 /* Deal with the toolbar */
5269 if (features
& PURPLE_CONNECTION_FLAG_HTML
)
5271 buttons
= PIDGIN_WEBVIEW_ALL
; /* Everything on */
5272 if (features
& PURPLE_CONNECTION_FLAG_NO_BGCOLOR
)
5273 buttons
&= ~PIDGIN_WEBVIEW_BACKCOLOR
;
5274 if (features
& PURPLE_CONNECTION_FLAG_NO_FONTSIZE
)
5276 buttons
&= ~PIDGIN_WEBVIEW_GROW
;
5277 buttons
&= ~PIDGIN_WEBVIEW_SHRINK
;
5279 if (features
& PURPLE_CONNECTION_FLAG_NO_URLDESC
)
5280 buttons
&= ~PIDGIN_WEBVIEW_LINKDESC
5282 buttons
= PIDGIN_WEBVIEW_SMILEY
| PIDGIN_WEBVIEW_IMAGE
;
5285 if (features
& PURPLE_CONNECTION_FLAG_NO_IMAGES
)
5286 buttons
&= ~PIDGIN_WEBVIEW_IMAGE
;
5288 if (features
& PURPLE_CONNECTION_FLAG_ALLOW_CUSTOM_SMILEY
)
5289 buttons
|= PIDGIN_WEBVIEW_CUSTOM_SMILEY
;
5291 buttons
&= ~PIDGIN_WEBVIEW_CUSTOM_SMILEY
;
5293 pidgin_webview_set_format_functions(PIDGIN_WEBVIEW(gtkconv
->entry
), buttons
);
5296 /* Deal with menu items */
5297 gtk_action_set_sensitive(win
->menu
->view_log
, TRUE
);
5298 gtk_action_set_sensitive(win
->menu
->add_pounce
, TRUE
);
5299 gtk_action_set_sensitive(win
->menu
->get_info
, (PURPLE_PROTOCOL_IMPLEMENTS(protocol
, SERVER
, get_info
)));
5300 gtk_action_set_sensitive(win
->menu
->invite
, (PURPLE_PROTOCOL_IMPLEMENTS(protocol
, CHAT
, invite
)));
5301 gtk_action_set_sensitive(win
->menu
->insert_link
, (features
& PURPLE_CONNECTION_FLAG_HTML
));
5302 gtk_action_set_sensitive(win
->menu
->insert_image
, !(features
& PURPLE_CONNECTION_FLAG_NO_IMAGES
));
5304 if (PURPLE_IS_IM_CONVERSATION(conv
))
5306 gboolean can_send_file
= FALSE
;
5307 const gchar
*name
= purple_conversation_get_name(conv
);
5309 if (PURPLE_IS_PROTOCOL_XFER(protocol
) &&
5310 purple_protocol_xfer_can_receive(PURPLE_PROTOCOL_XFER(protocol
), gc
, name
)
5312 can_send_file
= TRUE
;
5315 gtk_action_set_sensitive(win
->menu
->add
, (PURPLE_PROTOCOL_IMPLEMENTS(protocol
, SERVER
, add_buddy
)));
5316 gtk_action_set_sensitive(win
->menu
->remove
, (PURPLE_PROTOCOL_IMPLEMENTS(protocol
, SERVER
, remove_buddy
)));
5317 gtk_action_set_sensitive(win
->menu
->send_file
, can_send_file
);
5318 gtk_action_set_sensitive(win
->menu
->get_attention
, (PURPLE_IS_PROTOCOL_ATTENTION(protocol
)));
5319 gtk_action_set_sensitive(win
->menu
->alias
,
5320 (account
!= NULL
) &&
5321 (purple_blist_find_buddy(account
, purple_conversation_get_name(conv
)) != NULL
));
5323 else if (PURPLE_IS_CHAT_CONVERSATION(conv
))
5325 gtk_action_set_sensitive(win
->menu
->add
, (PURPLE_PROTOCOL_IMPLEMENTS(protocol
, CHAT
, join
)));
5326 gtk_action_set_sensitive(win
->menu
->remove
, (PURPLE_PROTOCOL_IMPLEMENTS(protocol
, CHAT
, join
)));
5327 gtk_action_set_sensitive(win
->menu
->alias
,
5328 (account
!= NULL
) &&
5329 (purple_blist_find_chat(account
, purple_conversation_get_name(conv
)) != NULL
));
5333 /* Account is offline */
5334 /* Or it's a chat that we've left. */
5336 /* Then deal with menu items */
5337 gtk_action_set_sensitive(win
->menu
->view_log
, TRUE
);
5338 gtk_action_set_sensitive(win
->menu
->send_file
, FALSE
);
5339 gtk_action_set_sensitive(win
->menu
->get_attention
, FALSE
);
5340 gtk_action_set_sensitive(win
->menu
->add_pounce
, TRUE
);
5341 gtk_action_set_sensitive(win
->menu
->get_info
, FALSE
);
5342 gtk_action_set_sensitive(win
->menu
->invite
, FALSE
);
5343 gtk_action_set_sensitive(win
->menu
->alias
, FALSE
);
5344 gtk_action_set_sensitive(win
->menu
->add
, FALSE
);
5345 gtk_action_set_sensitive(win
->menu
->remove
, FALSE
);
5346 gtk_action_set_sensitive(win
->menu
->insert_link
, TRUE
);
5347 gtk_action_set_sensitive(win
->menu
->insert_image
, FALSE
);
5351 * Update the window's icon
5353 if (pidgin_conv_window_is_active_conversation(conv
))
5356 if (PURPLE_IS_IM_CONVERSATION(conv
) &&
5357 (gtkconv
->u
.im
->anim
))
5359 PurpleBuddy
*buddy
= purple_blist_find_buddy(purple_conversation_get_account(conv
), purple_conversation_get_name(conv
));
5361 gdk_pixbuf_animation_get_static_image(gtkconv
->u
.im
->anim
);
5363 if (buddy
&& !PURPLE_BUDDY_IS_ONLINE(buddy
))
5364 gdk_pixbuf_saturate_and_pixelate(window_icon
, window_icon
, 0.0, FALSE
);
5366 g_object_ref(window_icon
);
5367 l
= g_list_append(l
, window_icon
);
5369 l
= pidgin_conv_get_tab_icons(conv
);
5371 gtk_window_set_icon_list(GTK_WINDOW(win
->window
), l
);
5372 if (window_icon
!= NULL
) {
5373 g_object_unref(G_OBJECT(window_icon
));
5380 pidgin_conv_update_fields(PurpleConversation
*conv
, PidginConvFields fields
)
5382 PidginConversation
*gtkconv
;
5383 PidginConvWindow
*win
;
5385 gtkconv
= PIDGIN_CONVERSATION(conv
);
5388 win
= pidgin_conv_get_window(gtkconv
);
5392 if (fields
& PIDGIN_CONV_SET_TITLE
)
5394 purple_conversation_autoset_title(conv
);
5397 if (fields
& PIDGIN_CONV_BUDDY_ICON
)
5399 if (PURPLE_IS_IM_CONVERSATION(conv
))
5400 pidgin_conv_update_buddy_icon(PURPLE_IM_CONVERSATION(conv
));
5403 if (fields
& PIDGIN_CONV_MENU
)
5405 gray_stuff_out(PIDGIN_CONVERSATION(conv
));
5406 generate_send_to_items(win
);
5407 regenerate_plugins_items(win
);
5410 if (fields
& PIDGIN_CONV_E2EE
)
5411 generate_e2ee_controls(win
);
5413 if (fields
& PIDGIN_CONV_TAB_ICON
)
5415 update_tab_icon(conv
);
5416 generate_send_to_items(win
); /* To update the icons in SendTo menu */
5419 if ((fields
& PIDGIN_CONV_TOPIC
) &&
5420 PURPLE_IS_CHAT_CONVERSATION(conv
))
5423 PidginChatPane
*gtkchat
= gtkconv
->u
.chat
;
5425 if (gtkchat
->topic_text
!= NULL
)
5427 topic
= purple_chat_conversation_get_topic(PURPLE_CHAT_CONVERSATION(conv
));
5429 gtk_entry_set_text(GTK_ENTRY(gtkchat
->topic_text
), topic
? topic
: "");
5430 gtk_widget_set_tooltip_text(gtkchat
->topic_text
,
5431 topic
? topic
: "");
5435 if ((fields
& PIDGIN_CONV_COLORIZE_TITLE
) ||
5436 (fields
& PIDGIN_CONV_SET_TITLE
) ||
5437 (fields
& PIDGIN_CONV_TOPIC
))
5440 PurpleIMConversation
*im
= NULL
;
5441 PurpleAccount
*account
= purple_conversation_get_account(conv
);
5442 PurpleBuddy
*buddy
= NULL
;
5443 char *markup
= NULL
;
5444 AtkObject
*accessibility_obj
;
5445 /* I think this is a little longer than it needs to be but I'm lazy. */
5448 if (PURPLE_IS_IM_CONVERSATION(conv
))
5449 im
= PURPLE_IM_CONVERSATION(conv
);
5451 if ((account
== NULL
) ||
5452 !purple_account_is_connected(account
) ||
5453 (PURPLE_IS_CHAT_CONVERSATION(conv
)
5454 && purple_chat_conversation_has_left(PURPLE_CHAT_CONVERSATION(conv
))))
5455 title
= g_strdup_printf("(%s)", purple_conversation_get_title(conv
));
5457 title
= g_strdup(purple_conversation_get_title(conv
));
5459 if (PURPLE_IS_IM_CONVERSATION(conv
)) {
5460 buddy
= purple_blist_find_buddy(account
, purple_conversation_get_name(conv
));
5462 markup
= pidgin_blist_get_name_markup(buddy
, FALSE
, FALSE
);
5466 } else if (PURPLE_IS_CHAT_CONVERSATION(conv
)) {
5467 const char *topic
= gtkconv
->u
.chat
->topic_text
5468 ? gtk_entry_get_text(GTK_ENTRY(gtkconv
->u
.chat
->topic_text
))
5470 const char *title
= purple_conversation_get_title(conv
);
5471 const char *name
= purple_conversation_get_name(conv
);
5473 char *topic_esc
, *unaliased
, *unaliased_esc
, *title_esc
;
5475 topic_esc
= topic
? g_markup_escape_text(topic
, -1) : NULL
;
5476 unaliased
= g_utf8_collate(title
, name
) ? g_strdup_printf("(%s)", name
) : NULL
;
5477 unaliased_esc
= unaliased
? g_markup_escape_text(unaliased
, -1) : NULL
;
5478 title_esc
= g_markup_escape_text(title
, -1);
5480 markup
= g_strdup_printf("%s%s<span size='smaller'>%s</span>%s<span color='%s' size='smaller'>%s</span>",
5482 unaliased_esc
? " " : "",
5483 unaliased_esc
? unaliased_esc
: "",
5484 topic_esc
&& *topic_esc
? "\n" : "",
5485 pidgin_get_dim_grey_string(gtkconv
->infopane
),
5486 topic_esc
? topic_esc
: "");
5491 g_free(unaliased_esc
);
5493 gtk_list_store_set(gtkconv
->infopane_model
, &(gtkconv
->infopane_iter
),
5494 CONV_TEXT_COLUMN
, markup
, -1);
5495 /* XXX seanegan Why do I have to do this? */
5496 gtk_widget_queue_draw(gtkconv
->infopane
);
5498 if (title
!= markup
)
5501 if (!gtk_widget_get_realized(gtkconv
->tab_label
))
5502 gtk_widget_realize(gtkconv
->tab_label
);
5504 accessibility_obj
= gtk_widget_get_accessible(gtkconv
->tab_cont
);
5506 purple_im_conversation_get_typing_state(im
) == PURPLE_IM_TYPING
) {
5507 atk_object_set_description(accessibility_obj
, _("Typing"));
5508 style
= "tab-label-typing";
5509 } else if (im
!= NULL
&&
5510 purple_im_conversation_get_typing_state(im
) == PURPLE_IM_TYPED
) {
5511 atk_object_set_description(accessibility_obj
, _("Stopped Typing"));
5512 style
= "tab-label-typed";
5513 } else if (gtkconv
->unseen_state
== PIDGIN_UNSEEN_NICK
) {
5514 atk_object_set_description(accessibility_obj
, _("Nick Said"));
5515 style
= "tab-label-attention";
5516 } else if (gtkconv
->unseen_state
== PIDGIN_UNSEEN_TEXT
) {
5517 atk_object_set_description(accessibility_obj
, _("Unread Messages"));
5518 if (PURPLE_IS_CHAT_CONVERSATION(gtkconv
->active_conv
))
5519 style
= "tab-label-unreadchat";
5521 style
= "tab-label-attention";
5522 } else if (gtkconv
->unseen_state
== PIDGIN_UNSEEN_EVENT
) {
5523 atk_object_set_description(accessibility_obj
, _("New Event"));
5524 style
= "tab-label-event";
5526 style
= "tab-label";
5529 gtk_widget_set_name(gtkconv
->tab_label
, style
);
5530 gtk_label_set_text(GTK_LABEL(gtkconv
->tab_label
), title
);
5531 gtk_widget_set_state_flags(gtkconv
->tab_label
, GTK_STATE_FLAG_ACTIVE
, TRUE
);
5533 if (gtkconv
->unseen_state
== PIDGIN_UNSEEN_TEXT
||
5534 gtkconv
->unseen_state
== PIDGIN_UNSEEN_NICK
||
5535 gtkconv
->unseen_state
== PIDGIN_UNSEEN_EVENT
) {
5536 PangoAttrList
*list
= pango_attr_list_new();
5537 PangoAttribute
*attr
= pango_attr_weight_new(PANGO_WEIGHT_BOLD
);
5538 attr
->start_index
= 0;
5539 attr
->end_index
= -1;
5540 pango_attr_list_insert(list
, attr
);
5541 gtk_label_set_attributes(GTK_LABEL(gtkconv
->tab_label
), list
);
5542 pango_attr_list_unref(list
);
5544 gtk_label_set_attributes(GTK_LABEL(gtkconv
->tab_label
), NULL
);
5546 if (pidgin_conv_window_is_active_conversation(conv
))
5547 update_typing_icon(gtkconv
);
5549 gtk_label_set_text(GTK_LABEL(gtkconv
->menu_label
), title
);
5550 if (pidgin_conv_window_is_active_conversation(conv
)) {
5551 const char* current_title
= gtk_window_get_title(GTK_WINDOW(win
->window
));
5552 if (current_title
== NULL
|| !purple_strequal(current_title
, title
))
5553 gtk_window_set_title(GTK_WINDOW(win
->window
), title
);
5561 pidgin_conv_updated(PurpleConversation
*conv
, PurpleConversationUpdateType type
)
5563 PidginConvFields flags
= 0;
5565 g_return_if_fail(conv
!= NULL
);
5567 if (type
== PURPLE_CONVERSATION_UPDATE_ACCOUNT
)
5569 flags
= PIDGIN_CONV_ALL
;
5571 else if (type
== PURPLE_CONVERSATION_UPDATE_TYPING
||
5572 type
== PURPLE_CONVERSATION_UPDATE_UNSEEN
||
5573 type
== PURPLE_CONVERSATION_UPDATE_TITLE
)
5575 flags
= PIDGIN_CONV_COLORIZE_TITLE
;
5577 else if (type
== PURPLE_CONVERSATION_UPDATE_TOPIC
)
5579 flags
= PIDGIN_CONV_TOPIC
;
5581 else if (type
== PURPLE_CONVERSATION_ACCOUNT_ONLINE
||
5582 type
== PURPLE_CONVERSATION_ACCOUNT_OFFLINE
)
5584 flags
= PIDGIN_CONV_MENU
| PIDGIN_CONV_TAB_ICON
| PIDGIN_CONV_SET_TITLE
;
5586 else if (type
== PURPLE_CONVERSATION_UPDATE_AWAY
)
5588 flags
= PIDGIN_CONV_TAB_ICON
;
5590 else if (type
== PURPLE_CONVERSATION_UPDATE_ADD
||
5591 type
== PURPLE_CONVERSATION_UPDATE_REMOVE
||
5592 type
== PURPLE_CONVERSATION_UPDATE_CHATLEFT
)
5594 flags
= PIDGIN_CONV_SET_TITLE
| PIDGIN_CONV_MENU
;
5596 else if (type
== PURPLE_CONVERSATION_UPDATE_ICON
)
5598 flags
= PIDGIN_CONV_BUDDY_ICON
;
5600 else if (type
== PURPLE_CONVERSATION_UPDATE_FEATURES
)
5602 flags
= PIDGIN_CONV_MENU
;
5604 else if (type
== PURPLE_CONVERSATION_UPDATE_E2EE
)
5606 flags
= PIDGIN_CONV_E2EE
| PIDGIN_CONV_MENU
;
5609 pidgin_conv_update_fields(conv
, flags
);
5613 wrote_msg_update_unseen_cb(PurpleConversation
*conv
, PurpleMessage
*msg
,
5616 PidginConversation
*gtkconv
= conv
? PIDGIN_CONVERSATION(conv
) : NULL
;
5617 PurpleMessageFlags flags
;
5618 if (conv
== NULL
|| (gtkconv
&& gtkconv
->win
!= hidden_convwin
))
5620 flags
= purple_message_get_flags(msg
);
5621 if (flags
& (PURPLE_MESSAGE_SEND
| PURPLE_MESSAGE_RECV
)) {
5622 PidginUnseenState unseen
= PIDGIN_UNSEEN_NONE
;
5624 if ((flags
& PURPLE_MESSAGE_NICK
) == PURPLE_MESSAGE_NICK
)
5625 unseen
= PIDGIN_UNSEEN_NICK
;
5626 else if (((flags
& PURPLE_MESSAGE_SYSTEM
) == PURPLE_MESSAGE_SYSTEM
) ||
5627 ((flags
& PURPLE_MESSAGE_ERROR
) == PURPLE_MESSAGE_ERROR
))
5628 unseen
= PIDGIN_UNSEEN_EVENT
;
5629 else if ((flags
& PURPLE_MESSAGE_NO_LOG
) == PURPLE_MESSAGE_NO_LOG
)
5630 unseen
= PIDGIN_UNSEEN_NO_LOG
;
5632 unseen
= PIDGIN_UNSEEN_TEXT
;
5634 conv_set_unseen(conv
, unseen
);
5638 static PurpleConversationUiOps conversation_ui_ops
=
5641 pidgin_conv_destroy
, /* destroy_conversation */
5642 NULL
, /* write_chat */
5643 NULL
, /* write_im */
5644 pidgin_conv_write_conv
, /* write_conv */
5645 pidgin_conv_chat_add_users
, /* chat_add_users */
5646 pidgin_conv_chat_rename_user
, /* chat_rename_user */
5647 pidgin_conv_chat_remove_users
, /* chat_remove_users */
5648 pidgin_conv_chat_update_user
, /* chat_update_user */
5649 pidgin_conv_present_conversation
, /* present */
5650 pidgin_conv_has_focus
, /* has_focus */
5651 NULL
, /* send_confirm */
5658 PurpleConversationUiOps
*
5659 pidgin_conversations_get_conv_ui_ops(void)
5661 return &conversation_ui_ops
;
5664 /**************************************************************************
5665 * Public conversation utility functions
5666 **************************************************************************/
5668 pidgin_conv_update_buddy_icon(PurpleIMConversation
*im
)
5670 PidginConversation
*gtkconv
;
5671 PurpleConversation
*conv
;
5672 PidginConvWindow
*win
;
5676 PurpleImage
*custom_img
= NULL
;
5677 gconstpointer data
= NULL
;
5685 int scale_width
, scale_height
;
5688 PurpleAccount
*account
;
5690 PurpleBuddyIcon
*icon
;
5692 conv
= PURPLE_CONVERSATION(im
);
5694 g_return_if_fail(conv
!= NULL
);
5695 g_return_if_fail(PIDGIN_IS_PIDGIN_CONVERSATION(conv
));
5697 gtkconv
= PIDGIN_CONVERSATION(conv
);
5699 if (conv
!= gtkconv
->active_conv
)
5702 if (!gtkconv
->u
.im
->show_icon
)
5705 account
= purple_conversation_get_account(conv
);
5707 /* Remove the current icon stuff */
5708 children
= gtk_container_get_children(GTK_CONTAINER(gtkconv
->u
.im
->icon_container
));
5710 /* We know there's only one child here. It'd be nice to shortcut to the
5711 event box, but we can't change the PidginConversation until 3.0 */
5712 event
= (GtkWidget
*)children
->data
;
5713 gtk_container_remove(GTK_CONTAINER(gtkconv
->u
.im
->icon_container
), event
);
5714 g_list_free(children
);
5717 if (gtkconv
->u
.im
->anim
!= NULL
)
5718 g_object_unref(G_OBJECT(gtkconv
->u
.im
->anim
));
5720 gtkconv
->u
.im
->anim
= NULL
;
5722 if (gtkconv
->u
.im
->icon_timer
!= 0)
5723 g_source_remove(gtkconv
->u
.im
->icon_timer
);
5725 gtkconv
->u
.im
->icon_timer
= 0;
5727 if (gtkconv
->u
.im
->iter
!= NULL
)
5728 g_object_unref(G_OBJECT(gtkconv
->u
.im
->iter
));
5730 gtkconv
->u
.im
->iter
= NULL
;
5732 if (!purple_prefs_get_bool(PIDGIN_PREFS_ROOT
"/conversations/im/show_buddy_icons"))
5735 if (purple_conversation_get_connection(conv
) == NULL
)
5738 buddy
= purple_blist_find_buddy(account
, purple_conversation_get_name(conv
));
5741 PurpleContact
*contact
= purple_buddy_get_contact(buddy
);
5743 custom_img
= purple_buddy_icons_node_find_custom_icon((PurpleBlistNode
*)contact
);
5745 /* There is a custom icon for this user */
5746 data
= purple_image_get_data(custom_img
);
5747 len
= purple_image_get_data_size(custom_img
);
5753 icon
= purple_im_conversation_get_icon(im
);
5756 gtk_widget_set_size_request(gtkconv
->u
.im
->icon_container
,
5757 -1, BUDDYICON_SIZE_MIN
);
5761 data
= purple_buddy_icon_get_data(icon
, &len
);
5764 gtk_widget_set_size_request(gtkconv
->u
.im
->icon_container
,
5765 -1, BUDDYICON_SIZE_MIN
);
5770 gtkconv
->u
.im
->anim
= pidgin_pixbuf_anim_from_data(data
, len
);
5772 g_object_unref(custom_img
);
5774 if (!gtkconv
->u
.im
->anim
) {
5775 purple_debug_error("gtkconv", "Couldn't load icon for conv %s\n",
5776 purple_conversation_get_name(conv
));
5780 if (gdk_pixbuf_animation_is_static_image(gtkconv
->u
.im
->anim
)) {
5782 gtkconv
->u
.im
->iter
= NULL
;
5783 stat
= gdk_pixbuf_animation_get_static_image(gtkconv
->u
.im
->anim
);
5784 buf
= gdk_pixbuf_add_alpha(stat
, FALSE
, 0, 0, 0);
5787 gtkconv
->u
.im
->iter
=
5788 gdk_pixbuf_animation_get_iter(gtkconv
->u
.im
->anim
, NULL
); /* LEAK */
5789 stat
= gdk_pixbuf_animation_iter_get_pixbuf(gtkconv
->u
.im
->iter
);
5790 buf
= gdk_pixbuf_add_alpha(stat
, FALSE
, 0, 0, 0);
5791 if (gtkconv
->u
.im
->animate
)
5792 start_anim(NULL
, gtkconv
);
5795 scale_width
= gdk_pixbuf_get_width(buf
);
5796 scale_height
= gdk_pixbuf_get_height(buf
);
5798 gtk_widget_get_size_request(gtkconv
->u
.im
->icon_container
, NULL
, &size
);
5799 size
= MIN(size
, MIN(scale_width
, scale_height
));
5801 /* Some sanity checks */
5802 size
= CLAMP(size
, BUDDYICON_SIZE_MIN
, BUDDYICON_SIZE_MAX
);
5803 if (scale_width
== scale_height
) {
5804 scale_width
= scale_height
= size
;
5805 } else if (scale_height
> scale_width
) {
5806 scale_width
= size
* scale_width
/ scale_height
;
5807 scale_height
= size
;
5809 scale_height
= size
* scale_height
/ scale_width
;
5812 scale
= gdk_pixbuf_scale_simple(buf
, scale_width
, scale_height
,
5813 GDK_INTERP_BILINEAR
);
5814 g_object_unref(buf
);
5815 if (pidgin_gdk_pixbuf_is_opaque(scale
))
5816 pidgin_gdk_pixbuf_make_round(scale
);
5818 event
= gtk_event_box_new();
5819 gtk_container_add(GTK_CONTAINER(gtkconv
->u
.im
->icon_container
), event
);
5820 gtk_event_box_set_visible_window(GTK_EVENT_BOX(event
), FALSE
);
5821 gtk_widget_add_events(event
,
5822 GDK_POINTER_MOTION_MASK
| GDK_LEAVE_NOTIFY_MASK
);
5823 g_signal_connect(G_OBJECT(event
), "button-press-event",
5824 G_CALLBACK(icon_menu
), gtkconv
);
5826 pidgin_tooltip_setup_for_widget(event
, gtkconv
, pidgin_conv_create_tooltip
, NULL
);
5827 gtk_widget_show(event
);
5829 gtkconv
->u
.im
->icon
= gtk_image_new_from_pixbuf(scale
);
5830 gtk_container_add(GTK_CONTAINER(event
), gtkconv
->u
.im
->icon
);
5831 gtk_widget_show(gtkconv
->u
.im
->icon
);
5833 g_object_unref(G_OBJECT(scale
));
5835 /* The buddy icon code needs badly to be fixed. */
5836 if(pidgin_conv_window_is_active_conversation(conv
))
5838 buf
= gdk_pixbuf_animation_get_static_image(gtkconv
->u
.im
->anim
);
5839 if (buddy
&& !PURPLE_BUDDY_IS_ONLINE(buddy
))
5840 gdk_pixbuf_saturate_and_pixelate(buf
, buf
, 0.0, FALSE
);
5841 gtk_window_set_icon(GTK_WINDOW(win
->window
), buf
);
5846 pidgin_conv_update_buttons_by_protocol(PurpleConversation
*conv
)
5848 PidginConvWindow
*win
;
5850 if (!PIDGIN_IS_PIDGIN_CONVERSATION(conv
))
5853 win
= PIDGIN_CONVERSATION(conv
)->win
;
5855 if (win
!= NULL
&& pidgin_conv_window_is_active_conversation(conv
))
5856 gray_stuff_out(PIDGIN_CONVERSATION(conv
));
5860 pidgin_conv_xy_to_right_infopane(PidginConvWindow
*win
, int x
, int y
)
5862 gint pane_x
, pane_y
, x_rel
;
5863 PidginConversation
*gtkconv
;
5864 GtkAllocation allocation
;
5866 gdk_window_get_origin(gtk_widget_get_window(win
->notebook
),
5869 gtkconv
= pidgin_conv_window_get_active_gtkconv(win
);
5870 gtk_widget_get_allocation(gtkconv
->infopane
, &allocation
);
5871 return (x_rel
> allocation
.x
+ allocation
.width
/ 2);
5875 pidgin_conv_get_tab_at_xy(PidginConvWindow
*win
, int x
, int y
, gboolean
*to_right
)
5877 gint nb_x
, nb_y
, x_rel
, y_rel
;
5878 GtkNotebook
*notebook
;
5879 GtkWidget
*page
, *tab
;
5880 gint i
, page_num
= -1;
5887 notebook
= GTK_NOTEBOOK(win
->notebook
);
5889 gdk_window_get_origin(gtk_widget_get_window(win
->notebook
), &nb_x
, &nb_y
);
5893 horiz
= (gtk_notebook_get_tab_pos(notebook
) == GTK_POS_TOP
||
5894 gtk_notebook_get_tab_pos(notebook
) == GTK_POS_BOTTOM
);
5896 count
= gtk_notebook_get_n_pages(GTK_NOTEBOOK(notebook
));
5898 for (i
= 0; i
< count
; i
++) {
5899 GtkAllocation allocation
;
5901 page
= gtk_notebook_get_nth_page(GTK_NOTEBOOK(notebook
), i
);
5902 tab
= gtk_notebook_get_tab_label(GTK_NOTEBOOK(notebook
), page
);
5903 gtk_widget_get_allocation(tab
, &allocation
);
5905 /* Make sure the tab is not hidden beyond an arrow */
5906 if (!gtk_widget_is_drawable(tab
) && gtk_notebook_get_show_tabs(notebook
))
5910 if (x_rel
>= allocation
.x
- PIDGIN_HIG_BOX_SPACE
&&
5911 x_rel
<= allocation
.x
+ allocation
.width
+ PIDGIN_HIG_BOX_SPACE
) {
5914 if (to_right
&& x_rel
>= allocation
.x
+ allocation
.width
/2)
5920 if (y_rel
>= allocation
.y
- PIDGIN_HIG_BOX_SPACE
&&
5921 y_rel
<= allocation
.y
+ allocation
.height
+ PIDGIN_HIG_BOX_SPACE
) {
5924 if (to_right
&& y_rel
>= allocation
.y
+ allocation
.height
/2)
5932 if (page_num
== -1) {
5933 /* Add after the last tab */
5934 page_num
= count
- 1;
5941 close_on_tabs_pref_cb(const char *name
, PurplePrefType type
,
5942 gconstpointer value
, gpointer data
)
5945 PurpleConversation
*conv
;
5946 PidginConversation
*gtkconv
;
5948 for (l
= purple_conversations_get_all(); l
!= NULL
; l
= l
->next
) {
5949 conv
= (PurpleConversation
*)l
->data
;
5951 if (!PIDGIN_IS_PIDGIN_CONVERSATION(conv
))
5954 gtkconv
= PIDGIN_CONVERSATION(conv
);
5957 gtk_widget_show(gtkconv
->close
);
5959 gtk_widget_hide(gtkconv
->close
);
5964 spellcheck_pref_cb(const char *name
, PurplePrefType type
,
5965 gconstpointer value
, gpointer data
)
5968 PurpleConversation
*conv
;
5969 PidginConversation
*gtkconv
;
5971 for (cl
= purple_conversations_get_all(); cl
!= NULL
; cl
= cl
->next
) {
5973 conv
= (PurpleConversation
*)cl
->data
;
5975 if (!PIDGIN_IS_PIDGIN_CONVERSATION(conv
))
5978 gtkconv
= PIDGIN_CONVERSATION(conv
);
5980 # warning toggle spell checking when talkatu #60 is done.
5985 tab_side_pref_cb(const char *name
, PurplePrefType type
,
5986 gconstpointer value
, gpointer data
)
5988 GList
*gtkwins
, *gtkconvs
;
5989 GtkPositionType pos
;
5990 PidginConvWindow
*gtkwin
;
5992 pos
= GPOINTER_TO_INT(value
);
5994 for (gtkwins
= pidgin_conv_windows_get_list(); gtkwins
!= NULL
; gtkwins
= gtkwins
->next
) {
5995 gtkwin
= gtkwins
->data
;
5996 gtk_notebook_set_tab_pos(GTK_NOTEBOOK(gtkwin
->notebook
), pos
&~8);
5997 for (gtkconvs
= gtkwin
->gtkconvs
; gtkconvs
!= NULL
; gtkconvs
= gtkconvs
->next
) {
5998 pidgin_conv_tab_pack(gtkwin
, gtkconvs
->data
);
6004 show_formatting_toolbar_pref_cb(const char *name
, PurplePrefType type
,
6005 gconstpointer value
, gpointer data
)
6008 PurpleConversation
*conv
;
6009 PidginConversation
*gtkconv
;
6010 PidginConvWindow
*win
;
6011 gboolean visible
= (gboolean
)GPOINTER_TO_INT(value
);
6013 for (l
= purple_conversations_get_all(); l
!= NULL
; l
= l
->next
)
6015 conv
= (PurpleConversation
*)l
->data
;
6017 if (!PIDGIN_IS_PIDGIN_CONVERSATION(conv
))
6020 gtkconv
= PIDGIN_CONVERSATION(conv
);
6023 gtk_toggle_action_set_active(
6024 GTK_TOGGLE_ACTION(win
->menu
->show_formatting_toolbar
),
6028 talkatu_editor_set_toolbar_visible(TALKATU_EDITOR(gtkconv
->editor
), visible
);
6033 animate_buddy_icons_pref_cb(const char *name
, PurplePrefType type
,
6034 gconstpointer value
, gpointer data
)
6037 PurpleConversation
*conv
;
6038 PidginConversation
*gtkconv
;
6039 PidginConvWindow
*win
;
6041 if (!purple_prefs_get_bool(PIDGIN_PREFS_ROOT
"/conversations/im/show_buddy_icons"))
6044 /* Set the "animate" flag for each icon based on the new preference */
6045 for (l
= purple_conversations_get_ims(); l
!= NULL
; l
= l
->next
) {
6046 conv
= (PurpleConversation
*)l
->data
;
6047 gtkconv
= PIDGIN_CONVERSATION(conv
);
6049 gtkconv
->u
.im
->animate
= GPOINTER_TO_INT(value
);
6052 /* Now either stop or start animation for the active conversation in each window */
6053 for (l
= pidgin_conv_windows_get_list(); l
!= NULL
; l
= l
->next
) {
6055 conv
= pidgin_conv_window_get_active_conversation(win
);
6056 pidgin_conv_update_buddy_icon(PURPLE_IM_CONVERSATION(conv
));
6061 show_buddy_icons_pref_cb(const char *name
, PurplePrefType type
,
6062 gconstpointer value
, gpointer data
)
6066 for (l
= purple_conversations_get_all(); l
!= NULL
; l
= l
->next
) {
6067 PurpleConversation
*conv
= l
->data
;
6068 if (!PIDGIN_CONVERSATION(conv
))
6070 if (GPOINTER_TO_INT(value
))
6071 gtk_widget_show(PIDGIN_CONVERSATION(conv
)->infopane_hbox
);
6073 gtk_widget_hide(PIDGIN_CONVERSATION(conv
)->infopane_hbox
);
6075 if (PURPLE_IS_IM_CONVERSATION(conv
)) {
6076 pidgin_conv_update_buddy_icon(PURPLE_IM_CONVERSATION(conv
));
6080 /* Make the tabs show/hide correctly */
6081 for (l
= pidgin_conv_windows_get_list(); l
!= NULL
; l
= l
->next
) {
6082 PidginConvWindow
*win
= l
->data
;
6083 if (pidgin_conv_window_get_gtkconv_count(win
) == 1)
6084 gtk_notebook_set_show_tabs(GTK_NOTEBOOK(win
->notebook
),
6085 GPOINTER_TO_INT(value
) == 0);
6090 show_protocol_icons_pref_cb(const char *name
, PurplePrefType type
,
6091 gconstpointer value
, gpointer data
)
6094 for (l
= purple_conversations_get_all(); l
!= NULL
; l
= l
->next
) {
6095 PurpleConversation
*conv
= l
->data
;
6096 if (PIDGIN_CONVERSATION(conv
))
6097 update_tab_icon(conv
);
6102 conv_placement_usetabs_cb(const char *name
, PurplePrefType type
,
6103 gconstpointer value
, gpointer data
)
6105 purple_prefs_trigger_callback(PIDGIN_PREFS_ROOT
"/conversations/placement");
6109 account_status_changed_cb(PurpleAccount
*account
, PurpleStatus
*oldstatus
,
6110 PurpleStatus
*newstatus
)
6113 PurpleConversation
*conv
= NULL
;
6114 PidginConversation
*gtkconv
;
6116 if(!purple_strequal(purple_prefs_get_string(PIDGIN_PREFS_ROOT
"/conversations/im/hide_new"), "away"))
6119 if(purple_status_is_available(oldstatus
) || !purple_status_is_available(newstatus
))
6122 for (l
= hidden_convwin
->gtkconvs
; l
; ) {
6126 conv
= gtkconv
->active_conv
;
6127 if (PURPLE_IS_CHAT_CONVERSATION(conv
) ||
6128 account
!= purple_conversation_get_account(conv
))
6131 pidgin_conv_attach_to_conversation(conv
);
6133 /* TODO: do we need to do anything for any other conversations that are in the same gtkconv here?
6134 * I'm a little concerned that not doing so will cause the "pending" indicator in the gtkblist not to be cleared. -DAA*/
6135 purple_conversation_update(conv
, PURPLE_CONVERSATION_UPDATE_UNSEEN
);
6140 hide_new_pref_cb(const char *name
, PurplePrefType type
,
6141 gconstpointer value
, gpointer data
)
6144 PurpleConversation
*conv
= NULL
;
6145 PidginConversation
*gtkconv
;
6146 gboolean when_away
= FALSE
;
6151 if(purple_strequal(purple_prefs_get_string(PIDGIN_PREFS_ROOT
"/conversations/im/hide_new"), "always"))
6154 if(purple_strequal(purple_prefs_get_string(PIDGIN_PREFS_ROOT
"/conversations/im/hide_new"), "away"))
6157 for (l
= hidden_convwin
->gtkconvs
; l
; )
6162 conv
= gtkconv
->active_conv
;
6164 if (PURPLE_IS_CHAT_CONVERSATION(conv
) ||
6165 gtkconv
->unseen_count
== 0 ||
6166 (when_away
&& !purple_status_is_available(
6167 purple_account_get_active_status(
6168 purple_conversation_get_account(conv
)))))
6171 pidgin_conv_attach_to_conversation(conv
);
6177 conv_placement_pref_cb(const char *name
, PurplePrefType type
,
6178 gconstpointer value
, gpointer data
)
6180 PidginConvPlacementFunc func
;
6182 if (!purple_strequal(name
, PIDGIN_PREFS_ROOT
"/conversations/placement"))
6185 func
= pidgin_conv_placement_get_fnc(value
);
6190 pidgin_conv_placement_set_current_func(func
);
6193 static PidginConversation
*
6194 get_gtkconv_with_contact(PurpleContact
*contact
)
6196 PurpleBlistNode
*node
;
6198 node
= ((PurpleBlistNode
*)contact
)->child
;
6200 for (; node
; node
= node
->next
)
6202 PurpleBuddy
*buddy
= (PurpleBuddy
*)node
;
6203 PurpleIMConversation
*im
;
6204 im
= purple_conversations_find_im_with_account(purple_buddy_get_name(buddy
), purple_buddy_get_account(buddy
));
6206 return PIDGIN_CONVERSATION(PURPLE_CONVERSATION(im
));
6212 account_signed_off_cb(PurpleConnection
*gc
, gpointer event
)
6216 for (iter
= purple_conversations_get_all(); iter
; iter
= iter
->next
)
6218 PurpleConversation
*conv
= iter
->data
;
6220 /* This seems fine in theory, but we also need to cover the
6221 * case of this account matching one of the other buddies in
6222 * one of the contacts containing the buddy corresponding to
6223 * a conversation. It's easier to just update them all. */
6224 /* if (purple_conversation_get_account(conv) == account) */
6225 pidgin_conv_update_fields(conv
, PIDGIN_CONV_TAB_ICON
|
6226 PIDGIN_CONV_MENU
| PIDGIN_CONV_COLORIZE_TITLE
);
6228 if (PURPLE_CONNECTION_IS_CONNECTED(gc
) &&
6229 PURPLE_IS_CHAT_CONVERSATION(conv
) &&
6230 purple_conversation_get_account(conv
) == purple_connection_get_account(gc
) &&
6231 g_object_get_data(G_OBJECT(conv
), "want-to-rejoin")) {
6232 GHashTable
*comps
= NULL
;
6233 PurpleChat
*chat
= purple_blist_find_chat(purple_conversation_get_account(conv
), purple_conversation_get_name(conv
));
6235 PurpleProtocol
*protocol
= purple_connection_get_protocol(gc
);
6236 comps
= purple_protocol_chat_iface_info_defaults(protocol
, gc
, purple_conversation_get_name(conv
));
6238 comps
= purple_chat_get_components(chat
);
6240 purple_serv_join_chat(gc
, comps
);
6241 if (chat
== NULL
&& comps
!= NULL
)
6242 g_hash_table_destroy(comps
);
6248 account_signing_off(PurpleConnection
*gc
)
6250 GList
*list
= purple_conversations_get_chats();
6251 PurpleAccount
*account
= purple_connection_get_account(gc
);
6253 /* We are about to sign off. See which chats we are currently in, and mark
6254 * them for rejoin on reconnect. */
6256 PurpleConversation
*conv
= list
->data
;
6257 if (!purple_chat_conversation_has_left(PURPLE_CHAT_CONVERSATION(conv
)) &&
6258 purple_conversation_get_account(conv
) == account
) {
6259 g_object_set_data(G_OBJECT(conv
), "want-to-rejoin", GINT_TO_POINTER(TRUE
));
6260 purple_conversation_write_system_message(conv
,
6261 _("The account has disconnected and you are no "
6262 "longer in this chat. You will automatically "
6263 "rejoin the chat when the account reconnects."),
6264 PURPLE_MESSAGE_NO_LOG
);
6271 update_buddy_status_changed(PurpleBuddy
*buddy
, PurpleStatus
*old
, PurpleStatus
*newstatus
)
6273 PidginConversation
*gtkconv
;
6274 PurpleConversation
*conv
;
6276 gtkconv
= get_gtkconv_with_contact(purple_buddy_get_contact(buddy
));
6279 conv
= gtkconv
->active_conv
;
6280 pidgin_conv_update_fields(conv
, PIDGIN_CONV_TAB_ICON
6281 | PIDGIN_CONV_COLORIZE_TITLE
6282 | PIDGIN_CONV_BUDDY_ICON
);
6283 if ((purple_status_is_online(old
) ^ purple_status_is_online(newstatus
)) != 0)
6284 pidgin_conv_update_fields(conv
, PIDGIN_CONV_MENU
);
6289 update_buddy_privacy_changed(PurpleBuddy
*buddy
)
6291 PidginConversation
*gtkconv
;
6292 PurpleConversation
*conv
;
6294 gtkconv
= get_gtkconv_with_contact(purple_buddy_get_contact(buddy
));
6296 conv
= gtkconv
->active_conv
;
6297 pidgin_conv_update_fields(conv
, PIDGIN_CONV_TAB_ICON
| PIDGIN_CONV_MENU
);
6302 update_buddy_idle_changed(PurpleBuddy
*buddy
, gboolean old
, gboolean newidle
)
6304 PurpleIMConversation
*im
;
6306 im
= purple_conversations_find_im_with_account(purple_buddy_get_name(buddy
), purple_buddy_get_account(buddy
));
6308 pidgin_conv_update_fields(PURPLE_CONVERSATION(im
), PIDGIN_CONV_TAB_ICON
);
6312 update_buddy_icon(PurpleBuddy
*buddy
)
6314 PurpleIMConversation
*im
;
6316 im
= purple_conversations_find_im_with_account(purple_buddy_get_name(buddy
), purple_buddy_get_account(buddy
));
6318 pidgin_conv_update_fields(PURPLE_CONVERSATION(im
), PIDGIN_CONV_BUDDY_ICON
);
6322 update_buddy_sign(PurpleBuddy
*buddy
, const char *which
)
6324 PurplePresence
*presence
;
6325 PurpleStatus
*on
, *off
;
6327 presence
= purple_buddy_get_presence(buddy
);
6330 off
= purple_presence_get_status(presence
, "offline");
6331 on
= purple_presence_get_status(presence
, "available");
6333 if (*(which
+1) == 'f')
6334 update_buddy_status_changed(buddy
, on
, off
);
6336 update_buddy_status_changed(buddy
, off
, on
);
6340 update_conversation_switched(PurpleConversation
*conv
)
6342 pidgin_conv_update_fields(conv
, PIDGIN_CONV_TAB_ICON
|
6343 PIDGIN_CONV_SET_TITLE
| PIDGIN_CONV_MENU
|
6344 PIDGIN_CONV_BUDDY_ICON
| PIDGIN_CONV_E2EE
);
6348 update_buddy_typing(PurpleAccount
*account
, const char *who
)
6350 PurpleConversation
*conv
;
6351 PidginConversation
*gtkconv
;
6353 conv
= PURPLE_CONVERSATION(purple_conversations_find_im_with_account(who
, account
));
6357 gtkconv
= PIDGIN_CONVERSATION(conv
);
6358 if (gtkconv
&& gtkconv
->active_conv
== conv
)
6359 pidgin_conv_update_fields(conv
, PIDGIN_CONV_COLORIZE_TITLE
);
6363 update_chat(PurpleChatConversation
*chat
)
6365 pidgin_conv_update_fields(PURPLE_CONVERSATION(chat
), PIDGIN_CONV_TOPIC
|
6366 PIDGIN_CONV_MENU
| PIDGIN_CONV_SET_TITLE
);
6370 update_chat_topic(PurpleChatConversation
*chat
, const char *old
, const char *new)
6372 pidgin_conv_update_fields(PURPLE_CONVERSATION(chat
), PIDGIN_CONV_TOPIC
);
6375 /* Message history stuff */
6377 /* Compare two PurpleMessages, according to time in ascending order. */
6379 message_compare(PurpleMessage
*m1
, PurpleMessage
*m2
)
6381 guint64 t1
= purple_message_get_time(m1
), t2
= purple_message_get_time(m2
);
6382 return (t1
> t2
) - (t1
< t2
);
6385 /* Adds some message history to the gtkconv. This happens in a idle-callback. */
6387 add_message_history_to_gtkconv(gpointer data
)
6389 PidginConversation
*gtkconv
= data
;
6391 int timer
= gtkconv
->attach_timer
;
6392 time_t when
= GPOINTER_TO_INT(g_object_get_data(G_OBJECT(gtkconv
->editor
), "attach-start-time"));
6393 gboolean im
= (PURPLE_IS_IM_CONVERSATION(gtkconv
->active_conv
));
6395 gtkconv
->attach_timer
= 0;
6396 while (gtkconv
->attach_current
&& count
< ADD_MESSAGE_HISTORY_AT_ONCE
) {
6397 PurpleMessage
*msg
= gtkconv
->attach_current
->data
;
6398 if (!im
&& when
&& (guint64
)when
< purple_message_get_time(msg
)) {
6399 g_object_set_data(G_OBJECT(gtkconv
->editor
), "attach-start-time", NULL
);
6401 /* XXX: should it be gtkconv->active_conv? */
6402 pidgin_conv_write_conv(gtkconv
->active_conv
, msg
);
6404 gtkconv
->attach_current
= g_list_delete_link(gtkconv
->attach_current
, gtkconv
->attach_current
);
6406 gtkconv
->attach_current
= gtkconv
->attach_current
->prev
;
6410 gtkconv
->attach_timer
= timer
;
6411 if (gtkconv
->attach_current
)
6414 g_source_remove(gtkconv
->attach_timer
);
6415 gtkconv
->attach_timer
= 0;
6417 /* Print any message that was sent while the old history was being added back. */
6419 GList
*iter
= gtkconv
->convs
;
6420 for (; iter
; iter
= iter
->next
) {
6421 PurpleConversation
*conv
= iter
->data
;
6422 GList
*history
= purple_conversation_get_message_history(conv
);
6423 for (; history
; history
= history
->next
) {
6424 PurpleMessage
*msg
= history
->data
;
6425 if (purple_message_get_time(msg
) > (guint64
)when
)
6426 msgs
= g_list_prepend(msgs
, msg
);
6429 msgs
= g_list_sort(msgs
, (GCompareFunc
)message_compare
);
6430 for (; msgs
; msgs
= g_list_delete_link(msgs
, msgs
)) {
6431 PurpleMessage
*msg
= msgs
->data
;
6432 /* XXX: see above - should it be active_conv? */
6433 pidgin_conv_write_conv(gtkconv
->active_conv
, msg
);
6435 g_object_set_data(G_OBJECT(gtkconv
->editor
), "attach-start-time", NULL
);
6438 g_object_set_data(G_OBJECT(gtkconv
->editor
), "attach-start-time", NULL
);
6439 purple_signal_emit(pidgin_conversations_get_handle(),
6440 "conversation-displayed", gtkconv
);
6445 pidgin_conv_attach(PurpleConversation
*conv
)
6448 g_object_set_data(G_OBJECT(conv
), "unseen-count", NULL
);
6449 g_object_set_data(G_OBJECT(conv
), "unseen-state", NULL
);
6450 purple_conversation_set_ui_ops(conv
, pidgin_conversations_get_conv_ui_ops());
6451 if (!PIDGIN_CONVERSATION(conv
))
6452 private_gtkconv_new(conv
, FALSE
);
6453 timer
= GPOINTER_TO_INT(g_object_get_data(G_OBJECT(conv
), "close-timer"));
6455 g_source_remove(timer
);
6456 g_object_set_data(G_OBJECT(conv
), "close-timer", NULL
);
6460 gboolean
pidgin_conv_attach_to_conversation(PurpleConversation
*conv
)
6463 PidginConversation
*gtkconv
;
6465 if (PIDGIN_IS_PIDGIN_CONVERSATION(conv
)) {
6466 /* This is pretty much always the case now. */
6467 gtkconv
= PIDGIN_CONVERSATION(conv
);
6468 if (gtkconv
->win
!= hidden_convwin
)
6470 pidgin_conv_window_remove_gtkconv(hidden_convwin
, gtkconv
);
6471 pidgin_conv_placement_place(gtkconv
);
6472 purple_signal_emit(pidgin_conversations_get_handle(),
6473 "conversation-displayed", gtkconv
);
6474 list
= gtkconv
->convs
;
6476 pidgin_conv_attach(list
->data
);
6482 pidgin_conv_attach(conv
);
6483 gtkconv
= PIDGIN_CONVERSATION(conv
);
6485 list
= purple_conversation_get_message_history(conv
);
6487 if (PURPLE_IS_IM_CONVERSATION(conv
)) {
6489 list
= g_list_copy(list
);
6490 for (convs
= purple_conversations_get_ims(); convs
; convs
= convs
->next
)
6491 if (convs
->data
!= conv
&&
6492 pidgin_conv_find_gtkconv(convs
->data
) == gtkconv
) {
6493 pidgin_conv_attach(convs
->data
);
6494 list
= g_list_concat(list
, g_list_copy(purple_conversation_get_message_history(convs
->data
)));
6496 list
= g_list_sort(list
, (GCompareFunc
)message_compare
);
6497 gtkconv
->attach_current
= list
;
6498 list
= g_list_last(list
);
6499 } else if (PURPLE_IS_CHAT_CONVERSATION(conv
)) {
6500 gtkconv
->attach_current
= g_list_last(list
);
6503 g_object_set_data(G_OBJECT(gtkconv
->editor
), "attach-start-time",
6504 GINT_TO_POINTER(purple_message_get_time(list
->data
)));
6505 gtkconv
->attach_timer
= g_idle_add(add_message_history_to_gtkconv
, gtkconv
);
6507 purple_signal_emit(pidgin_conversations_get_handle(),
6508 "conversation-displayed", gtkconv
);
6511 if (PURPLE_IS_CHAT_CONVERSATION(conv
)) {
6513 PurpleChatConversation
*chat
= PURPLE_CHAT_CONVERSATION(conv
);
6514 pidgin_conv_update_fields(conv
, PIDGIN_CONV_TOPIC
);
6515 users
= purple_chat_conversation_get_users(chat
);
6516 pidgin_conv_chat_add_users(chat
, users
, TRUE
);
6524 pidgin_conversations_get_handle(void)
6532 pidgin_conversations_pre_uninit(void);
6535 pidgin_conversations_init(void)
6537 void *handle
= pidgin_conversations_get_handle();
6538 void *blist_handle
= purple_blist_get_handle();
6540 e2ee_stock
= g_hash_table_new_full(g_str_hash
, g_str_equal
,
6541 g_free
, g_object_unref
);
6544 purple_prefs_add_none(PIDGIN_PREFS_ROOT
"/conversations");
6545 purple_prefs_add_bool(PIDGIN_PREFS_ROOT
"/conversations/use_smooth_scrolling", TRUE
);
6546 purple_prefs_add_bool(PIDGIN_PREFS_ROOT
"/conversations/close_on_tabs", TRUE
);
6547 purple_prefs_add_bool(PIDGIN_PREFS_ROOT
"/conversations/send_bold", FALSE
);
6548 purple_prefs_add_bool(PIDGIN_PREFS_ROOT
"/conversations/send_italic", FALSE
);
6549 purple_prefs_add_bool(PIDGIN_PREFS_ROOT
"/conversations/send_underline", FALSE
);
6550 purple_prefs_add_bool(PIDGIN_PREFS_ROOT
"/conversations/send_strike", FALSE
);
6551 purple_prefs_add_bool(PIDGIN_PREFS_ROOT
"/conversations/spellcheck", TRUE
);
6552 purple_prefs_add_bool(PIDGIN_PREFS_ROOT
"/conversations/show_incoming_formatting", TRUE
);
6553 /* TODO: it's about *remote* smileys, not local ones */
6554 purple_prefs_add_bool(PIDGIN_PREFS_ROOT
"/conversations/resize_custom_smileys", TRUE
);
6555 purple_prefs_add_int(PIDGIN_PREFS_ROOT
"/conversations/custom_smileys_size", 96);
6556 purple_prefs_add_int(PIDGIN_PREFS_ROOT
"/conversations/minimum_entry_lines", 2);
6558 purple_prefs_add_bool(PIDGIN_PREFS_ROOT
"/conversations/show_formatting_toolbar", TRUE
);
6560 purple_prefs_add_string(PIDGIN_PREFS_ROOT
"/conversations/placement", "last");
6561 purple_prefs_add_int(PIDGIN_PREFS_ROOT
"/conversations/placement_number", 1);
6562 purple_prefs_add_string(PIDGIN_PREFS_ROOT
"/conversations/bgcolor", "");
6563 purple_prefs_add_string(PIDGIN_PREFS_ROOT
"/conversations/fgcolor", "");
6564 purple_prefs_add_string(PIDGIN_PREFS_ROOT
"/conversations/font_face", "");
6565 purple_prefs_add_int(PIDGIN_PREFS_ROOT
"/conversations/font_size", 3);
6566 purple_prefs_add_bool(PIDGIN_PREFS_ROOT
"/conversations/tabs", TRUE
);
6567 purple_prefs_add_int(PIDGIN_PREFS_ROOT
"/conversations/tab_side", GTK_POS_TOP
);
6568 purple_prefs_add_int(PIDGIN_PREFS_ROOT
"/conversations/scrollback_lines", 4000);
6571 purple_prefs_add_bool(PIDGIN_PREFS_ROOT
"/conversations/use_theme_font", TRUE
);
6572 purple_prefs_add_string(PIDGIN_PREFS_ROOT
"/conversations/custom_font", "");
6575 /* Conversations -> Chat */
6576 purple_prefs_add_none(PIDGIN_PREFS_ROOT
"/conversations/chat");
6577 purple_prefs_add_int(PIDGIN_PREFS_ROOT
"/conversations/chat/entry_height", 54);
6578 purple_prefs_add_int(PIDGIN_PREFS_ROOT
"/conversations/chat/userlist_width", 80);
6579 purple_prefs_add_int(PIDGIN_PREFS_ROOT
"/conversations/chat/x", 0);
6580 purple_prefs_add_int(PIDGIN_PREFS_ROOT
"/conversations/chat/y", 0);
6581 purple_prefs_add_int(PIDGIN_PREFS_ROOT
"/conversations/chat/width", 340);
6582 purple_prefs_add_int(PIDGIN_PREFS_ROOT
"/conversations/chat/height", 390);
6584 /* Conversations -> IM */
6585 purple_prefs_add_none(PIDGIN_PREFS_ROOT
"/conversations/im");
6586 purple_prefs_add_int(PIDGIN_PREFS_ROOT
"/conversations/im/x", 0);
6587 purple_prefs_add_int(PIDGIN_PREFS_ROOT
"/conversations/im/y", 0);
6588 purple_prefs_add_int(PIDGIN_PREFS_ROOT
"/conversations/im/width", 340);
6589 purple_prefs_add_int(PIDGIN_PREFS_ROOT
"/conversations/im/height", 390);
6591 purple_prefs_add_bool(PIDGIN_PREFS_ROOT
"/conversations/im/animate_buddy_icons", TRUE
);
6593 purple_prefs_add_int(PIDGIN_PREFS_ROOT
"/conversations/im/entry_height", 54);
6594 purple_prefs_add_bool(PIDGIN_PREFS_ROOT
"/conversations/im/show_buddy_icons", TRUE
);
6596 purple_prefs_add_string(PIDGIN_PREFS_ROOT
"/conversations/im/hide_new", "never");
6597 purple_prefs_add_bool(PIDGIN_PREFS_ROOT
"/conversations/im/close_immediately", TRUE
);
6600 purple_prefs_add_bool(PIDGIN_PREFS_ROOT
"/win32/minimize_new_convs", FALSE
);
6603 /* Connect callbacks. */
6604 purple_prefs_connect_callback(handle
, PIDGIN_PREFS_ROOT
"/conversations/close_on_tabs",
6605 close_on_tabs_pref_cb
, NULL
);
6606 purple_prefs_connect_callback(handle
, PIDGIN_PREFS_ROOT
"/conversations/show_formatting_toolbar",
6607 show_formatting_toolbar_pref_cb
, NULL
);
6608 purple_prefs_connect_callback(handle
, PIDGIN_PREFS_ROOT
"/conversations/spellcheck",
6609 spellcheck_pref_cb
, NULL
);
6610 purple_prefs_connect_callback(handle
, PIDGIN_PREFS_ROOT
"/conversations/tab_side",
6611 tab_side_pref_cb
, NULL
);
6613 purple_prefs_connect_callback(handle
, PIDGIN_PREFS_ROOT
"/conversations/tabs",
6614 conv_placement_usetabs_cb
, NULL
);
6616 purple_prefs_connect_callback(handle
, PIDGIN_PREFS_ROOT
"/conversations/placement",
6617 conv_placement_pref_cb
, NULL
);
6618 purple_prefs_trigger_callback(PIDGIN_PREFS_ROOT
"/conversations/placement");
6620 purple_prefs_connect_callback(handle
, PIDGIN_PREFS_ROOT
"/conversations/minimum_entry_lines",
6621 minimum_entry_lines_pref_cb
, NULL
);
6624 purple_prefs_connect_callback(handle
, PIDGIN_PREFS_ROOT
"/conversations/im/animate_buddy_icons",
6625 animate_buddy_icons_pref_cb
, NULL
);
6626 purple_prefs_connect_callback(handle
, PIDGIN_PREFS_ROOT
"/conversations/im/show_buddy_icons",
6627 show_buddy_icons_pref_cb
, NULL
);
6628 purple_prefs_connect_callback(handle
, PIDGIN_PREFS_ROOT
"/blist/show_protocol_icons",
6629 show_protocol_icons_pref_cb
, NULL
);
6630 purple_prefs_connect_callback(handle
, PIDGIN_PREFS_ROOT
"/conversations/im/hide_new",
6631 hide_new_pref_cb
, NULL
);
6633 /**********************************************************************
6635 **********************************************************************/
6636 purple_signal_register(handle
, "conversation-dragging",
6637 purple_marshal_VOID__POINTER_POINTER
, G_TYPE_NONE
, 2,
6638 G_TYPE_POINTER
, /* pointer to a (PidginConvWindow *) */
6639 G_TYPE_POINTER
); /* pointer to a (PidginConvWindow *) */
6641 purple_signal_register(handle
, "conversation-timestamp",
6642 #if SIZEOF_TIME_T == 4
6643 purple_marshal_POINTER__POINTER_INT_BOOLEAN
,
6644 #elif SIZEOF_TIME_T == 8
6645 purple_marshal_POINTER__POINTER_INT64_BOOLEAN
,
6647 #error Unkown size of time_t
6649 G_TYPE_STRING
, 3, PURPLE_TYPE_CONVERSATION
,
6650 #if SIZEOF_TIME_T == 4
6652 #elif SIZEOF_TIME_T == 8
6655 # error Unknown size of time_t
6659 purple_signal_register(handle
, "displaying-im-msg",
6660 purple_marshal_BOOLEAN__POINTER_POINTER
,
6661 G_TYPE_BOOLEAN
, 2, PURPLE_TYPE_CONVERSATION
, PURPLE_TYPE_MESSAGE
);
6663 purple_signal_register(handle
, "displayed-im-msg",
6664 purple_marshal_VOID__POINTER_POINTER
, G_TYPE_NONE
, 2,
6665 PURPLE_TYPE_CONVERSATION
, PURPLE_TYPE_MESSAGE
);
6667 purple_signal_register(handle
, "displaying-chat-msg",
6668 purple_marshal_BOOLEAN__POINTER_POINTER
,
6669 G_TYPE_BOOLEAN
, 2, PURPLE_TYPE_CONVERSATION
, PURPLE_TYPE_MESSAGE
);
6671 purple_signal_register(handle
, "displayed-chat-msg",
6672 purple_marshal_VOID__POINTER_POINTER
, G_TYPE_NONE
, 2,
6673 PURPLE_TYPE_CONVERSATION
, PURPLE_TYPE_MESSAGE
);
6675 purple_signal_register(handle
, "conversation-switched",
6676 purple_marshal_VOID__POINTER
, G_TYPE_NONE
, 1,
6677 PURPLE_TYPE_CONVERSATION
);
6679 purple_signal_register(handle
, "conversation-hiding",
6680 purple_marshal_VOID__POINTER
, G_TYPE_NONE
, 1,
6681 G_TYPE_POINTER
); /* (PidginConversation *) */
6683 purple_signal_register(handle
, "conversation-displayed",
6684 purple_marshal_VOID__POINTER
, G_TYPE_NONE
, 1,
6685 G_TYPE_POINTER
); /* (PidginConversation *) */
6687 purple_signal_register(handle
, "chat-nick-autocomplete",
6688 purple_marshal_BOOLEAN__POINTER_BOOLEAN
,
6689 G_TYPE_BOOLEAN
, 1, PURPLE_TYPE_CONVERSATION
);
6691 purple_signal_register(handle
, "chat-nick-clicked",
6692 purple_marshal_BOOLEAN__POINTER_POINTER_UINT
,
6693 G_TYPE_BOOLEAN
, 3, PURPLE_TYPE_CONVERSATION
,
6694 G_TYPE_STRING
, G_TYPE_UINT
);
6696 purple_signal_register(handle
, "conversation-window-created",
6697 purple_marshal_VOID__POINTER
, G_TYPE_NONE
, 1,
6698 G_TYPE_POINTER
); /* (PidginConvWindow *) */
6701 /**********************************************************************
6703 **********************************************************************/
6704 purple_cmd_register("say", "S", PURPLE_CMD_P_DEFAULT
,
6705 PURPLE_CMD_FLAG_CHAT
| PURPLE_CMD_FLAG_IM
, NULL
,
6706 say_command_cb
, _("say <message>: Send a message normally as if you weren't using a command."), NULL
);
6707 purple_cmd_register("me", "S", PURPLE_CMD_P_DEFAULT
,
6708 PURPLE_CMD_FLAG_CHAT
| PURPLE_CMD_FLAG_IM
, NULL
,
6709 me_command_cb
, _("me <action>: Send an IRC style action to a buddy or chat."), NULL
);
6710 purple_cmd_register("debug", "w", PURPLE_CMD_P_DEFAULT
,
6711 PURPLE_CMD_FLAG_CHAT
| PURPLE_CMD_FLAG_IM
, NULL
,
6712 debug_command_cb
, _("debug <option>: Send various debug information to the current conversation."), NULL
);
6713 purple_cmd_register("clear", "", PURPLE_CMD_P_DEFAULT
,
6714 PURPLE_CMD_FLAG_CHAT
| PURPLE_CMD_FLAG_IM
, NULL
,
6715 clear_command_cb
, _("clear: Clears the conversation scrollback."), NULL
);
6716 purple_cmd_register("clearall", "", PURPLE_CMD_P_DEFAULT
,
6717 PURPLE_CMD_FLAG_CHAT
| PURPLE_CMD_FLAG_IM
, NULL
,
6718 clearall_command_cb
, _("clear: Clears all conversation scrollbacks."), NULL
);
6719 purple_cmd_register("help", "w", PURPLE_CMD_P_DEFAULT
,
6720 PURPLE_CMD_FLAG_CHAT
| PURPLE_CMD_FLAG_IM
| PURPLE_CMD_FLAG_ALLOW_WRONG_ARGS
, NULL
,
6721 help_command_cb
, _("help <command>: Help on a specific command."), NULL
);
6723 /**********************************************************************
6725 **********************************************************************/
6727 purple_signal_connect(purple_connections_get_handle(), "signed-on", handle
,
6728 G_CALLBACK(account_signed_off_cb
),
6729 GINT_TO_POINTER(PURPLE_CONVERSATION_ACCOUNT_ONLINE
));
6730 purple_signal_connect(purple_connections_get_handle(), "signed-off", handle
,
6731 G_CALLBACK(account_signed_off_cb
),
6732 GINT_TO_POINTER(PURPLE_CONVERSATION_ACCOUNT_OFFLINE
));
6733 purple_signal_connect(purple_connections_get_handle(), "signing-off", handle
,
6734 G_CALLBACK(account_signing_off
), NULL
);
6736 purple_signal_connect(purple_conversations_get_handle(), "writing-im-msg",
6737 handle
, G_CALLBACK(writing_msg
), NULL
);
6738 purple_signal_connect(purple_conversations_get_handle(), "writing-chat-msg",
6739 handle
, G_CALLBACK(writing_msg
), NULL
);
6740 purple_signal_connect(purple_conversations_get_handle(), "received-im-msg",
6741 handle
, G_CALLBACK(received_im_msg_cb
), NULL
);
6742 purple_signal_connect(purple_conversations_get_handle(), "cleared-message-history",
6743 handle
, G_CALLBACK(clear_conversation_scrollback_cb
), NULL
);
6745 purple_signal_connect(purple_conversations_get_handle(), "deleting-chat-user",
6746 handle
, G_CALLBACK(deleting_chat_user_cb
), NULL
);
6748 purple_conversations_set_ui_ops(&conversation_ui_ops
);
6750 hidden_convwin
= pidgin_conv_window_new();
6751 window_list
= g_list_remove(window_list
, hidden_convwin
);
6753 purple_signal_connect(purple_accounts_get_handle(), "account-status-changed",
6754 handle
, PURPLE_CALLBACK(account_status_changed_cb
), NULL
);
6756 purple_signal_connect_priority(purple_get_core(), "quitting", handle
,
6757 PURPLE_CALLBACK(pidgin_conversations_pre_uninit
), NULL
, PURPLE_SIGNAL_PRIORITY_HIGHEST
);
6759 /* Callbacks to update a conversation */
6760 purple_signal_connect(blist_handle
, "blist-node-added", handle
,
6761 G_CALLBACK(buddy_update_cb
), NULL
);
6762 purple_signal_connect(blist_handle
, "blist-node-removed", handle
,
6763 G_CALLBACK(buddy_update_cb
), NULL
);
6764 purple_signal_connect(blist_handle
, "buddy-signed-on",
6765 handle
, PURPLE_CALLBACK(update_buddy_sign
), "on");
6766 purple_signal_connect(blist_handle
, "buddy-signed-off",
6767 handle
, PURPLE_CALLBACK(update_buddy_sign
), "off");
6768 purple_signal_connect(blist_handle
, "buddy-status-changed",
6769 handle
, PURPLE_CALLBACK(update_buddy_status_changed
), NULL
);
6770 purple_signal_connect(blist_handle
, "buddy-privacy-changed",
6771 handle
, PURPLE_CALLBACK(update_buddy_privacy_changed
), NULL
);
6772 purple_signal_connect(blist_handle
, "buddy-idle-changed",
6773 handle
, PURPLE_CALLBACK(update_buddy_idle_changed
), NULL
);
6774 purple_signal_connect(blist_handle
, "buddy-icon-changed",
6775 handle
, PURPLE_CALLBACK(update_buddy_icon
), NULL
);
6776 purple_signal_connect(purple_conversations_get_handle(), "buddy-typing",
6777 handle
, PURPLE_CALLBACK(update_buddy_typing
), NULL
);
6778 purple_signal_connect(purple_conversations_get_handle(), "buddy-typing-stopped",
6779 handle
, PURPLE_CALLBACK(update_buddy_typing
), NULL
);
6780 purple_signal_connect(pidgin_conversations_get_handle(), "conversation-switched",
6781 handle
, PURPLE_CALLBACK(update_conversation_switched
), NULL
);
6782 purple_signal_connect(purple_conversations_get_handle(), "chat-left", handle
,
6783 PURPLE_CALLBACK(update_chat
), NULL
);
6784 purple_signal_connect(purple_conversations_get_handle(), "chat-joined", handle
,
6785 PURPLE_CALLBACK(update_chat
), NULL
);
6786 purple_signal_connect(purple_conversations_get_handle(), "chat-topic-changed", handle
,
6787 PURPLE_CALLBACK(update_chat_topic
), NULL
);
6788 purple_signal_connect_priority(purple_conversations_get_handle(), "conversation-updated", handle
,
6789 PURPLE_CALLBACK(pidgin_conv_updated
), NULL
,
6790 PURPLE_SIGNAL_PRIORITY_LOWEST
);
6791 purple_signal_connect(purple_conversations_get_handle(), "wrote-im-msg", handle
,
6792 PURPLE_CALLBACK(wrote_msg_update_unseen_cb
), NULL
);
6793 purple_signal_connect(purple_conversations_get_handle(), "wrote-chat-msg", handle
,
6794 PURPLE_CALLBACK(wrote_msg_update_unseen_cb
), NULL
);
6798 pidgin_conversations_pre_uninit(void)
6800 g_hash_table_destroy(e2ee_stock
);
6804 /* Invalidate the first tab color set */
6805 static gboolean tab_color_fuse
= TRUE
;
6808 pidgin_conversations_set_tab_colors(void)
6810 /* Set default tab colors */
6812 GtkSettings
*settings
;
6813 GtkStyle
*parent
, *now
;
6815 const char *stylename
;
6816 const char *labelname
;
6819 {"pidgin_tab_label_typing_default", "tab-label-typing", "#4e9a06"},
6820 {"pidgin_tab_label_typed_default", "tab-label-typed", "#c4a000"},
6821 {"pidgin_tab_label_attention_default", "tab-label-attention", "#006aff"},
6822 {"pidgin_tab_label_unreadchat_default", "tab-label-unreadchat", "#cc0000"},
6823 {"pidgin_tab_label_event_default", "tab-label-event", "#888a85"},
6828 if(tab_color_fuse
) {
6829 tab_color_fuse
= FALSE
;
6833 str
= g_string_new(NULL
);
6834 settings
= gtk_settings_get_default();
6835 parent
= gtk_rc_get_style_by_paths(settings
, "tab-container.tab-label*",
6838 for (iter
= 0; styles
[iter
].stylename
; iter
++) {
6839 now
= gtk_rc_get_style_by_paths(settings
, styles
[iter
].labelname
, NULL
, G_TYPE_NONE
);
6840 if (parent
== now
||
6841 (parent
&& now
&& parent
->rc_style
== now
->rc_style
)) {
6845 gdk_rgba_parse(&color
, styles
[iter
].color
);
6846 pidgin_style_adjust_contrast(gtk_widget_get_default_style(), &color
);
6848 color_str
= gdk_rgba_to_string(&color
);
6849 g_string_append_printf(str
, "style \"%s\" {\n"
6850 "fg[ACTIVE] = \"%s\"\n"
6852 "widget \"*%s\" style \"%s\"\n",
6853 styles
[iter
].stylename
,
6855 styles
[iter
].labelname
, styles
[iter
].stylename
);
6859 gtk_rc_parse_string(str
->str
);
6860 g_string_free(str
, TRUE
);
6861 gtk_rc_reset_styles(settings
);
6865 pidgin_conversations_uninit(void)
6867 purple_prefs_disconnect_by_handle(pidgin_conversations_get_handle());
6868 purple_signals_disconnect_by_handle(pidgin_conversations_get_handle());
6869 purple_signals_unregister_by_instance(pidgin_conversations_get_handle());
6872 /**************************************************************************
6873 * PidginConversation GBoxed code
6874 **************************************************************************/
6875 static PidginConversation
*
6876 pidgin_conversation_ref(PidginConversation
*gtkconv
)
6878 g_return_val_if_fail(gtkconv
!= NULL
, NULL
);
6880 gtkconv
->box_count
++;
6886 pidgin_conversation_unref(PidginConversation
*gtkconv
)
6888 g_return_if_fail(gtkconv
!= NULL
);
6889 g_return_if_fail(gtkconv
->box_count
>= 0);
6891 if (!gtkconv
->box_count
--)
6892 pidgin_conv_destroy(gtkconv
->active_conv
);
6896 pidgin_conversation_get_type(void)
6898 static GType type
= 0;
6901 type
= g_boxed_type_register_static("PidginConversation",
6902 (GBoxedCopyFunc
)pidgin_conversation_ref
,
6903 (GBoxedFreeFunc
)pidgin_conversation_unref
);
6924 /* down here is where gtkconvwin.c ought to start. except they share like every freaking function,
6925 * and touch each others' private members all day long */
6929 * Pidgin is the legal property of its developers, whose names are too numerous
6930 * to list here. Please refer to the COPYRIGHT file distributed with this
6931 * source distribution.
6933 * This program is free software; you can redistribute it and/or modify
6934 * it under the terms of the GNU General Public License as published by
6935 * the Free Software Foundation; either version 2 of the License, or
6936 * (at your option) any later version.
6938 * This program is distributed in the hope that it will be useful,
6939 * but WITHOUT ANY WARRANTY; without even the implied warranty of
6940 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
6941 * GNU General Public License for more details.
6943 * You should have received a copy of the GNU General Public License
6944 * along with this program; if not, write to the Free Software
6945 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA
6948 #include "internal.h"
6952 #include <gdk/gdkkeysyms.h>
6954 #include "account.h"
6959 #include "protocol.h"
6960 #include "request.h"
6963 #include "gtkdnd-hints.h"
6964 #include "gtkblist.h"
6965 #include "gtkconv.h"
6966 #include "gtkdialogs.h"
6967 #include "gtkmenutray.h"
6968 #include "gtkpounce.h"
6969 #include "gtkprefs.h"
6970 #include "gtkprivacy.h"
6971 #include "gtkutils.h"
6972 #include "pidginstock.h"
6975 do_close(GtkWidget
*w
, int resp
, PidginConvWindow
*win
)
6977 gtk_widget_destroy(warn_close_dialog
);
6978 warn_close_dialog
= NULL
;
6980 if (resp
== GTK_RESPONSE_OK
)
6981 pidgin_conv_window_destroy(win
);
6985 build_warn_close_dialog(PidginConvWindow
*gtkwin
)
6987 GtkWidget
*label
, *vbox
, *hbox
, *img
;
6989 g_return_if_fail(warn_close_dialog
== NULL
);
6991 warn_close_dialog
= gtk_dialog_new_with_buttons(_("Confirm close"),
6992 GTK_WINDOW(gtkwin
->window
), GTK_DIALOG_MODAL
,
6993 GTK_STOCK_CANCEL
, GTK_RESPONSE_CANCEL
,
6994 GTK_STOCK_CLOSE
, GTK_RESPONSE_OK
, NULL
);
6996 gtk_dialog_set_default_response(GTK_DIALOG(warn_close_dialog
),
6999 gtk_container_set_border_width(GTK_CONTAINER(warn_close_dialog
),
7001 gtk_window_set_resizable(GTK_WINDOW(warn_close_dialog
), FALSE
);
7003 /* Setup the outside spacing. */
7004 vbox
= gtk_dialog_get_content_area(GTK_DIALOG(warn_close_dialog
));
7006 gtk_box_set_spacing(GTK_BOX(vbox
), 12);
7007 gtk_container_set_border_width(GTK_CONTAINER(vbox
), 6);
7009 img
= gtk_image_new_from_icon_name("dialog-warning",
7010 GTK_ICON_SIZE_DIALOG
);
7012 /* Setup the inner hbox and put the dialog's icon in it. */
7013 hbox
= gtk_box_new(GTK_ORIENTATION_HORIZONTAL
, 12);
7014 gtk_container_add(GTK_CONTAINER(vbox
), hbox
);
7015 gtk_box_pack_start(GTK_BOX(hbox
), img
, FALSE
, FALSE
, 0);
7016 gtk_widget_set_halign(img
, GTK_ALIGN_START
);
7017 gtk_widget_set_valign(img
, GTK_ALIGN_START
);
7019 /* Setup the right vbox. */
7020 vbox
= gtk_box_new(GTK_ORIENTATION_VERTICAL
, 12);
7021 gtk_container_add(GTK_CONTAINER(hbox
), vbox
);
7023 label
= gtk_label_new(_("You have unread messages. Are you sure you want to close the window?"));
7024 gtk_widget_set_size_request(label
, 350, -1);
7025 gtk_label_set_line_wrap(GTK_LABEL(label
), TRUE
);
7026 gtk_label_set_xalign(GTK_LABEL(label
), 0);
7027 gtk_label_set_yalign(GTK_LABEL(label
), 0);
7028 gtk_box_pack_start(GTK_BOX(vbox
), label
, FALSE
, FALSE
, 0);
7030 /* Connect the signals. */
7031 g_signal_connect(G_OBJECT(warn_close_dialog
), "response",
7032 G_CALLBACK(do_close
), gtkwin
);
7036 /**************************************************************************
7038 **************************************************************************/
7041 close_win_cb(GtkWidget
*w
, GdkEventAny
*e
, gpointer d
)
7043 PidginConvWindow
*win
= d
;
7046 /* If there are unread messages then show a warning dialog */
7047 for (l
= pidgin_conv_window_get_gtkconvs(win
);
7048 l
!= NULL
; l
= l
->next
)
7050 PidginConversation
*gtkconv
= l
->data
;
7051 if (PURPLE_IS_IM_CONVERSATION(gtkconv
->active_conv
) &&
7052 gtkconv
->unseen_state
>= PIDGIN_UNSEEN_TEXT
)
7054 build_warn_close_dialog(win
);
7055 gtk_widget_show_all(warn_close_dialog
);
7061 pidgin_conv_window_destroy(win
);
7067 conv_set_unseen(PurpleConversation
*conv
, PidginUnseenState state
)
7069 int unseen_count
= 0;
7070 PidginUnseenState unseen_state
= PIDGIN_UNSEEN_NONE
;
7072 if(g_object_get_data(G_OBJECT(conv
), "unseen-count"))
7073 unseen_count
= GPOINTER_TO_INT(g_object_get_data(G_OBJECT(conv
), "unseen-count"));
7075 if(g_object_get_data(G_OBJECT(conv
), "unseen-state"))
7076 unseen_state
= GPOINTER_TO_INT(g_object_get_data(G_OBJECT(conv
), "unseen-state"));
7078 if (state
== PIDGIN_UNSEEN_NONE
)
7081 unseen_state
= PIDGIN_UNSEEN_NONE
;
7085 if (state
>= PIDGIN_UNSEEN_TEXT
)
7088 if (state
> unseen_state
)
7089 unseen_state
= state
;
7092 g_object_set_data(G_OBJECT(conv
), "unseen-count", GINT_TO_POINTER(unseen_count
));
7093 g_object_set_data(G_OBJECT(conv
), "unseen-state", GINT_TO_POINTER(unseen_state
));
7095 purple_conversation_update(conv
, PURPLE_CONVERSATION_UPDATE_UNSEEN
);
7099 gtkconv_set_unseen(PidginConversation
*gtkconv
, PidginUnseenState state
)
7101 if (state
== PIDGIN_UNSEEN_NONE
)
7103 gtkconv
->unseen_count
= 0;
7104 gtkconv
->unseen_state
= PIDGIN_UNSEEN_NONE
;
7108 if (state
>= PIDGIN_UNSEEN_TEXT
)
7109 gtkconv
->unseen_count
++;
7111 if (state
> gtkconv
->unseen_state
)
7112 gtkconv
->unseen_state
= state
;
7115 g_object_set_data(G_OBJECT(gtkconv
->active_conv
), "unseen-count", GINT_TO_POINTER(gtkconv
->unseen_count
));
7116 g_object_set_data(G_OBJECT(gtkconv
->active_conv
), "unseen-state", GINT_TO_POINTER(gtkconv
->unseen_state
));
7118 purple_conversation_update(gtkconv
->active_conv
, PURPLE_CONVERSATION_UPDATE_UNSEEN
);
7122 * When a conversation window is focused, we know the user
7123 * has looked at it so we know there are no longer unseen
7127 focus_win_cb(GtkWidget
*w
, GdkEventFocus
*e
, gpointer d
)
7129 PidginConvWindow
*win
= d
;
7130 PidginConversation
*gtkconv
= pidgin_conv_window_get_active_gtkconv(win
);
7133 gtkconv_set_unseen(gtkconv
, PIDGIN_UNSEEN_NONE
);
7139 notebook_init_grab(PidginConvWindow
*gtkwin
, GtkWidget
*widget
, GdkEvent
*event
)
7141 static GdkCursor
*cursor
= NULL
;
7144 gtkwin
->in_drag
= TRUE
;
7146 if (gtkwin
->drag_leave_signal
) {
7147 g_signal_handler_disconnect(G_OBJECT(widget
),
7148 gtkwin
->drag_leave_signal
);
7149 gtkwin
->drag_leave_signal
= 0;
7152 if (cursor
== NULL
) {
7153 GdkDisplay
*display
= gtk_widget_get_display(gtkwin
->notebook
);
7154 cursor
= gdk_cursor_new_for_display(display
, GDK_FLEUR
);
7157 /* Grab the pointer */
7158 gtk_grab_add(gtkwin
->notebook
);
7159 device
= gdk_event_get_device(event
);
7160 if (!gdk_display_device_is_grabbed(gdk_device_get_display(device
),
7162 gdk_seat_grab(gdk_event_get_seat(event
),
7163 gtk_widget_get_window(gtkwin
->notebook
),
7164 GDK_SEAT_CAPABILITY_ALL_POINTING
, FALSE
, cursor
, event
,
7170 notebook_motion_cb(GtkWidget
*widget
, GdkEventButton
*e
, PidginConvWindow
*win
)
7174 * Make sure the user moved the mouse far enough for the
7175 * drag to be initiated.
7177 if (win
->in_predrag
) {
7178 if (e
->x_root
< win
->drag_min_x
||
7179 e
->x_root
>= win
->drag_max_x
||
7180 e
->y_root
< win
->drag_min_y
||
7181 e
->y_root
>= win
->drag_max_y
) {
7183 win
->in_predrag
= FALSE
;
7184 notebook_init_grab(win
, widget
, (GdkEvent
*)e
);
7187 else { /* Otherwise, draw the arrows. */
7188 PidginConvWindow
*dest_win
;
7189 GtkNotebook
*dest_notebook
;
7192 gboolean horiz_tabs
= FALSE
;
7193 gboolean to_right
= FALSE
;
7195 /* Get the window that the cursor is over. */
7196 dest_win
= pidgin_conv_window_get_at_event((GdkEvent
*)e
);
7198 if (dest_win
== NULL
) {
7199 pidgin_dnd_hints_hide_all();
7204 dest_notebook
= GTK_NOTEBOOK(dest_win
->notebook
);
7206 if (gtk_notebook_get_show_tabs(dest_notebook
)) {
7207 page_num
= pidgin_conv_get_tab_at_xy(dest_win
,
7208 e
->x_root
, e
->y_root
, &to_right
);
7209 to_right
= to_right
&& (win
!= dest_win
);
7210 tab
= pidgin_conv_window_get_gtkconv_at_index(dest_win
, page_num
)->tabby
;
7213 to_right
= pidgin_conv_xy_to_right_infopane(dest_win
, e
->x_root
, e
->y_root
);
7214 tab
= pidgin_conv_window_get_gtkconv_at_index(dest_win
, page_num
)->infopane_hbox
;
7217 if (gtk_notebook_get_tab_pos(dest_notebook
) == GTK_POS_TOP
||
7218 gtk_notebook_get_tab_pos(dest_notebook
) == GTK_POS_BOTTOM
) {
7222 if (gtk_notebook_get_show_tabs(dest_notebook
) == FALSE
&& win
== dest_win
)
7224 /* dragging a tab from a single-tabbed window over its own window */
7225 pidgin_dnd_hints_hide_all();
7227 } else if (horiz_tabs
) {
7228 if (((gpointer
)win
== (gpointer
)dest_win
&& win
->drag_tab
< page_num
) || to_right
) {
7229 pidgin_dnd_hints_show_relative(HINT_ARROW_DOWN
, tab
, HINT_POSITION_RIGHT
, HINT_POSITION_TOP
);
7230 pidgin_dnd_hints_show_relative(HINT_ARROW_UP
, tab
, HINT_POSITION_RIGHT
, HINT_POSITION_BOTTOM
);
7232 pidgin_dnd_hints_show_relative(HINT_ARROW_DOWN
, tab
, HINT_POSITION_LEFT
, HINT_POSITION_TOP
);
7233 pidgin_dnd_hints_show_relative(HINT_ARROW_UP
, tab
, HINT_POSITION_LEFT
, HINT_POSITION_BOTTOM
);
7236 if (((gpointer
)win
== (gpointer
)dest_win
&& win
->drag_tab
< page_num
) || to_right
) {
7237 pidgin_dnd_hints_show_relative(HINT_ARROW_RIGHT
, tab
, HINT_POSITION_LEFT
, HINT_POSITION_BOTTOM
);
7238 pidgin_dnd_hints_show_relative(HINT_ARROW_LEFT
, tab
, HINT_POSITION_RIGHT
, HINT_POSITION_BOTTOM
);
7240 pidgin_dnd_hints_show_relative(HINT_ARROW_RIGHT
, tab
, HINT_POSITION_LEFT
, HINT_POSITION_TOP
);
7241 pidgin_dnd_hints_show_relative(HINT_ARROW_LEFT
, tab
, HINT_POSITION_RIGHT
, HINT_POSITION_TOP
);
7250 notebook_leave_cb(GtkWidget
*widget
, GdkEventCrossing
*e
, PidginConvWindow
*win
)
7255 if (e
->x_root
< win
->drag_min_x
||
7256 e
->x_root
>= win
->drag_max_x
||
7257 e
->y_root
< win
->drag_min_y
||
7258 e
->y_root
>= win
->drag_max_y
) {
7260 win
->in_predrag
= FALSE
;
7261 notebook_init_grab(win
, widget
, (GdkEvent
*)e
);
7272 infopane_press_cb(GtkWidget
*widget
, GdkEventButton
*e
, PidginConversation
*gtkconv
)
7274 if (e
->type
== GDK_2BUTTON_PRESS
&& e
->button
== GDK_BUTTON_PRIMARY
) {
7275 if (infopane_entry_activate(gtkconv
))
7279 if (e
->type
!= GDK_BUTTON_PRESS
)
7282 if (e
->button
== GDK_BUTTON_PRIMARY
) {
7284 GtkAllocation allocation
;
7286 gtk_widget_get_allocation(gtkconv
->infopane_hbox
, &allocation
);
7288 if (gtkconv
->win
->in_drag
)
7291 gtkconv
->win
->in_predrag
= TRUE
;
7292 gtkconv
->win
->drag_tab
= gtk_notebook_page_num(GTK_NOTEBOOK(gtkconv
->win
->notebook
), gtkconv
->tab_cont
);
7294 gdk_window_get_origin(gtk_widget_get_window(gtkconv
->infopane_hbox
), &nb_x
, &nb_y
);
7296 gtkconv
->win
->drag_min_x
= allocation
.x
+ nb_x
;
7297 gtkconv
->win
->drag_min_y
= allocation
.y
+ nb_y
;
7298 gtkconv
->win
->drag_max_x
= allocation
.width
+ gtkconv
->win
->drag_min_x
;
7299 gtkconv
->win
->drag_max_y
= allocation
.height
+ gtkconv
->win
->drag_min_y
;
7301 gtkconv
->win
->drag_motion_signal
= g_signal_connect(G_OBJECT(gtkconv
->win
->notebook
), "motion_notify_event",
7302 G_CALLBACK(notebook_motion_cb
), gtkconv
->win
);
7303 gtkconv
->win
->drag_leave_signal
= g_signal_connect(G_OBJECT(gtkconv
->win
->notebook
), "leave_notify_event",
7304 G_CALLBACK(notebook_leave_cb
), gtkconv
->win
);
7308 if (gdk_event_triggers_context_menu((GdkEvent
*)e
)) {
7309 /* Right click was pressed. Popup the context menu. */
7310 GtkWidget
*menu
= gtk_menu_new(), *sub
;
7311 gboolean populated
= populate_menu_with_options(menu
, gtkconv
, TRUE
);
7313 sub
= gtk_menu_item_get_submenu(GTK_MENU_ITEM(gtkconv
->win
->menu
->send_to
));
7314 if (sub
&& gtk_widget_is_sensitive(gtkconv
->win
->menu
->send_to
)) {
7315 GtkWidget
*item
= gtk_menu_item_new_with_mnemonic(_("S_end To"));
7317 pidgin_separator(menu
);
7318 gtk_menu_shell_append(GTK_MENU_SHELL(menu
), item
);
7319 gtk_menu_item_set_submenu(GTK_MENU_ITEM(item
), sub
);
7320 gtk_widget_show(item
);
7321 gtk_widget_show_all(sub
);
7322 } else if (!populated
) {
7323 gtk_widget_destroy(menu
);
7327 gtk_widget_show_all(menu
);
7328 gtk_menu_popup_at_pointer(GTK_MENU(menu
), (GdkEvent
*)e
);
7335 notebook_press_cb(GtkWidget
*widget
, GdkEventButton
*e
, PidginConvWindow
*win
)
7341 GtkAllocation allocation
;
7343 if (e
->button
== GDK_BUTTON_MIDDLE
&& e
->type
== GDK_BUTTON_PRESS
) {
7344 PidginConversation
*gtkconv
;
7345 tab_clicked
= pidgin_conv_get_tab_at_xy(win
, e
->x_root
, e
->y_root
, NULL
);
7347 if (tab_clicked
== -1)
7350 gtkconv
= pidgin_conv_window_get_gtkconv_at_index(win
, tab_clicked
);
7351 close_conv_cb(NULL
, gtkconv
);
7356 if (e
->button
!= GDK_BUTTON_PRIMARY
|| e
->type
!= GDK_BUTTON_PRESS
)
7361 purple_debug(PURPLE_DEBUG_WARNING
, "gtkconv",
7362 "Already in the middle of a window drag at tab_press_cb\n");
7367 * Make sure a tab was actually clicked. The arrow buttons
7370 tab_clicked
= pidgin_conv_get_tab_at_xy(win
, e
->x_root
, e
->y_root
, NULL
);
7372 if (tab_clicked
== -1)
7376 * Get the relative position of the press event, with regards to
7377 * the position of the notebook.
7379 gdk_window_get_origin(gtk_widget_get_window(win
->notebook
), &nb_x
, &nb_y
);
7381 /* Reset the min/max x/y */
7382 win
->drag_min_x
= 0;
7383 win
->drag_min_y
= 0;
7384 win
->drag_max_x
= 0;
7385 win
->drag_max_y
= 0;
7387 /* Find out which tab was dragged. */
7388 page
= gtk_notebook_get_nth_page(GTK_NOTEBOOK(win
->notebook
), tab_clicked
);
7389 tab
= gtk_notebook_get_tab_label(GTK_NOTEBOOK(win
->notebook
), page
);
7391 gtk_widget_get_allocation(tab
, &allocation
);
7393 win
->drag_min_x
= allocation
.x
+ nb_x
;
7394 win
->drag_min_y
= allocation
.y
+ nb_y
;
7395 win
->drag_max_x
= allocation
.width
+ win
->drag_min_x
;
7396 win
->drag_max_y
= allocation
.height
+ win
->drag_min_y
;
7398 /* Make sure the click occurred in the tab. */
7399 if (e
->x_root
< win
->drag_min_x
||
7400 e
->x_root
>= win
->drag_max_x
||
7401 e
->y_root
< win
->drag_min_y
||
7402 e
->y_root
>= win
->drag_max_y
) {
7407 win
->in_predrag
= TRUE
;
7408 win
->drag_tab
= tab_clicked
;
7410 /* Connect the new motion signals. */
7411 win
->drag_motion_signal
=
7412 g_signal_connect(G_OBJECT(widget
), "motion_notify_event",
7413 G_CALLBACK(notebook_motion_cb
), win
);
7415 win
->drag_leave_signal
=
7416 g_signal_connect(G_OBJECT(widget
), "leave_notify_event",
7417 G_CALLBACK(notebook_leave_cb
), win
);
7423 notebook_release_cb(GtkWidget
*widget
, GdkEventButton
*e
, PidginConvWindow
*win
)
7425 PidginConvWindow
*dest_win
;
7426 GtkNotebook
*dest_notebook
;
7427 PidginConversation
*active_gtkconv
;
7428 PidginConversation
*gtkconv
;
7429 gint dest_page_num
= 0;
7430 gboolean new_window
= FALSE
;
7431 gboolean to_right
= FALSE
;
7435 * Don't check to make sure that the event's window matches the
7436 * widget's, because we may be getting an event passed on from the
7439 if (e
->button
!= GDK_BUTTON_PRIMARY
&& e
->type
!= GDK_BUTTON_RELEASE
)
7442 device
= gdk_event_get_device((GdkEvent
*)e
);
7443 if (gdk_display_device_is_grabbed(gdk_device_get_display(device
), device
)) {
7444 gdk_seat_ungrab(gdk_event_get_seat((GdkEvent
*)e
));
7445 gtk_grab_remove(widget
);
7448 if (!win
->in_predrag
&& !win
->in_drag
)
7451 /* Disconnect the motion signal. */
7452 if (win
->drag_motion_signal
) {
7453 g_signal_handler_disconnect(G_OBJECT(widget
),
7454 win
->drag_motion_signal
);
7456 win
->drag_motion_signal
= 0;
7460 * If we're in a pre-drag, we'll also need to disconnect the leave
7463 if (win
->in_predrag
) {
7464 win
->in_predrag
= FALSE
;
7466 if (win
->drag_leave_signal
) {
7467 g_signal_handler_disconnect(G_OBJECT(widget
),
7468 win
->drag_leave_signal
);
7470 win
->drag_leave_signal
= 0;
7474 /* If we're not in drag... */
7475 /* We're perfectly normal people! */
7479 win
->in_drag
= FALSE
;
7481 pidgin_dnd_hints_hide_all();
7483 dest_win
= pidgin_conv_window_get_at_event((GdkEvent
*)e
);
7485 active_gtkconv
= pidgin_conv_window_get_active_gtkconv(win
);
7487 if (dest_win
== NULL
) {
7488 /* If the current window doesn't have any other conversations,
7489 * there isn't much point transferring the conv to a new window. */
7490 if (pidgin_conv_window_get_gtkconv_count(win
) > 1) {
7491 /* Make a new window to stick this to. */
7492 dest_win
= pidgin_conv_window_new();
7497 if (dest_win
== NULL
)
7500 purple_signal_emit(pidgin_conversations_get_handle(),
7501 "conversation-dragging", win
, dest_win
);
7503 /* Get the destination page number. */
7505 dest_notebook
= GTK_NOTEBOOK(dest_win
->notebook
);
7506 if (gtk_notebook_get_show_tabs(dest_notebook
)) {
7507 dest_page_num
= pidgin_conv_get_tab_at_xy(dest_win
,
7508 e
->x_root
, e
->y_root
, &to_right
);
7511 to_right
= pidgin_conv_xy_to_right_infopane(dest_win
, e
->x_root
, e
->y_root
);
7515 gtkconv
= pidgin_conv_window_get_gtkconv_at_index(win
, win
->drag_tab
);
7517 if (win
== dest_win
) {
7518 gtk_notebook_reorder_child(GTK_NOTEBOOK(win
->notebook
), gtkconv
->tab_cont
, dest_page_num
);
7520 pidgin_conv_window_remove_gtkconv(win
, gtkconv
);
7521 pidgin_conv_window_add_gtkconv(dest_win
, gtkconv
);
7522 gtk_notebook_reorder_child(GTK_NOTEBOOK(dest_win
->notebook
), gtkconv
->tab_cont
, dest_page_num
+ to_right
);
7523 pidgin_conv_window_switch_gtkconv(dest_win
, gtkconv
);
7525 gint win_width
, win_height
;
7527 gtk_window_get_size(GTK_WINDOW(dest_win
->window
),
7528 &win_width
, &win_height
);
7529 #ifdef _WIN32 /* only override window manager placement on Windows */
7530 gtk_window_move(GTK_WINDOW(dest_win
->window
),
7531 e
->x_root
- (win_width
/ 2),
7532 e
->y_root
- (win_height
/ 2));
7535 pidgin_conv_window_show(dest_win
);
7539 gtk_widget_grab_focus(active_gtkconv
->editor
);
7546 before_switch_conv_cb(GtkNotebook
*notebook
, GtkWidget
*page
, gint page_num
,
7549 PidginConvWindow
*win
;
7550 PurpleConversation
*conv
;
7551 PidginConversation
*gtkconv
;
7554 conv
= pidgin_conv_window_get_active_conversation(win
);
7556 g_return_if_fail(conv
!= NULL
);
7558 if (!PURPLE_IS_IM_CONVERSATION(conv
))
7561 gtkconv
= PIDGIN_CONVERSATION(conv
);
7563 if (gtkconv
->u
.im
->typing_timer
!= 0) {
7564 g_source_remove(gtkconv
->u
.im
->typing_timer
);
7565 gtkconv
->u
.im
->typing_timer
= 0;
7568 stop_anim(NULL
, gtkconv
);
7572 close_window(GtkWidget
*w
, PidginConvWindow
*win
)
7574 close_win_cb(w
, NULL
, win
);
7578 detach_tab_cb(GtkWidget
*w
, PidginConvWindow
*win
)
7580 PidginConvWindow
*new_window
;
7581 PidginConversation
*gtkconv
;
7583 gtkconv
= win
->clicked_tab
;
7588 /* Nothing to do if there's only one tab in the window */
7589 if (pidgin_conv_window_get_gtkconv_count(win
) == 1)
7592 pidgin_conv_window_remove_gtkconv(win
, gtkconv
);
7594 new_window
= pidgin_conv_window_new();
7595 pidgin_conv_window_add_gtkconv(new_window
, gtkconv
);
7596 pidgin_conv_window_show(new_window
);
7600 close_others_cb(GtkWidget
*w
, PidginConvWindow
*win
)
7603 PidginConversation
*gtkconv
;
7605 gtkconv
= win
->clicked_tab
;
7610 for (iter
= pidgin_conv_window_get_gtkconvs(win
); iter
; )
7612 PidginConversation
*gconv
= iter
->data
;
7615 if (gconv
!= gtkconv
)
7617 close_conv_cb(NULL
, gconv
);
7623 close_tab_cb(GtkWidget
*w
, PidginConvWindow
*win
)
7625 PidginConversation
*gtkconv
;
7627 gtkconv
= win
->clicked_tab
;
7630 close_conv_cb(NULL
, gtkconv
);
7634 notebook_menu_switch_cb(GtkWidget
*item
, GtkWidget
*child
)
7636 GtkNotebook
*notebook
;
7639 notebook
= GTK_NOTEBOOK(gtk_widget_get_parent(child
));
7640 index
= gtk_notebook_page_num(notebook
, child
);
7641 gtk_notebook_set_current_page(notebook
, index
);
7645 notebook_menu_update_label_cb(GtkWidget
*child
, GParamSpec
*pspec
,
7646 GtkNotebook
*notebook
)
7651 item
= g_object_get_data(G_OBJECT(child
), "popup-menu-item");
7652 label
= gtk_bin_get_child(GTK_BIN(item
));
7654 gtk_container_remove(GTK_CONTAINER(item
), label
);
7656 label
= gtk_notebook_get_menu_label(notebook
, child
);
7658 gtk_widget_show(label
);
7659 gtk_container_add(GTK_CONTAINER(item
), label
);
7660 gtk_widget_show(item
);
7662 gtk_widget_hide(item
);
7667 notebook_add_tab_to_menu_cb(GtkNotebook
*notebook
, GtkWidget
*child
,
7668 guint page_num
, PidginConvWindow
*win
)
7673 item
= gtk_menu_item_new();
7674 label
= gtk_notebook_get_menu_label(notebook
, child
);
7676 gtk_widget_show(label
);
7677 gtk_container_add(GTK_CONTAINER(item
), label
);
7678 gtk_widget_show(item
);
7681 g_signal_connect(child
, "child-notify::menu-label",
7682 G_CALLBACK(notebook_menu_update_label_cb
), notebook
);
7683 g_signal_connect(item
, "activate",
7684 G_CALLBACK(notebook_menu_switch_cb
), child
);
7685 g_object_set_data(G_OBJECT(child
), "popup-menu-item", item
);
7687 gtk_menu_shell_insert(GTK_MENU_SHELL(win
->notebook_menu
), item
, page_num
);
7691 notebook_remove_tab_from_menu_cb(GtkNotebook
*notebook
, GtkWidget
*child
,
7692 guint page_num
, PidginConvWindow
*win
)
7696 /* Disconnecting the "child-notify::menu-label" signal. */
7697 g_signal_handlers_disconnect_by_data(child
, notebook
);
7699 item
= g_object_get_data(G_OBJECT(child
), "popup-menu-item");
7700 gtk_container_remove(GTK_CONTAINER(win
->notebook_menu
), item
);
7705 notebook_reorder_tab_in_menu_cb(GtkNotebook
*notebook
, GtkWidget
*child
,
7706 guint page_num
, PidginConvWindow
*win
)
7710 item
= g_object_get_data(G_OBJECT(child
), "popup-menu-item");
7711 gtk_menu_reorder_child(GTK_MENU(win
->notebook_menu
), item
, page_num
);
7715 notebook_right_click_menu_cb(GtkNotebook
*notebook
, GdkEventButton
*event
,
7716 PidginConvWindow
*win
)
7719 PidginConversation
*gtkconv
;
7721 if (!gdk_event_triggers_context_menu((GdkEvent
*)event
))
7724 gtkconv
= pidgin_conv_window_get_gtkconv_at_index(win
,
7725 pidgin_conv_get_tab_at_xy(win
, event
->x_root
, event
->y_root
, NULL
));
7727 win
->clicked_tab
= gtkconv
;
7729 menu
= win
->notebook_menu
;
7731 gtk_menu_popup_at_pointer(GTK_MENU(menu
), (GdkEvent
*)event
);
7737 remove_edit_entry(PidginConversation
*gtkconv
, GtkWidget
*entry
)
7739 g_signal_handlers_disconnect_matched(G_OBJECT(entry
), G_SIGNAL_MATCH_DATA
,
7740 0, 0, NULL
, NULL
, gtkconv
);
7741 gtk_widget_show(gtkconv
->infopane
);
7742 gtk_widget_grab_focus(gtkconv
->editor
);
7743 gtk_widget_destroy(entry
);
7747 alias_focus_cb(GtkWidget
*widget
, GdkEventFocus
*event
, gpointer user_data
)
7749 remove_edit_entry(user_data
, widget
);
7754 alias_key_press_cb(GtkWidget
*widget
, GdkEventKey
*event
, gpointer user_data
)
7756 if (event
->keyval
== GDK_KEY_Escape
) {
7757 remove_edit_entry(user_data
, widget
);
7764 alias_cb(GtkEntry
*entry
, gpointer user_data
)
7766 PidginConversation
*gtkconv
;
7767 PurpleConversation
*conv
;
7768 PurpleAccount
*account
;
7771 gtkconv
= (PidginConversation
*)user_data
;
7772 if (gtkconv
== NULL
) {
7775 conv
= gtkconv
->active_conv
;
7776 account
= purple_conversation_get_account(conv
);
7777 name
= purple_conversation_get_name(conv
);
7779 if (PURPLE_IS_IM_CONVERSATION(conv
)) {
7781 buddy
= purple_blist_find_buddy(account
, name
);
7782 if (buddy
!= NULL
) {
7783 purple_buddy_set_local_alias(buddy
, gtk_entry_get_text(entry
));
7785 purple_serv_alias_buddy(buddy
);
7786 } else if (PURPLE_IS_CHAT_CONVERSATION(conv
)) {
7787 gtk_entry_set_text(GTK_ENTRY(gtkconv
->u
.chat
->topic_text
), gtk_entry_get_text(entry
));
7788 topic_callback(NULL
, gtkconv
);
7790 remove_edit_entry(user_data
, GTK_WIDGET(entry
));
7794 infopane_entry_activate(PidginConversation
*gtkconv
)
7796 GtkWidget
*entry
= NULL
;
7797 PurpleConversation
*conv
= gtkconv
->active_conv
;
7798 const char *text
= NULL
;
7800 if (!gtk_widget_get_visible(gtkconv
->infopane
)) {
7801 /* There's already an entry for alias. Let's not create another one. */
7805 if (!purple_account_is_connected(purple_conversation_get_account(gtkconv
->active_conv
))) {
7806 /* Do not allow aliasing someone on a disconnected account. */
7810 if (PURPLE_IS_IM_CONVERSATION(conv
)) {
7811 PurpleBuddy
*buddy
= purple_blist_find_buddy(purple_conversation_get_account(gtkconv
->active_conv
), purple_conversation_get_name(gtkconv
->active_conv
));
7813 /* This buddy isn't in your buddy list, so we can't alias him */
7816 text
= purple_buddy_get_contact_alias(buddy
);
7817 } else if (PURPLE_IS_CHAT_CONVERSATION(conv
)) {
7818 PurpleConnection
*gc
;
7819 PurpleProtocol
*protocol
= NULL
;
7821 gc
= purple_conversation_get_connection(conv
);
7823 protocol
= purple_connection_get_protocol(gc
);
7824 if (protocol
&& !PURPLE_PROTOCOL_IMPLEMENTS(protocol
, CHAT
, set_topic
))
7825 /* This protocol doesn't support setting the chat room topic */
7828 text
= purple_chat_conversation_get_topic(PURPLE_CHAT_CONVERSATION(conv
));
7832 entry
= gtk_entry_new();
7833 gtk_entry_set_has_frame(GTK_ENTRY(entry
), FALSE
);
7834 gtk_entry_set_width_chars(GTK_ENTRY(entry
), 10);
7835 gtk_entry_set_alignment(GTK_ENTRY(entry
), 0.5);
7837 gtk_box_pack_start(GTK_BOX(gtkconv
->infopane_hbox
), entry
, TRUE
, TRUE
, 0);
7838 /* after the tab label */
7839 gtk_box_reorder_child(GTK_BOX(gtkconv
->infopane_hbox
), entry
, 0);
7841 g_signal_connect(G_OBJECT(entry
), "activate", G_CALLBACK(alias_cb
), gtkconv
);
7842 g_signal_connect(G_OBJECT(entry
), "focus-out-event", G_CALLBACK(alias_focus_cb
), gtkconv
);
7843 g_signal_connect(G_OBJECT(entry
), "key-press-event", G_CALLBACK(alias_key_press_cb
), gtkconv
);
7846 gtk_entry_set_text(GTK_ENTRY(entry
), text
);
7847 gtk_widget_show(entry
);
7848 gtk_widget_hide(gtkconv
->infopane
);
7849 gtk_widget_grab_focus(entry
);
7855 window_keypress_cb(GtkWidget
*widget
, GdkEventKey
*event
, PidginConvWindow
*win
)
7857 PidginConversation
*gtkconv
= pidgin_conv_window_get_active_gtkconv(win
);
7859 return conv_keypress_common(gtkconv
, event
);
7863 switch_conv_cb(GtkNotebook
*notebook
, GtkWidget
*page
, gint page_num
,
7866 PidginConvWindow
*win
;
7867 PurpleConversation
*conv
;
7868 PidginConversation
*gtkconv
;
7869 const char *sound_method
;
7872 gtkconv
= pidgin_conv_window_get_gtkconv_at_index(win
, page_num
);
7873 conv
= gtkconv
->active_conv
;
7875 g_return_if_fail(conv
!= NULL
);
7877 /* clear unseen flag if conversation is not hidden */
7878 if(!pidgin_conv_is_hidden(gtkconv
)) {
7879 gtkconv_set_unseen(gtkconv
, PIDGIN_UNSEEN_NONE
);
7882 /* Update the menubar */
7884 gtk_toggle_action_set_active(GTK_TOGGLE_ACTION(gtkconv
->win
->menu
->logging
),
7885 purple_conversation_is_logging(conv
));
7887 generate_send_to_items(win
);
7888 generate_e2ee_controls(win
);
7889 regenerate_options_items(win
);
7890 regenerate_plugins_items(win
);
7892 pidgin_conv_switch_active_conversation(conv
);
7894 sound_method
= purple_prefs_get_string(PIDGIN_PREFS_ROOT
"/sound/method");
7895 if (!purple_strequal(sound_method
, "none"))
7896 gtk_toggle_action_set_active(GTK_TOGGLE_ACTION(win
->menu
->sounds
),
7897 gtkconv
->make_sound
);
7899 gtk_toggle_action_set_active(GTK_TOGGLE_ACTION(win
->menu
->show_formatting_toolbar
),
7900 purple_prefs_get_bool(PIDGIN_PREFS_ROOT
"/conversations/show_formatting_toolbar"));
7903 * We pause icons when they are not visible. If this icon should
7904 * be animated then start it back up again.
7906 if (PURPLE_IS_IM_CONVERSATION(conv
) &&
7907 (gtkconv
->u
.im
->animate
))
7908 start_anim(NULL
, gtkconv
);
7910 purple_signal_emit(pidgin_conversations_get_handle(), "conversation-switched", conv
);
7913 /**************************************************************************
7915 **************************************************************************/
7918 pidgin_conv_windows_get_list()
7924 make_status_icon_list(const char *stock
, GtkWidget
*w
)
7927 l
= g_list_append(l
,
7928 gtk_widget_render_icon(w
, stock
,
7929 gtk_icon_size_from_name(PIDGIN_ICON_SIZE_TANGO_EXTRA_SMALL
), "GtkWindow"));
7930 l
= g_list_append(l
,
7931 gtk_widget_render_icon(w
, stock
,
7932 gtk_icon_size_from_name(PIDGIN_ICON_SIZE_TANGO_SMALL
), "GtkWindow"));
7933 l
= g_list_append(l
,
7934 gtk_widget_render_icon(w
, stock
,
7935 gtk_icon_size_from_name(PIDGIN_ICON_SIZE_TANGO_MEDIUM
), "GtkWindow"));
7936 l
= g_list_append(l
,
7937 gtk_widget_render_icon(w
, stock
,
7938 gtk_icon_size_from_name(PIDGIN_ICON_SIZE_TANGO_LARGE
), "GtkWindow"));
7943 create_icon_lists(GtkWidget
*w
)
7945 available_list
= make_status_icon_list(PIDGIN_STOCK_STATUS_AVAILABLE
, w
);
7946 busy_list
= make_status_icon_list(PIDGIN_STOCK_STATUS_BUSY
, w
);
7947 xa_list
= make_status_icon_list(PIDGIN_STOCK_STATUS_XA
, w
);
7948 offline_list
= make_status_icon_list(PIDGIN_STOCK_STATUS_OFFLINE
, w
);
7949 away_list
= make_status_icon_list(PIDGIN_STOCK_STATUS_AWAY
, w
);
7950 protocol_lists
= g_hash_table_new(g_str_hash
, g_str_equal
);
7954 plugin_changed_cb(PurplePlugin
*p
, gpointer data
)
7956 regenerate_plugins_items(data
);
7959 static gboolean
gtk_conv_configure_cb(GtkWidget
*w
, GdkEventConfigure
*event
, gpointer data
) {
7962 if (gtk_widget_get_visible(w
))
7963 gtk_window_get_position(GTK_WINDOW(w
), &x
, &y
);
7965 return FALSE
; /* carry on normally */
7967 /* Workaround for GTK+ bug # 169811 - "configure_event" is fired
7968 * when the window is being maximized */
7969 if (gdk_window_get_state(gtk_widget_get_window(w
)) & GDK_WINDOW_STATE_MAXIMIZED
)
7972 /* don't save off-screen positioning */
7973 if (x
+ event
->width
< 0 ||
7974 y
+ event
->height
< 0 ||
7975 x
> gdk_screen_width() ||
7976 y
> gdk_screen_height())
7977 return FALSE
; /* carry on normally */
7979 /* store the position */
7980 purple_prefs_set_int(PIDGIN_PREFS_ROOT
"/conversations/im/x", x
);
7981 purple_prefs_set_int(PIDGIN_PREFS_ROOT
"/conversations/im/y", y
);
7982 purple_prefs_set_int(PIDGIN_PREFS_ROOT
"/conversations/im/width", event
->width
);
7983 purple_prefs_set_int(PIDGIN_PREFS_ROOT
"/conversations/im/height", event
->height
);
7985 /* continue to handle event normally */
7991 pidgin_conv_set_position_size(PidginConvWindow
*win
, int conv_x
, int conv_y
,
7992 int conv_width
, int conv_height
)
7994 /* if the window exists, is hidden, we're saving positions, and the
7995 * position is sane... */
7996 if (win
&& win
->window
&&
7997 !gtk_widget_get_visible(win
->window
) && conv_width
!= 0) {
7999 #ifdef _WIN32 /* only override window manager placement on Windows */
8000 /* ...check position is on screen... */
8001 if (conv_x
>= gdk_screen_width())
8002 conv_x
= gdk_screen_width() - 100;
8003 else if (conv_x
+ conv_width
< 0)
8006 if (conv_y
>= gdk_screen_height())
8007 conv_y
= gdk_screen_height() - 100;
8008 else if (conv_y
+ conv_height
< 0)
8011 /* ...and move it back. */
8012 gtk_window_move(GTK_WINDOW(win
->window
), conv_x
, conv_y
);
8014 gtk_window_resize(GTK_WINDOW(win
->window
), conv_width
, conv_height
);
8019 pidgin_conv_restore_position(PidginConvWindow
*win
) {
8020 pidgin_conv_set_position_size(win
,
8021 purple_prefs_get_int(PIDGIN_PREFS_ROOT
"/conversations/im/x"),
8022 purple_prefs_get_int(PIDGIN_PREFS_ROOT
"/conversations/im/y"),
8023 purple_prefs_get_int(PIDGIN_PREFS_ROOT
"/conversations/im/width"),
8024 purple_prefs_get_int(PIDGIN_PREFS_ROOT
"/conversations/im/height"));
8028 pidgin_conv_window_new()
8030 PidginConvWindow
*win
;
8031 GtkPositionType pos
;
8032 GtkWidget
*testidea
;
8036 GdkModifierType state
;
8038 win
= g_malloc0(sizeof(PidginConvWindow
));
8039 win
->menu
= g_malloc0(sizeof(PidginConvWindowMenu
));
8041 window_list
= g_list_append(window_list
, win
);
8043 /* Create the window. */
8044 win
->window
= pidgin_create_window(NULL
, 0, "conversation", TRUE
);
8045 /*_pidgin_widget_set_accessible_name(win->window, "Conversations");*/
8046 if (!gtk_get_current_event_state(&state
))
8047 gtk_window_set_focus_on_map(GTK_WINDOW(win
->window
), FALSE
);
8049 /* Etan: I really think this entire function call should happen only
8050 * when we are on Windows but I was informed that back before we used
8051 * to save the window position we stored the window size, so I'm
8052 * leaving it for now. */
8053 #if TRUE || defined(_WIN32)
8054 pidgin_conv_restore_position(win
);
8057 if (available_list
== NULL
) {
8058 create_icon_lists(win
->window
);
8061 g_signal_connect(G_OBJECT(win
->window
), "delete_event",
8062 G_CALLBACK(close_win_cb
), win
);
8063 g_signal_connect(G_OBJECT(win
->window
), "focus_in_event",
8064 G_CALLBACK(focus_win_cb
), win
);
8066 /* Intercept keystrokes from the menu items */
8067 g_signal_connect(G_OBJECT(win
->window
), "key_press_event",
8068 G_CALLBACK(window_keypress_cb
), win
);
8071 /* Create the notebook. */
8072 win
->notebook
= gtk_notebook_new();
8074 pos
= purple_prefs_get_int(PIDGIN_PREFS_ROOT
"/conversations/tab_side");
8076 gtk_notebook_set_tab_pos(GTK_NOTEBOOK(win
->notebook
), pos
);
8077 gtk_notebook_set_scrollable(GTK_NOTEBOOK(win
->notebook
), TRUE
);
8078 gtk_notebook_set_show_tabs(GTK_NOTEBOOK(win
->notebook
), FALSE
);
8079 gtk_notebook_set_show_border(GTK_NOTEBOOK(win
->notebook
), TRUE
);
8081 menu
= win
->notebook_menu
= gtk_menu_new();
8083 pidgin_separator(GTK_WIDGET(menu
));
8085 item
= gtk_menu_item_new_with_label(_("Close other tabs"));
8086 gtk_widget_show(item
);
8087 gtk_menu_shell_append(GTK_MENU_SHELL(menu
), item
);
8088 g_signal_connect(G_OBJECT(item
), "activate",
8089 G_CALLBACK(close_others_cb
), win
);
8091 item
= gtk_menu_item_new_with_label(_("Close all tabs"));
8092 gtk_widget_show(item
);
8093 gtk_menu_shell_append(GTK_MENU_SHELL(menu
), item
);
8094 g_signal_connect(G_OBJECT(item
), "activate",
8095 G_CALLBACK(close_window
), win
);
8097 pidgin_separator(menu
);
8099 item
= gtk_menu_item_new_with_label(_("Detach this tab"));
8100 gtk_widget_show(item
);
8101 gtk_menu_shell_append(GTK_MENU_SHELL(menu
), item
);
8102 g_signal_connect(G_OBJECT(item
), "activate",
8103 G_CALLBACK(detach_tab_cb
), win
);
8105 item
= gtk_menu_item_new_with_label(_("Close this tab"));
8106 gtk_widget_show(item
);
8107 gtk_menu_shell_append(GTK_MENU_SHELL(menu
), item
);
8108 g_signal_connect(G_OBJECT(item
), "activate",
8109 G_CALLBACK(close_tab_cb
), win
);
8111 g_signal_connect(G_OBJECT(win
->notebook
), "page-added",
8112 G_CALLBACK(notebook_add_tab_to_menu_cb
), win
);
8113 g_signal_connect(G_OBJECT(win
->notebook
), "page-removed",
8114 G_CALLBACK(notebook_remove_tab_from_menu_cb
), win
);
8115 g_signal_connect(G_OBJECT(win
->notebook
), "page-reordered",
8116 G_CALLBACK(notebook_reorder_tab_in_menu_cb
), win
);
8118 g_signal_connect(G_OBJECT(win
->notebook
), "button-press-event",
8119 G_CALLBACK(notebook_right_click_menu_cb
), win
);
8121 gtk_widget_show(win
->notebook
);
8123 g_signal_connect(G_OBJECT(win
->notebook
), "switch_page",
8124 G_CALLBACK(before_switch_conv_cb
), win
);
8125 g_signal_connect_after(G_OBJECT(win
->notebook
), "switch_page",
8126 G_CALLBACK(switch_conv_cb
), win
);
8128 /* Setup the tab drag and drop signals. */
8129 gtk_widget_add_events(win
->notebook
,
8130 GDK_BUTTON1_MOTION_MASK
| GDK_LEAVE_NOTIFY_MASK
);
8131 g_signal_connect(G_OBJECT(win
->notebook
), "button_press_event",
8132 G_CALLBACK(notebook_press_cb
), win
);
8133 g_signal_connect(G_OBJECT(win
->notebook
), "button_release_event",
8134 G_CALLBACK(notebook_release_cb
), win
);
8136 testidea
= gtk_box_new(GTK_ORIENTATION_VERTICAL
, 0);
8138 /* Setup the menubar. */
8139 menubar
= setup_menubar(win
);
8140 gtk_box_pack_start(GTK_BOX(testidea
), menubar
, FALSE
, TRUE
, 0);
8142 gtk_box_pack_start(GTK_BOX(testidea
), win
->notebook
, TRUE
, TRUE
, 0);
8144 gtk_container_add(GTK_CONTAINER(win
->window
), testidea
);
8146 gtk_widget_show(testidea
);
8148 /* Update the plugin actions when plugins are (un)loaded */
8149 purple_signal_connect(purple_plugins_get_handle(), "plugin-load",
8150 win
, PURPLE_CALLBACK(plugin_changed_cb
), win
);
8151 purple_signal_connect(purple_plugins_get_handle(), "plugin-unload",
8152 win
, PURPLE_CALLBACK(plugin_changed_cb
), win
);
8156 g_signal_connect(G_OBJECT(win
->window
), "show",
8157 G_CALLBACK(winpidgin_ensure_onscreen
), win
->window
);
8159 if (purple_prefs_get_bool(PIDGIN_PREFS_ROOT
"/win32/minimize_new_convs")
8160 && !gtk_get_current_event_state(&state
))
8161 gtk_window_iconify(GTK_WINDOW(win
->window
));
8164 purple_signal_emit(pidgin_conversations_get_handle(),
8165 "conversation-window-created", win
);
8168 pidgin_conversations_set_tab_colors();
8174 pidgin_conv_window_destroy(PidginConvWindow
*win
)
8176 if (win
->gtkconvs
) {
8177 GList
*iter
= win
->gtkconvs
;
8180 PidginConversation
*gtkconv
= iter
->data
;
8182 close_conv_cb(NULL
, gtkconv
);
8187 purple_prefs_disconnect_by_handle(win
);
8188 window_list
= g_list_remove(window_list
, win
);
8190 gtk_widget_destroy(win
->notebook_menu
);
8191 gtk_widget_destroy(win
->window
);
8193 g_object_unref(G_OBJECT(win
->menu
->ui
));
8195 purple_notify_close_with_handle(win
);
8196 purple_signals_disconnect_by_handle(win
);
8203 pidgin_conv_window_show(PidginConvWindow
*win
)
8205 gtk_widget_show(win
->window
);
8209 pidgin_conv_window_hide(PidginConvWindow
*win
)
8211 gtk_widget_hide(win
->window
);
8215 pidgin_conv_window_raise(PidginConvWindow
*win
)
8217 gdk_window_raise(GDK_WINDOW(gtk_widget_get_window(win
->window
)));
8221 pidgin_conv_window_switch_gtkconv(PidginConvWindow
*win
, PidginConversation
*gtkconv
)
8223 gtk_notebook_set_current_page(GTK_NOTEBOOK(win
->notebook
),
8224 gtk_notebook_page_num(GTK_NOTEBOOK(win
->notebook
),
8225 gtkconv
->tab_cont
));
8229 gtkconv_tab_set_tip(GtkWidget
*widget
, GdkEventCrossing
*event
, PidginConversation
*gtkconv
)
8231 /* PANGO_VERSION_CHECK macro was introduced in 1.15. So we need this double check. */
8232 #ifndef PANGO_VERSION_CHECK
8233 #define pango_layout_is_ellipsized(l) TRUE
8234 #elif !PANGO_VERSION_CHECK(1,16,0)
8235 #define pango_layout_is_ellipsized(l) TRUE
8237 PangoLayout
*layout
;
8239 layout
= gtk_label_get_layout(GTK_LABEL(gtkconv
->tab_label
));
8240 if (pango_layout_is_ellipsized(layout
))
8241 gtk_widget_set_tooltip_text(widget
, gtk_label_get_text(GTK_LABEL(gtkconv
->tab_label
)));
8243 gtk_widget_set_tooltip_text(widget
, NULL
);
8249 set_default_tab_colors(GtkWidget
*widget
)
8252 GtkCssProvider
*provider
;
8253 GError
*error
= NULL
;
8257 const char *labelname
;
8260 {"tab-label-typing", "#4e9a06"},
8261 {"tab-label-typed", "#c4a000"},
8262 {"tab-label-attention", "#006aff"},
8263 {"tab-label-unreadchat", "#cc0000"},
8264 {"tab-label-event", "#888a85"},
8268 str
= g_string_new(NULL
);
8270 for (iter
= 0; styles
[iter
].labelname
; iter
++) {
8271 g_string_append_printf(str
,
8275 styles
[iter
].labelname
,
8276 styles
[iter
].color
);
8279 provider
= gtk_css_provider_new();
8281 gtk_css_provider_load_from_data(provider
, str
->str
, str
->len
, &error
);
8283 gtk_style_context_add_provider(gtk_widget_get_style_context(widget
),
8284 GTK_STYLE_PROVIDER(provider
),
8285 GTK_STYLE_PROVIDER_PRIORITY_APPLICATION
);
8288 g_error_free(error
);
8289 g_string_free(str
, TRUE
);
8293 pidgin_conv_window_add_gtkconv(PidginConvWindow
*win
, PidginConversation
*gtkconv
)
8295 PurpleConversation
*conv
= gtkconv
->active_conv
;
8296 PidginConversation
*focus_gtkconv
;
8297 GtkWidget
*tab_cont
= gtkconv
->tab_cont
;
8298 const gchar
*tmp_lab
;
8300 win
->gtkconvs
= g_list_append(win
->gtkconvs
, gtkconv
);
8303 if (win
->gtkconvs
&& win
->gtkconvs
->next
&& win
->gtkconvs
->next
->next
== NULL
)
8304 pidgin_conv_tab_pack(win
, ((PidginConversation
*)win
->gtkconvs
->data
));
8308 gtkconv
->close
= pidgin_create_small_button(gtk_label_new("×"));
8309 gtk_widget_set_tooltip_text(gtkconv
->close
, _("Close conversation"));
8311 g_signal_connect(gtkconv
->close
, "clicked", G_CALLBACK (close_conv_cb
), gtkconv
);
8314 gtkconv
->icon
= gtk_image_new();
8315 gtkconv
->menu_icon
= gtk_image_new();
8316 g_object_set(G_OBJECT(gtkconv
->icon
),
8317 "icon-size", gtk_icon_size_from_name(PIDGIN_ICON_SIZE_TANGO_MICROSCOPIC
),
8319 g_object_set(G_OBJECT(gtkconv
->menu_icon
),
8320 "icon-size", gtk_icon_size_from_name(PIDGIN_ICON_SIZE_TANGO_MICROSCOPIC
),
8322 gtk_widget_show(gtkconv
->icon
);
8323 update_tab_icon(conv
);
8326 gtkconv
->tab_label
= gtk_label_new(tmp_lab
= purple_conversation_get_title(conv
));
8327 set_default_tab_colors(gtkconv
->tab_label
);
8328 gtk_widget_set_name(gtkconv
->tab_label
, "tab-label");
8330 gtkconv
->menu_tabby
= gtk_box_new(GTK_ORIENTATION_HORIZONTAL
, PIDGIN_HIG_BOX_SPACE
);
8331 gtkconv
->menu_label
= gtk_label_new(tmp_lab
);
8332 gtk_box_pack_start(GTK_BOX(gtkconv
->menu_tabby
), gtkconv
->menu_icon
, FALSE
, FALSE
, 0);
8334 gtk_widget_show_all(gtkconv
->menu_icon
);
8336 gtk_box_pack_start(GTK_BOX(gtkconv
->menu_tabby
), gtkconv
->menu_label
, TRUE
, TRUE
, 0);
8337 gtk_widget_show(gtkconv
->menu_label
);
8338 gtk_label_set_xalign(GTK_LABEL(gtkconv
->menu_label
), 0);
8339 gtk_label_set_yalign(GTK_LABEL(gtkconv
->menu_label
), 0);
8341 gtk_widget_show(gtkconv
->menu_tabby
);
8343 if (PURPLE_IS_IM_CONVERSATION(conv
))
8344 pidgin_conv_update_buddy_icon(PURPLE_IM_CONVERSATION(conv
));
8346 /* Build and set conversations tab */
8347 pidgin_conv_tab_pack(win
, gtkconv
);
8349 gtk_notebook_set_menu_label(GTK_NOTEBOOK(win
->notebook
), tab_cont
, gtkconv
->menu_tabby
);
8351 gtk_widget_show(tab_cont
);
8353 if (pidgin_conv_window_get_gtkconv_count(win
) == 1) {
8354 /* Er, bug in notebooks? Switch to the page manually. */
8355 gtk_notebook_set_current_page(GTK_NOTEBOOK(win
->notebook
), 0);
8357 gtk_notebook_set_show_tabs(GTK_NOTEBOOK(win
->notebook
), TRUE
);
8360 focus_gtkconv
= g_list_nth_data(pidgin_conv_window_get_gtkconvs(win
),
8361 gtk_notebook_get_current_page(GTK_NOTEBOOK(win
->notebook
)));
8362 gtk_widget_grab_focus(focus_gtkconv
->editor
);
8364 if (pidgin_conv_window_get_gtkconv_count(win
) == 1)
8365 update_send_to_selection(win
);
8369 pidgin_conv_tab_pack(PidginConvWindow
*win
, PidginConversation
*gtkconv
)
8371 gboolean tabs_side
= FALSE
;
8373 GtkWidget
*first
, *third
, *ebox
, *parent
;
8375 if (purple_prefs_get_int(PIDGIN_PREFS_ROOT
"/conversations/tab_side") == GTK_POS_LEFT
||
8376 purple_prefs_get_int(PIDGIN_PREFS_ROOT
"/conversations/tab_side") == GTK_POS_RIGHT
)
8378 else if (purple_prefs_get_int(PIDGIN_PREFS_ROOT
"/conversations/tab_side") == (GTK_POS_LEFT
|8))
8380 else if (purple_prefs_get_int(PIDGIN_PREFS_ROOT
"/conversations/tab_side") == (GTK_POS_RIGHT
|8))
8384 g_object_set(G_OBJECT(gtkconv
->tab_label
), "ellipsize", PANGO_ELLIPSIZE_END
, NULL
);
8385 gtk_label_set_width_chars(GTK_LABEL(gtkconv
->tab_label
), 4);
8387 g_object_set(G_OBJECT(gtkconv
->tab_label
), "ellipsize", PANGO_ELLIPSIZE_NONE
, NULL
);
8388 gtk_label_set_width_chars(GTK_LABEL(gtkconv
->tab_label
), -1);
8392 gtk_label_set_width_chars(
8393 GTK_LABEL(gtkconv
->tab_label
),
8394 MIN(g_utf8_strlen(gtk_label_get_text(GTK_LABEL(gtkconv
->tab_label
)), -1), 12)
8398 gtk_label_set_angle(GTK_LABEL(gtkconv
->tab_label
), angle
);
8401 gtkconv
->tabby
= gtk_box_new(GTK_ORIENTATION_VERTICAL
, PIDGIN_HIG_BOX_SPACE
);
8403 gtkconv
->tabby
= gtk_box_new(GTK_ORIENTATION_HORIZONTAL
, PIDGIN_HIG_BOX_SPACE
);
8404 gtk_widget_set_name(gtkconv
->tabby
, "tab-container");
8406 /* select the correct ordering for verticle tabs */
8408 first
= gtkconv
->close
;
8409 third
= gtkconv
->icon
;
8411 first
= gtkconv
->icon
;
8412 third
= gtkconv
->close
;
8415 ebox
= gtk_event_box_new();
8416 gtk_event_box_set_visible_window(GTK_EVENT_BOX(ebox
), FALSE
);
8417 gtk_container_add(GTK_CONTAINER(ebox
), gtkconv
->tabby
);
8418 g_signal_connect(G_OBJECT(ebox
), "enter-notify-event",
8419 G_CALLBACK(gtkconv_tab_set_tip
), gtkconv
);
8421 parent
= gtk_widget_get_parent(gtkconv
->tab_label
);
8422 if (parent
!= NULL
) {
8423 /* reparent old widgets on preference changes */
8424 g_object_ref(first
);
8425 g_object_ref(gtkconv
->tab_label
);
8426 g_object_ref(third
);
8427 gtk_container_remove(GTK_CONTAINER(parent
), first
);
8428 gtk_container_remove(GTK_CONTAINER(parent
), gtkconv
->tab_label
);
8429 gtk_container_remove(GTK_CONTAINER(parent
), third
);
8432 gtk_box_pack_start(GTK_BOX(gtkconv
->tabby
), first
, FALSE
, FALSE
, 0);
8433 gtk_box_pack_start(GTK_BOX(gtkconv
->tabby
), gtkconv
->tab_label
, TRUE
, TRUE
, 0);
8434 gtk_box_pack_start(GTK_BOX(gtkconv
->tabby
), third
, FALSE
, FALSE
, 0);
8436 if (parent
== NULL
) {
8437 /* Add this pane to the conversation's notebook. */
8438 gtk_notebook_append_page(GTK_NOTEBOOK(win
->notebook
), gtkconv
->tab_cont
, ebox
);
8440 /* reparent old widgets on preference changes */
8441 g_object_unref(first
);
8442 g_object_unref(gtkconv
->tab_label
);
8443 g_object_unref(third
);
8445 /* Reset the tabs label to the new version */
8446 gtk_notebook_set_tab_label(GTK_NOTEBOOK(win
->notebook
), gtkconv
->tab_cont
, ebox
);
8449 gtk_container_child_set(GTK_CONTAINER(win
->notebook
), gtkconv
->tab_cont
,
8450 "tab-expand", !tabs_side
&& !angle
,
8451 "tab-fill", TRUE
, NULL
);
8453 if (pidgin_conv_window_get_gtkconv_count(win
) == 1)
8454 gtk_notebook_set_show_tabs(GTK_NOTEBOOK(win
->notebook
),
8455 purple_prefs_get_bool(PIDGIN_PREFS_ROOT
"/conversations/tabs") &&
8456 (!purple_prefs_get_bool(PIDGIN_PREFS_ROOT
"/conversations/im/show_buddy_icons") ||
8457 purple_prefs_get_int(PIDGIN_PREFS_ROOT
"/conversations/tab_side") != GTK_POS_TOP
));
8459 /* show the widgets */
8460 /* gtk_widget_show(gtkconv->icon); */
8461 gtk_widget_show(gtkconv
->tab_label
);
8462 if (purple_prefs_get_bool(PIDGIN_PREFS_ROOT
"/conversations/close_on_tabs"))
8463 gtk_widget_show(gtkconv
->close
);
8464 gtk_widget_show(gtkconv
->tabby
);
8465 gtk_widget_show(ebox
);
8469 pidgin_conv_window_remove_gtkconv(PidginConvWindow
*win
, PidginConversation
*gtkconv
)
8473 index
= gtk_notebook_page_num(GTK_NOTEBOOK(win
->notebook
), gtkconv
->tab_cont
);
8475 g_object_ref_sink(G_OBJECT(gtkconv
->tab_cont
));
8477 gtk_notebook_remove_page(GTK_NOTEBOOK(win
->notebook
), index
);
8479 win
->gtkconvs
= g_list_remove(win
->gtkconvs
, gtkconv
);
8481 g_signal_handlers_disconnect_matched(win
->window
, G_SIGNAL_MATCH_DATA
,
8482 0, 0, NULL
, NULL
, gtkconv
);
8484 if (win
->gtkconvs
&& win
->gtkconvs
->next
== NULL
)
8485 pidgin_conv_tab_pack(win
, win
->gtkconvs
->data
);
8487 if (!win
->gtkconvs
&& win
!= hidden_convwin
)
8488 pidgin_conv_window_destroy(win
);
8491 PidginConversation
*
8492 pidgin_conv_window_get_gtkconv_at_index(const PidginConvWindow
*win
, int index
)
8494 GtkWidget
*tab_cont
;
8498 tab_cont
= gtk_notebook_get_nth_page(GTK_NOTEBOOK(win
->notebook
), index
);
8499 return tab_cont
? g_object_get_data(G_OBJECT(tab_cont
), "PidginConversation") : NULL
;
8502 PidginConversation
*
8503 pidgin_conv_window_get_active_gtkconv(const PidginConvWindow
*win
)
8506 GtkWidget
*tab_cont
;
8508 index
= gtk_notebook_get_current_page(GTK_NOTEBOOK(win
->notebook
));
8511 tab_cont
= gtk_notebook_get_nth_page(GTK_NOTEBOOK(win
->notebook
), index
);
8514 return g_object_get_data(G_OBJECT(tab_cont
), "PidginConversation");
8518 PurpleConversation
*
8519 pidgin_conv_window_get_active_conversation(const PidginConvWindow
*win
)
8521 PidginConversation
*gtkconv
;
8523 gtkconv
= pidgin_conv_window_get_active_gtkconv(win
);
8524 return gtkconv
? gtkconv
->active_conv
: NULL
;
8528 pidgin_conv_window_is_active_conversation(const PurpleConversation
*conv
)
8530 return conv
== pidgin_conv_window_get_active_conversation(PIDGIN_CONVERSATION(conv
)->win
);
8534 pidgin_conv_window_has_focus(PidginConvWindow
*win
)
8536 gboolean has_focus
= FALSE
;
8538 g_object_get(G_OBJECT(win
->window
), "has-toplevel-focus", &has_focus
, NULL
);
8544 pidgin_conv_window_get_at_event(GdkEvent
*event
)
8546 PidginConvWindow
*win
;
8551 gdkwin
= gdk_device_get_window_at_position(gdk_event_get_device(event
),
8555 gdkwin
= gdk_window_get_toplevel(gdkwin
);
8557 for (l
= pidgin_conv_windows_get_list(); l
!= NULL
; l
= l
->next
) {
8560 if (gdkwin
== gtk_widget_get_window(win
->window
))
8568 pidgin_conv_window_get_gtkconvs(PidginConvWindow
*win
)
8570 return win
->gtkconvs
;
8574 pidgin_conv_window_get_gtkconv_count(PidginConvWindow
*win
)
8576 return g_list_length(win
->gtkconvs
);
8580 pidgin_conv_window_first_im(void)
8582 GList
*wins
, *convs
;
8583 PidginConvWindow
*win
;
8584 PidginConversation
*conv
;
8586 for (wins
= pidgin_conv_windows_get_list(); wins
!= NULL
; wins
= wins
->next
) {
8589 for (convs
= win
->gtkconvs
;
8591 convs
= convs
->next
) {
8595 if (PURPLE_IS_IM_CONVERSATION(conv
->active_conv
))
8604 pidgin_conv_window_last_im(void)
8606 GList
*wins
, *convs
;
8607 PidginConvWindow
*win
;
8608 PidginConversation
*conv
;
8610 for (wins
= g_list_last(pidgin_conv_windows_get_list());
8612 wins
= wins
->prev
) {
8616 for (convs
= win
->gtkconvs
;
8618 convs
= convs
->next
) {
8622 if (PURPLE_IS_IM_CONVERSATION(conv
->active_conv
))
8631 pidgin_conv_window_first_chat(void)
8633 GList
*wins
, *convs
;
8634 PidginConvWindow
*win
;
8635 PidginConversation
*conv
;
8637 for (wins
= pidgin_conv_windows_get_list(); wins
!= NULL
; wins
= wins
->next
) {
8640 for (convs
= win
->gtkconvs
;
8642 convs
= convs
->next
) {
8646 if (PURPLE_IS_CHAT_CONVERSATION(conv
->active_conv
))
8655 pidgin_conv_window_last_chat(void)
8657 GList
*wins
, *convs
;
8658 PidginConvWindow
*win
;
8659 PidginConversation
*conv
;
8661 for (wins
= g_list_last(pidgin_conv_windows_get_list());
8663 wins
= wins
->prev
) {
8667 for (convs
= win
->gtkconvs
;
8669 convs
= convs
->next
) {
8673 if (PURPLE_IS_CHAT_CONVERSATION(conv
->active_conv
))
8682 /**************************************************************************
8683 * Conversation placement functions
8684 **************************************************************************/
8689 PidginConvPlacementFunc fnc
;
8691 } ConvPlacementData
;
8693 static GList
*conv_placement_fncs
= NULL
;
8694 static PidginConvPlacementFunc place_conv
= NULL
;
8696 /* This one places conversations in the last made window. */
8698 conv_placement_last_created_win(PidginConversation
*conv
)
8700 PidginConvWindow
*win
;
8702 GList
*l
= g_list_last(pidgin_conv_windows_get_list());
8703 win
= l
? l
->data
: NULL
;;
8706 win
= pidgin_conv_window_new();
8708 g_signal_connect(G_OBJECT(win
->window
), "configure_event",
8709 G_CALLBACK(gtk_conv_configure_cb
), NULL
);
8711 pidgin_conv_window_add_gtkconv(win
, conv
);
8712 pidgin_conv_window_show(win
);
8714 pidgin_conv_window_add_gtkconv(win
, conv
);
8718 /* This one places conversations in the last made window of the same type. */
8720 conv_placement_last_created_win_type_configured_cb(GtkWidget
*w
,
8721 GdkEventConfigure
*event
, PidginConversation
*conv
)
8726 if (gtk_widget_get_visible(w
))
8727 gtk_window_get_position(GTK_WINDOW(w
), &x
, &y
);
8729 return FALSE
; /* carry on normally */
8731 /* Workaround for GTK+ bug # 169811 - "configure_event" is fired
8732 * when the window is being maximized */
8733 if (gdk_window_get_state(gtk_widget_get_window(w
)) & GDK_WINDOW_STATE_MAXIMIZED
)
8736 /* don't save off-screen positioning */
8737 if (x
+ event
->width
< 0 ||
8738 y
+ event
->height
< 0 ||
8739 x
> gdk_screen_width() ||
8740 y
> gdk_screen_height())
8741 return FALSE
; /* carry on normally */
8743 for (all
= conv
->convs
; all
!= NULL
; all
= all
->next
) {
8744 if (PURPLE_IS_IM_CONVERSATION(conv
->active_conv
) != PURPLE_IS_IM_CONVERSATION(all
->data
)) {
8745 /* this window has different types of conversation, don't save */
8750 if (PURPLE_IS_IM_CONVERSATION(conv
->active_conv
)) {
8751 purple_prefs_set_int(PIDGIN_PREFS_ROOT
"/conversations/im/x", x
);
8752 purple_prefs_set_int(PIDGIN_PREFS_ROOT
"/conversations/im/y", y
);
8753 purple_prefs_set_int(PIDGIN_PREFS_ROOT
"/conversations/im/width", event
->width
);
8754 purple_prefs_set_int(PIDGIN_PREFS_ROOT
"/conversations/im/height", event
->height
);
8755 } else if (PURPLE_IS_CHAT_CONVERSATION(conv
->active_conv
)) {
8756 purple_prefs_set_int(PIDGIN_PREFS_ROOT
"/conversations/chat/x", x
);
8757 purple_prefs_set_int(PIDGIN_PREFS_ROOT
"/conversations/chat/y", y
);
8758 purple_prefs_set_int(PIDGIN_PREFS_ROOT
"/conversations/chat/width", event
->width
);
8759 purple_prefs_set_int(PIDGIN_PREFS_ROOT
"/conversations/chat/height", event
->height
);
8766 conv_placement_last_created_win_type(PidginConversation
*conv
)
8768 PidginConvWindow
*win
;
8770 if (PURPLE_IS_IM_CONVERSATION(conv
->active_conv
))
8771 win
= pidgin_conv_window_last_im();
8773 win
= pidgin_conv_window_last_chat();
8776 win
= pidgin_conv_window_new();
8778 if (PURPLE_IS_IM_CONVERSATION(conv
->active_conv
) ||
8779 purple_prefs_get_int(PIDGIN_PREFS_ROOT
"/conversations/chat/width") == 0) {
8780 pidgin_conv_set_position_size(win
,
8781 purple_prefs_get_int(PIDGIN_PREFS_ROOT
"/conversations/im/x"),
8782 purple_prefs_get_int(PIDGIN_PREFS_ROOT
"/conversations/im/y"),
8783 purple_prefs_get_int(PIDGIN_PREFS_ROOT
"/conversations/im/width"),
8784 purple_prefs_get_int(PIDGIN_PREFS_ROOT
"/conversations/im/height"));
8785 } else if (PURPLE_IS_CHAT_CONVERSATION(conv
->active_conv
)) {
8786 pidgin_conv_set_position_size(win
,
8787 purple_prefs_get_int(PIDGIN_PREFS_ROOT
"/conversations/chat/x"),
8788 purple_prefs_get_int(PIDGIN_PREFS_ROOT
"/conversations/chat/y"),
8789 purple_prefs_get_int(PIDGIN_PREFS_ROOT
"/conversations/chat/width"),
8790 purple_prefs_get_int(PIDGIN_PREFS_ROOT
"/conversations/chat/height"));
8793 pidgin_conv_window_add_gtkconv(win
, conv
);
8794 pidgin_conv_window_show(win
);
8796 g_signal_connect(G_OBJECT(win
->window
), "configure_event",
8797 G_CALLBACK(conv_placement_last_created_win_type_configured_cb
), conv
);
8799 pidgin_conv_window_add_gtkconv(win
, conv
);
8802 /* This one places each conversation in its own window. */
8804 conv_placement_new_window(PidginConversation
*conv
)
8806 PidginConvWindow
*win
;
8808 win
= pidgin_conv_window_new();
8810 g_signal_connect(G_OBJECT(win
->window
), "configure_event",
8811 G_CALLBACK(gtk_conv_configure_cb
), NULL
);
8813 pidgin_conv_window_add_gtkconv(win
, conv
);
8815 pidgin_conv_window_show(win
);
8818 static PurpleGroup
*
8819 conv_get_group(PidginConversation
*conv
)
8821 PurpleGroup
*group
= NULL
;
8823 if (PURPLE_IS_IM_CONVERSATION(conv
->active_conv
)) {
8826 buddy
= purple_blist_find_buddy(purple_conversation_get_account(conv
->active_conv
),
8827 purple_conversation_get_name(conv
->active_conv
));
8830 group
= purple_buddy_get_group(buddy
);
8832 } else if (PURPLE_IS_CHAT_CONVERSATION(conv
->active_conv
)) {
8835 chat
= purple_blist_find_chat(purple_conversation_get_account(conv
->active_conv
),
8836 purple_conversation_get_name(conv
->active_conv
));
8839 group
= purple_chat_get_group(chat
);
8846 * This groups things by, well, group. Buddies from groups will always be
8847 * grouped together, and a buddy from a group not belonging to any currently
8848 * open windows will get a new window.
8851 conv_placement_by_group(PidginConversation
*conv
)
8853 PurpleGroup
*group
= NULL
;
8856 group
= conv_get_group(conv
);
8858 /* Go through the list of IMs and find one with this group. */
8859 for (wl
= pidgin_conv_windows_get_list(); wl
!= NULL
; wl
= wl
->next
) {
8860 PidginConvWindow
*win2
;
8861 PidginConversation
*conv2
;
8862 PurpleGroup
*group2
= NULL
;
8866 for (cl
= win2
->gtkconvs
;
8871 group2
= conv_get_group(conv2
);
8873 if (group
== group2
) {
8874 pidgin_conv_window_add_gtkconv(win2
, conv
);
8881 /* Make a new window. */
8882 conv_placement_new_window(conv
);
8885 /* This groups things by account. Otherwise, the same semantics as above */
8887 conv_placement_by_account(PidginConversation
*conv
)
8889 GList
*wins
, *convs
;
8890 PurpleAccount
*account
;
8892 account
= purple_conversation_get_account(conv
->active_conv
);
8894 /* Go through the list of IMs and find one with this group. */
8895 for (wins
= pidgin_conv_windows_get_list(); wins
!= NULL
; wins
= wins
->next
) {
8896 PidginConvWindow
*win2
;
8897 PidginConversation
*conv2
;
8901 for (convs
= win2
->gtkconvs
;
8903 convs
= convs
->next
) {
8904 conv2
= convs
->data
;
8906 if (account
== purple_conversation_get_account(conv2
->active_conv
)) {
8907 pidgin_conv_window_add_gtkconv(win2
, conv
);
8913 /* Make a new window. */
8914 conv_placement_new_window(conv
);
8917 static ConvPlacementData
*
8918 get_conv_placement_data(const char *id
)
8920 ConvPlacementData
*data
= NULL
;
8923 for (n
= conv_placement_fncs
; n
; n
= n
->next
) {
8925 if (purple_strequal(data
->id
, id
))
8933 add_conv_placement_fnc(const char *id
, const char *name
,
8934 PidginConvPlacementFunc fnc
)
8936 ConvPlacementData
*data
;
8938 data
= g_new(ConvPlacementData
, 1);
8940 data
->id
= g_strdup(id
);
8941 data
->name
= g_strdup(name
);
8944 conv_placement_fncs
= g_list_append(conv_placement_fncs
, data
);
8948 ensure_default_funcs(void)
8950 if (conv_placement_fncs
== NULL
) {
8951 add_conv_placement_fnc("last", _("Last created window"),
8952 conv_placement_last_created_win
);
8953 add_conv_placement_fnc("im_chat", _("Separate IM and Chat windows"),
8954 conv_placement_last_created_win_type
);
8955 add_conv_placement_fnc("new", _("New window"),
8956 conv_placement_new_window
);
8957 add_conv_placement_fnc("group", _("By group"),
8958 conv_placement_by_group
);
8959 add_conv_placement_fnc("account", _("By account"),
8960 conv_placement_by_account
);
8965 pidgin_conv_placement_get_options(void)
8967 GList
*n
, *list
= NULL
;
8968 ConvPlacementData
*data
;
8970 ensure_default_funcs();
8972 for (n
= conv_placement_fncs
; n
; n
= n
->next
) {
8974 list
= g_list_append(list
, data
->name
);
8975 list
= g_list_append(list
, data
->id
);
8983 pidgin_conv_placement_add_fnc(const char *id
, const char *name
,
8984 PidginConvPlacementFunc fnc
)
8986 g_return_if_fail(id
!= NULL
);
8987 g_return_if_fail(name
!= NULL
);
8988 g_return_if_fail(fnc
!= NULL
);
8990 ensure_default_funcs();
8992 add_conv_placement_fnc(id
, name
, fnc
);
8996 pidgin_conv_placement_remove_fnc(const char *id
)
8998 ConvPlacementData
*data
= get_conv_placement_data(id
);
9003 conv_placement_fncs
= g_list_remove(conv_placement_fncs
, data
);
9011 pidgin_conv_placement_get_name(const char *id
)
9013 ConvPlacementData
*data
;
9015 ensure_default_funcs();
9017 data
= get_conv_placement_data(id
);
9025 PidginConvPlacementFunc
9026 pidgin_conv_placement_get_fnc(const char *id
)
9028 ConvPlacementData
*data
;
9030 ensure_default_funcs();
9032 data
= get_conv_placement_data(id
);
9041 pidgin_conv_placement_set_current_func(PidginConvPlacementFunc func
)
9043 g_return_if_fail(func
!= NULL
);
9045 /* If tabs are enabled, set the function, otherwise, NULL it out. */
9046 if (purple_prefs_get_bool(PIDGIN_PREFS_ROOT
"/conversations/tabs"))
9052 PidginConvPlacementFunc
9053 pidgin_conv_placement_get_current_func(void)
9059 pidgin_conv_placement_place(PidginConversation
*gtkconv
)
9062 place_conv(gtkconv
);
9064 conv_placement_new_window(gtkconv
);
9068 pidgin_conv_is_hidden(PidginConversation
*gtkconv
)
9070 g_return_val_if_fail(gtkconv
!= NULL
, FALSE
);
9072 return (gtkconv
->win
== hidden_convwin
);
9076 gdouble
luminance(GdkRGBA color
)
9080 gdouble cutoff
= 0.03928, scale
= 12.92;
9081 gdouble a
= 0.055, d
= 1.055, p
= 2.2;
9087 r
= (rr
> cutoff
) ? pow((rr
+a
)/d
, p
) : rr
/scale
;
9088 g
= (gg
> cutoff
) ? pow((gg
+a
)/d
, p
) : gg
/scale
;
9089 b
= (bb
> cutoff
) ? pow((bb
+a
)/d
, p
) : bb
/scale
;
9091 return (r
*0.2126 + g
*0.7152 + b
*0.0722);
9094 /* Algorithm from https://www.w3.org/TR/2008/REC-WCAG20-20081211/relative-luminance.xml */
9096 color_is_visible(GdkRGBA foreground
, GdkRGBA background
, gdouble min_contrast_ratio
)
9098 gdouble lfg
, lbg
, lmin
, lmax
;
9099 gdouble luminosity_ratio
;
9102 lfg
= luminance(foreground
);
9103 lbg
= luminance(background
);
9106 lmax
= lfg
, lmin
= lbg
;
9108 lmax
= lbg
, lmin
= lfg
;
9110 nr
= lmax
+ 0.05, dr
= lmin
- 0.05;
9111 if (dr
< 0.005 && dr
> -0.005)
9114 luminosity_ratio
= nr
/dr
;
9115 if ( luminosity_ratio
< 0)
9116 luminosity_ratio
*= -1.0;
9117 return (luminosity_ratio
> min_contrast_ratio
);
9122 generate_nick_colors(guint numcolors
, GdkRGBA background
)
9125 GArray
*colors
= g_array_new(FALSE
, FALSE
, sizeof(GdkRGBA
));
9126 GdkRGBA nick_highlight
;
9128 time_t breakout_time
;
9130 gdk_rgba_parse(&nick_highlight
, DEFAULT_HIGHLIGHT_COLOR
);
9131 gdk_rgba_parse(&send_color
, DEFAULT_SEND_COLOR
);
9133 pidgin_style_adjust_contrast(NULL
, &nick_highlight
);
9134 pidgin_style_adjust_contrast(NULL
, &send_color
);
9136 srand(background
.red
* 65535 + background
.green
* 65535 + background
.blue
* 65535 + 1);
9138 breakout_time
= time(NULL
) + 3;
9140 /* first we look through the list of "good" colors: colors that differ from every other color in the
9141 * list. only some of them will differ from the background color though. lets see if we can find
9142 * numcolors of them that do
9144 while (i
< numcolors
&& j
< PIDGIN_NUM_NICK_SEED_COLORS
&& time(NULL
) < breakout_time
)
9146 GdkRGBA color
= nick_seed_colors
[j
];
9148 if (color_is_visible(color
, background
, MIN_LUMINANCE_CONTRAST_RATIO
) &&
9149 color_is_visible(color
, nick_highlight
, MIN_LUMINANCE_CONTRAST_RATIO
) &&
9150 color_is_visible(color
, send_color
, MIN_LUMINANCE_CONTRAST_RATIO
))
9152 g_array_append_val(colors
, color
);
9158 /* we might not have found numcolors in the last loop. if we did, we'll never enter this one.
9159 * if we did not, lets just find some colors that don't conflict with the background. its
9160 * expensive to find colors that not only don't conflict with the background, but also do not
9161 * conflict with each other.
9163 while(i
< numcolors
&& time(NULL
) < breakout_time
)
9165 GdkRGBA color
= {g_random_double_range(0, 1), g_random_double_range(0, 1), g_random_double_range(0, 1), 1};
9167 if (color_is_visible(color
, background
, MIN_LUMINANCE_CONTRAST_RATIO
) &&
9168 color_is_visible(color
, nick_highlight
, MIN_LUMINANCE_CONTRAST_RATIO
) &&
9169 color_is_visible(color
, send_color
, MIN_LUMINANCE_CONTRAST_RATIO
))
9171 g_array_append_val(colors
, color
);
9176 if (i
< numcolors
) {
9177 purple_debug_warning("gtkconv", "Unable to generate enough random colors before timeout. %u colors found.\n", i
);
9181 /* To remove errors caused by an empty array. */
9182 GdkRGBA color
= {0.5, 0.5, 0.5, 1.0};
9183 g_array_append_val(colors
, color
);
9189 /**************************************************************************
9190 * PidginConvWindow GBoxed code
9191 **************************************************************************/
9192 static PidginConvWindow
*
9193 pidgin_conv_window_ref(PidginConvWindow
*win
)
9195 g_return_val_if_fail(win
!= NULL
, NULL
);
9203 pidgin_conv_window_unref(PidginConvWindow
*win
)
9205 g_return_if_fail(win
!= NULL
);
9206 g_return_if_fail(win
->box_count
>= 0);
9208 if (!win
->box_count
--)
9209 pidgin_conv_window_destroy(win
);
9213 pidgin_conv_window_get_type(void)
9215 static GType type
= 0;
9218 type
= g_boxed_type_register_static("PidginConvWindow",
9219 (GBoxedCopyFunc
)pidgin_conv_window_ref
,
9220 (GBoxedFreeFunc
)pidgin_conv_window_unref
);