3 * Pidgin is the legal property of its developers, whose names are too numerous
4 * to list here. Please refer to the COPYRIGHT file distributed with this
7 * This program is free software; you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation; either version 2 of the License, or
10 * (at your option) any later version.
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
17 * You should have received a copy of the GNU General Public License
18 * along with this program; if not, write to the Free Software
19 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA
27 # include <X11/Xlib.h>
30 #include <gdk/gdkkeysyms.h>
35 #include "attention.h"
40 #include "glibcompat.h"
42 #include "image-store.h"
48 #include "smiley-parser.h"
52 #include "gtkinternal.h"
53 #include "gtkdnd-hints.h"
56 #include "gtkconvwin.h"
57 #include "gtkdialogs.h"
58 #include "gtkmenutray.h"
59 #include "gtkpounce.h"
61 #include "gtkprivacy.h"
64 #include "pidgingdkpixbuf.h"
65 #include "pidgininvitedialog.h"
66 #include "pidginlog.h"
67 #include "pidginmessage.h"
68 #include "pidginstock.h"
69 #include "pidgintooltip.h"
71 #include "gtknickcolors.h"
73 #define GTK_TOOLTIPS_VAR gtkconv->tooltips
74 #include "gtk3compat.h"
76 #define ADD_MESSAGE_HISTORY_AT_ONCE 100
79 * A GTK+ Instant Message pane.
91 /* Buddy icon stuff */
92 GtkWidget
*icon_container
;
96 GdkPixbufAnimation
*anim
;
97 GdkPixbufAnimationIter
*iter
;
104 struct _PidginChatPane
108 GtkWidget
*topic_text
;
111 #define CLOSE_CONV_TIMEOUT_SECS (10 * 60)
113 #define AUTO_RESPONSE "<AUTO-REPLY> : "
117 PIDGIN_CONV_SET_TITLE
= 1 << 0,
118 PIDGIN_CONV_BUDDY_ICON
= 1 << 1,
119 PIDGIN_CONV_MENU
= 1 << 2,
120 PIDGIN_CONV_TAB_ICON
= 1 << 3,
121 PIDGIN_CONV_TOPIC
= 1 << 4,
122 PIDGIN_CONV_SMILEY_THEME
= 1 << 5,
123 PIDGIN_CONV_COLORIZE_TITLE
= 1 << 6,
124 PIDGIN_CONV_E2EE
= 1 << 7
131 CONV_PROTOCOL_ICON_COLUMN
,
133 } PidginInfopaneColumns
;
135 #define PIDGIN_CONV_ALL ((1 << 7) - 1)
137 /* XXX: These color defines shouldn't really be here. But the nick-color
138 * generation algorithm uses them, so keeping these around until we fix that. */
139 #define DEFAULT_SEND_COLOR "#204a87"
140 #define DEFAULT_HIGHLIGHT_COLOR "#AF7F00"
142 #define BUDDYICON_SIZE_MIN 32
143 #define BUDDYICON_SIZE_MAX 96
145 #define MIN_LUMINANCE_CONTRAST_RATIO 4.5
147 #define NICK_COLOR_GENERATE_COUNT 220
148 static GArray
*generated_nick_colors
= NULL
;
150 /* These probably won't conflict with any WebKit values. */
151 #define PIDGIN_DRAG_BLIST_NODE (1337)
152 #define PIDGIN_DRAG_IM_CONTACT (31337)
154 static GtkWidget
*invite_dialog
= NULL
;
155 static GtkWidget
*warn_close_dialog
= NULL
;
157 static PidginConvWindow
*hidden_convwin
= NULL
;
158 static GList
*window_list
= NULL
;
160 /* Lists of status icons at all available sizes for use as window icons */
161 static GList
*available_list
= NULL
;
162 static GList
*away_list
= NULL
;
163 static GList
*busy_list
= NULL
;
164 static GList
*xa_list
= NULL
;
165 static GList
*offline_list
= NULL
;
166 static GHashTable
*protocol_lists
= NULL
;
167 static GHashTable
*e2ee_stock
= NULL
;
169 static gboolean
update_send_to_selection(PidginConvWindow
*win
);
170 static void generate_send_to_items(PidginConvWindow
*win
);
172 /* Prototypes. <-- because Paco-Paco hates this comment. */
173 static gboolean
infopane_entry_activate(PidginConversation
*gtkconv
);
174 static void got_typing_keypress(PidginConversation
*gtkconv
, gboolean first
);
175 static void gray_stuff_out(PidginConversation
*gtkconv
);
176 static void add_chat_user_common(PurpleChatConversation
*chat
, PurpleChatUser
*cb
, const char *old_name
);
177 static void pidgin_conv_updated(PurpleConversation
*conv
, PurpleConversationUpdateType type
);
178 static void conv_set_unseen(PurpleConversation
*gtkconv
, PidginUnseenState state
);
179 static void gtkconv_set_unseen(PidginConversation
*gtkconv
, PidginUnseenState state
);
180 static void update_typing_icon(PidginConversation
*gtkconv
);
181 static void update_typing_message(PidginConversation
*gtkconv
, const char *message
);
182 gboolean
pidgin_conv_has_focus(PurpleConversation
*conv
);
183 static GArray
* generate_nick_colors(guint numcolors
, GdkRGBA background
);
184 gdouble
luminance(GdkRGBA color
);
185 static gboolean
color_is_visible(GdkRGBA foreground
, GdkRGBA background
, gdouble min_contrast_ratio
);
186 static GtkTextTag
*get_buddy_tag(PurpleChatConversation
*chat
, const char *who
, PurpleMessageFlags flag
, gboolean create
);
187 static void pidgin_conv_update_fields(PurpleConversation
*conv
, PidginConvFields fields
);
188 static void focus_out_from_menubar(GtkWidget
*wid
, PidginConvWindow
*win
);
189 static void pidgin_conv_tab_pack(PidginConvWindow
*win
, PidginConversation
*gtkconv
);
190 static gboolean
infopane_press_cb(GtkWidget
*widget
, GdkEventButton
*e
, PidginConversation
*conv
);
191 static void hide_conv(PidginConversation
*gtkconv
, gboolean closetimer
);
193 static void pidgin_conv_set_position_size(PidginConvWindow
*win
, int x
, int y
,
194 int width
, int height
);
195 static gboolean
pidgin_conv_xy_to_right_infopane(PidginConvWindow
*win
, int x
, int y
);
197 static const GdkRGBA
*
198 get_nick_color(PidginConversation
*gtkconv
, const gchar
*name
)
203 col
.red
= col
.green
= col
.blue
= 0;
208 col
= g_array_index(gtkconv
->nick_colors
, GdkRGBA
,
209 g_str_hash(name
) % gtkconv
->nick_colors
->len
);
214 static PurpleBlistNode
*
215 get_conversation_blist_node(PurpleConversation
*conv
)
217 PurpleAccount
*account
= purple_conversation_get_account(conv
);
218 PurpleBlistNode
*node
= NULL
;
220 if (PURPLE_IS_IM_CONVERSATION(conv
)) {
221 node
= PURPLE_BLIST_NODE(purple_blist_find_buddy(account
, purple_conversation_get_name(conv
)));
222 node
= node
? node
->parent
: NULL
;
223 } else if (PURPLE_IS_CHAT_CONVERSATION(conv
)) {
224 node
= PURPLE_BLIST_NODE(purple_blist_find_chat(account
, purple_conversation_get_name(conv
)));
230 /**************************************************************************
232 **************************************************************************/
235 close_this_sucker(gpointer data
)
237 PidginConversation
*gtkconv
= data
;
238 GList
*list
= g_list_copy(gtkconv
->convs
);
239 g_list_foreach(list
, (GFunc
)g_object_unref
, NULL
);
245 close_conv_cb(GtkButton
*button
, PidginConversation
*gtkconv
)
247 /* We are going to destroy the conversations immediately only if the 'close immediately'
248 * preference is selected. Otherwise, close the conversation after a reasonable timeout
249 * (I am going to consider 10 minutes as a 'reasonable timeout' here.
250 * For chats, close immediately if the chat is not in the buddylist, or if the chat is
251 * not marked 'Persistent' */
252 PurpleConversation
*conv
= gtkconv
->active_conv
;
253 PurpleAccount
*account
= purple_conversation_get_account(conv
);
254 const char *name
= purple_conversation_get_name(conv
);
256 if (PURPLE_IS_IM_CONVERSATION(conv
)) {
257 if (purple_prefs_get_bool(PIDGIN_PREFS_ROOT
"/conversations/im/close_immediately"))
258 close_this_sucker(gtkconv
);
260 hide_conv(gtkconv
, TRUE
);
261 } else if (PURPLE_IS_CHAT_CONVERSATION(conv
)) {
262 PurpleChat
*chat
= purple_blist_find_chat(account
, name
);
264 !purple_blist_node_get_bool(&chat
->node
, "gtk-persistent"))
265 close_this_sucker(gtkconv
);
267 hide_conv(gtkconv
, FALSE
);
274 lbox_size_allocate_cb(GtkWidget
*w
, GtkAllocation
*allocation
, gpointer data
)
276 purple_prefs_set_int(PIDGIN_PREFS_ROOT
"/conversations/chat/userlist_width", allocation
->width
== 1 ? 0 : allocation
->width
);
282 pidgin_get_cmd_prefix(void)
288 say_command_cb(PurpleConversation
*conv
,
289 const char *cmd
, char **args
, char **error
, void *data
)
291 purple_conversation_send(conv
, args
[0]);
293 return PURPLE_CMD_RET_OK
;
297 me_command_cb(PurpleConversation
*conv
,
298 const char *cmd
, char **args
, char **error
, void *data
)
302 tmp
= g_strdup_printf("/me %s", args
[0]);
303 purple_conversation_send(conv
, tmp
);
306 return PURPLE_CMD_RET_OK
;
310 debug_command_cb(PurpleConversation
*conv
,
311 const char *cmd
, char **args
, char **error
, void *data
)
315 if (!g_ascii_strcasecmp(args
[0], "version")) {
316 tmp
= g_strdup_printf("Using Pidgin v%s with libpurple v%s.",
317 DISPLAY_VERSION
, purple_core_get_version());
318 } else if (!g_ascii_strcasecmp(args
[0], "plugins")) {
319 /* Show all the loaded plugins, including plugins marked internal.
320 * This is intentional, since third party protocols are often sources of bugs, and some
321 * plugin loaders can also be buggy.
323 GString
*str
= g_string_new("Loaded Plugins: ");
324 const GList
*plugins
= purple_plugins_get_loaded();
326 for (; plugins
; plugins
= plugins
->next
) {
327 PurplePluginInfo
*info
= purple_plugin_get_info(PURPLE_PLUGIN(plugins
->data
));
328 str
= g_string_append(str
, purple_plugin_info_get_name(info
));
331 str
= g_string_append(str
, ", ");
334 str
= g_string_append(str
, "(none)");
337 tmp
= g_string_free(str
, FALSE
);
338 } else if (!g_ascii_strcasecmp(args
[0], "unsafe")) {
339 if (purple_debug_is_unsafe()) {
340 purple_debug_set_unsafe(FALSE
);
341 purple_conversation_write_system_message(conv
,
342 _("Unsafe debugging is now disabled."),
343 PURPLE_MESSAGE_NO_LOG
);
345 purple_debug_set_unsafe(TRUE
);
346 purple_conversation_write_system_message(conv
,
347 _("Unsafe debugging is now enabled."),
348 PURPLE_MESSAGE_NO_LOG
);
351 return PURPLE_CMD_RET_OK
;
352 } else if (!g_ascii_strcasecmp(args
[0], "verbose")) {
353 if (purple_debug_is_verbose()) {
354 purple_debug_set_verbose(FALSE
);
355 purple_conversation_write_system_message(conv
,
356 _("Verbose debugging is now disabled."),
357 PURPLE_MESSAGE_NO_LOG
);
359 purple_debug_set_verbose(TRUE
);
360 purple_conversation_write_system_message(conv
,
361 _("Verbose debugging is now enabled."),
362 PURPLE_MESSAGE_NO_LOG
);
365 return PURPLE_CMD_RET_OK
;
367 purple_conversation_write_system_message(conv
,
368 _("Supported debug options are: plugins, version, unsafe, verbose"),
369 PURPLE_MESSAGE_NO_LOG
);
370 return PURPLE_CMD_RET_OK
;
373 markup
= g_markup_escape_text(tmp
, -1);
374 purple_conversation_send(conv
, markup
);
378 return PURPLE_CMD_RET_OK
;
381 static void clear_conversation_scrollback_cb(PurpleConversation
*conv
,
384 PidginConversation
*gtkconv
= PIDGIN_CONVERSATION(conv
);
386 if (PIDGIN_CONVERSATION(conv
)) {
387 gtkconv
->last_flags
= 0;
392 clear_command_cb(PurpleConversation
*conv
,
393 const char *cmd
, char **args
, char **error
, void *data
)
395 purple_conversation_clear_message_history(conv
);
396 return PURPLE_CMD_RET_OK
;
400 clearall_command_cb(PurpleConversation
*conv
,
401 const char *cmd
, char **args
, char **error
, void *data
)
404 for (l
= purple_conversations_get_all(); l
!= NULL
; l
= l
->next
)
405 purple_conversation_clear_message_history(PURPLE_CONVERSATION(l
->data
));
407 return PURPLE_CMD_RET_OK
;
411 help_command_cb(PurpleConversation
*conv
,
412 const char *cmd
, char **args
, char **error
, void *data
)
417 if (args
[0] != NULL
) {
418 s
= g_string_new("");
419 text
= purple_cmd_help(conv
, args
[0]);
422 for (l
= text
; l
; l
= l
->next
)
424 g_string_append_printf(s
, "%s\n", (char *)l
->data
);
426 g_string_append_printf(s
, "%s", (char *)l
->data
);
428 g_string_append(s
, _("No such command (in this context)."));
431 s
= g_string_new(_("Use \"/help <command>\" for help with a "
432 "specific command.<br/>The following commands are available "
433 "in this context:<br/>"));
435 text
= purple_cmd_list(conv
);
436 for (l
= text
; l
; l
= l
->next
)
438 g_string_append_printf(s
, "%s, ", (char *)l
->data
);
440 g_string_append_printf(s
, "%s.", (char *)l
->data
);
444 purple_conversation_write_system_message(conv
, s
->str
, PURPLE_MESSAGE_NO_LOG
);
445 g_string_free(s
, TRUE
);
447 return PURPLE_CMD_RET_OK
;
451 send_history_add(PidginConversation
*gtkconv
, const char *message
)
455 first
= g_list_first(gtkconv
->send_history
);
457 first
->data
= g_strdup(message
);
458 gtkconv
->send_history
= g_list_prepend(first
, NULL
);
462 check_for_and_do_command(PurpleConversation
*conv
)
464 PidginConversation
*gtkconv
;
465 GtkWidget
*view
= NULL
;
466 GtkTextBuffer
*buffer
= NULL
;
469 gboolean retval
= FALSE
;
471 gtkconv
= PIDGIN_CONVERSATION(conv
);
472 prefix
= pidgin_get_cmd_prefix();
474 view
= talkatu_editor_get_view(TALKATU_EDITOR(gtkconv
->editor
));
475 buffer
= gtk_text_view_get_buffer(GTK_TEXT_VIEW(view
));
477 cmd
= talkatu_buffer_get_plain_text(TALKATU_BUFFER(buffer
));
479 if (cmd
&& purple_str_has_prefix(cmd
, prefix
)) {
480 PurpleCmdStatus status
;
481 char *error
, *cmdline
, *markup
, *send_history
;
483 send_history
= talkatu_markup_get_html(buffer
, NULL
);
484 send_history_add(gtkconv
, send_history
);
485 g_free(send_history
);
487 cmdline
= cmd
+ strlen(prefix
);
489 if (purple_strequal(cmdline
, "xyzzy")) {
490 purple_conversation_write_system_message(conv
,
491 "Nothing happens", PURPLE_MESSAGE_NO_LOG
);
496 /* Docs are unclear on whether or not prefix should be removed from
497 * the markup so, ignoring for now. Notably if the markup is
498 * `<b>/foo arg1</b>` we now have to move the bold tag around?
500 markup
= talkatu_markup_get_html(buffer
, NULL
);
501 status
= purple_cmd_do_command(conv
, cmdline
, markup
, &error
);
505 case PURPLE_CMD_STATUS_OK
:
508 case PURPLE_CMD_STATUS_NOT_FOUND
:
510 PurpleProtocol
*protocol
= NULL
;
511 PurpleConnection
*gc
;
513 if ((gc
= purple_conversation_get_connection(conv
)))
514 protocol
= purple_connection_get_protocol(gc
);
516 if ((protocol
!= NULL
) && (purple_protocol_get_options(protocol
) & OPT_PROTO_SLASH_COMMANDS_NATIVE
)) {
519 /* If the first word in the entered text has a '/' in it, then the user
520 * probably didn't mean it as a command. So send the text as message. */
521 spaceslash
= cmdline
;
522 while (*spaceslash
&& *spaceslash
!= ' ' && *spaceslash
!= '/')
525 if (*spaceslash
!= '/') {
526 purple_conversation_write_system_message(conv
,
527 _("Unknown command."), PURPLE_MESSAGE_NO_LOG
);
533 case PURPLE_CMD_STATUS_WRONG_ARGS
:
534 purple_conversation_write_system_message(conv
,
535 _("Syntax Error: You typed the wrong "
536 "number of arguments to that command."),
537 PURPLE_MESSAGE_NO_LOG
);
540 case PURPLE_CMD_STATUS_FAILED
:
541 purple_conversation_write_system_message(conv
,
542 error
? error
: _("Your command failed for an unknown reason."),
543 PURPLE_MESSAGE_NO_LOG
);
547 case PURPLE_CMD_STATUS_WRONG_TYPE
:
548 if(PURPLE_IS_IM_CONVERSATION(conv
))
549 purple_conversation_write_system_message(conv
,
550 _("That command only works in chats, not IMs."),
551 PURPLE_MESSAGE_NO_LOG
);
553 purple_conversation_write_system_message(conv
,
554 _("That command only works in IMs, not chats."),
555 PURPLE_MESSAGE_NO_LOG
);
558 case PURPLE_CMD_STATUS_WRONG_PROTOCOL
:
559 purple_conversation_write_system_message(conv
,
560 _("That command doesn't work on this protocol."),
561 PURPLE_MESSAGE_NO_LOG
);
573 send_cb(GtkWidget
*widget
, PidginConversation
*gtkconv
)
575 PurpleConversation
*conv
= gtkconv
->active_conv
;
576 PurpleAccount
*account
;
577 PurpleMessageFlags flags
= 0;
578 GtkTextBuffer
*buffer
= NULL
;
581 account
= purple_conversation_get_account(conv
);
583 buffer
= talkatu_editor_get_buffer(TALKATU_EDITOR(gtkconv
->editor
));
585 if (check_for_and_do_command(conv
)) {
586 talkatu_buffer_clear(TALKATU_BUFFER(buffer
));
590 if (PURPLE_IS_CHAT_CONVERSATION(conv
) &&
591 purple_chat_conversation_has_left(PURPLE_CHAT_CONVERSATION(conv
))) {
595 if (!purple_account_is_connected(account
)) {
599 content
= talkatu_markup_get_html(buffer
, NULL
);
600 if (purple_strequal(content
, "")) {
607 /* XXX: is there a better way to tell if the message has images? */
608 // if (strstr(buf, "<img ") != NULL)
609 // flags |= PURPLE_MESSAGE_IMAGES;
611 purple_conversation_send_with_flags(conv
, content
, flags
);
615 talkatu_buffer_clear(TALKATU_BUFFER(buffer
));
616 gtkconv_set_unseen(gtkconv
, PIDGIN_UNSEEN_NONE
);
620 add_remove_cb(GtkWidget
*widget
, PidginConversation
*gtkconv
)
622 PurpleAccount
*account
;
624 PurpleConversation
*conv
= gtkconv
->active_conv
;
626 account
= purple_conversation_get_account(conv
);
627 name
= purple_conversation_get_name(conv
);
629 if (PURPLE_IS_IM_CONVERSATION(conv
)) {
632 b
= purple_blist_find_buddy(account
, name
);
634 pidgin_dialogs_remove_buddy(b
);
635 else if (account
!= NULL
&& purple_account_is_connected(account
))
636 purple_blist_request_add_buddy(account
, (char *)name
, NULL
, NULL
);
637 } else if (PURPLE_IS_CHAT_CONVERSATION(conv
)) {
640 c
= purple_blist_find_chat(account
, name
);
642 pidgin_dialogs_remove_chat(c
);
643 else if (account
!= NULL
&& purple_account_is_connected(account
))
644 purple_blist_request_add_chat(account
, NULL
, NULL
, name
);
648 static void chat_do_info(PidginConversation
*gtkconv
, const char *who
)
650 PurpleChatConversation
*chat
= PURPLE_CHAT_CONVERSATION(gtkconv
->active_conv
);
651 PurpleConnection
*gc
;
653 if ((gc
= purple_conversation_get_connection(gtkconv
->active_conv
))) {
654 pidgin_retrieve_user_info_in_chat(gc
, who
, purple_chat_conversation_get_id(chat
));
660 info_cb(GtkWidget
*widget
, PidginConversation
*gtkconv
)
662 PurpleConversation
*conv
= gtkconv
->active_conv
;
664 if (PURPLE_IS_IM_CONVERSATION(conv
)) {
665 pidgin_retrieve_user_info(purple_conversation_get_connection(conv
),
666 purple_conversation_get_name(conv
));
667 } else if (PURPLE_IS_CHAT_CONVERSATION(conv
)) {
668 /* Get info of the person currently selected in the GtkTreeView */
669 PidginChatPane
*gtkchat
;
672 GtkTreeSelection
*sel
;
675 gtkchat
= gtkconv
->u
.chat
;
677 model
= gtk_tree_view_get_model(GTK_TREE_VIEW(gtkchat
->list
));
678 sel
= gtk_tree_view_get_selection(GTK_TREE_VIEW(gtkchat
->list
));
680 if (gtk_tree_selection_get_selected(sel
, NULL
, &iter
))
681 gtk_tree_model_get(GTK_TREE_MODEL(model
), &iter
, CHAT_USERS_NAME_COLUMN
, &name
, -1);
685 chat_do_info(gtkconv
, name
);
691 block_cb(GtkWidget
*widget
, PidginConversation
*gtkconv
)
693 PurpleConversation
*conv
= gtkconv
->active_conv
;
694 PurpleAccount
*account
;
696 account
= purple_conversation_get_account(conv
);
698 if (account
!= NULL
&& purple_account_is_connected(account
))
699 pidgin_request_add_block(account
, purple_conversation_get_name(conv
));
703 unblock_cb(GtkWidget
*widget
, PidginConversation
*gtkconv
)
705 PurpleConversation
*conv
= gtkconv
->active_conv
;
706 PurpleAccount
*account
;
708 account
= purple_conversation_get_account(conv
);
710 if (account
!= NULL
&& purple_account_is_connected(account
))
711 pidgin_request_add_permit(account
, purple_conversation_get_name(conv
));
715 do_invite(GtkWidget
*w
, int resp
, gpointer data
)
717 PidginInviteDialog
*dialog
= PIDGIN_INVITE_DIALOG(w
);
718 PurpleChatConversation
*chat
= pidgin_invite_dialog_get_conversation(dialog
);
719 const gchar
*contact
, *message
;
721 if (resp
== GTK_RESPONSE_ACCEPT
) {
722 contact
= pidgin_invite_dialog_get_contact(dialog
);
723 if (!g_ascii_strcasecmp(contact
, ""))
726 message
= pidgin_invite_dialog_get_message(dialog
);
728 purple_serv_chat_invite(purple_conversation_get_connection(PURPLE_CONVERSATION(chat
)),
729 purple_chat_conversation_get_id(chat
),
733 g_clear_pointer(&invite_dialog
, gtk_widget_destroy
);
737 invite_cb(GtkWidget
*widget
, PidginConversation
*gtkconv
)
739 PurpleChatConversation
*chat
= PURPLE_CHAT_CONVERSATION(gtkconv
->active_conv
);
741 if (invite_dialog
== NULL
) {
742 invite_dialog
= pidgin_invite_dialog_new(chat
);
744 /* Connect the signals. */
745 g_signal_connect(G_OBJECT(invite_dialog
), "response",
746 G_CALLBACK(do_invite
), NULL
);
749 gtk_widget_show_all(invite_dialog
);
753 menu_new_conv_cb(GtkAction
*action
, gpointer data
)
759 menu_join_chat_cb(GtkAction
*action
, gpointer data
)
761 pidgin_blist_joinchat_show();
765 savelog_writefile_cb(void *user_data
, const char *filename
)
767 PurpleConversation
*conv
= (PurpleConversation
*)user_data
;
768 PidginConversation
*gtkconv
= PIDGIN_CONVERSATION(conv
);
769 GtkTextBuffer
*buffer
= NULL
;
774 if ((fp
= g_fopen(filename
, "w+")) == NULL
) {
775 purple_notify_error(PIDGIN_CONVERSATION(conv
), NULL
,
776 _("Unable to open file."), NULL
,
777 purple_request_cpar_from_conversation(conv
));
781 name
= purple_conversation_get_name(conv
);
783 fprintf(fp
, "<html>\n");
784 fprintf(fp
, "<head>\n");
785 fprintf(fp
, "<meta http-equiv=\"content-type\" content=\"text/html; charset=UTF-8\">\n");
786 fprintf(fp
, "<title>%s</title>\n", name
);
787 fprintf(fp
, "</head>\n");
789 fprintf(fp
, "<body>\n");
790 fprintf(fp
, _("<h1>Conversation with %s</h1>\n"), name
);
791 buffer
= gtk_text_view_get_buffer(GTK_TEXT_VIEW(gtkconv
->history
));
792 text
= talkatu_markup_get_html(buffer
, NULL
);
793 fprintf(fp
, "%s", text
);
795 fprintf(fp
, "\n</body>\n");
797 fprintf(fp
, "</html>\n");
802 * It would be kinda cool if this gave the option of saving a
803 * plaintext v. HTML file.
806 menu_save_as_cb(GtkAction
*action
, gpointer data
)
808 PidginConvWindow
*win
= data
;
809 PurpleConversation
*conv
= pidgin_conv_window_get_active_conversation(win
);
810 PurpleAccount
*account
= purple_conversation_get_account(conv
);
811 PurpleBuddy
*buddy
= purple_blist_find_buddy(account
, purple_conversation_get_name(conv
));
817 name
= purple_buddy_get_contact_alias(buddy
);
819 name
= purple_normalize(account
, purple_conversation_get_name(conv
));
821 buf
= g_strdup_printf("%s.html", name
);
822 for (c
= buf
; *c
; c
++)
824 if (*c
== '/' || *c
== '\\')
827 purple_request_file(PIDGIN_CONVERSATION(conv
), _("Save Conversation"),
828 buf
, TRUE
, G_CALLBACK(savelog_writefile_cb
), NULL
,
829 purple_request_cpar_from_conversation(conv
), conv
);
835 menu_view_log_cb(GtkAction
*action
, gpointer data
)
837 PidginConvWindow
*win
= data
;
838 PurpleConversation
*conv
;
840 PidginBuddyList
*gtkblist
;
842 PurpleAccount
*account
;
846 conv
= pidgin_conv_window_get_active_conversation(win
);
848 if (PURPLE_IS_IM_CONVERSATION(conv
))
849 type
= PURPLE_LOG_IM
;
850 else if (PURPLE_IS_CHAT_CONVERSATION(conv
))
851 type
= PURPLE_LOG_CHAT
;
855 gtkblist
= pidgin_blist_get_default_gtk_blist();
857 pidgin_set_cursor(gtkblist
->window
, GDK_WATCH
);
858 pidgin_set_cursor(win
->window
, GDK_WATCH
);
860 name
= purple_conversation_get_name(conv
);
861 account
= purple_conversation_get_account(conv
);
863 buddies
= purple_blist_find_buddies(account
, name
);
864 for (cur
= buddies
; cur
!= NULL
; cur
= cur
->next
)
866 PurpleBlistNode
*node
= cur
->data
;
867 if ((node
!= NULL
) && ((node
->prev
!= NULL
) || (node
->next
!= NULL
)))
869 pidgin_log_show_contact((PurpleContact
*)node
->parent
);
870 g_slist_free(buddies
);
871 pidgin_clear_cursor(gtkblist
->window
);
872 pidgin_clear_cursor(win
->window
);
876 g_slist_free(buddies
);
878 pidgin_log_show(type
, name
, account
);
880 pidgin_clear_cursor(gtkblist
->window
);
881 pidgin_clear_cursor(win
->window
);
885 menu_clear_cb(GtkAction
*action
, gpointer data
)
887 PidginConvWindow
*win
= data
;
888 PurpleConversation
*conv
;
890 conv
= pidgin_conv_window_get_active_conversation(win
);
891 purple_conversation_clear_message_history(conv
);
895 menu_find_cb(GtkAction
*action
, gpointer data
)
897 PidginConvWindow
*gtkwin
= data
;
898 PidginConversation
*gtkconv
= pidgin_conv_window_get_active_gtkconv(gtkwin
);
899 gtk_widget_show_all(gtkconv
->quickfind_container
);
900 gtk_widget_grab_focus(gtkconv
->quickfind_entry
);
905 menu_initiate_media_call_cb(GtkAction
*action
, gpointer data
)
907 PidginConvWindow
*win
= (PidginConvWindow
*)data
;
908 PurpleConversation
*conv
= pidgin_conv_window_get_active_conversation(win
);
909 PurpleAccount
*account
= purple_conversation_get_account(conv
);
911 purple_protocol_initiate_media(account
,
912 purple_conversation_get_name(conv
),
913 action
== win
->menu
->audio_call
? PURPLE_MEDIA_AUDIO
:
914 action
== win
->menu
->video_call
? PURPLE_MEDIA_VIDEO
:
915 action
== win
->menu
->audio_video_call
? PURPLE_MEDIA_AUDIO
|
916 PURPLE_MEDIA_VIDEO
: PURPLE_MEDIA_NONE
);
921 menu_send_file_cb(GtkAction
*action
, gpointer data
)
923 PidginConvWindow
*win
= data
;
924 PurpleConversation
*conv
= pidgin_conv_window_get_active_conversation(win
);
926 if (PURPLE_IS_IM_CONVERSATION(conv
)) {
927 purple_serv_send_file(purple_conversation_get_connection(conv
), purple_conversation_get_name(conv
), NULL
);
933 menu_get_attention_cb(GObject
*obj
, gpointer data
)
935 PidginConvWindow
*win
= data
;
936 PurpleConversation
*conv
= pidgin_conv_window_get_active_conversation(win
);
938 if (PURPLE_IS_IM_CONVERSATION(conv
)) {
940 if ((GtkAction
*)obj
== win
->menu
->get_attention
)
943 index
= GPOINTER_TO_INT(g_object_get_data(G_OBJECT(obj
), "index"));
944 purple_protocol_send_attention(purple_conversation_get_connection(conv
),
945 purple_conversation_get_name(conv
), index
);
950 menu_add_pounce_cb(GtkAction
*action
, gpointer data
)
952 PidginConvWindow
*win
= data
;
953 PurpleConversation
*conv
;
955 conv
= pidgin_conv_window_get_active_gtkconv(win
)->active_conv
;
957 pidgin_pounce_editor_show(purple_conversation_get_account(conv
),
958 purple_conversation_get_name(conv
), NULL
);
962 menu_alias_cb(GtkAction
*action
, gpointer data
)
964 PidginConvWindow
*win
= data
;
965 PurpleConversation
*conv
;
966 PurpleAccount
*account
;
969 conv
= pidgin_conv_window_get_active_conversation(win
);
970 account
= purple_conversation_get_account(conv
);
971 name
= purple_conversation_get_name(conv
);
973 if (PURPLE_IS_IM_CONVERSATION(conv
)) {
976 b
= purple_blist_find_buddy(account
, name
);
978 pidgin_dialogs_alias_buddy(b
);
979 } else if (PURPLE_IS_CHAT_CONVERSATION(conv
)) {
982 c
= purple_blist_find_chat(account
, name
);
984 pidgin_dialogs_alias_chat(c
);
989 menu_get_info_cb(GtkAction
*action
, gpointer data
)
991 PidginConvWindow
*win
= data
;
992 PurpleConversation
*conv
;
994 conv
= pidgin_conv_window_get_active_conversation(win
);
996 info_cb(NULL
, PIDGIN_CONVERSATION(conv
));
1000 menu_invite_cb(GtkAction
*action
, gpointer data
)
1002 PidginConvWindow
*win
= data
;
1003 PurpleConversation
*conv
;
1005 conv
= pidgin_conv_window_get_active_conversation(win
);
1007 invite_cb(NULL
, PIDGIN_CONVERSATION(conv
));
1011 menu_block_cb(GtkAction
*action
, gpointer data
)
1013 PidginConvWindow
*win
= data
;
1014 PurpleConversation
*conv
;
1016 conv
= pidgin_conv_window_get_active_conversation(win
);
1018 block_cb(NULL
, PIDGIN_CONVERSATION(conv
));
1022 menu_unblock_cb(GtkAction
*action
, gpointer data
)
1024 PidginConvWindow
*win
= data
;
1025 PurpleConversation
*conv
;
1027 conv
= pidgin_conv_window_get_active_conversation(win
);
1029 unblock_cb(NULL
, PIDGIN_CONVERSATION(conv
));
1033 menu_add_remove_cb(GtkAction
*action
, gpointer data
)
1035 PidginConvWindow
*win
= data
;
1036 PurpleConversation
*conv
;
1038 conv
= pidgin_conv_window_get_active_conversation(win
);
1040 add_remove_cb(NULL
, PIDGIN_CONVERSATION(conv
));
1044 close_already(gpointer data
)
1046 g_object_unref(data
);
1051 hide_conv(PidginConversation
*gtkconv
, gboolean closetimer
)
1055 purple_signal_emit(pidgin_conversations_get_handle(),
1056 "conversation-hiding", gtkconv
);
1058 for (list
= g_list_copy(gtkconv
->convs
); list
; list
= g_list_delete_link(list
, list
)) {
1059 PurpleConversation
*conv
= list
->data
;
1061 guint timer
= GPOINTER_TO_INT(g_object_get_data(G_OBJECT(conv
), "close-timer"));
1063 g_source_remove(timer
);
1064 timer
= g_timeout_add_seconds(CLOSE_CONV_TIMEOUT_SECS
, close_already
, conv
);
1065 g_object_set_data(G_OBJECT(conv
), "close-timer", GINT_TO_POINTER(timer
));
1067 pidgin_conv_window_remove_gtkconv(gtkconv
->win
, gtkconv
);
1068 pidgin_conv_window_add_gtkconv(hidden_convwin
, gtkconv
);
1073 menu_close_conv_cb(GtkAction
*action
, gpointer data
)
1075 PidginConvWindow
*win
= data
;
1077 close_conv_cb(NULL
, PIDGIN_CONVERSATION(pidgin_conv_window_get_active_conversation(win
)));
1081 menu_logging_cb(GtkAction
*action
, gpointer data
)
1083 PidginConvWindow
*win
= data
;
1084 PurpleConversation
*conv
;
1086 PurpleBlistNode
*node
;
1088 conv
= pidgin_conv_window_get_active_conversation(win
);
1093 logging
= gtk_toggle_action_get_active(GTK_TOGGLE_ACTION(action
));
1095 if (logging
== purple_conversation_is_logging(conv
))
1098 node
= get_conversation_blist_node(conv
);
1102 /* Enable logging first so the message below can be logged. */
1103 purple_conversation_set_logging(conv
, TRUE
);
1105 purple_conversation_write_system_message(conv
,
1106 _("Logging started. Future messages in this conversation will be logged."), 0);
1110 purple_conversation_write_system_message(conv
,
1111 _("Logging stopped. Future messages in this conversation will not be logged."), 0);
1113 /* Disable the logging second, so that the above message can be logged. */
1114 purple_conversation_set_logging(conv
, FALSE
);
1117 /* Save the setting IFF it's different than the pref. */
1118 if (PURPLE_IS_IM_CONVERSATION(conv
)) {
1119 if (logging
== purple_prefs_get_bool("/purple/logging/log_ims"))
1120 purple_blist_node_remove_setting(node
, "enable-logging");
1122 purple_blist_node_set_bool(node
, "enable-logging", logging
);
1123 } else if (PURPLE_IS_CHAT_CONVERSATION(conv
)) {
1124 if (logging
== purple_prefs_get_bool("/purple/logging/log_chats"))
1125 purple_blist_node_remove_setting(node
, "enable-logging");
1127 purple_blist_node_set_bool(node
, "enable-logging", logging
);
1132 menu_toolbar_cb(GtkAction
*action
, gpointer data
)
1134 purple_prefs_set_bool(PIDGIN_PREFS_ROOT
"/conversations/show_formatting_toolbar",
1135 gtk_toggle_action_get_active(GTK_TOGGLE_ACTION(action
)));
1139 menu_sounds_cb(GtkAction
*action
, gpointer data
)
1141 PidginConvWindow
*win
= data
;
1142 PurpleConversation
*conv
;
1143 PidginConversation
*gtkconv
;
1144 PurpleBlistNode
*node
;
1146 conv
= pidgin_conv_window_get_active_conversation(win
);
1151 gtkconv
= PIDGIN_CONVERSATION(conv
);
1153 gtkconv
->make_sound
=
1154 gtk_toggle_action_get_active(GTK_TOGGLE_ACTION(action
));
1155 node
= get_conversation_blist_node(conv
);
1157 purple_blist_node_set_bool(node
, "gtk-mute-sound", !gtkconv
->make_sound
);
1161 chat_do_im(PidginConversation
*gtkconv
, const char *who
)
1163 PurpleConversation
*conv
= gtkconv
->active_conv
;
1164 PurpleAccount
*account
;
1165 PurpleConnection
*gc
;
1166 PurpleProtocol
*protocol
= NULL
;
1167 gchar
*real_who
= NULL
;
1169 account
= purple_conversation_get_account(conv
);
1170 g_return_if_fail(account
!= NULL
);
1172 gc
= purple_account_get_connection(account
);
1173 g_return_if_fail(gc
!= NULL
);
1175 protocol
= purple_connection_get_protocol(gc
);
1178 real_who
= purple_protocol_chat_iface_get_user_real_name(protocol
, gc
,
1179 purple_chat_conversation_get_id(PURPLE_CHAT_CONVERSATION(conv
)), who
);
1181 if(!who
&& !real_who
)
1184 pidgin_dialogs_im_with_user(account
, real_who
? real_who
: who
);
1189 static void pidgin_conv_chat_update_user(PurpleChatUser
*chatuser
);
1192 ignore_cb(GtkWidget
*w
, PidginConversation
*gtkconv
)
1194 PurpleChatConversation
*chat
= PURPLE_CHAT_CONVERSATION(gtkconv
->active_conv
);
1197 name
= g_object_get_data(G_OBJECT(w
), "user_data");
1202 if (purple_chat_conversation_is_ignored_user(chat
, name
))
1203 purple_chat_conversation_unignore(chat
, name
);
1205 purple_chat_conversation_ignore(chat
, name
);
1207 pidgin_conv_chat_update_user(purple_chat_conversation_find_user(chat
, name
));
1211 menu_chat_im_cb(GtkWidget
*w
, PidginConversation
*gtkconv
)
1213 const char *who
= g_object_get_data(G_OBJECT(w
), "user_data");
1215 chat_do_im(gtkconv
, who
);
1219 menu_chat_send_file_cb(GtkWidget
*w
, PidginConversation
*gtkconv
)
1221 PurpleProtocol
*protocol
;
1222 PurpleConversation
*conv
= gtkconv
->active_conv
;
1223 const char *who
= g_object_get_data(G_OBJECT(w
), "user_data");
1224 PurpleConnection
*gc
= purple_conversation_get_connection(conv
);
1225 gchar
*real_who
= NULL
;
1227 g_return_if_fail(gc
!= NULL
);
1229 protocol
= purple_connection_get_protocol(gc
);
1232 real_who
= purple_protocol_chat_iface_get_user_real_name(protocol
, gc
,
1233 purple_chat_conversation_get_id(PURPLE_CHAT_CONVERSATION(conv
)), who
);
1235 purple_serv_send_file(gc
, real_who
? real_who
: who
, NULL
);
1240 menu_chat_info_cb(GtkWidget
*w
, PidginConversation
*gtkconv
)
1244 who
= g_object_get_data(G_OBJECT(w
), "user_data");
1246 chat_do_info(gtkconv
, who
);
1250 menu_chat_add_remove_cb(GtkWidget
*w
, PidginConversation
*gtkconv
)
1252 PurpleConversation
*conv
= gtkconv
->active_conv
;
1253 PurpleAccount
*account
;
1257 account
= purple_conversation_get_account(conv
);
1258 name
= g_object_get_data(G_OBJECT(w
), "user_data");
1259 b
= purple_blist_find_buddy(account
, name
);
1262 pidgin_dialogs_remove_buddy(b
);
1263 else if (account
!= NULL
&& purple_account_is_connected(account
))
1264 purple_blist_request_add_buddy(account
, name
, NULL
, NULL
);
1266 gtk_widget_grab_focus(PIDGIN_CONVERSATION(conv
)->entry
);
1270 create_chat_menu(PurpleChatConversation
*chat
, const char *who
, PurpleConnection
*gc
)
1272 static GtkWidget
*menu
= NULL
;
1273 PurpleProtocol
*protocol
= NULL
;
1274 PurpleConversation
*conv
= PURPLE_CONVERSATION(chat
);
1275 PurpleAccount
*account
= purple_conversation_get_account(conv
);
1276 gboolean is_me
= FALSE
;
1278 PurpleBuddy
*buddy
= NULL
;
1281 protocol
= purple_connection_get_protocol(gc
);
1284 * If a menu already exists, destroy it before creating a new one,
1285 * thus freeing-up the memory it occupied.
1288 gtk_widget_destroy(menu
);
1290 if (purple_strequal(purple_chat_conversation_get_nick(chat
), purple_normalize(account
, who
)))
1293 menu
= gtk_menu_new();
1296 button
= pidgin_new_menu_item(menu
, _("IM"),
1297 PIDGIN_STOCK_TOOLBAR_MESSAGE_NEW
,
1298 G_CALLBACK(menu_chat_im_cb
),
1299 PIDGIN_CONVERSATION(conv
));
1302 gtk_widget_set_sensitive(button
, FALSE
);
1304 g_object_set_data_full(G_OBJECT(button
), "user_data", g_strdup(who
), g_free
);
1307 if (protocol
&& PURPLE_IS_PROTOCOL_XFER(protocol
))
1309 gboolean can_receive_file
= TRUE
;
1311 button
= pidgin_new_menu_item(menu
, _("Send File"),
1312 PIDGIN_STOCK_TOOLBAR_SEND_FILE
, G_CALLBACK(menu_chat_send_file_cb
),
1313 PIDGIN_CONVERSATION(conv
));
1315 if (gc
== NULL
|| protocol
== NULL
)
1316 can_receive_file
= FALSE
;
1318 gchar
*real_who
= NULL
;
1319 real_who
= purple_protocol_chat_iface_get_user_real_name(protocol
, gc
,
1320 purple_chat_conversation_get_id(chat
), who
);
1322 if (!purple_protocol_xfer_can_receive(
1323 PURPLE_PROTOCOL_XFER(protocol
),
1324 gc
, real_who
? real_who
: who
)) {
1325 can_receive_file
= FALSE
;
1331 if (!can_receive_file
)
1332 gtk_widget_set_sensitive(button
, FALSE
);
1334 g_object_set_data_full(G_OBJECT(button
), "user_data", g_strdup(who
), g_free
);
1338 if (purple_chat_conversation_is_ignored_user(chat
, who
))
1339 button
= pidgin_new_menu_item(menu
, _("Un-Ignore"),
1340 PIDGIN_STOCK_IGNORE
, G_CALLBACK(ignore_cb
),
1341 PIDGIN_CONVERSATION(conv
));
1343 button
= pidgin_new_menu_item(menu
, _("Ignore"),
1344 PIDGIN_STOCK_IGNORE
, G_CALLBACK(ignore_cb
),
1345 PIDGIN_CONVERSATION(conv
));
1348 gtk_widget_set_sensitive(button
, FALSE
);
1350 g_object_set_data_full(G_OBJECT(button
), "user_data", g_strdup(who
), g_free
);
1353 if (protocol
&& PURPLE_PROTOCOL_IMPLEMENTS(protocol
, SERVER
, get_info
)) {
1354 button
= pidgin_new_menu_item(menu
, _("Info"),
1355 PIDGIN_STOCK_TOOLBAR_USER_INFO
,
1356 G_CALLBACK(menu_chat_info_cb
),
1357 PIDGIN_CONVERSATION(conv
));
1360 gtk_widget_set_sensitive(button
, FALSE
);
1362 g_object_set_data_full(G_OBJECT(button
), "user_data", g_strdup(who
), g_free
);
1365 if (!is_me
&& protocol
&& !(purple_protocol_get_options(protocol
) & OPT_PROTO_UNIQUE_CHATNAME
) && PURPLE_PROTOCOL_IMPLEMENTS(protocol
, SERVER
, add_buddy
)) {
1366 if ((buddy
= purple_blist_find_buddy(account
, who
)) != NULL
)
1367 button
= pidgin_new_menu_item(menu
, _("Remove"),
1369 G_CALLBACK(menu_chat_add_remove_cb
),
1370 PIDGIN_CONVERSATION(conv
));
1372 button
= pidgin_new_menu_item(menu
, _("Add"),
1374 G_CALLBACK(menu_chat_add_remove_cb
),
1375 PIDGIN_CONVERSATION(conv
));
1378 gtk_widget_set_sensitive(button
, FALSE
);
1380 g_object_set_data_full(G_OBJECT(button
), "user_data", g_strdup(who
), g_free
);
1385 if (purple_account_is_connected(account
))
1386 pidgin_append_blist_node_proto_menu(menu
, purple_account_get_connection(account
),
1387 (PurpleBlistNode
*)buddy
);
1388 pidgin_append_blist_node_extended_menu(menu
, (PurpleBlistNode
*)buddy
);
1389 gtk_widget_show_all(menu
);
1397 gtkconv_chat_popup_menu_cb(GtkWidget
*widget
, PidginConversation
*gtkconv
)
1399 PurpleConversation
*conv
= gtkconv
->active_conv
;
1400 PidginChatPane
*gtkchat
;
1401 PurpleConnection
*gc
;
1402 PurpleAccount
*account
;
1403 GtkTreeSelection
*sel
;
1405 GtkTreeModel
*model
;
1409 gtkconv
= PIDGIN_CONVERSATION(conv
);
1410 gtkchat
= gtkconv
->u
.chat
;
1411 account
= purple_conversation_get_account(conv
);
1412 gc
= purple_account_get_connection(account
);
1414 model
= gtk_tree_view_get_model(GTK_TREE_VIEW(gtkchat
->list
));
1416 sel
= gtk_tree_view_get_selection(GTK_TREE_VIEW(gtkchat
->list
));
1417 if(!gtk_tree_selection_get_selected(sel
, NULL
, &iter
))
1420 gtk_tree_model_get(GTK_TREE_MODEL(model
), &iter
, CHAT_USERS_NAME_COLUMN
, &who
, -1);
1421 menu
= create_chat_menu (PURPLE_CHAT_CONVERSATION(conv
), who
, gc
);
1422 pidgin_menu_popup_at_treeview_selection(menu
, widget
);
1430 right_click_chat_cb(GtkWidget
*widget
, GdkEventButton
*event
,
1431 PidginConversation
*gtkconv
)
1433 PurpleConversation
*conv
= gtkconv
->active_conv
;
1434 PidginChatPane
*gtkchat
;
1435 PurpleConnection
*gc
;
1436 PurpleAccount
*account
;
1439 GtkTreeModel
*model
;
1440 GtkTreeViewColumn
*column
;
1444 gtkchat
= gtkconv
->u
.chat
;
1445 account
= purple_conversation_get_account(conv
);
1446 gc
= purple_account_get_connection(account
);
1448 model
= gtk_tree_view_get_model(GTK_TREE_VIEW(gtkchat
->list
));
1450 gtk_tree_view_get_path_at_pos(GTK_TREE_VIEW(gtkchat
->list
),
1451 event
->x
, event
->y
, &path
, &column
, &x
, &y
);
1456 gtk_tree_selection_select_path(GTK_TREE_SELECTION(
1457 gtk_tree_view_get_selection(GTK_TREE_VIEW(gtkchat
->list
))), path
);
1458 gtk_tree_view_set_cursor(GTK_TREE_VIEW(gtkchat
->list
),
1460 gtk_widget_grab_focus(GTK_WIDGET(gtkchat
->list
));
1462 gtk_tree_model_get_iter(GTK_TREE_MODEL(model
), &iter
, path
);
1463 gtk_tree_model_get(GTK_TREE_MODEL(model
), &iter
, CHAT_USERS_NAME_COLUMN
, &who
, -1);
1465 /* emit chat-nick-clicked signal */
1466 if (event
->type
== GDK_BUTTON_PRESS
) {
1467 gint plugin_return
= GPOINTER_TO_INT(purple_signal_emit_return_1(
1468 pidgin_conversations_get_handle(), "chat-nick-clicked",
1469 conv
, who
, event
->button
));
1474 if (event
->button
== GDK_BUTTON_PRIMARY
&& event
->type
== GDK_2BUTTON_PRESS
) {
1475 chat_do_im(gtkconv
, who
);
1476 } else if (gdk_event_triggers_context_menu((GdkEvent
*)event
)) {
1477 GtkWidget
*menu
= create_chat_menu (PURPLE_CHAT_CONVERSATION(conv
), who
, gc
);
1478 gtk_menu_popup_at_pointer(GTK_MENU(menu
), (GdkEvent
*)event
);
1483 gtk_tree_path_free(path
);
1489 activate_list_cb(GtkTreeView
*list
, GtkTreePath
*path
, GtkTreeViewColumn
*column
, PidginConversation
*gtkconv
)
1492 GtkTreeModel
*model
;
1495 model
= gtk_tree_view_get_model(GTK_TREE_VIEW(list
));
1497 gtk_tree_model_get_iter(GTK_TREE_MODEL(model
), &iter
, path
);
1498 gtk_tree_model_get(GTK_TREE_MODEL(model
), &iter
, CHAT_USERS_NAME_COLUMN
, &who
, -1);
1499 chat_do_im(gtkconv
, who
);
1505 move_to_next_unread_tab(PidginConversation
*gtkconv
, gboolean forward
)
1507 PidginConversation
*next_gtkconv
= NULL
, *most_active
= NULL
;
1508 PidginUnseenState unseen_state
= PIDGIN_UNSEEN_NONE
;
1509 PidginConvWindow
*win
;
1510 int initial
, i
, total
, diff
;
1513 initial
= gtk_notebook_page_num(GTK_NOTEBOOK(win
->notebook
),
1515 total
= pidgin_conv_window_get_gtkconv_count(win
);
1516 /* By adding total here, the moduli calculated later will always have two
1517 * positive arguments. x % y where x < 0 is not guaranteed to return a
1520 diff
= (forward
? 1 : -1) + total
;
1522 for (i
= (initial
+ diff
) % total
; i
!= initial
; i
= (i
+ diff
) % total
) {
1523 next_gtkconv
= pidgin_conv_window_get_gtkconv_at_index(win
, i
);
1524 if (next_gtkconv
->unseen_state
> unseen_state
) {
1525 most_active
= next_gtkconv
;
1526 unseen_state
= most_active
->unseen_state
;
1527 if(PIDGIN_UNSEEN_NICK
== unseen_state
) /* highest possible state */
1532 if (most_active
== NULL
) { /* no new messages */
1533 i
= (i
+ diff
) % total
;
1534 most_active
= pidgin_conv_window_get_gtkconv_at_index(win
, i
);
1537 if (most_active
!= NULL
&& most_active
!= gtkconv
)
1538 pidgin_conv_window_switch_gtkconv(win
, most_active
);
1542 gtkconv_cycle_focus(PidginConversation
*gtkconv
, GtkDirectionType dir
)
1544 PurpleConversation
*conv
= gtkconv
->active_conv
;
1545 gboolean chat
= PURPLE_IS_CHAT_CONVERSATION(conv
);
1546 GtkWidget
*next
= NULL
;
1551 {gtkconv
->entry
, gtkconv
->history
},
1552 {gtkconv
->history
, chat
? gtkconv
->u
.chat
->list
: gtkconv
->entry
},
1553 {chat
? gtkconv
->u
.chat
->list
: NULL
, gtkconv
->entry
},
1557 for (ptr
= transitions
; !next
&& ptr
->from
; ptr
++) {
1558 GtkWidget
*from
, *to
;
1559 if (dir
== GTK_DIR_TAB_FORWARD
) {
1566 if (gtk_widget_is_focus(from
))
1571 gtk_widget_grab_focus(next
);
1576 update_typing_inserting(PidginConversation
*gtkconv
)
1578 GtkTextBuffer
*buffer
= NULL
;
1579 gboolean is_empty
= FALSE
;
1581 g_return_if_fail(gtkconv
!= NULL
);
1583 buffer
= talkatu_editor_get_buffer(TALKATU_EDITOR(gtkconv
->editor
));
1584 is_empty
= talkatu_buffer_get_is_empty(TALKATU_BUFFER(buffer
));
1586 got_typing_keypress(gtkconv
, is_empty
);
1590 update_typing_deleting_cb(PidginConversation
*gtkconv
)
1592 PurpleIMConversation
*im
= PURPLE_IM_CONVERSATION(gtkconv
->active_conv
);
1593 GtkTextBuffer
*buffer
= NULL
;
1595 buffer
= talkatu_editor_get_buffer(TALKATU_EDITOR(gtkconv
->editor
));
1597 if (!talkatu_buffer_get_is_empty(buffer
)) {
1598 /* We deleted all the text, so turn off typing. */
1599 purple_im_conversation_stop_send_typed_timeout(im
);
1601 purple_serv_send_typing(purple_conversation_get_connection(gtkconv
->active_conv
),
1602 purple_conversation_get_name(gtkconv
->active_conv
),
1603 PURPLE_IM_NOT_TYPING
);
1606 /* We're deleting, but not all of it, so it counts as typing. */
1607 got_typing_keypress(gtkconv
, FALSE
);
1614 update_typing_deleting(PidginConversation
*gtkconv
)
1616 GtkTextBuffer
*buffer
= NULL
;
1618 g_return_if_fail(gtkconv
!= NULL
);
1620 buffer
= talkatu_editor_get_buffer(TALKATU_EDITOR(gtkconv
->editor
));
1622 if (!talkatu_buffer_get_is_empty(TALKATU_BUFFER(buffer
))) {
1623 g_timeout_add(0, (GSourceFunc
)update_typing_deleting_cb
, gtkconv
);
1628 conv_keypress_common(PidginConversation
*gtkconv
, GdkEventKey
*event
)
1630 PidginConvWindow
*win
;
1634 curconv
= gtk_notebook_get_current_page(GTK_NOTEBOOK(win
->notebook
));
1636 /* clear any tooltips */
1637 pidgin_tooltip_destroy();
1639 /* If CTRL was held down... */
1640 if (event
->state
& GDK_CONTROL_MASK
) {
1641 switch (event
->keyval
) {
1642 case GDK_KEY_Page_Down
:
1643 case GDK_KEY_KP_Page_Down
:
1645 if (!pidgin_conv_window_get_gtkconv_at_index(win
, curconv
+ 1))
1646 gtk_notebook_set_current_page(GTK_NOTEBOOK(win
->notebook
), 0);
1648 gtk_notebook_set_current_page(GTK_NOTEBOOK(win
->notebook
), curconv
+ 1);
1652 case GDK_KEY_Page_Up
:
1653 case GDK_KEY_KP_Page_Up
:
1655 if (!pidgin_conv_window_get_gtkconv_at_index(win
, curconv
- 1))
1656 gtk_notebook_set_current_page(GTK_NOTEBOOK(win
->notebook
), -1);
1658 gtk_notebook_set_current_page(GTK_NOTEBOOK(win
->notebook
), curconv
- 1);
1663 case GDK_KEY_KP_Tab
:
1664 case GDK_KEY_ISO_Left_Tab
:
1665 if (event
->state
& GDK_SHIFT_MASK
) {
1666 move_to_next_unread_tab(gtkconv
, FALSE
);
1668 move_to_next_unread_tab(gtkconv
, TRUE
);
1675 gtk_notebook_reorder_child(GTK_NOTEBOOK(win
->notebook
),
1676 gtk_notebook_get_nth_page(GTK_NOTEBOOK(win
->notebook
), curconv
),
1681 case GDK_KEY_period
:
1682 gtk_notebook_reorder_child(GTK_NOTEBOOK(win
->notebook
),
1683 gtk_notebook_get_nth_page(GTK_NOTEBOOK(win
->notebook
), curconv
),
1684 (curconv
+ 1) % gtk_notebook_get_n_pages(GTK_NOTEBOOK(win
->notebook
)));
1688 if (gtkconv_cycle_focus(gtkconv
, event
->state
& GDK_SHIFT_MASK
? GTK_DIR_TAB_BACKWARD
: GTK_DIR_TAB_FORWARD
))
1691 } /* End of switch */
1694 /* If ALT (or whatever) was held down... */
1695 else if (event
->state
& GDK_MOD1_MASK
)
1697 if (event
->keyval
> '0' && event
->keyval
<= '9')
1699 guint switchto
= event
->keyval
- '1';
1700 if (switchto
< pidgin_conv_window_get_gtkconv_count(win
))
1701 gtk_notebook_set_current_page(GTK_NOTEBOOK(win
->notebook
), switchto
);
1707 /* If neither CTRL nor ALT were held down... */
1710 switch (event
->keyval
) {
1712 if (gtk_widget_is_focus(GTK_WIDGET(win
->notebook
))) {
1713 infopane_entry_activate(gtkconv
);
1718 if (gtkconv_cycle_focus(gtkconv
, event
->state
& GDK_SHIFT_MASK
? GTK_DIR_TAB_BACKWARD
: GTK_DIR_TAB_FORWARD
))
1727 entry_key_press_cb(GtkWidget
*entry
, GdkEventKey
*event
, gpointer data
)
1729 PurpleConversation
*conv
;
1730 PidginConversation
*gtkconv
;
1732 gtkconv
= (PidginConversation
*)data
;
1733 conv
= gtkconv
->active_conv
;
1735 if (conv_keypress_common(gtkconv
, event
))
1738 /* If CTRL was held down... */
1739 if (event
->state
& GDK_CONTROL_MASK
) {
1741 /* If ALT (or whatever) was held down... */
1742 else if (event
->state
& GDK_MOD1_MASK
) {
1745 /* If neither CTRL nor ALT were held down... */
1747 switch (event
->keyval
) {
1749 case GDK_KEY_KP_Tab
:
1750 case GDK_KEY_ISO_Left_Tab
:
1751 if (gtkconv
->entry
!= entry
)
1755 plugin_return
= GPOINTER_TO_INT(purple_signal_emit_return_1(
1756 pidgin_conversations_get_handle(), "chat-nick-autocomplete",
1757 conv
, event
->state
& GDK_SHIFT_MASK
));
1758 return plugin_return
;
1762 case GDK_KEY_Page_Up
:
1763 case GDK_KEY_KP_Page_Up
:
1764 talkatu_history_page_up(TALKATU_HISTORY(gtkconv
->history
));
1768 case GDK_KEY_Page_Down
:
1769 case GDK_KEY_KP_Page_Down
:
1770 talkatu_history_page_down(TALKATU_HISTORY(gtkconv
->history
));
1774 case GDK_KEY_KP_Enter
:
1775 case GDK_KEY_Return
:
1776 send_cb(entry
, gtkconv
);
1783 if (PURPLE_IS_IM_CONVERSATION(conv
) &&
1784 purple_prefs_get_bool("/purple/conversations/im/send_typing")) {
1786 switch (event
->keyval
) {
1787 case GDK_KEY_BackSpace
:
1788 case GDK_KEY_Delete
:
1789 case GDK_KEY_KP_Delete
:
1790 update_typing_deleting(gtkconv
);
1793 update_typing_inserting(gtkconv
);
1801 * If someone tries to type into the conversation backlog of a
1802 * conversation window then we yank focus from the conversation backlog
1803 * and give it to the text entry box so that people can type
1804 * all the live long day and it will get entered into the entry box.
1807 refocus_entry_cb(GtkWidget
*widget
, GdkEventKey
*event
, gpointer data
)
1809 PidginConversation
*gtkconv
= data
;
1811 /* If we have a valid key for the conversation display, then exit */
1812 if ((event
->state
& GDK_CONTROL_MASK
) ||
1813 (event
->keyval
== GDK_KEY_F6
) ||
1814 (event
->keyval
== GDK_KEY_F10
) ||
1815 (event
->keyval
== GDK_KEY_Menu
) ||
1816 (event
->keyval
== GDK_KEY_Shift_L
) ||
1817 (event
->keyval
== GDK_KEY_Shift_R
) ||
1818 (event
->keyval
== GDK_KEY_Control_L
) ||
1819 (event
->keyval
== GDK_KEY_Control_R
) ||
1820 (event
->keyval
== GDK_KEY_Escape
) ||
1821 (event
->keyval
== GDK_KEY_Up
) ||
1822 (event
->keyval
== GDK_KEY_Down
) ||
1823 (event
->keyval
== GDK_KEY_Left
) ||
1824 (event
->keyval
== GDK_KEY_Right
) ||
1825 (event
->keyval
== GDK_KEY_Page_Up
) ||
1826 (event
->keyval
== GDK_KEY_KP_Page_Up
) ||
1827 (event
->keyval
== GDK_KEY_Page_Down
) ||
1828 (event
->keyval
== GDK_KEY_KP_Page_Down
) ||
1829 (event
->keyval
== GDK_KEY_Home
) ||
1830 (event
->keyval
== GDK_KEY_End
) ||
1831 (event
->keyval
== GDK_KEY_Tab
) ||
1832 (event
->keyval
== GDK_KEY_KP_Tab
) ||
1833 (event
->keyval
== GDK_KEY_ISO_Left_Tab
))
1835 if (event
->type
== GDK_KEY_PRESS
)
1836 return conv_keypress_common(gtkconv
, event
);
1840 gtk_widget_grab_focus(gtkconv
->entry
);
1841 gtk_widget_event(gtkconv
->entry
, (GdkEvent
*)event
);
1847 regenerate_options_items(PidginConvWindow
*win
);
1850 pidgin_conv_switch_active_conversation(PurpleConversation
*conv
)
1852 PidginConversation
*gtkconv
;
1853 PurpleConversation
*old_conv
;
1854 PurpleConnectionFlags features
;
1856 g_return_if_fail(conv
!= NULL
);
1858 gtkconv
= PIDGIN_CONVERSATION(conv
);
1859 old_conv
= gtkconv
->active_conv
;
1861 purple_debug_info("gtkconv", "setting active conversation on toolbar %p\n",
1864 if (old_conv
== conv
)
1867 purple_conversation_close_logs(old_conv
);
1868 gtkconv
->active_conv
= conv
;
1870 purple_conversation_set_logging(conv
,
1871 gtk_toggle_action_get_active(GTK_TOGGLE_ACTION(gtkconv
->win
->menu
->logging
)));
1873 purple_signal_emit(pidgin_conversations_get_handle(), "conversation-switched", conv
);
1875 gray_stuff_out(gtkconv
);
1876 update_typing_icon(gtkconv
);
1877 g_object_set_data(G_OBJECT(gtkconv
->entry
), "transient_buddy", NULL
);
1878 regenerate_options_items(gtkconv
->win
);
1880 gtk_window_set_title(GTK_WINDOW(gtkconv
->win
->window
),
1881 gtk_label_get_text(GTK_LABEL(gtkconv
->tab_label
)));
1885 menu_conv_sel_send_cb(GObject
*m
, gpointer data
)
1887 PurpleAccount
*account
= g_object_get_data(m
, "purple_account");
1888 gchar
*name
= g_object_get_data(m
, "purple_buddy_name");
1889 PurpleIMConversation
*im
;
1891 if (gtk_check_menu_item_get_active((GtkCheckMenuItem
*) m
) == FALSE
)
1894 im
= purple_im_conversation_new(account
, name
);
1895 pidgin_conv_switch_active_conversation(PURPLE_CONVERSATION(im
));
1898 /**************************************************************************
1899 * A bunch of buddy icon functions
1900 **************************************************************************/
1902 static GList
*get_protocol_icon_list(PurpleAccount
*account
)
1905 PurpleProtocol
*protocol
=
1906 purple_protocols_find(purple_account_get_protocol_id(account
));
1907 const char *protoname
= purple_protocol_class_list_icon(protocol
, account
, NULL
);
1908 l
= g_hash_table_lookup(protocol_lists
, protoname
);
1912 l
= g_list_prepend(l
, pidgin_create_protocol_icon(account
, PIDGIN_PROTOCOL_ICON_LARGE
));
1913 l
= g_list_prepend(l
, pidgin_create_protocol_icon(account
, PIDGIN_PROTOCOL_ICON_MEDIUM
));
1914 l
= g_list_prepend(l
, pidgin_create_protocol_icon(account
, PIDGIN_PROTOCOL_ICON_SMALL
));
1916 g_hash_table_insert(protocol_lists
, g_strdup(protoname
), l
);
1921 pidgin_conv_get_tab_icons(PurpleConversation
*conv
)
1923 PurpleAccount
*account
= NULL
;
1924 const char *name
= NULL
;
1926 g_return_val_if_fail(conv
!= NULL
, NULL
);
1928 account
= purple_conversation_get_account(conv
);
1929 name
= purple_conversation_get_name(conv
);
1931 g_return_val_if_fail(account
!= NULL
, NULL
);
1932 g_return_val_if_fail(name
!= NULL
, NULL
);
1934 /* Use the buddy icon, if possible */
1935 if (PURPLE_IS_IM_CONVERSATION(conv
)) {
1936 PurpleBuddy
*b
= purple_blist_find_buddy(account
, name
);
1939 p
= purple_buddy_get_presence(b
);
1940 if (purple_presence_is_status_primitive_active(p
, PURPLE_STATUS_AWAY
))
1942 if (purple_presence_is_status_primitive_active(p
, PURPLE_STATUS_UNAVAILABLE
))
1944 if (purple_presence_is_status_primitive_active(p
, PURPLE_STATUS_EXTENDED_AWAY
))
1946 if (purple_presence_is_status_primitive_active(p
, PURPLE_STATUS_OFFLINE
))
1947 return offline_list
;
1949 return available_list
;
1953 return get_protocol_icon_list(account
);
1957 pidgin_conv_get_icon_stock(PurpleConversation
*conv
)
1959 PurpleAccount
*account
= NULL
;
1960 const char *stock
= NULL
;
1962 g_return_val_if_fail(conv
!= NULL
, NULL
);
1964 account
= purple_conversation_get_account(conv
);
1965 g_return_val_if_fail(account
!= NULL
, NULL
);
1967 /* Use the buddy icon, if possible */
1968 if (PURPLE_IS_IM_CONVERSATION(conv
)) {
1969 const char *name
= NULL
;
1971 name
= purple_conversation_get_name(conv
);
1972 b
= purple_blist_find_buddy(account
, name
);
1974 PurplePresence
*p
= purple_buddy_get_presence(b
);
1975 PurpleStatus
*active
= purple_presence_get_active_status(p
);
1976 PurpleStatusType
*type
= purple_status_get_status_type(active
);
1977 PurpleStatusPrimitive prim
= purple_status_type_get_primitive(type
);
1978 stock
= pidgin_stock_id_from_status_primitive(prim
);
1980 stock
= PIDGIN_STOCK_STATUS_PERSON
;
1983 stock
= PIDGIN_STOCK_STATUS_CHAT
;
1990 pidgin_conv_get_icon(PurpleConversation
*conv
, GtkWidget
*parent
, const char *icon_size
)
1992 PurpleAccount
*account
= NULL
;
1993 const char *name
= NULL
;
1994 const char *stock
= NULL
;
1995 GdkPixbuf
*status
= NULL
;
1998 g_return_val_if_fail(conv
!= NULL
, NULL
);
2000 account
= purple_conversation_get_account(conv
);
2001 name
= purple_conversation_get_name(conv
);
2003 g_return_val_if_fail(account
!= NULL
, NULL
);
2004 g_return_val_if_fail(name
!= NULL
, NULL
);
2006 /* Use the buddy icon, if possible */
2007 if (PURPLE_IS_IM_CONVERSATION(conv
)) {
2008 PurpleBuddy
*b
= purple_blist_find_buddy(account
, name
);
2010 /* I hate this hack. It fixes a bug where the pending message icon
2011 * displays in the conv tab even though it shouldn't.
2012 * A better solution would be great. */
2013 purple_blist_update_node(NULL
, PURPLE_BLIST_NODE(b
));
2017 stock
= pidgin_conv_get_icon_stock(conv
);
2018 size
= gtk_icon_size_from_name(icon_size
);
2019 status
= gtk_widget_render_icon (parent
, stock
, size
, "GtkWidget");
2024 pidgin_conv_get_tab_icon(PurpleConversation
*conv
, gboolean small_icon
)
2026 const char *icon_size
= small_icon
? PIDGIN_ICON_SIZE_TANGO_MICROSCOPIC
: PIDGIN_ICON_SIZE_TANGO_EXTRA_SMALL
;
2027 return pidgin_conv_get_icon(conv
, PIDGIN_CONVERSATION(conv
)->icon
, icon_size
);
2032 update_tab_icon(PurpleConversation
*conv
)
2034 PidginConversation
*gtkconv
;
2035 PidginConvWindow
*win
;
2037 GdkPixbuf
*emblem
= NULL
;
2038 const char *status
= NULL
;
2039 const char *infopane_status
= NULL
;
2041 g_return_if_fail(conv
!= NULL
);
2043 gtkconv
= PIDGIN_CONVERSATION(conv
);
2045 if (conv
!= gtkconv
->active_conv
)
2048 status
= infopane_status
= pidgin_conv_get_icon_stock(conv
);
2050 if (PURPLE_IS_IM_CONVERSATION(conv
)) {
2051 PurpleBuddy
*b
= purple_blist_find_buddy(purple_conversation_get_account(conv
), purple_conversation_get_name(conv
));
2053 emblem
= pidgin_blist_get_emblem((PurpleBlistNode
*)b
);
2056 g_return_if_fail(status
!= NULL
);
2058 g_object_set(G_OBJECT(gtkconv
->icon
), "stock", status
, NULL
);
2059 g_object_set(G_OBJECT(gtkconv
->menu_icon
), "stock", status
, NULL
);
2061 gtk_list_store_set(GTK_LIST_STORE(gtkconv
->infopane_model
),
2062 &(gtkconv
->infopane_iter
),
2063 CONV_ICON_COLUMN
, infopane_status
, -1);
2065 gtk_list_store_set(GTK_LIST_STORE(gtkconv
->infopane_model
),
2066 &(gtkconv
->infopane_iter
),
2067 CONV_EMBLEM_COLUMN
, emblem
, -1);
2069 g_object_unref(emblem
);
2071 if (purple_prefs_get_bool(PIDGIN_PREFS_ROOT
"/blist/show_protocol_icons")) {
2072 emblem
= pidgin_create_protocol_icon(purple_conversation_get_account(gtkconv
->active_conv
), PIDGIN_PROTOCOL_ICON_SMALL
);
2077 gtk_list_store_set(GTK_LIST_STORE(gtkconv
->infopane_model
),
2078 &(gtkconv
->infopane_iter
),
2079 CONV_PROTOCOL_ICON_COLUMN
, emblem
, -1);
2081 g_object_unref(emblem
);
2083 /* XXX seanegan Why do I have to do this? */
2084 gtk_widget_queue_resize(gtkconv
->infopane
);
2085 gtk_widget_queue_draw(gtkconv
->infopane
);
2087 if (pidgin_conv_window_is_active_conversation(conv
) &&
2088 (!PURPLE_IS_IM_CONVERSATION(conv
) || gtkconv
->u
.im
->anim
== NULL
))
2090 l
= pidgin_conv_get_tab_icons(conv
);
2092 gtk_window_set_icon_list(GTK_WINDOW(win
->window
), l
);
2097 redraw_icon(gpointer data
)
2099 PidginConversation
*gtkconv
= (PidginConversation
*)data
;
2100 PurpleConversation
*conv
= gtkconv
->active_conv
;
2101 PurpleAccount
*account
;
2106 int scale_width
, scale_height
;
2109 gtkconv
= PIDGIN_CONVERSATION(conv
);
2110 account
= purple_conversation_get_account(conv
);
2112 if (!(account
&& purple_account_get_connection(account
))) {
2113 gtkconv
->u
.im
->icon_timer
= 0;
2117 gdk_pixbuf_animation_iter_advance(gtkconv
->u
.im
->iter
, NULL
);
2118 buf
= gdk_pixbuf_animation_iter_get_pixbuf(gtkconv
->u
.im
->iter
);
2120 scale_width
= gdk_pixbuf_get_width(buf
);
2121 scale_height
= gdk_pixbuf_get_height(buf
);
2123 gtk_widget_get_size_request(gtkconv
->u
.im
->icon_container
, NULL
, &size
);
2124 size
= MIN(size
, MIN(scale_width
, scale_height
));
2125 size
= CLAMP(size
, BUDDYICON_SIZE_MIN
, BUDDYICON_SIZE_MAX
);
2127 if (scale_width
== scale_height
) {
2128 scale_width
= scale_height
= size
;
2129 } else if (scale_height
> scale_width
) {
2130 scale_width
= size
* scale_width
/ scale_height
;
2131 scale_height
= size
;
2133 scale_height
= size
* scale_height
/ scale_width
;
2137 scale
= gdk_pixbuf_scale_simple(buf
, scale_width
, scale_height
,
2138 GDK_INTERP_BILINEAR
);
2139 if (pidgin_gdk_pixbuf_is_opaque(scale
))
2140 pidgin_gdk_pixbuf_make_round(scale
);
2142 gtk_image_set_from_pixbuf(GTK_IMAGE(gtkconv
->u
.im
->icon
), scale
);
2143 g_object_unref(G_OBJECT(scale
));
2144 gtk_widget_queue_draw(gtkconv
->u
.im
->icon
);
2146 delay
= gdk_pixbuf_animation_iter_get_delay_time(gtkconv
->u
.im
->iter
);
2151 gtkconv
->u
.im
->icon_timer
= g_timeout_add(delay
, redraw_icon
, gtkconv
);
2157 start_anim(GtkWidget
*widget
, PidginConversation
*gtkconv
)
2161 if (gtkconv
->u
.im
->anim
== NULL
)
2164 if (gtkconv
->u
.im
->icon_timer
!= 0)
2167 if (gdk_pixbuf_animation_is_static_image(gtkconv
->u
.im
->anim
))
2170 delay
= gdk_pixbuf_animation_iter_get_delay_time(gtkconv
->u
.im
->iter
);
2175 gtkconv
->u
.im
->icon_timer
= g_timeout_add(delay
, redraw_icon
, gtkconv
);
2179 remove_icon(GtkWidget
*widget
, PidginConversation
*gtkconv
)
2183 PurpleConversation
*conv
= gtkconv
->active_conv
;
2185 g_return_if_fail(conv
!= NULL
);
2187 gtk_widget_set_size_request(gtkconv
->u
.im
->icon_container
, -1, BUDDYICON_SIZE_MIN
);
2188 children
= gtk_container_get_children(GTK_CONTAINER(gtkconv
->u
.im
->icon_container
));
2190 /* We know there's only one child here. It'd be nice to shortcut to the
2191 event box, but we can't change the PidginConversation until 3.0 */
2192 event
= (GtkWidget
*)children
->data
;
2193 gtk_container_remove(GTK_CONTAINER(gtkconv
->u
.im
->icon_container
), event
);
2194 g_list_free(children
);
2197 if (gtkconv
->u
.im
->anim
!= NULL
)
2198 g_object_unref(G_OBJECT(gtkconv
->u
.im
->anim
));
2200 if (gtkconv
->u
.im
->icon_timer
!= 0)
2201 g_source_remove(gtkconv
->u
.im
->icon_timer
);
2203 if (gtkconv
->u
.im
->iter
!= NULL
)
2204 g_object_unref(G_OBJECT(gtkconv
->u
.im
->iter
));
2206 gtkconv
->u
.im
->icon_timer
= 0;
2207 gtkconv
->u
.im
->icon
= NULL
;
2208 gtkconv
->u
.im
->anim
= NULL
;
2209 gtkconv
->u
.im
->iter
= NULL
;
2210 gtkconv
->u
.im
->show_icon
= FALSE
;
2214 saveicon_writefile_cb(void *user_data
, const char *filename
)
2216 PidginConversation
*gtkconv
= (PidginConversation
*)user_data
;
2217 PurpleIMConversation
*im
= PURPLE_IM_CONVERSATION(gtkconv
->active_conv
);
2218 PurpleBuddyIcon
*icon
;
2222 icon
= purple_im_conversation_get_icon(im
);
2223 data
= purple_buddy_icon_get_data(icon
, &len
);
2225 if ((len
<= 0) || (data
== NULL
) || !purple_util_write_data_to_file_absolute(filename
, data
, len
)) {
2226 purple_notify_error(gtkconv
, NULL
, _("Unable to save icon file to disk."), NULL
, NULL
);
2231 custom_icon_sel_cb(const char *filename
, gpointer data
)
2236 PurpleContact
*contact
;
2237 PidginConversation
*gtkconv
= data
;
2238 PurpleConversation
*conv
= gtkconv
->active_conv
;
2239 PurpleAccount
*account
= purple_conversation_get_account(conv
);
2241 name
= purple_conversation_get_name(conv
);
2242 buddy
= purple_blist_find_buddy(account
, name
);
2244 purple_debug_info("custom-icon", "You can only set custom icons for people on your buddylist.\n");
2247 contact
= purple_buddy_get_contact(buddy
);
2249 purple_buddy_icons_node_set_custom_icon_from_file((PurpleBlistNode
*)contact
, filename
);
2254 set_custom_icon_cb(GtkWidget
*widget
, PidginConversation
*gtkconv
)
2256 GtkWidget
*win
= pidgin_buddy_icon_chooser_new(GTK_WINDOW(gtkconv
->win
->window
),
2257 custom_icon_sel_cb
, gtkconv
);
2258 gtk_widget_show_all(win
);
2262 change_size_cb(GtkWidget
*widget
, PidginConversation
*gtkconv
)
2265 PurpleConversation
*conv
= gtkconv
->active_conv
;
2268 gtk_widget_get_size_request(gtkconv
->u
.im
->icon_container
, NULL
, &size
);
2270 if (size
== BUDDYICON_SIZE_MAX
) {
2271 size
= BUDDYICON_SIZE_MIN
;
2273 size
= BUDDYICON_SIZE_MAX
;
2276 gtk_widget_set_size_request(gtkconv
->u
.im
->icon_container
, -1, size
);
2277 pidgin_conv_update_buddy_icon(PURPLE_IM_CONVERSATION(conv
));
2279 buddies
= purple_blist_find_buddies(purple_conversation_get_account(conv
),
2280 purple_conversation_get_name(conv
));
2281 for (; buddies
; buddies
= g_slist_delete_link(buddies
, buddies
)) {
2282 PurpleBuddy
*buddy
= buddies
->data
;
2283 PurpleContact
*contact
= purple_buddy_get_contact(buddy
);
2284 purple_blist_node_set_int((PurpleBlistNode
*)contact
, "pidgin-infopane-iconsize", size
);
2289 remove_custom_icon_cb(GtkWidget
*widget
, PidginConversation
*gtkconv
)
2293 PurpleAccount
*account
;
2294 PurpleContact
*contact
;
2295 PurpleConversation
*conv
= gtkconv
->active_conv
;
2297 account
= purple_conversation_get_account(conv
);
2298 name
= purple_conversation_get_name(conv
);
2299 buddy
= purple_blist_find_buddy(account
, name
);
2303 contact
= purple_buddy_get_contact(buddy
);
2305 purple_buddy_icons_node_set_custom_icon_from_file((PurpleBlistNode
*)contact
, NULL
);
2309 icon_menu_save_cb(GtkWidget
*widget
, PidginConversation
*gtkconv
)
2311 PurpleConversation
*conv
= gtkconv
->active_conv
;
2315 g_return_if_fail(conv
!= NULL
);
2317 ext
= purple_buddy_icon_get_extension(purple_im_conversation_get_icon(PURPLE_IM_CONVERSATION(conv
)));
2319 buf
= g_strdup_printf("%s.%s", purple_normalize(purple_conversation_get_account(conv
), purple_conversation_get_name(conv
)), ext
);
2321 purple_request_file(gtkconv
, _("Save Icon"), buf
, TRUE
,
2322 G_CALLBACK(saveicon_writefile_cb
), NULL
,
2323 purple_request_cpar_from_conversation(conv
), gtkconv
);
2329 stop_anim(GtkWidget
*widget
, PidginConversation
*gtkconv
)
2331 if (gtkconv
->u
.im
->icon_timer
!= 0)
2332 g_source_remove(gtkconv
->u
.im
->icon_timer
);
2334 gtkconv
->u
.im
->icon_timer
= 0;
2339 toggle_icon_animate_cb(GtkWidget
*w
, PidginConversation
*gtkconv
)
2341 gtkconv
->u
.im
->animate
=
2342 gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(w
));
2344 if (gtkconv
->u
.im
->animate
)
2345 start_anim(NULL
, gtkconv
);
2347 stop_anim(NULL
, gtkconv
);
2351 icon_menu(GtkWidget
*widget
, GdkEventButton
*e
, PidginConversation
*gtkconv
)
2353 static GtkWidget
*menu
= NULL
;
2354 PurpleConversation
*conv
;
2357 if (e
->button
== GDK_BUTTON_PRIMARY
&& e
->type
== GDK_BUTTON_PRESS
) {
2358 change_size_cb(NULL
, gtkconv
);
2362 if (!gdk_event_triggers_context_menu((GdkEvent
*)e
)) {
2367 * If a menu already exists, destroy it before creating a new one,
2368 * thus freeing-up the memory it occupied.
2371 gtk_widget_destroy(menu
);
2373 menu
= gtk_menu_new();
2375 if (gtkconv
->u
.im
->anim
&&
2376 !(gdk_pixbuf_animation_is_static_image(gtkconv
->u
.im
->anim
)))
2378 pidgin_new_check_item(menu
, _("Animate"),
2379 G_CALLBACK(toggle_icon_animate_cb
), gtkconv
,
2380 gtkconv
->u
.im
->icon_timer
);
2383 pidgin_new_menu_item(menu
, _("Hide Icon"), NULL
,
2384 G_CALLBACK(remove_icon
), gtkconv
);
2386 pidgin_new_menu_item(menu
, _("Save Icon As..."), GTK_STOCK_SAVE_AS
,
2387 G_CALLBACK(icon_menu_save_cb
), gtkconv
);
2389 pidgin_new_menu_item(menu
, _("Set Custom Icon..."), NULL
,
2390 G_CALLBACK(set_custom_icon_cb
), gtkconv
);
2392 pidgin_new_menu_item(menu
, _("Change Size"), NULL
,
2393 G_CALLBACK(change_size_cb
), gtkconv
);
2395 /* Is there a custom icon for this person? */
2396 conv
= gtkconv
->active_conv
;
2397 buddy
= purple_blist_find_buddy(purple_conversation_get_account(conv
),
2398 purple_conversation_get_name(conv
));
2401 PurpleContact
*contact
= purple_buddy_get_contact(buddy
);
2402 if (contact
&& purple_buddy_icons_node_has_custom_icon((PurpleBlistNode
*)contact
))
2404 pidgin_new_menu_item(menu
, _("Remove Custom Icon"),
2405 NULL
, G_CALLBACK(remove_custom_icon_cb
),
2410 gtk_menu_popup_at_pointer(GTK_MENU(menu
), (GdkEvent
*)e
);
2415 /**************************************************************************
2416 * End of the bunch of buddy icon functions
2417 **************************************************************************/
2419 pidgin_conv_present_conversation(PurpleConversation
*conv
)
2421 PidginConversation
*gtkconv
;
2422 GdkModifierType state
;
2424 pidgin_conv_attach_to_conversation(conv
);
2425 gtkconv
= PIDGIN_CONVERSATION(conv
);
2427 pidgin_conv_switch_active_conversation(conv
);
2428 /* Switch the tab only if the user initiated the event by pressing
2429 * a button or hitting a key. */
2430 if (gtk_get_current_event_state(&state
))
2431 pidgin_conv_window_switch_gtkconv(gtkconv
->win
, gtkconv
);
2432 gtk_window_present(GTK_WINDOW(gtkconv
->win
->window
));
2436 pidgin_conversations_get_unseen(GList
*l
,
2437 PidginUnseenState min_state
,
2438 gboolean hidden_only
,
2444 for (; l
!= NULL
&& (max_count
== 0 || c
< max_count
); l
= l
->next
) {
2445 PurpleConversation
*conv
= (PurpleConversation
*)l
->data
;
2446 PidginConversation
*gtkconv
= PIDGIN_CONVERSATION(conv
);
2448 if(gtkconv
== NULL
|| gtkconv
->active_conv
!= conv
)
2451 if (gtkconv
->unseen_state
>= min_state
2453 (hidden_only
&& gtkconv
->win
== hidden_convwin
))) {
2455 r
= g_list_prepend(r
, conv
);
2464 pidgin_conversations_get_unseen_all(PidginUnseenState min_state
,
2465 gboolean hidden_only
,
2468 return pidgin_conversations_get_unseen(purple_conversations_get_all(),
2469 min_state
, hidden_only
, max_count
);
2473 pidgin_conversations_get_unseen_ims(PidginUnseenState min_state
,
2474 gboolean hidden_only
,
2477 return pidgin_conversations_get_unseen(purple_conversations_get_ims(),
2478 min_state
, hidden_only
, max_count
);
2482 pidgin_conversations_get_unseen_chats(PidginUnseenState min_state
,
2483 gboolean hidden_only
,
2486 return pidgin_conversations_get_unseen(purple_conversations_get_chats(),
2487 min_state
, hidden_only
, max_count
);
2491 unseen_conv_menu_cb(GtkMenuItem
*item
, PurpleConversation
*conv
)
2493 g_return_if_fail(conv
!= NULL
);
2494 pidgin_conv_present_conversation(conv
);
2498 unseen_all_conv_menu_cb(GtkMenuItem
*item
, GList
*list
)
2500 g_return_if_fail(list
!= NULL
);
2501 /* Do not free the list from here. It will be freed from the
2502 * 'destroy' callback on the menuitem. */
2504 pidgin_conv_present_conversation(list
->data
);
2510 pidgin_conversations_fill_menu(GtkWidget
*menu
, GList
*convs
)
2515 g_return_val_if_fail(menu
!= NULL
, 0);
2516 g_return_val_if_fail(convs
!= NULL
, 0);
2518 for (l
= convs
; l
!= NULL
; l
= l
->next
) {
2519 PurpleConversation
*conv
= (PurpleConversation
*)l
->data
;
2520 PidginConversation
*gtkconv
= PIDGIN_CONVERSATION(conv
);
2522 GtkWidget
*icon
= gtk_image_new_from_stock(pidgin_conv_get_icon_stock(conv
),
2523 gtk_icon_size_from_name(PIDGIN_ICON_SIZE_TANGO_MICROSCOPIC
));
2525 gchar
*text
= g_strdup_printf("%s (%d)",
2526 gtk_label_get_text(GTK_LABEL(gtkconv
->tab_label
)),
2527 gtkconv
->unseen_count
);
2529 item
= gtk_image_menu_item_new_with_label(text
);
2530 gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(item
), icon
);
2531 g_signal_connect(G_OBJECT(item
), "activate", G_CALLBACK(unseen_conv_menu_cb
), conv
);
2532 gtk_menu_shell_append(GTK_MENU_SHELL(menu
), item
);
2538 /* There are more than one conversation. Add an option to show all conversations. */
2540 GList
*list
= g_list_copy(convs
);
2542 pidgin_separator(menu
);
2544 item
= gtk_menu_item_new_with_label(_("Show All"));
2545 g_signal_connect(G_OBJECT(item
), "activate", G_CALLBACK(unseen_all_conv_menu_cb
), list
);
2546 g_signal_connect_swapped(G_OBJECT(item
), "destroy", G_CALLBACK(g_list_free
), list
);
2547 gtk_menu_shell_append(GTK_MENU_SHELL(menu
), item
);
2554 pidgin_conv_get_window(PidginConversation
*gtkconv
)
2556 g_return_val_if_fail(gtkconv
!= NULL
, NULL
);
2557 return gtkconv
->win
;
2560 static GtkActionEntry menu_entries
[] =
2561 /* TODO: fill out tooltips... */
2563 /* Conversation menu */
2564 { "ConversationMenu", NULL
, N_("_Conversation"), NULL
, NULL
, NULL
},
2565 { "NewInstantMessage", PIDGIN_STOCK_TOOLBAR_MESSAGE_NEW
, N_("New Instant _Message..."), "<control>M", NULL
, G_CALLBACK(menu_new_conv_cb
) },
2566 { "JoinAChat", PIDGIN_STOCK_CHAT
, N_("Join a _Chat..."), NULL
, NULL
, G_CALLBACK(menu_join_chat_cb
) },
2567 { "Find", GTK_STOCK_FIND
, N_("_Find..."), NULL
, NULL
, G_CALLBACK(menu_find_cb
) },
2568 { "ViewLog", NULL
, N_("View _Log"), NULL
, NULL
, G_CALLBACK(menu_view_log_cb
) },
2569 { "SaveAs", GTK_STOCK_SAVE_AS
, N_("_Save As..."), NULL
, NULL
, G_CALLBACK(menu_save_as_cb
) },
2570 { "ClearScrollback", GTK_STOCK_CLEAR
, N_("Clea_r Scrollback"), "<control>L", NULL
, G_CALLBACK(menu_clear_cb
) },
2573 { "MediaMenu", NULL
, N_("M_edia"), NULL
, NULL
, NULL
},
2574 { "AudioCall", PIDGIN_STOCK_TOOLBAR_AUDIO_CALL
, N_("_Audio Call"), NULL
, NULL
, G_CALLBACK(menu_initiate_media_call_cb
) },
2575 { "VideoCall", PIDGIN_STOCK_TOOLBAR_VIDEO_CALL
, N_("_Video Call"), NULL
, NULL
, G_CALLBACK(menu_initiate_media_call_cb
) },
2576 { "AudioVideoCall", PIDGIN_STOCK_TOOLBAR_VIDEO_CALL
, N_("Audio/Video _Call"), NULL
, NULL
, G_CALLBACK(menu_initiate_media_call_cb
) },
2579 { "SendFile", PIDGIN_STOCK_TOOLBAR_SEND_FILE
, N_("Se_nd File..."), NULL
, NULL
, G_CALLBACK(menu_send_file_cb
) },
2580 { "GetAttention", PIDGIN_STOCK_TOOLBAR_SEND_ATTENTION
, N_("Get _Attention"), NULL
, NULL
, G_CALLBACK(menu_get_attention_cb
) },
2581 { "AddBuddyPounce", NULL
, N_("Add Buddy _Pounce..."), NULL
, NULL
, G_CALLBACK(menu_add_pounce_cb
) },
2582 { "GetInfo", PIDGIN_STOCK_TOOLBAR_USER_INFO
, N_("_Get Info"), "<control>O", NULL
, G_CALLBACK(menu_get_info_cb
) },
2583 { "Invite", NULL
, N_("In_vite..."), NULL
, NULL
, G_CALLBACK(menu_invite_cb
) },
2584 { "MoreMenu", NULL
, N_("M_ore"), NULL
, NULL
, NULL
},
2585 { "Alias", NULL
, N_("Al_ias..."), NULL
, NULL
, G_CALLBACK(menu_alias_cb
) },
2586 { "Block", PIDGIN_STOCK_TOOLBAR_BLOCK
, N_("_Block..."), NULL
, NULL
, G_CALLBACK(menu_block_cb
) },
2587 { "Unblock", PIDGIN_STOCK_TOOLBAR_UNBLOCK
, N_("_Unblock..."), NULL
, NULL
, G_CALLBACK(menu_unblock_cb
) },
2588 { "Add", GTK_STOCK_ADD
, N_("_Add..."), NULL
, NULL
, G_CALLBACK(menu_add_remove_cb
) },
2589 { "Remove", GTK_STOCK_REMOVE
, N_("_Remove..."), NULL
, NULL
, G_CALLBACK(menu_add_remove_cb
) },
2590 { "InsertLink", PIDGIN_STOCK_TOOLBAR_INSERT_LINK
, N_("Insert Lin_k..."), NULL
, NULL
, NULL
},
2591 { "InsertImage", PIDGIN_STOCK_TOOLBAR_INSERT_IMAGE
, N_("Insert Imag_e..."), NULL
, NULL
, NULL
},
2592 { "Close", GTK_STOCK_CLOSE
, N_("_Close"), "<control>W", NULL
, G_CALLBACK(menu_close_conv_cb
) },
2595 { "OptionsMenu", NULL
, N_("_Options"), NULL
, NULL
, NULL
},
2599 static const GtkToggleActionEntry menu_toggle_entries
[] = {
2600 { "EnableLogging", NULL
, N_("Enable _Logging"), NULL
, NULL
, G_CALLBACK(menu_logging_cb
), FALSE
},
2601 { "EnableSounds", NULL
, N_("Enable _Sounds"), NULL
, NULL
, G_CALLBACK(menu_sounds_cb
), FALSE
},
2602 { "ShowFormattingToolbars", NULL
, N_("Show Formatting _Toolbars"), NULL
, NULL
, G_CALLBACK(menu_toolbar_cb
), FALSE
},
2605 static const char *conversation_menu
=
2607 "<menubar name='Conversation'>"
2608 "<menu action='ConversationMenu'>"
2609 "<menuitem action='NewInstantMessage'/>"
2610 "<menuitem action='JoinAChat'/>"
2612 "<menuitem action='Find'/>"
2613 "<menuitem action='ViewLog'/>"
2614 "<menuitem action='SaveAs'/>"
2615 "<menuitem action='ClearScrollback'/>"
2618 "<menu action='MediaMenu'>"
2619 "<menuitem action='AudioCall'/>"
2620 "<menuitem action='VideoCall'/>"
2621 "<menuitem action='AudioVideoCall'/>"
2624 "<menuitem action='SendFile'/>"
2625 "<menuitem action='GetAttention'/>"
2626 "<menuitem action='AddBuddyPounce'/>"
2627 "<menuitem action='GetInfo'/>"
2628 "<menuitem action='Invite'/>"
2629 "<menu action='MoreMenu'/>"
2631 "<menuitem action='Alias'/>"
2632 "<menuitem action='Block'/>"
2633 "<menuitem action='Unblock'/>"
2634 "<menuitem action='Add'/>"
2635 "<menuitem action='Remove'/>"
2637 "<menuitem action='InsertLink'/>"
2638 "<menuitem action='InsertImage'/>"
2640 "<menuitem action='Close'/>"
2642 "<menu action='OptionsMenu'>"
2643 "<menuitem action='EnableLogging'/>"
2644 "<menuitem action='EnableSounds'/>"
2646 "<menuitem action='ShowFormattingToolbars'/>"
2652 sound_method_pref_changed_cb(const char *name
, PurplePrefType type
,
2653 gconstpointer value
, gpointer data
)
2655 PidginConvWindow
*win
= data
;
2656 const char *method
= value
;
2658 if (purple_strequal(method
, "none"))
2660 gtk_toggle_action_set_active(GTK_TOGGLE_ACTION(win
->menu
->sounds
),
2662 gtk_action_set_sensitive(win
->menu
->sounds
, FALSE
);
2666 PidginConversation
*gtkconv
= pidgin_conv_window_get_active_gtkconv(win
);
2668 if (gtkconv
!= NULL
)
2669 gtk_toggle_action_set_active(GTK_TOGGLE_ACTION(win
->menu
->sounds
),
2670 gtkconv
->make_sound
);
2671 gtk_action_set_sensitive(win
->menu
->sounds
, TRUE
);
2675 /* Returns TRUE if some items were added to the menu, FALSE otherwise */
2677 populate_menu_with_options(GtkWidget
*menu
, PidginConversation
*gtkconv
, gboolean all
)
2680 PurpleConversation
*conv
;
2681 PurpleAccount
*account
;
2682 PurpleBlistNode
*node
= NULL
;
2683 PurpleChat
*chat
= NULL
;
2684 PurpleBuddy
*buddy
= NULL
;
2687 conv
= gtkconv
->active_conv
;
2688 account
= purple_conversation_get_account(conv
);
2690 if (PURPLE_IS_CHAT_CONVERSATION(conv
)) {
2691 chat
= purple_blist_find_chat(account
, purple_conversation_get_name(conv
));
2693 if ((chat
== NULL
) && (gtkconv
->history
!= NULL
)) {
2694 chat
= g_object_get_data(G_OBJECT(gtkconv
->history
), "transient_chat");
2697 if ((chat
== NULL
) && (gtkconv
->history
!= NULL
)) {
2698 GHashTable
*components
;
2699 PurpleAccount
*account
= purple_conversation_get_account(conv
);
2700 PurpleProtocol
*protocol
=
2701 purple_protocols_find(purple_account_get_protocol_id(account
));
2702 if (purple_account_get_connection(account
) != NULL
&&
2703 PURPLE_PROTOCOL_IMPLEMENTS(protocol
, CHAT
, info_defaults
)) {
2704 components
= purple_protocol_chat_iface_info_defaults(protocol
, purple_account_get_connection(account
),
2705 purple_conversation_get_name(conv
));
2707 components
= g_hash_table_new_full(g_str_hash
, g_str_equal
,
2709 g_hash_table_replace(components
, g_strdup("channel"),
2710 g_strdup(purple_conversation_get_name(conv
)));
2712 chat
= purple_chat_new(account
, NULL
, components
);
2713 purple_blist_node_set_transient((PurpleBlistNode
*)chat
, TRUE
);
2714 g_object_set_data_full(G_OBJECT(gtkconv
->history
), "transient_chat",
2715 chat
, (GDestroyNotify
)purple_blist_remove_chat
);
2718 if (!purple_account_is_connected(account
))
2721 buddy
= purple_blist_find_buddy(account
, purple_conversation_get_name(conv
));
2722 if (!buddy
&& gtkconv
->history
) {
2723 buddy
= g_object_get_data(G_OBJECT(gtkconv
->history
), "transient_buddy");
2726 buddy
= purple_buddy_new(account
, purple_conversation_get_name(conv
), NULL
);
2727 purple_blist_node_set_transient((PurpleBlistNode
*)buddy
, TRUE
);
2728 g_object_set_data_full(G_OBJECT(gtkconv
->history
), "transient_buddy",
2729 buddy
, (GDestroyNotify
)g_object_unref
);
2735 node
= (PurpleBlistNode
*)chat
;
2737 node
= (PurpleBlistNode
*)buddy
;
2739 /* Now add the stuff */
2742 pidgin_blist_make_buddy_menu(menu
, buddy
, TRUE
);
2747 if (purple_account_is_connected(account
))
2748 pidgin_append_blist_node_proto_menu(menu
, purple_account_get_connection(account
), node
);
2749 pidgin_append_blist_node_extended_menu(menu
, node
);
2752 if ((list
= gtk_container_get_children(GTK_CONTAINER(menu
))) == NULL
) {
2762 regenerate_media_items(PidginConvWindow
*win
)
2765 PurpleAccount
*account
;
2766 PurpleConversation
*conv
;
2768 conv
= pidgin_conv_window_get_active_conversation(win
);
2771 purple_debug_error("gtkconv", "couldn't get active conversation"
2772 " when regenerating media items\n");
2776 account
= purple_conversation_get_account(conv
);
2778 if (account
== NULL
) {
2779 purple_debug_error("gtkconv", "couldn't get account when"
2780 " regenerating media items\n");
2785 * Check if account support voice and/or calls, and
2786 * if the current buddy supports it.
2788 if (account
!= NULL
&& PURPLE_IS_IM_CONVERSATION(conv
)) {
2789 PurpleMediaCaps caps
=
2790 purple_protocol_get_media_caps(account
,
2791 purple_conversation_get_name(conv
));
2793 gtk_action_set_sensitive(win
->menu
->audio_call
,
2794 caps
& PURPLE_MEDIA_CAPS_AUDIO
2796 gtk_action_set_sensitive(win
->menu
->video_call
,
2797 caps
& PURPLE_MEDIA_CAPS_VIDEO
2799 gtk_action_set_sensitive(win
->menu
->audio_video_call
,
2800 caps
& PURPLE_MEDIA_CAPS_AUDIO_VIDEO
2802 } else if (PURPLE_IS_CHAT_CONVERSATION(conv
)) {
2803 /* for now, don't care about chats... */
2804 gtk_action_set_sensitive(win
->menu
->audio_call
, FALSE
);
2805 gtk_action_set_sensitive(win
->menu
->video_call
, FALSE
);
2806 gtk_action_set_sensitive(win
->menu
->audio_video_call
, FALSE
);
2808 gtk_action_set_sensitive(win
->menu
->audio_call
, FALSE
);
2809 gtk_action_set_sensitive(win
->menu
->video_call
, FALSE
);
2810 gtk_action_set_sensitive(win
->menu
->audio_video_call
, FALSE
);
2816 regenerate_attention_items(PidginConvWindow
*win
)
2818 GtkWidget
*attention
;
2820 PurpleConversation
*conv
;
2821 PurpleConnection
*pc
;
2822 PurpleProtocol
*protocol
= NULL
;
2825 conv
= pidgin_conv_window_get_active_conversation(win
);
2829 attention
= gtk_ui_manager_get_widget(win
->menu
->ui
,
2830 "/Conversation/ConversationMenu/GetAttention");
2832 /* Remove the previous entries */
2833 gtk_menu_item_set_submenu(GTK_MENU_ITEM(attention
), NULL
);
2835 pc
= purple_conversation_get_connection(conv
);
2837 protocol
= purple_connection_get_protocol(pc
);
2839 if (protocol
&& PURPLE_IS_PROTOCOL_ATTENTION(protocol
)) {
2840 list
= purple_protocol_attention_get_types(PURPLE_PROTOCOL_ATTENTION(protocol
), purple_connection_get_account(pc
));
2842 /* Multiple attention types */
2843 if (list
&& list
->next
) {
2846 menu
= gtk_menu_new();
2848 PurpleAttentionType
*type
;
2849 GtkWidget
*menuitem
;
2853 menuitem
= gtk_menu_item_new_with_label(purple_attention_type_get_name(type
));
2854 g_object_set_data(G_OBJECT(menuitem
), "index", GINT_TO_POINTER(index
));
2855 g_signal_connect(G_OBJECT(menuitem
), "activate",
2856 G_CALLBACK(menu_get_attention_cb
),
2858 gtk_menu_shell_append(GTK_MENU_SHELL(menu
), menuitem
);
2861 list
= g_list_delete_link(list
, list
);
2864 gtk_menu_item_set_submenu(GTK_MENU_ITEM(attention
), menu
);
2865 gtk_widget_show_all(menu
);
2871 regenerate_options_items(PidginConvWindow
*win
)
2874 PidginConversation
*gtkconv
;
2876 GtkWidget
*more_menu
;
2878 gtkconv
= pidgin_conv_window_get_active_gtkconv(win
);
2879 more_menu
= gtk_ui_manager_get_widget(win
->menu
->ui
,
2880 "/Conversation/ConversationMenu/MoreMenu");
2881 gtk_widget_show(more_menu
);
2882 menu
= gtk_menu_item_get_submenu(GTK_MENU_ITEM(more_menu
));
2884 /* Remove the previous entries */
2885 for (list
= gtk_container_get_children(GTK_CONTAINER(menu
)); list
; )
2887 GtkWidget
*w
= list
->data
;
2888 list
= g_list_delete_link(list
, list
);
2889 gtk_widget_destroy(w
);
2892 if (!populate_menu_with_options(menu
, gtkconv
, FALSE
))
2894 GtkWidget
*item
= gtk_menu_item_new_with_label(_("No actions available"));
2895 gtk_menu_shell_append(GTK_MENU_SHELL(menu
), item
);
2896 gtk_widget_set_sensitive(item
, FALSE
);
2899 gtk_widget_show_all(menu
);
2903 remove_from_list(GtkWidget
*widget
, PidginConvWindow
*win
)
2905 GList
*list
= g_object_get_data(G_OBJECT(win
->window
), "plugin-actions");
2906 list
= g_list_remove(list
, widget
);
2907 g_object_set_data(G_OBJECT(win
->window
), "plugin-actions", list
);
2911 regenerate_plugins_items(PidginConvWindow
*win
)
2913 GList
*action_items
;
2916 PidginConversation
*gtkconv
;
2917 PurpleConversation
*conv
;
2920 if (win
->window
== NULL
|| win
== hidden_convwin
)
2923 gtkconv
= pidgin_conv_window_get_active_gtkconv(win
);
2924 if (gtkconv
== NULL
)
2927 conv
= gtkconv
->active_conv
;
2928 action_items
= g_object_get_data(G_OBJECT(win
->window
), "plugin-actions");
2930 /* Remove the old menuitems */
2931 while (action_items
) {
2932 g_signal_handlers_disconnect_by_func(G_OBJECT(action_items
->data
),
2933 G_CALLBACK(remove_from_list
), win
);
2934 gtk_widget_destroy(action_items
->data
);
2935 action_items
= g_list_delete_link(action_items
, action_items
);
2938 item
= gtk_ui_manager_get_widget(win
->menu
->ui
, "/Conversation/OptionsMenu");
2939 menu
= gtk_menu_item_get_submenu(GTK_MENU_ITEM(item
));
2941 list
= purple_conversation_get_extended_menu(conv
);
2943 action_items
= g_list_prepend(NULL
, (item
= pidgin_separator(menu
)));
2944 g_signal_connect(G_OBJECT(item
), "destroy", G_CALLBACK(remove_from_list
), win
);
2947 for(; list
; list
= g_list_delete_link(list
, list
)) {
2948 PurpleActionMenu
*act
= (PurpleActionMenu
*) list
->data
;
2949 item
= pidgin_append_menu_action(menu
, act
, conv
);
2950 action_items
= g_list_prepend(action_items
, item
);
2951 gtk_widget_show_all(item
);
2952 g_signal_connect(G_OBJECT(item
), "destroy", G_CALLBACK(remove_from_list
), win
);
2954 g_object_set_data(G_OBJECT(win
->window
), "plugin-actions", action_items
);
2957 static void menubar_activated(GtkWidget
*item
, gpointer data
)
2959 PidginConvWindow
*win
= data
;
2960 regenerate_media_items(win
);
2961 regenerate_options_items(win
);
2962 regenerate_plugins_items(win
);
2963 regenerate_attention_items(win
);
2965 /* The following are to make sure the 'More' submenu is not regenerated every time
2966 * the focus shifts from 'Conversations' to some other menu and back. */
2967 g_signal_handlers_block_by_func(G_OBJECT(item
), G_CALLBACK(menubar_activated
), data
);
2968 g_signal_connect(G_OBJECT(win
->menu
->menubar
), "deactivate", G_CALLBACK(focus_out_from_menubar
), data
);
2972 focus_out_from_menubar(GtkWidget
*wid
, PidginConvWindow
*win
)
2974 /* The menubar has been deactivated. Make sure the 'More' submenu is regenerated next time
2975 * the 'Conversation' menu pops up. */
2976 GtkWidget
*menuitem
= gtk_ui_manager_get_widget(win
->menu
->ui
, "/Conversation/ConversationMenu");
2977 g_signal_handlers_unblock_by_func(G_OBJECT(menuitem
), G_CALLBACK(menubar_activated
), win
);
2978 g_signal_handlers_disconnect_by_func(G_OBJECT(win
->menu
->menubar
),
2979 G_CALLBACK(focus_out_from_menubar
), win
);
2983 setup_menubar(PidginConvWindow
*win
)
2985 GtkAccelGroup
*accel_group
;
2987 GtkActionGroup
*action_group
;
2989 GtkWidget
*menuitem
;
2991 action_group
= gtk_action_group_new("ConversationActions");
2992 gtk_action_group_set_translation_domain(action_group
, PACKAGE
);
2993 gtk_action_group_add_actions(action_group
,
2995 G_N_ELEMENTS(menu_entries
),
2997 gtk_action_group_add_toggle_actions(action_group
,
2998 menu_toggle_entries
,
2999 G_N_ELEMENTS(menu_toggle_entries
),
3002 win
->menu
->ui
= gtk_ui_manager_new();
3003 gtk_ui_manager_insert_action_group(win
->menu
->ui
, action_group
, 0);
3005 accel_group
= gtk_ui_manager_get_accel_group(win
->menu
->ui
);
3006 gtk_window_add_accel_group(GTK_WINDOW(win
->window
), accel_group
);
3007 g_signal_connect(G_OBJECT(accel_group
), "accel-changed",
3008 G_CALLBACK(pidgin_save_accels_cb
), NULL
);
3011 if (!gtk_ui_manager_add_ui_from_string(win
->menu
->ui
, conversation_menu
, -1, &error
))
3013 g_message("building menus failed: %s", error
->message
);
3014 g_error_free(error
);
3018 win
->menu
->menubar
=
3019 gtk_ui_manager_get_widget(win
->menu
->ui
, "/Conversation");
3021 /* Make sure the 'Conversation ⇨ More' menuitems are regenerated whenever
3022 * the 'Conversation' menu pops up because the entries can change after the
3023 * conversation is created. */
3024 menuitem
= gtk_ui_manager_get_widget(win
->menu
->ui
, "/Conversation/ConversationMenu");
3025 g_signal_connect(G_OBJECT(menuitem
), "activate", G_CALLBACK(menubar_activated
), win
);
3027 win
->menu
->view_log
=
3028 gtk_ui_manager_get_action(win
->menu
->ui
,
3029 "/Conversation/ConversationMenu/ViewLog");
3032 win
->menu
->audio_call
=
3033 gtk_ui_manager_get_action(win
->menu
->ui
,
3034 "/Conversation/ConversationMenu/MediaMenu/AudioCall");
3035 win
->menu
->video_call
=
3036 gtk_ui_manager_get_action(win
->menu
->ui
,
3037 "/Conversation/ConversationMenu/MediaMenu/VideoCall");
3038 win
->menu
->audio_video_call
=
3039 gtk_ui_manager_get_action(win
->menu
->ui
,
3040 "/Conversation/ConversationMenu/MediaMenu/AudioVideoCall");
3045 win
->menu
->send_file
=
3046 gtk_ui_manager_get_action(win
->menu
->ui
,
3047 "/Conversation/ConversationMenu/SendFile");
3049 win
->menu
->get_attention
=
3050 gtk_ui_manager_get_action(win
->menu
->ui
,
3051 "/Conversation/ConversationMenu/GetAttention");
3053 win
->menu
->add_pounce
=
3054 gtk_ui_manager_get_action(win
->menu
->ui
,
3055 "/Conversation/ConversationMenu/AddBuddyPounce");
3059 win
->menu
->get_info
=
3060 gtk_ui_manager_get_action(win
->menu
->ui
,
3061 "/Conversation/ConversationMenu/GetInfo");
3064 gtk_ui_manager_get_action(win
->menu
->ui
,
3065 "/Conversation/ConversationMenu/Invite");
3070 gtk_ui_manager_get_action(win
->menu
->ui
,
3071 "/Conversation/ConversationMenu/Alias");
3074 gtk_ui_manager_get_action(win
->menu
->ui
,
3075 "/Conversation/ConversationMenu/Block");
3077 win
->menu
->unblock
=
3078 gtk_ui_manager_get_action(win
->menu
->ui
,
3079 "/Conversation/ConversationMenu/Unblock");
3082 gtk_ui_manager_get_action(win
->menu
->ui
,
3083 "/Conversation/ConversationMenu/Add");
3086 gtk_ui_manager_get_action(win
->menu
->ui
,
3087 "/Conversation/ConversationMenu/Remove");
3091 win
->menu
->insert_link
=
3092 gtk_ui_manager_get_action(win
->menu
->ui
,
3093 "/Conversation/ConversationMenu/InsertLink");
3095 win
->menu
->insert_image
=
3096 gtk_ui_manager_get_action(win
->menu
->ui
,
3097 "/Conversation/ConversationMenu/InsertImage");
3101 win
->menu
->logging
=
3102 gtk_ui_manager_get_action(win
->menu
->ui
,
3103 "/Conversation/OptionsMenu/EnableLogging");
3105 gtk_ui_manager_get_action(win
->menu
->ui
,
3106 "/Conversation/OptionsMenu/EnableSounds");
3107 method
= purple_prefs_get_string(PIDGIN_PREFS_ROOT
"/sound/method");
3108 if (purple_strequal(method
, "none"))
3110 gtk_toggle_action_set_active(GTK_TOGGLE_ACTION(win
->menu
->sounds
),
3112 gtk_action_set_sensitive(win
->menu
->sounds
, FALSE
);
3114 purple_prefs_connect_callback(win
, PIDGIN_PREFS_ROOT
"/sound/method",
3115 sound_method_pref_changed_cb
, win
);
3117 win
->menu
->show_formatting_toolbar
=
3118 gtk_ui_manager_get_action(win
->menu
->ui
,
3119 "/Conversation/OptionsMenu/ShowFormattingToolbars");
3121 win
->menu
->tray
= pidgin_menu_tray_new();
3122 gtk_menu_shell_append(GTK_MENU_SHELL(win
->menu
->menubar
),
3124 gtk_widget_show(win
->menu
->tray
);
3126 gtk_widget_show(win
->menu
->menubar
);
3128 return win
->menu
->menubar
;
3132 /**************************************************************************
3134 **************************************************************************/
3137 got_typing_keypress(PidginConversation
*gtkconv
, gboolean first
)
3139 PurpleConversation
*conv
= gtkconv
->active_conv
;
3140 PurpleIMConversation
*im
;
3143 * We know we got something, so we at least have to make sure we don't
3144 * send PURPLE_IM_TYPED any time soon.
3147 im
= PURPLE_IM_CONVERSATION(conv
);
3149 purple_im_conversation_stop_send_typed_timeout(im
);
3150 purple_im_conversation_start_send_typed_timeout(im
);
3152 /* Check if we need to send another PURPLE_IM_TYPING message */
3153 if (first
|| (purple_im_conversation_get_type_again(im
) != 0 &&
3154 time(NULL
) > purple_im_conversation_get_type_again(im
)))
3156 unsigned int timeout
;
3157 timeout
= purple_serv_send_typing(purple_conversation_get_connection(conv
),
3158 purple_conversation_get_name(conv
),
3160 purple_im_conversation_set_type_again(im
, timeout
);
3166 typing_animation(gpointer data
) {
3167 PidginConversation
*gtkconv
= data
;
3168 PidginConvWindow
*gtkwin
= gtkconv
->win
;
3169 const char *stock_id
= NULL
;
3171 if(gtkconv
!= pidgin_conv_window_get_active_gtkconv(gtkwin
)) {
3175 switch (rand() % 5) {
3177 stock_id
= PIDGIN_STOCK_ANIMATION_TYPING0
;
3180 stock_id
= PIDGIN_STOCK_ANIMATION_TYPING1
;
3183 stock_id
= PIDGIN_STOCK_ANIMATION_TYPING2
;
3186 stock_id
= PIDGIN_STOCK_ANIMATION_TYPING3
;
3189 stock_id
= PIDGIN_STOCK_ANIMATION_TYPING4
;
3192 if (gtkwin
->menu
->typing_icon
== NULL
) {
3193 gtkwin
->menu
->typing_icon
= gtk_image_new_from_stock(stock_id
, GTK_ICON_SIZE_MENU
);
3194 pidgin_menu_tray_append(PIDGIN_MENU_TRAY(gtkwin
->menu
->tray
),
3195 gtkwin
->menu
->typing_icon
,
3196 _("User is typing..."));
3198 gtk_image_set_from_stock(GTK_IMAGE(gtkwin
->menu
->typing_icon
), stock_id
, GTK_ICON_SIZE_MENU
);
3200 gtk_widget_show(gtkwin
->menu
->typing_icon
);
3206 update_typing_message(PidginConversation
*gtkconv
, const char *message
)
3208 /* TODO WEBKIT: this is not handled at all */
3210 GtkTextBuffer
*buffer
;
3211 GtkTextMark
*stmark
, *enmark
;
3213 if (g_object_get_data(G_OBJECT(gtkconv
->imhtml
), "disable-typing-notification"))
3216 buffer
= gtk_text_view_get_buffer(GTK_TEXT_VIEW(gtkconv
->imhtml
));
3217 stmark
= gtk_text_buffer_get_mark(buffer
, "typing-notification-start");
3218 enmark
= gtk_text_buffer_get_mark(buffer
, "typing-notification-end");
3219 if (stmark
&& enmark
) {
3220 GtkTextIter start
, end
;
3221 gtk_text_buffer_get_iter_at_mark(buffer
, &start
, stmark
);
3222 gtk_text_buffer_get_iter_at_mark(buffer
, &end
, enmark
);
3223 gtk_text_buffer_delete_mark(buffer
, stmark
);
3224 gtk_text_buffer_delete_mark(buffer
, enmark
);
3225 gtk_text_buffer_delete(buffer
, &start
, &end
);
3226 } else if (message
&& *message
== '\n' && message
[1] == ' ' && message
[2] == '\0')
3231 message
= "\n "; /* The blank space is required to avoid a GTK+/Pango bug */
3236 gtk_text_buffer_get_end_iter(buffer
, &iter
);
3237 gtk_text_buffer_create_mark(buffer
, "typing-notification-start", &iter
, TRUE
);
3238 gtk_text_buffer_insert_with_tags_by_name(buffer
, &iter
, message
, -1, "TYPING-NOTIFICATION", NULL
);
3239 gtk_text_buffer_get_end_iter(buffer
, &iter
);
3240 gtk_text_buffer_create_mark(buffer
, "typing-notification-end", &iter
, TRUE
);
3246 update_typing_icon(PidginConversation
*gtkconv
)
3248 PurpleIMConversation
*im
;
3249 char *message
= NULL
;
3251 if (!PURPLE_IS_IM_CONVERSATION(gtkconv
->active_conv
))
3254 im
= PURPLE_IM_CONVERSATION(gtkconv
->active_conv
);
3256 if (purple_im_conversation_get_typing_state(im
) == PURPLE_IM_NOT_TYPING
) {
3258 update_typing_message(gtkconv
, NULL
);
3260 update_typing_message(gtkconv
, "\n ");
3265 if (purple_im_conversation_get_typing_state(im
) == PURPLE_IM_TYPING
) {
3266 message
= g_strdup_printf(_("\n%s is typing..."), purple_conversation_get_title(PURPLE_CONVERSATION(im
)));
3268 message
= g_strdup_printf(_("\n%s has stopped typing"), purple_conversation_get_title(PURPLE_CONVERSATION(im
)));
3271 update_typing_message(gtkconv
, message
);
3276 update_send_to_selection(PidginConvWindow
*win
)
3278 PurpleAccount
*account
;
3279 PurpleConversation
*conv
;
3284 conv
= pidgin_conv_window_get_active_conversation(win
);
3289 account
= purple_conversation_get_account(conv
);
3291 if (account
== NULL
)
3294 if (win
->menu
->send_to
== NULL
)
3297 if (!(b
= purple_blist_find_buddy(account
, purple_conversation_get_name(conv
))))
3300 gtk_widget_show(win
->menu
->send_to
);
3302 menu
= gtk_menu_item_get_submenu(GTK_MENU_ITEM(win
->menu
->send_to
));
3304 for (child
= gtk_container_get_children(GTK_CONTAINER(menu
));
3306 child
= g_list_delete_link(child
, child
)) {
3308 GtkWidget
*item
= child
->data
;
3309 PurpleBuddy
*item_buddy
;
3310 PurpleAccount
*item_account
= g_object_get_data(G_OBJECT(item
), "purple_account");
3311 gchar
*buddy_name
= g_object_get_data(G_OBJECT(item
),
3312 "purple_buddy_name");
3313 item_buddy
= purple_blist_find_buddy(item_account
, buddy_name
);
3315 if (b
== item_buddy
) {
3316 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(item
), TRUE
);
3326 send_to_item_enter_notify_cb(GtkWidget
*menuitem
, GdkEventCrossing
*event
, GtkWidget
*label
)
3328 gtk_widget_set_sensitive(GTK_WIDGET(label
), TRUE
);
3333 send_to_item_leave_notify_cb(GtkWidget
*menuitem
, GdkEventCrossing
*event
, GtkWidget
*label
)
3335 gtk_widget_set_sensitive(GTK_WIDGET(label
), FALSE
);
3340 e2ee_state_to_gtkimage(PurpleE2eeState
*state
)
3344 img
= _pidgin_e2ee_stock_icon_get(
3345 purple_e2ee_state_get_stock_icon(state
));
3349 return gtk_image_new_from_pixbuf(pidgin_pixbuf_from_image(img
));
3353 create_sendto_item(GtkWidget
*menu
, GtkSizeGroup
*sg
, GSList
**group
,
3354 PurpleBuddy
*buddy
, PurpleAccount
*account
, const char *name
,
3355 gboolean e2ee_enabled
)
3360 GtkWidget
*e2ee_image
= NULL
;
3361 GtkWidget
*menuitem
;
3365 /* Create a pixmap for the protocol icon. */
3366 pixbuf
= pidgin_create_protocol_icon(account
, PIDGIN_PROTOCOL_ICON_SMALL
);
3368 /* Now convert it to GtkImage */
3370 image
= gtk_image_new();
3373 image
= gtk_image_new_from_pixbuf(pixbuf
);
3374 g_object_unref(G_OBJECT(pixbuf
));
3378 PurpleIMConversation
*im
;
3379 PurpleE2eeState
*state
= NULL
;
3381 im
= purple_conversations_find_im_with_account(
3382 purple_buddy_get_name(buddy
), purple_buddy_get_account(buddy
));
3384 state
= purple_conversation_get_e2ee_state(PURPLE_CONVERSATION(im
));
3386 e2ee_image
= e2ee_state_to_gtkimage(state
);
3388 e2ee_image
= gtk_image_new();
3391 gtk_size_group_add_widget(sg
, image
);
3393 /* Make our menu item */
3394 text
= g_strdup_printf("%s (%s)", name
, purple_account_get_name_for_display(account
));
3395 menuitem
= gtk_radio_menu_item_new_with_label(*group
, text
);
3397 *group
= gtk_radio_menu_item_get_group(GTK_RADIO_MENU_ITEM(menuitem
));
3399 /* Do some evil, see some evil, speak some evil. */
3400 box
= gtk_box_new(GTK_ORIENTATION_HORIZONTAL
, 0);
3402 label
= gtk_bin_get_child(GTK_BIN(menuitem
));
3403 g_object_ref(label
);
3404 gtk_container_remove(GTK_CONTAINER(menuitem
), label
);
3406 gtk_box_pack_start(GTK_BOX(box
), image
, FALSE
, FALSE
, 0);
3408 gtk_box_pack_start(GTK_BOX(box
), label
, TRUE
, TRUE
, 4);
3410 gtk_box_pack_start(GTK_BOX(box
), e2ee_image
, FALSE
, FALSE
, 0);
3412 if (buddy
!= NULL
&&
3413 !purple_presence_is_online(purple_buddy_get_presence(buddy
)))
3415 gtk_widget_set_sensitive(label
, FALSE
);
3417 /* Set the label sensitive when the menuitem is highlighted and
3418 * insensitive again when the mouse leaves it. This way, it
3419 * doesn't appear weird from the highlighting of the embossed
3420 * (insensitive style) text.*/
3421 g_signal_connect(menuitem
, "enter-notify-event",
3422 G_CALLBACK(send_to_item_enter_notify_cb
), label
);
3423 g_signal_connect(menuitem
, "leave-notify-event",
3424 G_CALLBACK(send_to_item_leave_notify_cb
), label
);
3427 g_object_unref(label
);
3429 gtk_container_add(GTK_CONTAINER(menuitem
), box
);
3431 gtk_widget_show(label
);
3432 gtk_widget_show(image
);
3434 gtk_widget_show(e2ee_image
);
3435 gtk_widget_show(box
);
3437 /* Set our data and callbacks. */
3438 g_object_set_data(G_OBJECT(menuitem
), "purple_account", account
);
3439 g_object_set_data_full(G_OBJECT(menuitem
), "purple_buddy_name", g_strdup(name
), g_free
);
3441 g_signal_connect(G_OBJECT(menuitem
), "activate",
3442 G_CALLBACK(menu_conv_sel_send_cb
), NULL
);
3444 gtk_widget_show(menuitem
);
3445 gtk_menu_shell_append(GTK_MENU_SHELL(menu
), menuitem
);
3449 compare_buddy_presence(PurplePresence
*p1
, PurplePresence
*p2
)
3451 /* This is necessary because multiple PurpleBuddy's don't share the same
3452 * PurplePresence anymore.
3454 PurpleBuddy
*b1
= purple_buddy_presence_get_buddy(PURPLE_BUDDY_PRESENCE(p1
));
3455 PurpleBuddy
*b2
= purple_buddy_presence_get_buddy(PURPLE_BUDDY_PRESENCE(p2
));
3456 if (purple_buddy_get_account(b1
) == purple_buddy_get_account(b2
) &&
3457 purple_strequal(purple_buddy_get_name(b1
), purple_buddy_get_name(b2
)))
3463 generate_send_to_items(PidginConvWindow
*win
)
3466 GSList
*group
= NULL
;
3467 GtkSizeGroup
*sg
= gtk_size_group_new(GTK_SIZE_GROUP_HORIZONTAL
);
3468 PidginConversation
*gtkconv
;
3471 g_return_if_fail(win
!= NULL
);
3473 gtkconv
= pidgin_conv_window_get_active_gtkconv(win
);
3475 g_return_if_fail(gtkconv
!= NULL
);
3477 if (win
->menu
->send_to
!= NULL
)
3478 gtk_widget_destroy(win
->menu
->send_to
);
3480 /* Build the Send To menu */
3481 win
->menu
->send_to
= gtk_menu_item_new_with_mnemonic(_("S_end To"));
3482 gtk_widget_show(win
->menu
->send_to
);
3484 menu
= gtk_menu_new();
3485 gtk_menu_shell_insert(GTK_MENU_SHELL(win
->menu
->menubar
),
3486 win
->menu
->send_to
, 2);
3487 gtk_menu_item_set_submenu(GTK_MENU_ITEM(win
->menu
->send_to
), menu
);
3489 gtk_widget_show(menu
);
3491 if (PURPLE_IS_IM_CONVERSATION(gtkconv
->active_conv
)) {
3492 buds
= purple_blist_find_buddies(purple_conversation_get_account(gtkconv
->active_conv
), purple_conversation_get_name(gtkconv
->active_conv
));
3496 /* The user isn't on the buddy list. So we don't create any sendto menu. */
3500 gboolean e2ee_enabled
= FALSE
;
3501 GList
*list
= NULL
, *iter
;
3502 for (l
= buds
; l
!= NULL
; l
= l
->next
)
3504 PurpleBlistNode
*node
;
3506 node
= PURPLE_BLIST_NODE(purple_buddy_get_contact(PURPLE_BUDDY(l
->data
)));
3508 for (node
= node
->child
; node
!= NULL
; node
= node
->next
)
3510 PurpleBuddy
*buddy
= (PurpleBuddy
*)node
;
3511 PurpleAccount
*account
;
3512 PurpleIMConversation
*im
;
3514 if (!PURPLE_IS_BUDDY(node
))
3517 im
= purple_conversations_find_im_with_account(purple_buddy_get_name(buddy
), purple_buddy_get_account(buddy
));
3518 if (im
&& purple_conversation_get_e2ee_state(PURPLE_CONVERSATION(im
)) != NULL
)
3519 e2ee_enabled
= TRUE
;
3521 account
= purple_buddy_get_account(buddy
);
3522 /* TODO WEBKIT: (I'm not actually sure if this is webkit-related --Mark Doliner) */
3523 if (purple_account_is_connected(account
) /*|| account == purple_conversation_get_account(gtkconv->active_conv)*/)
3525 /* Use the PurplePresence to get unique buddies. */
3526 PurplePresence
*presence
= purple_buddy_get_presence(buddy
);
3527 if (!g_list_find_custom(list
, presence
, (GCompareFunc
)compare_buddy_presence
))
3528 list
= g_list_prepend(list
, presence
);
3533 /* Create the sendto menu only if it has more than one item to show */
3534 if (list
&& list
->next
) {
3535 /* Loop over the list backwards so we get the items in the right order,
3536 * since we did a g_list_prepend() earlier. */
3537 for (iter
= g_list_last(list
); iter
!= NULL
; iter
= iter
->prev
) {
3538 PurplePresence
*pre
= iter
->data
;
3539 PurpleBuddy
*buddy
= purple_buddy_presence_get_buddy(PURPLE_BUDDY_PRESENCE(pre
));
3540 create_sendto_item(menu
, sg
, &group
, buddy
,
3541 purple_buddy_get_account(buddy
), purple_buddy_get_name(buddy
), e2ee_enabled
);
3551 gtk_widget_show(win
->menu
->send_to
);
3552 /* TODO: This should never be insensitive. Possibly hidden or not. */
3554 gtk_widget_set_sensitive(win
->menu
->send_to
, FALSE
);
3555 update_send_to_selection(win
);
3559 _pidgin_e2ee_stock_icon_get(const gchar
*stock_name
)
3561 gchar filename
[100], *path
;
3564 /* core is quitting */
3565 if (e2ee_stock
== NULL
)
3568 if (g_hash_table_lookup_extended(e2ee_stock
, stock_name
, NULL
, (gpointer
*)&image
))
3571 g_snprintf(filename
, sizeof(filename
), "e2ee-%s.png", stock_name
);
3572 path
= g_build_filename(PURPLE_DATADIR
, "pidgin", "icons",
3573 "hicolor", "16x16", "status", filename
, NULL
);
3574 image
= purple_image_new_from_file(path
, NULL
);
3577 g_hash_table_insert(e2ee_stock
, g_strdup(stock_name
), image
);
3582 generate_e2ee_controls(PidginConvWindow
*win
)
3584 PidginConversation
*gtkconv
;
3585 PurpleConversation
*conv
;
3586 PurpleE2eeState
*state
;
3587 PurpleE2eeProvider
*provider
;
3589 GList
*menu_actions
, *it
;
3590 GtkWidget
*e2ee_image
;
3592 gtkconv
= pidgin_conv_window_get_active_gtkconv(win
);
3593 g_return_if_fail(gtkconv
!= NULL
);
3595 conv
= gtkconv
->active_conv
;
3596 g_return_if_fail(conv
!= NULL
);
3598 if (win
->menu
->e2ee
!= NULL
) {
3599 gtk_widget_destroy(win
->menu
->e2ee
);
3600 win
->menu
->e2ee
= NULL
;
3603 provider
= purple_e2ee_provider_get_main();
3604 state
= purple_conversation_get_e2ee_state(conv
);
3605 if (state
== NULL
|| provider
== NULL
)
3607 if (purple_e2ee_state_get_provider(state
) != provider
)
3610 win
->menu
->e2ee
= gtk_image_menu_item_new_with_label(
3611 purple_e2ee_provider_get_name(provider
));
3613 menu
= gtk_menu_new();
3614 gtk_menu_shell_insert(GTK_MENU_SHELL(win
->menu
->menubar
),
3615 win
->menu
->e2ee
, 3);
3616 gtk_menu_item_set_submenu(GTK_MENU_ITEM(win
->menu
->e2ee
), menu
);
3618 e2ee_image
= e2ee_state_to_gtkimage(state
);
3620 gtk_image_menu_item_set_image(
3621 GTK_IMAGE_MENU_ITEM(win
->menu
->e2ee
), e2ee_image
);
3624 gtk_widget_set_tooltip_text(win
->menu
->e2ee
,
3625 purple_e2ee_state_get_name(state
));
3627 menu_actions
= purple_e2ee_provider_get_conv_menu_actions(provider
, conv
);
3628 for (it
= menu_actions
; it
; it
= g_list_next(it
)) {
3629 PurpleActionMenu
*action
= it
->data
;
3631 gtk_widget_show_all(
3632 pidgin_append_menu_action(menu
, action
, conv
));
3634 g_list_free(menu_actions
);
3636 gtk_widget_show(win
->menu
->e2ee
);
3637 gtk_widget_show(menu
);
3641 get_chat_user_status_icon(PurpleChatConversation
*chat
, const char *name
, PurpleChatUserFlags flags
)
3643 const char *image
= NULL
;
3645 if (flags
& PURPLE_CHAT_USER_FOUNDER
) {
3646 image
= PIDGIN_STOCK_STATUS_FOUNDER
;
3647 } else if (flags
& PURPLE_CHAT_USER_OP
) {
3648 image
= PIDGIN_STOCK_STATUS_OPERATOR
;
3649 } else if (flags
& PURPLE_CHAT_USER_HALFOP
) {
3650 image
= PIDGIN_STOCK_STATUS_HALFOP
;
3651 } else if (flags
& PURPLE_CHAT_USER_VOICE
) {
3652 image
= PIDGIN_STOCK_STATUS_VOICE
;
3653 } else if ((!flags
) && purple_chat_conversation_is_ignored_user(chat
, name
)) {
3654 image
= PIDGIN_STOCK_STATUS_IGNORED
;
3662 deleting_chat_user_cb(PurpleChatUser
*cb
)
3664 GtkTreeRowReference
*ref
= purple_chat_user_get_ui_data(cb
);
3667 gtk_tree_row_reference_free(ref
);
3668 purple_chat_user_set_ui_data(cb
, NULL
);
3673 add_chat_user_common(PurpleChatConversation
*chat
, PurpleChatUser
*cb
, const char *old_name
)
3675 PidginConversation
*gtkconv
;
3676 PurpleConversation
*conv
;
3677 PidginChatPane
*gtkchat
;
3678 PurpleConnection
*gc
;
3679 PurpleProtocol
*protocol
;
3682 GtkTreePath
*newpath
;
3685 gboolean is_me
= FALSE
;
3687 const gchar
*name
, *alias
;
3688 gchar
*tmp
, *alias_key
;
3689 PurpleChatUserFlags flags
;
3690 GdkRGBA
*color
= NULL
;
3692 alias
= purple_chat_user_get_alias(cb
);
3693 name
= purple_chat_user_get_name(cb
);
3694 flags
= purple_chat_user_get_flags(cb
);
3696 conv
= PURPLE_CONVERSATION(chat
);
3697 gtkconv
= PIDGIN_CONVERSATION(conv
);
3698 gtkchat
= gtkconv
->u
.chat
;
3699 gc
= purple_conversation_get_connection(conv
);
3701 if (!gc
|| !(protocol
= purple_connection_get_protocol(gc
)))
3704 tm
= gtk_tree_view_get_model(GTK_TREE_VIEW(gtkchat
->list
));
3705 ls
= GTK_LIST_STORE(tm
);
3707 stock
= get_chat_user_status_icon(chat
, name
, flags
);
3709 if (purple_strequal(purple_chat_conversation_get_nick(chat
), purple_normalize(purple_conversation_get_account(conv
), old_name
!= NULL
? old_name
: name
)))
3712 is_buddy
= purple_chat_user_is_buddy(cb
);
3714 tmp
= g_utf8_casefold(alias
, -1);
3715 alias_key
= g_utf8_collate_key(tmp
, -1);
3720 /* TODO WEBKIT: No tags in webkit stuff, yet. */
3721 GtkTextTag
*tag
= gtk_text_tag_table_lookup(
3722 gtk_text_buffer_get_tag_table(GTK_IMHTML(gtkconv
->webview
)->text_buffer
),
3724 g_object_get(tag
, "foreground-rgba", &color
, NULL
);
3728 if ((tag
= get_buddy_tag(chat
, name
, 0, FALSE
)))
3729 g_object_set(G_OBJECT(tag
), "style", PANGO_STYLE_NORMAL
, NULL
);
3730 if ((tag
= get_buddy_tag(chat
, name
, PURPLE_MESSAGE_NICK
, FALSE
)))
3731 g_object_set(G_OBJECT(tag
), "style", PANGO_STYLE_NORMAL
, NULL
);
3732 color
= (GdkRGBA
*)get_nick_color(gtkconv
, name
);
3735 gtk_list_store_insert_with_values(ls
, &iter
,
3737 * The GTK docs are mute about the effects of the "row" value for performance.
3738 * X-Chat hardcodes their value to 0 (prepend) and -1 (append), so we will too.
3739 * It *might* be faster to search the gtk_list_store and set row accurately,
3740 * but no one in #gtk+ seems to know anything about it either.
3741 * Inserting in the "wrong" location has no visible ill effects. - F.P.
3744 CHAT_USERS_ICON_STOCK_COLUMN
, stock
,
3745 CHAT_USERS_ALIAS_COLUMN
, alias
,
3746 CHAT_USERS_ALIAS_KEY_COLUMN
, alias_key
,
3747 CHAT_USERS_NAME_COLUMN
, name
,
3748 CHAT_USERS_FLAGS_COLUMN
, flags
,
3749 CHAT_USERS_COLOR_COLUMN
, color
,
3750 CHAT_USERS_WEIGHT_COLUMN
, is_buddy
? PANGO_WEIGHT_BOLD
: PANGO_WEIGHT_NORMAL
,
3753 if (purple_chat_user_get_ui_data(cb
)) {
3754 GtkTreeRowReference
*ref
= purple_chat_user_get_ui_data(cb
);
3755 gtk_tree_row_reference_free(ref
);
3758 newpath
= gtk_tree_model_get_path(tm
, &iter
);
3759 purple_chat_user_set_ui_data(cb
, gtk_tree_row_reference_new(tm
, newpath
));
3760 gtk_tree_path_free(newpath
);
3764 gdk_rgba_free(color
);
3769 static void topic_callback(GtkWidget
*w
, PidginConversation
*gtkconv
)
3771 PurpleProtocol
*protocol
= NULL
;
3772 PurpleConnection
*gc
;
3773 PurpleConversation
*conv
= gtkconv
->active_conv
;
3774 PidginChatPane
*gtkchat
;
3776 const char *current_topic
;
3778 gc
= purple_conversation_get_connection(conv
);
3780 if(!gc
|| !(protocol
= purple_connection_get_protocol(gc
)))
3783 if(!PURPLE_PROTOCOL_IMPLEMENTS(protocol
, CHAT
, set_topic
))
3786 gtkconv
= PIDGIN_CONVERSATION(conv
);
3787 gtkchat
= gtkconv
->u
.chat
;
3788 new_topic
= g_strdup(gtk_entry_get_text(GTK_ENTRY(gtkchat
->topic_text
)));
3789 current_topic
= purple_chat_conversation_get_topic(PURPLE_CHAT_CONVERSATION(conv
));
3791 if(current_topic
&& !g_utf8_collate(new_topic
, current_topic
)){
3797 gtk_entry_set_text(GTK_ENTRY(gtkchat
->topic_text
), current_topic
);
3799 gtk_entry_set_text(GTK_ENTRY(gtkchat
->topic_text
), "");
3801 purple_protocol_chat_iface_set_topic(protocol
, gc
, purple_chat_conversation_get_id(PURPLE_CHAT_CONVERSATION(conv
)),
3808 sort_chat_users(GtkTreeModel
*model
, GtkTreeIter
*a
, GtkTreeIter
*b
, gpointer userdata
)
3810 PurpleChatUserFlags f1
= 0, f2
= 0;
3811 char *user1
= NULL
, *user2
= NULL
;
3812 gboolean buddy1
= FALSE
, buddy2
= FALSE
;
3815 gtk_tree_model_get(model
, a
,
3816 CHAT_USERS_ALIAS_KEY_COLUMN
, &user1
,
3817 CHAT_USERS_FLAGS_COLUMN
, &f1
,
3818 CHAT_USERS_WEIGHT_COLUMN
, &buddy1
,
3820 gtk_tree_model_get(model
, b
,
3821 CHAT_USERS_ALIAS_KEY_COLUMN
, &user2
,
3822 CHAT_USERS_FLAGS_COLUMN
, &f2
,
3823 CHAT_USERS_WEIGHT_COLUMN
, &buddy2
,
3826 /* Only sort by membership levels */
3827 f1
&= PURPLE_CHAT_USER_VOICE
| PURPLE_CHAT_USER_HALFOP
| PURPLE_CHAT_USER_OP
|
3828 PURPLE_CHAT_USER_FOUNDER
;
3829 f2
&= PURPLE_CHAT_USER_VOICE
| PURPLE_CHAT_USER_HALFOP
| PURPLE_CHAT_USER_OP
|
3830 PURPLE_CHAT_USER_FOUNDER
;
3832 ret
= g_strcmp0(user1
, user2
);
3834 if (user1
!= NULL
&& user2
!= NULL
) {
3836 /* sort more important users first */
3837 ret
= (f1
> f2
) ? -1 : 1;
3838 } else if (buddy1
!= buddy2
) {
3839 ret
= (buddy1
> buddy2
) ? -1 : 1;
3850 update_chat_alias(PurpleBuddy
*buddy
, PurpleChatConversation
*chat
, PurpleConnection
*gc
, PurpleProtocol
*protocol
)
3852 PidginConversation
*gtkconv
= PIDGIN_CONVERSATION(PURPLE_CONVERSATION(chat
));
3853 PurpleAccount
*account
= purple_conversation_get_account(PURPLE_CONVERSATION(chat
));
3854 GtkTreeModel
*model
;
3855 char *normalized_name
;
3859 g_return_if_fail(buddy
!= NULL
);
3860 g_return_if_fail(chat
!= NULL
);
3862 /* This is safe because this callback is only used in chats, not IMs. */
3863 model
= gtk_tree_view_get_model(GTK_TREE_VIEW(gtkconv
->u
.chat
->list
));
3865 if (!gtk_tree_model_get_iter_first(GTK_TREE_MODEL(model
), &iter
))
3868 normalized_name
= g_strdup(purple_normalize(account
, purple_buddy_get_name(buddy
)));
3873 gtk_tree_model_get(model
, &iter
, CHAT_USERS_NAME_COLUMN
, &name
, -1);
3875 if (purple_strequal(normalized_name
, purple_normalize(account
, name
))) {
3876 const char *alias
= name
;
3878 char *alias_key
= NULL
;
3879 PurpleBuddy
*buddy2
;
3881 if (!purple_strequal(purple_chat_conversation_get_nick(chat
), purple_normalize(account
, name
))) {
3882 /* This user is not me, so look into updating the alias. */
3884 if ((buddy2
= purple_blist_find_buddy(account
, name
)) != NULL
) {
3885 alias
= purple_buddy_get_contact_alias(buddy2
);
3888 tmp
= g_utf8_casefold(alias
, -1);
3889 alias_key
= g_utf8_collate_key(tmp
, -1);
3892 gtk_list_store_set(GTK_LIST_STORE(model
), &iter
,
3893 CHAT_USERS_ALIAS_COLUMN
, alias
,
3894 CHAT_USERS_ALIAS_KEY_COLUMN
, alias_key
,
3902 f
= gtk_tree_model_iter_next(model
, &iter
);
3907 g_free(normalized_name
);
3911 blist_node_aliased_cb(PurpleBlistNode
*node
, const char *old_alias
, PurpleChatConversation
*chat
)
3913 PurpleConnection
*gc
;
3914 PurpleProtocol
*protocol
;
3915 PurpleConversation
*conv
= PURPLE_CONVERSATION(chat
);
3917 g_return_if_fail(node
!= NULL
);
3918 g_return_if_fail(conv
!= NULL
);
3920 gc
= purple_conversation_get_connection(conv
);
3921 g_return_if_fail(gc
!= NULL
);
3922 g_return_if_fail(purple_connection_get_protocol(gc
) != NULL
);
3923 protocol
= purple_connection_get_protocol(gc
);
3925 if (purple_protocol_get_options(protocol
) & OPT_PROTO_UNIQUE_CHATNAME
)
3928 if (PURPLE_IS_CONTACT(node
))
3930 PurpleBlistNode
*bnode
;
3932 for(bnode
= node
->child
; bnode
; bnode
= bnode
->next
) {
3934 if(!PURPLE_IS_BUDDY(bnode
))
3937 update_chat_alias((PurpleBuddy
*)bnode
, chat
, gc
, protocol
);
3940 else if (PURPLE_IS_BUDDY(node
))
3941 update_chat_alias((PurpleBuddy
*)node
, chat
, gc
, protocol
);
3942 else if (PURPLE_IS_CHAT(node
) &&
3943 purple_conversation_get_account(conv
) == purple_chat_get_account((PurpleChat
*)node
))
3945 if (old_alias
== NULL
|| g_utf8_collate(old_alias
, purple_conversation_get_title(conv
)) == 0)
3946 pidgin_conv_update_fields(conv
, PIDGIN_CONV_SET_TITLE
);
3951 buddy_cb_common(PurpleBuddy
*buddy
, PurpleChatConversation
*chat
, gboolean is_buddy
)
3953 GtkTreeModel
*model
;
3954 char *normalized_name
;
3956 GtkTextTag
*texttag
;
3957 PurpleConversation
*conv
= PURPLE_CONVERSATION(chat
);
3960 g_return_if_fail(buddy
!= NULL
);
3961 g_return_if_fail(conv
!= NULL
);
3963 /* Do nothing if the buddy does not belong to the conv's account */
3964 if (purple_buddy_get_account(buddy
) != purple_conversation_get_account(conv
))
3967 /* This is safe because this callback is only used in chats, not IMs. */
3968 model
= gtk_tree_view_get_model(GTK_TREE_VIEW(PIDGIN_CONVERSATION(conv
)->u
.chat
->list
));
3970 if (!gtk_tree_model_get_iter_first(GTK_TREE_MODEL(model
), &iter
))
3973 normalized_name
= g_strdup(purple_normalize(purple_conversation_get_account(conv
), purple_buddy_get_name(buddy
)));
3978 gtk_tree_model_get(model
, &iter
, CHAT_USERS_NAME_COLUMN
, &name
, -1);
3980 if (purple_strequal(normalized_name
, purple_normalize(purple_conversation_get_account(conv
), name
))) {
3981 gtk_list_store_set(GTK_LIST_STORE(model
), &iter
,
3982 CHAT_USERS_WEIGHT_COLUMN
, is_buddy
? PANGO_WEIGHT_BOLD
: PANGO_WEIGHT_NORMAL
, -1);
3987 f
= gtk_tree_model_iter_next(model
, &iter
);
3992 g_free(normalized_name
);
3994 blist_node_aliased_cb((PurpleBlistNode
*)buddy
, NULL
, chat
);
3996 texttag
= get_buddy_tag(chat
, purple_buddy_get_name(buddy
), 0, FALSE
); /* XXX: do we want the normalized name? */
3998 g_object_set(texttag
, "weight", is_buddy
? PANGO_WEIGHT_BOLD
: PANGO_WEIGHT_NORMAL
, NULL
);
4003 buddy_added_cb(PurpleBlistNode
*node
, PurpleChatConversation
*chat
)
4005 if (!PURPLE_IS_BUDDY(node
))
4008 buddy_cb_common(PURPLE_BUDDY(node
), chat
, TRUE
);
4012 buddy_removed_cb(PurpleBlistNode
*node
, PurpleChatConversation
*chat
)
4014 if (!PURPLE_IS_BUDDY(node
))
4017 /* If there's another buddy for the same "dude" on the list, do nothing. */
4018 if (purple_blist_find_buddy(purple_buddy_get_account(PURPLE_BUDDY(node
)),
4019 purple_buddy_get_name(PURPLE_BUDDY(node
))) != NULL
)
4022 buddy_cb_common(PURPLE_BUDDY(node
), chat
, FALSE
);
4026 minimum_entry_lines_pref_cb(const char *name
,
4027 PurplePrefType type
,
4028 gconstpointer value
,
4031 GList
*l
= purple_conversations_get_all();
4032 PurpleConversation
*conv
;
4036 conv
= (PurpleConversation
*)l
->data
;
4038 if (PIDGIN_IS_PIDGIN_CONVERSATION(conv
))
4039 resize_webview_cb(PIDGIN_CONVERSATION(conv
));
4046 setup_chat_topic(PidginConversation
*gtkconv
, GtkWidget
*vbox
)
4048 PurpleConversation
*conv
= gtkconv
->active_conv
;
4049 PurpleConnection
*gc
= purple_conversation_get_connection(conv
);
4050 PurpleProtocol
*protocol
= purple_connection_get_protocol(gc
);
4051 if (purple_protocol_get_options(protocol
) & OPT_PROTO_CHAT_TOPIC
)
4053 GtkWidget
*hbox
, *label
;
4054 PidginChatPane
*gtkchat
= gtkconv
->u
.chat
;
4056 hbox
= gtk_box_new(GTK_ORIENTATION_HORIZONTAL
, PIDGIN_HIG_BOX_SPACE
);
4057 gtk_box_pack_start(GTK_BOX(vbox
), hbox
, FALSE
, FALSE
, 0);
4059 label
= gtk_label_new(_("Topic:"));
4060 gtk_box_pack_start(GTK_BOX(hbox
), label
, FALSE
, FALSE
, 0);
4062 gtkchat
->topic_text
= gtk_entry_new();
4063 gtk_widget_set_size_request(gtkchat
->topic_text
, -1, BUDDYICON_SIZE_MIN
);
4065 if(!PURPLE_PROTOCOL_IMPLEMENTS(protocol
, CHAT
, set_topic
)) {
4066 gtk_editable_set_editable(GTK_EDITABLE(gtkchat
->topic_text
), FALSE
);
4068 g_signal_connect(G_OBJECT(gtkchat
->topic_text
), "activate",
4069 G_CALLBACK(topic_callback
), gtkconv
);
4072 gtk_box_pack_start(GTK_BOX(hbox
), gtkchat
->topic_text
, TRUE
, TRUE
, 0);
4073 g_signal_connect(G_OBJECT(gtkchat
->topic_text
), "key_press_event",
4074 G_CALLBACK(entry_key_press_cb
), gtkconv
);
4079 pidgin_conv_userlist_create_tooltip(GtkWidget
*tipwindow
, GtkTreePath
*path
,
4080 gpointer userdata
, int *w
, int *h
)
4082 PidginConversation
*gtkconv
= userdata
;
4084 GtkTreeModel
*model
= gtk_tree_view_get_model(GTK_TREE_VIEW(gtkconv
->u
.chat
->list
));
4085 PurpleConversation
*conv
= gtkconv
->active_conv
;
4086 PurpleBlistNode
*node
;
4087 PurpleProtocol
*protocol
;
4088 PurpleAccount
*account
= purple_conversation_get_account(conv
);
4091 if (purple_account_get_connection(account
) == NULL
)
4094 if (!gtk_tree_model_get_iter(GTK_TREE_MODEL(model
), &iter
, path
))
4097 gtk_tree_model_get(GTK_TREE_MODEL(model
), &iter
, CHAT_USERS_NAME_COLUMN
, &who
, -1);
4099 protocol
= purple_connection_get_protocol(purple_account_get_connection(account
));
4100 node
= (PurpleBlistNode
*)(purple_blist_find_buddy(purple_conversation_get_account(conv
), who
));
4101 if (node
&& protocol
&& (purple_protocol_get_options(protocol
) & OPT_PROTO_UNIQUE_CHATNAME
))
4102 pidgin_blist_draw_tooltip(node
, gtkconv
->infopane
);
4109 setup_chat_userlist(PidginConversation
*gtkconv
, GtkWidget
*hpaned
)
4111 PidginChatPane
*gtkchat
= gtkconv
->u
.chat
;
4112 GtkWidget
*lbox
, *list
;
4114 GtkCellRenderer
*rend
;
4115 GtkTreeViewColumn
*col
;
4117 void *blist_handle
= purple_blist_get_handle();
4118 PurpleConversation
*conv
= gtkconv
->active_conv
;
4120 /* Build the right pane. */
4121 lbox
= gtk_box_new(GTK_ORIENTATION_VERTICAL
, PIDGIN_HIG_BOX_SPACE
);
4122 gtk_paned_pack2(GTK_PANED(hpaned
), lbox
, FALSE
, TRUE
);
4123 gtk_widget_show(lbox
);
4125 /* Setup the label telling how many people are in the room. */
4126 gtkchat
->count
= gtk_label_new(_("0 people in room"));
4127 gtk_label_set_ellipsize(GTK_LABEL(gtkchat
->count
), PANGO_ELLIPSIZE_END
);
4128 gtk_box_pack_start(GTK_BOX(lbox
), gtkchat
->count
, FALSE
, FALSE
, 0);
4129 gtk_widget_show(gtkchat
->count
);
4131 /* Setup the list of users. */
4133 ls
= gtk_list_store_new(CHAT_USERS_COLUMNS
, GDK_TYPE_PIXBUF
, G_TYPE_STRING
,
4134 G_TYPE_STRING
, G_TYPE_STRING
, G_TYPE_INT
,
4135 GDK_TYPE_RGBA
, G_TYPE_INT
, G_TYPE_STRING
);
4136 gtk_tree_sortable_set_sort_func(GTK_TREE_SORTABLE(ls
), CHAT_USERS_ALIAS_KEY_COLUMN
,
4137 sort_chat_users
, NULL
, NULL
);
4139 list
= gtk_tree_view_new_with_model(GTK_TREE_MODEL(ls
));
4141 /* Allow a user to specify gtkrc settings for the chat userlist only */
4142 gtk_widget_set_name(list
, "pidgin_conv_userlist");
4144 rend
= gtk_cell_renderer_pixbuf_new();
4145 g_object_set(G_OBJECT(rend
),
4146 "stock-size", gtk_icon_size_from_name(PIDGIN_ICON_SIZE_TANGO_EXTRA_SMALL
),
4148 col
= gtk_tree_view_column_new_with_attributes(NULL
, rend
,
4149 "stock-id", CHAT_USERS_ICON_STOCK_COLUMN
, NULL
);
4150 gtk_tree_view_column_set_sizing(col
, GTK_TREE_VIEW_COLUMN_AUTOSIZE
);
4151 gtk_tree_view_append_column(GTK_TREE_VIEW(list
), col
);
4152 ul_width
= purple_prefs_get_int(PIDGIN_PREFS_ROOT
"/conversations/chat/userlist_width");
4153 gtk_widget_set_size_request(lbox
, ul_width
, -1);
4155 /* Hack to prevent completely collapsed userlist coming back with a 1 pixel width.
4156 * I would have liked to use the GtkPaned "max-position", but for some reason that didn't work */
4158 gtk_paned_set_position(GTK_PANED(hpaned
), 999999);
4160 g_signal_connect(G_OBJECT(list
), "button_press_event",
4161 G_CALLBACK(right_click_chat_cb
), gtkconv
);
4162 g_signal_connect(G_OBJECT(list
), "row-activated",
4163 G_CALLBACK(activate_list_cb
), gtkconv
);
4164 g_signal_connect(G_OBJECT(list
), "popup-menu",
4165 G_CALLBACK(gtkconv_chat_popup_menu_cb
), gtkconv
);
4166 g_signal_connect(G_OBJECT(lbox
), "size-allocate", G_CALLBACK(lbox_size_allocate_cb
), gtkconv
);
4168 pidgin_tooltip_setup_for_treeview(list
, gtkconv
,
4169 pidgin_conv_userlist_create_tooltip
, NULL
);
4171 rend
= gtk_cell_renderer_text_new();
4173 "foreground-set", TRUE
,
4176 g_object_set(G_OBJECT(rend
), "editable", TRUE
, NULL
);
4178 col
= gtk_tree_view_column_new_with_attributes(NULL
, rend
,
4179 "text", CHAT_USERS_ALIAS_COLUMN
,
4180 "foreground-rgba", CHAT_USERS_COLOR_COLUMN
,
4181 "weight", CHAT_USERS_WEIGHT_COLUMN
,
4184 purple_signal_connect(blist_handle
, "blist-node-added",
4185 gtkchat
, PURPLE_CALLBACK(buddy_added_cb
), conv
);
4186 purple_signal_connect(blist_handle
, "blist-node-removed",
4187 gtkchat
, PURPLE_CALLBACK(buddy_removed_cb
), conv
);
4188 purple_signal_connect(blist_handle
, "blist-node-aliased",
4189 gtkchat
, PURPLE_CALLBACK(blist_node_aliased_cb
), conv
);
4191 gtk_tree_view_column_set_expand(col
, TRUE
);
4192 g_object_set(rend
, "ellipsize", PANGO_ELLIPSIZE_END
, NULL
);
4194 gtk_tree_view_append_column(GTK_TREE_VIEW(list
), col
);
4196 gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(list
), FALSE
);
4197 gtk_widget_show(list
);
4199 gtkchat
->list
= list
;
4201 gtk_box_pack_start(GTK_BOX(lbox
),
4202 pidgin_make_scrollable(list
, GTK_POLICY_AUTOMATIC
, GTK_POLICY_AUTOMATIC
, GTK_SHADOW_IN
, -1, -1),
4207 pidgin_conv_create_tooltip(GtkWidget
*tipwindow
, gpointer userdata
, int *w
, int *h
)
4209 PurpleBlistNode
*node
= NULL
;
4210 PurpleConversation
*conv
;
4211 PidginConversation
*gtkconv
= userdata
;
4213 conv
= gtkconv
->active_conv
;
4214 if (PURPLE_IS_CHAT_CONVERSATION(conv
)) {
4215 node
= (PurpleBlistNode
*)(purple_blist_find_chat(purple_conversation_get_account(conv
), purple_conversation_get_name(conv
)));
4217 node
= g_object_get_data(G_OBJECT(gtkconv
->history
), "transient_chat");
4219 node
= (PurpleBlistNode
*)(purple_blist_find_buddy(purple_conversation_get_account(conv
), purple_conversation_get_name(conv
)));
4221 /* Using the transient blist nodes to show the tooltip doesn't quite work yet. */
4223 node
= g_object_get_data(G_OBJECT(gtkconv
->webview
), "transient_buddy");
4228 pidgin_blist_draw_tooltip(node
, gtkconv
->infopane
);
4233 setup_common_pane(PidginConversation
*gtkconv
)
4235 GtkWidget
*vbox
, *sw
, *event_box
, *view
;
4236 GtkCellRenderer
*rend
;
4238 PurpleConversation
*conv
= gtkconv
->active_conv
;
4240 gboolean chat
= PURPLE_IS_CHAT_CONVERSATION(conv
);
4241 int buddyicon_size
= 0;
4243 /* Setup the top part of the pane */
4244 vbox
= gtk_box_new(GTK_ORIENTATION_VERTICAL
, PIDGIN_HIG_BOX_SPACE
);
4245 gtk_widget_show(vbox
);
4247 /* Setup the info pane */
4248 event_box
= gtk_event_box_new();
4249 gtk_event_box_set_visible_window(GTK_EVENT_BOX(event_box
), FALSE
);
4250 gtk_widget_show(event_box
);
4251 gtkconv
->infopane_hbox
= gtk_box_new(GTK_ORIENTATION_HORIZONTAL
, 0);
4252 gtk_box_pack_start(GTK_BOX(vbox
), event_box
, FALSE
, FALSE
, 0);
4253 gtk_container_add(GTK_CONTAINER(event_box
), gtkconv
->infopane_hbox
);
4254 gtk_widget_show(gtkconv
->infopane_hbox
);
4255 gtk_widget_add_events(event_box
,
4256 GDK_POINTER_MOTION_MASK
| GDK_LEAVE_NOTIFY_MASK
);
4257 g_signal_connect(G_OBJECT(event_box
), "button-press-event",
4258 G_CALLBACK(infopane_press_cb
), gtkconv
);
4260 pidgin_tooltip_setup_for_widget(event_box
, gtkconv
,
4261 pidgin_conv_create_tooltip
, NULL
);
4263 gtkconv
->infopane
= gtk_cell_view_new();
4264 gtkconv
->infopane_model
= gtk_list_store_new(CONV_NUM_COLUMNS
, G_TYPE_STRING
, G_TYPE_STRING
, GDK_TYPE_PIXBUF
, GDK_TYPE_PIXBUF
);
4265 gtk_cell_view_set_model(GTK_CELL_VIEW(gtkconv
->infopane
),
4266 GTK_TREE_MODEL(gtkconv
->infopane_model
));
4267 g_object_unref(gtkconv
->infopane_model
);
4268 gtk_list_store_append(gtkconv
->infopane_model
, &(gtkconv
->infopane_iter
));
4269 gtk_box_pack_start(GTK_BOX(gtkconv
->infopane_hbox
), gtkconv
->infopane
, TRUE
, TRUE
, 0);
4270 path
= gtk_tree_path_new_from_string("0");
4271 gtk_cell_view_set_displayed_row(GTK_CELL_VIEW(gtkconv
->infopane
), path
);
4272 gtk_tree_path_free(path
);
4275 /* This empty widget is used to ensure that the infopane is consistently
4276 sized for chat windows. The correct fix is to put an icon in the chat
4277 window as well, because that would make "Set Custom Icon" consistent
4278 for both the buddy list and the chat window, but PidginConversation
4279 is pretty much stuck until 3.0. */
4280 GtkWidget
*sizing_vbox
;
4281 sizing_vbox
= gtk_box_new(GTK_ORIENTATION_VERTICAL
, 0);
4282 gtk_widget_set_size_request(sizing_vbox
, -1, BUDDYICON_SIZE_MIN
);
4283 gtk_box_pack_start(GTK_BOX(gtkconv
->infopane_hbox
), sizing_vbox
, FALSE
, FALSE
, 0);
4284 gtk_widget_show(sizing_vbox
);
4287 gtkconv
->u
.im
->icon_container
= gtk_box_new(GTK_ORIENTATION_VERTICAL
, 0);
4289 if ((buddy
= purple_blist_find_buddy(purple_conversation_get_account(conv
),
4290 purple_conversation_get_name(conv
))) != NULL
) {
4291 PurpleContact
*contact
= purple_buddy_get_contact(buddy
);
4293 buddyicon_size
= purple_blist_node_get_int((PurpleBlistNode
*)contact
, "pidgin-infopane-iconsize");
4296 buddyicon_size
= CLAMP(buddyicon_size
, BUDDYICON_SIZE_MIN
, BUDDYICON_SIZE_MAX
);
4297 gtk_widget_set_size_request(gtkconv
->u
.im
->icon_container
, -1, buddyicon_size
);
4299 gtk_box_pack_start(GTK_BOX(gtkconv
->infopane_hbox
),
4300 gtkconv
->u
.im
->icon_container
, FALSE
, FALSE
, 0);
4302 gtk_widget_show(gtkconv
->u
.im
->icon_container
);
4305 gtk_widget_show(gtkconv
->infopane
);
4307 rend
= gtk_cell_renderer_pixbuf_new();
4308 gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(gtkconv
->infopane
), rend
, FALSE
);
4309 gtk_cell_layout_set_attributes(GTK_CELL_LAYOUT(gtkconv
->infopane
), rend
, "stock-id", CONV_ICON_COLUMN
, NULL
);
4310 g_object_set(rend
, "xalign", 0.0, "xpad", 6, "ypad", 0,
4311 "stock-size", gtk_icon_size_from_name(PIDGIN_ICON_SIZE_TANGO_EXTRA_SMALL
),
4314 rend
= gtk_cell_renderer_text_new();
4315 gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(gtkconv
->infopane
), rend
, TRUE
);
4316 gtk_cell_layout_set_attributes(GTK_CELL_LAYOUT(gtkconv
->infopane
), rend
, "markup", CONV_TEXT_COLUMN
, NULL
);
4317 g_object_set(rend
, "ypad", 0, "yalign", 0.5, NULL
);
4319 g_object_set(rend
, "ellipsize", PANGO_ELLIPSIZE_END
, NULL
);
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
, "pixbuf", CONV_PROTOCOL_ICON_COLUMN
, NULL
);
4324 g_object_set(rend
, "xalign", 0.0, "xpad", 3, "ypad", 0, NULL
);
4326 rend
= gtk_cell_renderer_pixbuf_new();
4327 gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(gtkconv
->infopane
), rend
, FALSE
);
4328 gtk_cell_layout_set_attributes(GTK_CELL_LAYOUT(gtkconv
->infopane
), rend
, "pixbuf", CONV_EMBLEM_COLUMN
, NULL
);
4329 g_object_set(rend
, "xalign", 0.0, "xpad", 6, "ypad", 0, NULL
);
4331 /* Setup the history widget */
4332 sw
= gtk_scrolled_window_new(NULL
, NULL
);
4333 gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(sw
), GTK_SHADOW_IN
);
4334 gtk_scrolled_window_set_policy(
4335 GTK_SCROLLED_WINDOW(sw
),
4340 gtkconv
->history_buffer
= talkatu_history_buffer_new();
4341 gtkconv
->history
= talkatu_history_new();
4342 gtk_text_view_set_buffer(GTK_TEXT_VIEW(gtkconv
->history
), gtkconv
->history_buffer
);
4343 gtk_text_view_set_wrap_mode(GTK_TEXT_VIEW(gtkconv
->history
), GTK_WRAP_WORD
);
4344 gtk_container_add(GTK_CONTAINER(sw
), gtkconv
->history
);
4350 setup_chat_topic(gtkconv
, vbox
);
4352 /* Add the talkatu history */
4353 hpaned
= gtk_paned_new(GTK_ORIENTATION_HORIZONTAL
);
4354 gtk_box_pack_start(GTK_BOX(vbox
), hpaned
, TRUE
, TRUE
, 0);
4355 gtk_widget_show(hpaned
);
4356 gtk_paned_pack1(GTK_PANED(hpaned
), sw
, TRUE
, TRUE
);
4358 /* Now add the userlist */
4359 setup_chat_userlist(gtkconv
, hpaned
);
4361 gtk_box_pack_start(GTK_BOX(vbox
), sw
, TRUE
, TRUE
, 0);
4363 gtk_widget_show_all(sw
);
4365 g_object_set_data(G_OBJECT(gtkconv
->history
), "gtkconv", gtkconv
);
4367 g_signal_connect(G_OBJECT(gtkconv
->history
), "key_press_event",
4368 G_CALLBACK(refocus_entry_cb
), gtkconv
);
4369 g_signal_connect(G_OBJECT(gtkconv
->history
), "key_release_event",
4370 G_CALLBACK(refocus_entry_cb
), gtkconv
);
4372 gtkconv
->lower_hbox
= gtk_box_new(GTK_ORIENTATION_HORIZONTAL
, PIDGIN_HIG_BOX_SPACE
);
4373 gtk_box_pack_start(GTK_BOX(vbox
), gtkconv
->lower_hbox
, FALSE
, FALSE
, 0);
4374 gtk_widget_show(gtkconv
->lower_hbox
);
4376 /* Setup the entry widget and all signals */
4377 gtkconv
->editor
= talkatu_editor_new();
4378 talkatu_editor_set_buffer(TALKATU_EDITOR(gtkconv
->editor
), talkatu_html_buffer_new());
4379 gtk_box_pack_start(GTK_BOX(gtkconv
->lower_hbox
), gtkconv
->editor
, TRUE
, TRUE
, 0);
4381 view
= talkatu_editor_get_view(TALKATU_EDITOR(gtkconv
->editor
));
4382 gtk_widget_set_name(view
, "pidgin_conv_entry");
4383 talkatu_view_set_send_binding(TALKATU_VIEW(view
), TALKATU_VIEW_SEND_BINDING_RETURN
| TALKATU_VIEW_SEND_BINDING_KP_ENTER
);
4387 G_CALLBACK(send_cb
),
4392 /* For sending typing notifications for IMs */
4393 gtkconv
->u
.im
->typing_timer
= 0;
4394 gtkconv
->u
.im
->animate
= purple_prefs_get_bool(PIDGIN_PREFS_ROOT
"/conversations/im/animate_buddy_icons");
4395 gtkconv
->u
.im
->show_icon
= TRUE
;
4401 static PidginConversation
*
4402 pidgin_conv_find_gtkconv(PurpleConversation
* conv
)
4404 PurpleBuddy
*bud
= purple_blist_find_buddy(purple_conversation_get_account(conv
), purple_conversation_get_name(conv
));
4406 PurpleBlistNode
*cn
, *bn
;
4411 if (!(c
= purple_buddy_get_contact(bud
)))
4414 cn
= PURPLE_BLIST_NODE(c
);
4415 for (bn
= purple_blist_node_get_first_child(cn
); bn
; bn
= purple_blist_node_get_sibling_next(bn
)) {
4416 PurpleBuddy
*b
= PURPLE_BUDDY(bn
);
4417 PurpleIMConversation
*im
;
4418 if ((im
= purple_conversations_find_im_with_account(purple_buddy_get_name(b
), purple_buddy_get_account(b
)))) {
4419 if (PIDGIN_CONVERSATION(PURPLE_CONVERSATION(im
)))
4420 return PIDGIN_CONVERSATION(PURPLE_CONVERSATION(im
));
4428 buddy_update_cb(PurpleBlistNode
*bnode
, gpointer null
)
4432 g_return_if_fail(bnode
);
4433 if (!PURPLE_IS_BUDDY(bnode
))
4436 for (list
= pidgin_conv_windows_get_list(); list
; list
= list
->next
)
4438 PidginConvWindow
*win
= list
->data
;
4439 PurpleConversation
*conv
= pidgin_conv_window_get_active_conversation(win
);
4441 if (!PURPLE_IS_IM_CONVERSATION(conv
))
4444 pidgin_conv_update_fields(conv
, PIDGIN_CONV_MENU
);
4449 ignore_middle_click(GtkWidget
*widget
, GdkEventButton
*e
, gpointer null
)
4451 /* A click on the pane is propagated to the notebook containing the pane.
4452 * So if Stu accidentally aims high and middle clicks on the pane-handle,
4453 * it causes a conversation tab to close. Let's stop that from happening.
4455 if (e
->button
== GDK_BUTTON_MIDDLE
&& e
->type
== GDK_BUTTON_PRESS
)
4460 /**************************************************************************
4461 * Conversation UI operations
4462 **************************************************************************/
4464 private_gtkconv_new(PurpleConversation
*conv
, gboolean hidden
)
4466 PidginConversation
*gtkconv
;
4467 GtkWidget
*pane
= NULL
;
4468 GtkWidget
*tab_cont
;
4469 PurpleBlistNode
*convnode
;
4470 GtkTargetList
*targets
;
4472 if (PURPLE_IS_IM_CONVERSATION(conv
) && (gtkconv
= pidgin_conv_find_gtkconv(conv
))) {
4473 purple_conversation_set_ui_data(conv
, gtkconv
);
4474 if (!g_list_find(gtkconv
->convs
, conv
))
4475 gtkconv
->convs
= g_list_prepend(gtkconv
->convs
, conv
);
4476 pidgin_conv_switch_active_conversation(conv
);
4480 gtkconv
= g_new0(PidginConversation
, 1);
4481 purple_conversation_set_ui_data(conv
, gtkconv
);
4482 gtkconv
->active_conv
= conv
;
4483 gtkconv
->convs
= g_list_prepend(gtkconv
->convs
, conv
);
4484 gtkconv
->send_history
= g_list_append(NULL
, NULL
);
4486 /* Setup some initial variables. */
4487 gtkconv
->unseen_state
= PIDGIN_UNSEEN_NONE
;
4488 gtkconv
->unseen_count
= 0;
4489 gtkconv
->last_flags
= 0;
4491 if (PURPLE_IS_IM_CONVERSATION(conv
)) {
4492 gtkconv
->u
.im
= g_malloc0(sizeof(PidginImPane
));
4493 } else if (PURPLE_IS_CHAT_CONVERSATION(conv
)) {
4494 gtkconv
->u
.chat
= g_malloc0(sizeof(PidginChatPane
));
4496 pane
= setup_common_pane(gtkconv
);
4499 if (PURPLE_IS_CHAT_CONVERSATION(conv
))
4500 g_free(gtkconv
->u
.chat
);
4501 else if (PURPLE_IS_IM_CONVERSATION(conv
))
4502 g_free(gtkconv
->u
.im
);
4505 purple_conversation_set_ui_data(conv
, NULL
);
4509 g_signal_connect(G_OBJECT(pane
), "button_press_event",
4510 G_CALLBACK(ignore_middle_click
), NULL
);
4512 /* Setup the container for the tab. */
4513 gtkconv
->tab_cont
= tab_cont
= gtk_box_new(GTK_ORIENTATION_VERTICAL
, PIDGIN_HIG_BOX_SPACE
);
4514 g_object_set_data(G_OBJECT(tab_cont
), "PidginConversation", gtkconv
);
4515 gtk_container_set_border_width(GTK_CONTAINER(tab_cont
), PIDGIN_HIG_BOX_SPACE
);
4516 gtk_box_pack_start(GTK_BOX(tab_cont
), pane
, TRUE
, TRUE
, 0);
4517 gtk_widget_show(pane
);
4519 convnode
= get_conversation_blist_node(conv
);
4520 if (convnode
== NULL
|| !purple_blist_node_get_bool(convnode
, "gtk-mute-sound"))
4521 gtkconv
->make_sound
= TRUE
;
4523 if (convnode
!= NULL
&& purple_blist_node_has_setting(convnode
, "enable-logging")) {
4524 gboolean logging
= purple_blist_node_get_bool(convnode
, "enable-logging");
4525 purple_conversation_set_logging(conv
, logging
);
4528 talkatu_editor_set_toolbar_visible(
4529 TALKATU_EDITOR(gtkconv
->editor
),
4530 purple_prefs_get_bool(PIDGIN_PREFS_ROOT
"/conversations/show_formatting_toolbar")
4533 if (purple_prefs_get_bool(PIDGIN_PREFS_ROOT
"/conversations/im/show_buddy_icons"))
4534 gtk_widget_show(gtkconv
->infopane_hbox
);
4536 gtk_widget_hide(gtkconv
->infopane_hbox
);
4539 g_signal_connect_swapped(G_OBJECT(pane
), "focus",
4540 G_CALLBACK(gtk_widget_grab_focus
),
4544 pidgin_conv_window_add_gtkconv(hidden_convwin
, gtkconv
);
4546 pidgin_conv_placement_place(gtkconv
);
4548 if (generated_nick_colors
== NULL
) {
4551 color
= gtk_widget_get_style(gtkconv
->history
)->base
[GTK_STATE_NORMAL
];
4552 rgba
.red
= color
.red
/ 65535.0;
4553 rgba
.green
= color
.green
/ 65535.0;
4554 rgba
.blue
= color
.blue
/ 65535.0;
4556 generated_nick_colors
= generate_nick_colors(NICK_COLOR_GENERATE_COUNT
, rgba
);
4559 gtkconv
->nick_colors
= g_array_ref(generated_nick_colors
);
4563 pidgin_conv_new_hidden(PurpleConversation
*conv
)
4565 private_gtkconv_new(conv
, TRUE
);
4569 pidgin_conv_new(PurpleConversation
*conv
)
4571 private_gtkconv_new(conv
, FALSE
);
4572 if (PIDGIN_IS_PIDGIN_CONVERSATION(conv
))
4573 purple_signal_emit(pidgin_conversations_get_handle(),
4574 "conversation-displayed", PIDGIN_CONVERSATION(conv
));
4578 received_im_msg_cb(PurpleAccount
*account
, char *sender
, char *message
,
4579 PurpleConversation
*conv
, PurpleMessageFlags flags
)
4581 PurpleConversationUiOps
*ui_ops
= pidgin_conversations_get_conv_ui_ops();
4582 gboolean hide
= FALSE
;
4585 /* create hidden conv if hide_new pref is always */
4586 if (purple_strequal(purple_prefs_get_string(PIDGIN_PREFS_ROOT
"/conversations/im/hide_new"), "always"))
4589 /* create hidden conv if hide_new pref is away and account is away */
4590 if (purple_strequal(purple_prefs_get_string(PIDGIN_PREFS_ROOT
"/conversations/im/hide_new"), "away") &&
4591 !purple_status_is_available(purple_account_get_active_status(account
)))
4594 if (conv
&& PIDGIN_IS_PIDGIN_CONVERSATION(conv
) && !hide
) {
4595 PidginConversation
*gtkconv
= PIDGIN_CONVERSATION(conv
);
4596 if (gtkconv
->win
== hidden_convwin
) {
4597 pidgin_conv_attach_to_conversation(gtkconv
->active_conv
);
4603 ui_ops
->create_conversation
= pidgin_conv_new_hidden
;
4604 purple_im_conversation_new(account
, sender
);
4605 ui_ops
->create_conversation
= pidgin_conv_new
;
4608 /* Somebody wants to keep this conversation around, so don't time it out */
4610 timer
= GPOINTER_TO_INT(g_object_get_data(G_OBJECT(conv
), "close-timer"));
4612 g_source_remove(timer
);
4613 g_object_set_data(G_OBJECT(conv
), "close-timer", GINT_TO_POINTER(0));
4619 pidgin_conv_destroy(PurpleConversation
*conv
)
4621 PidginConversation
*gtkconv
= PIDGIN_CONVERSATION(conv
);
4623 gtkconv
->convs
= g_list_remove(gtkconv
->convs
, conv
);
4624 /* Don't destroy ourselves until all our convos are gone */
4625 if (gtkconv
->convs
) {
4626 /* Make sure the destroyed conversation is not the active one */
4627 if (gtkconv
->active_conv
== conv
) {
4628 gtkconv
->active_conv
= gtkconv
->convs
->data
;
4629 purple_conversation_update(gtkconv
->active_conv
, PURPLE_CONVERSATION_UPDATE_FEATURES
);
4634 pidgin_conv_window_remove_gtkconv(gtkconv
->win
, gtkconv
);
4636 /* If the "Save Conversation" or "Save Icon" dialogs are open then close them */
4637 purple_request_close_with_handle(gtkconv
);
4638 purple_notify_close_with_handle(gtkconv
);
4640 gtk_widget_destroy(gtkconv
->tab_cont
);
4641 g_object_unref(gtkconv
->tab_cont
);
4643 if (PURPLE_IS_IM_CONVERSATION(conv
)) {
4644 if (gtkconv
->u
.im
->icon_timer
!= 0)
4645 g_source_remove(gtkconv
->u
.im
->icon_timer
);
4647 if (gtkconv
->u
.im
->anim
!= NULL
)
4648 g_object_unref(G_OBJECT(gtkconv
->u
.im
->anim
));
4650 if (gtkconv
->u
.im
->typing_timer
!= 0)
4651 g_source_remove(gtkconv
->u
.im
->typing_timer
);
4653 g_free(gtkconv
->u
.im
);
4654 } else if (PURPLE_IS_CHAT_CONVERSATION(conv
)) {
4655 purple_signals_disconnect_by_handle(gtkconv
->u
.chat
);
4656 g_free(gtkconv
->u
.chat
);
4659 gtkconv
->send_history
= g_list_first(gtkconv
->send_history
);
4660 g_list_foreach(gtkconv
->send_history
, (GFunc
)g_free
, NULL
);
4661 g_list_free(gtkconv
->send_history
);
4663 if (gtkconv
->attach_timer
) {
4664 g_source_remove(gtkconv
->attach_timer
);
4667 g_array_unref(gtkconv
->nick_colors
);
4674 get_text_tag_color(GtkTextTag
*tag
)
4676 GdkRGBA
*color
= NULL
;
4677 gboolean set
= FALSE
;
4678 static char colcode
[] = "#XXXXXX";
4680 g_object_get(G_OBJECT(tag
), "foreground-set", &set
, "foreground-rgba", &color
, NULL
);
4682 g_snprintf(colcode
, sizeof(colcode
), "#%02x%02x%02x",
4683 (unsigned int)(color
->red
* 255),
4684 (unsigned int)(color
->green
* 255),
4685 (unsigned int)(color
->blue
* 255));
4689 gdk_rgba_free(color
);
4693 /* The callback for an event on a link tag. */
4694 static gboolean
buddytag_event(GtkTextTag
*tag
, GObject
*imhtml
,
4695 GdkEvent
*event
, GtkTextIter
*arg2
, gpointer data
)
4697 if (event
->type
== GDK_BUTTON_PRESS
4698 || event
->type
== GDK_2BUTTON_PRESS
) {
4699 GdkEventButton
*btn_event
= (GdkEventButton
*) event
;
4700 PurpleConversation
*conv
= data
;
4704 g_object_get(G_OBJECT(tag
), "name", &name
, NULL
);
4706 /* strlen("BUDDY " or "HILIT ") == 6 */
4707 g_return_val_if_fail((name
!= NULL
) && (strlen(name
) > 6), FALSE
);
4709 buddyname
= name
+ 6;
4711 /* emit chat-nick-clicked signal */
4712 if (event
->type
== GDK_BUTTON_PRESS
) {
4713 gint plugin_return
= GPOINTER_TO_INT(purple_signal_emit_return_1(
4714 pidgin_conversations_get_handle(), "chat-nick-clicked",
4715 data
, buddyname
, btn_event
->button
));
4716 if (plugin_return
) {
4722 if (btn_event
->button
== GDK_BUTTON_PRIMARY
&& event
->type
== GDK_2BUTTON_PRESS
) {
4723 chat_do_im(PIDGIN_CONVERSATION(conv
), buddyname
);
4727 } else if (btn_event
->button
== GDK_BUTTON_MIDDLE
&& event
->type
== GDK_2BUTTON_PRESS
) {
4728 chat_do_info(PIDGIN_CONVERSATION(conv
), buddyname
);
4732 } else if (gdk_event_triggers_context_menu(event
)) {
4733 GtkTextIter start
, end
;
4735 /* we shouldn't display the popup
4736 * if the user has selected something: */
4737 if (!gtk_text_buffer_get_selection_bounds(
4738 gtk_text_iter_get_buffer(arg2
),
4740 GtkWidget
*menu
= NULL
;
4741 PurpleConnection
*gc
=
4742 purple_conversation_get_connection(conv
);
4744 menu
= create_chat_menu(conv
, buddyname
, gc
);
4745 gtk_menu_popup_at_pointer(GTK_MENU(menu
), event
);
4749 /* Don't propagate the event any further */
4761 static GtkTextTag
*get_buddy_tag(PurpleChatConversation
*chat
, const char *who
, PurpleMessageFlags flag
,
4766 PidginConversation
*gtkconv
= PIDGIN_CONVERSATION(conv
);
4767 GtkTextTag
*buddytag
;
4769 gboolean highlight
= (flag
& PURPLE_MESSAGE_NICK
);
4770 GtkTextBuffer
*buffer
= GTK_IMHTML(gtkconv
->imhtml
)->text_buffer
;
4772 str
= g_strdup_printf(highlight
? "HILIT %s" : "BUDDY %s", who
);
4774 buddytag
= gtk_text_tag_table_lookup(
4775 gtk_text_buffer_get_tag_table(buffer
), str
);
4777 if (buddytag
== NULL
&& create
) {
4779 buddytag
= gtk_text_buffer_create_tag(buffer
, str
,
4780 "foreground", get_text_tag_color(gtk_text_tag_table_lookup(
4781 gtk_text_buffer_get_tag_table(buffer
), "highlight-name")),
4782 "weight", PANGO_WEIGHT_BOLD
,
4785 buddytag
= gtk_text_buffer_create_tag(
4787 "foreground-rgba", get_nick_color(gtkconv
, who
),
4788 "weight", purple_blist_find_buddy(purple_conversation_get_account(conv
), who
) ? PANGO_WEIGHT_BOLD
: PANGO_WEIGHT_NORMAL
,
4791 g_object_set_data(G_OBJECT(buddytag
), "cursor", "");
4792 g_signal_connect(G_OBJECT(buddytag
), "event",
4793 G_CALLBACK(buddytag_event
), conv
);
4804 writing_msg(PurpleConversation
*conv
, PurpleMessage
*msg
, gpointer _unused
)
4806 PidginConversation
*gtkconv
;
4808 g_return_val_if_fail(msg
!= NULL
, FALSE
);
4810 if (!(purple_message_get_flags(msg
) & PURPLE_MESSAGE_ACTIVE_ONLY
))
4813 g_return_val_if_fail(conv
!= NULL
, FALSE
);
4814 gtkconv
= PIDGIN_CONVERSATION(conv
);
4815 g_return_val_if_fail(gtkconv
!= NULL
, FALSE
);
4817 if (conv
== gtkconv
->active_conv
)
4820 purple_debug_info("gtkconv",
4821 "Suppressing message for an inactive conversation");
4827 pidgin_conv_write_conv(PurpleConversation
*conv
, PurpleMessage
*pmsg
)
4829 PidginMessage
*pidgin_msg
= NULL
;
4830 PurpleMessageFlags flags
;
4831 PidginConversation
*gtkconv
;
4832 PurpleConnection
*gc
;
4833 PurpleAccount
*account
;
4834 gboolean plugin_return
;
4836 g_return_if_fail(conv
!= NULL
);
4837 gtkconv
= PIDGIN_CONVERSATION(conv
);
4838 g_return_if_fail(gtkconv
!= NULL
);
4839 flags
= purple_message_get_flags(pmsg
);
4842 if (gtkconv
->attach_timer
) {
4843 /* We are currently in the process of filling up the buffer with the message
4844 * history of the conversation. So we do not need to add the message here.
4845 * Instead, this message will be added to the message-list, which in turn will
4846 * be processed and displayed by the attach-callback.
4851 if (conv
!= gtkconv
->active_conv
)
4853 /* Set the active conversation to the one that just messaged us. */
4854 /* TODO: consider not doing this if the account is offline or something */
4855 if (flags
& (PURPLE_MESSAGE_SEND
| PURPLE_MESSAGE_RECV
))
4856 pidgin_conv_switch_active_conversation(conv
);
4860 account
= purple_conversation_get_account(conv
);
4861 g_return_if_fail(account
!= NULL
);
4862 gc
= purple_account_get_connection(account
);
4863 g_return_if_fail(gc
!= NULL
|| !(flags
& (PURPLE_MESSAGE_SEND
| PURPLE_MESSAGE_RECV
)));
4865 plugin_return
= GPOINTER_TO_INT(purple_signal_emit_return_1(
4866 pidgin_conversations_get_handle(),
4867 (PURPLE_IS_IM_CONVERSATION(conv
) ? "displaying-im-msg" : "displaying-chat-msg"),
4874 pidgin_msg
= pidgin_message_new(pmsg
);
4875 talkatu_history_buffer_write_message(
4876 TALKATU_HISTORY_BUFFER(gtkconv
->history_buffer
),
4877 TALKATU_MESSAGE(pidgin_msg
)
4880 /* Tab highlighting stuff */
4881 if (!(flags
& PURPLE_MESSAGE_SEND
) && !pidgin_conv_has_focus(conv
))
4883 PidginUnseenState unseen
= PIDGIN_UNSEEN_NONE
;
4885 if ((flags
& PURPLE_MESSAGE_NICK
) == PURPLE_MESSAGE_NICK
)
4886 unseen
= PIDGIN_UNSEEN_NICK
;
4887 else if (((flags
& PURPLE_MESSAGE_SYSTEM
) == PURPLE_MESSAGE_SYSTEM
) ||
4888 ((flags
& PURPLE_MESSAGE_ERROR
) == PURPLE_MESSAGE_ERROR
))
4889 unseen
= PIDGIN_UNSEEN_EVENT
;
4890 else if ((flags
& PURPLE_MESSAGE_NO_LOG
) == PURPLE_MESSAGE_NO_LOG
)
4891 unseen
= PIDGIN_UNSEEN_NO_LOG
;
4893 unseen
= PIDGIN_UNSEEN_TEXT
;
4895 gtkconv_set_unseen(gtkconv
, unseen
);
4898 /* on rejoin only request message history from after this message */
4899 if (flags
& (PURPLE_MESSAGE_SEND
| PURPLE_MESSAGE_RECV
) &&
4900 PURPLE_IS_CHAT_CONVERSATION(conv
)) {
4901 PurpleChat
*chat
= purple_blist_find_chat(
4902 purple_conversation_get_account(conv
),
4903 purple_conversation_get_name(conv
));
4905 GHashTable
*comps
= purple_chat_get_components(chat
);
4906 time_t now
, history_since
, prev_history_since
= 0;
4907 struct tm
*history_since_tm
;
4908 const char *history_since_s
, *prev_history_since_s
;
4910 history_since
= purple_message_get_time(pmsg
) + 1;
4912 prev_history_since_s
= g_hash_table_lookup(comps
,
4914 if (prev_history_since_s
!= NULL
)
4915 prev_history_since
= purple_str_to_time(
4916 prev_history_since_s
, TRUE
, NULL
, NULL
,
4920 /* in case of incorrectly stored timestamps */
4921 if (prev_history_since
> now
)
4922 prev_history_since
= now
;
4923 /* in case of delayed messages */
4924 if (history_since
< prev_history_since
)
4925 history_since
= prev_history_since
;
4927 history_since_tm
= gmtime(&history_since
);
4928 history_since_s
= purple_utf8_strftime(
4929 "%Y-%m-%dT%H:%M:%SZ", history_since_tm
);
4930 if (!purple_strequal(prev_history_since_s
,
4932 g_hash_table_replace(comps
,
4933 g_strdup("history_since"),
4934 g_strdup(history_since_s
));
4938 purple_signal_emit(pidgin_conversations_get_handle(),
4939 (PURPLE_IS_IM_CONVERSATION(conv
) ? "displayed-im-msg" : "displayed-chat-msg"),
4941 update_typing_message(gtkconv
, NULL
);
4944 static gboolean
get_iter_from_chatuser(PurpleChatUser
*cb
, GtkTreeIter
*iter
)
4946 GtkTreeRowReference
*ref
;
4948 GtkTreeModel
*model
;
4950 g_return_val_if_fail(cb
!= NULL
, FALSE
);
4952 ref
= purple_chat_user_get_ui_data(cb
);
4956 if ((path
= gtk_tree_row_reference_get_path(ref
)) == NULL
)
4959 model
= gtk_tree_row_reference_get_model(ref
);
4960 if (!gtk_tree_model_get_iter(GTK_TREE_MODEL(model
), iter
, path
)) {
4961 gtk_tree_path_free(path
);
4965 gtk_tree_path_free(path
);
4970 pidgin_conv_chat_add_users(PurpleChatConversation
*chat
, GList
*cbuddies
, gboolean new_arrivals
)
4972 PidginConversation
*gtkconv
;
4973 PidginChatPane
*gtkchat
;
4980 gtkconv
= PIDGIN_CONVERSATION(PURPLE_CONVERSATION(chat
));
4981 gtkchat
= gtkconv
->u
.chat
;
4983 num_users
= purple_chat_conversation_get_users_count(chat
);
4985 g_snprintf(tmp
, sizeof(tmp
),
4986 ngettext("%d person in room", "%d people in room",
4990 gtk_label_set_text(GTK_LABEL(gtkchat
->count
), tmp
);
4992 ls
= GTK_LIST_STORE(gtk_tree_view_get_model(GTK_TREE_VIEW(gtkchat
->list
)));
4994 gtk_tree_sortable_set_sort_column_id(GTK_TREE_SORTABLE(ls
), GTK_TREE_SORTABLE_UNSORTED_SORT_COLUMN_ID
,
4995 GTK_TREE_SORTABLE_UNSORTED_SORT_COLUMN_ID
);
4999 add_chat_user_common(chat
, (PurpleChatUser
*)l
->data
, NULL
);
5003 /* Currently GTK+ maintains our sorted list after it's in the tree.
5004 * This may change if it turns out we can manage it faster ourselves.
5006 gtk_tree_sortable_set_sort_column_id(GTK_TREE_SORTABLE(ls
), CHAT_USERS_ALIAS_KEY_COLUMN
,
5007 GTK_SORT_ASCENDING
);
5011 pidgin_conv_chat_rename_user(PurpleChatConversation
*chat
, const char *old_name
,
5012 const char *new_name
, const char *new_alias
)
5014 PidginConversation
*gtkconv
;
5015 PidginChatPane
*gtkchat
;
5016 PurpleChatUser
*old_chatuser
, *new_chatuser
;
5018 GtkTreeModel
*model
;
5021 gtkconv
= PIDGIN_CONVERSATION(PURPLE_CONVERSATION(chat
));
5022 gtkchat
= gtkconv
->u
.chat
;
5024 model
= gtk_tree_view_get_model(GTK_TREE_VIEW(gtkchat
->list
));
5026 if (!gtk_tree_model_get_iter_first(GTK_TREE_MODEL(model
), &iter
))
5029 if ((tag
= get_buddy_tag(chat
, old_name
, 0, FALSE
)))
5030 g_object_set(G_OBJECT(tag
), "style", PANGO_STYLE_ITALIC
, NULL
);
5031 if ((tag
= get_buddy_tag(chat
, old_name
, PURPLE_MESSAGE_NICK
, FALSE
)))
5032 g_object_set(G_OBJECT(tag
), "style", PANGO_STYLE_ITALIC
, NULL
);
5034 old_chatuser
= purple_chat_conversation_find_user(chat
, old_name
);
5038 if (get_iter_from_chatuser(old_chatuser
, &iter
)) {
5039 GtkTreeRowReference
*ref
= purple_chat_user_get_ui_data(old_chatuser
);
5041 gtk_list_store_remove(GTK_LIST_STORE(model
), &iter
);
5042 gtk_tree_row_reference_free(ref
);
5043 purple_chat_user_set_ui_data(old_chatuser
, NULL
);
5046 g_return_if_fail(new_alias
!= NULL
);
5048 new_chatuser
= purple_chat_conversation_find_user(chat
, new_name
);
5050 add_chat_user_common(chat
, new_chatuser
, old_name
);
5054 pidgin_conv_chat_remove_users(PurpleChatConversation
*chat
, GList
*users
)
5056 PidginConversation
*gtkconv
;
5057 PidginChatPane
*gtkchat
;
5059 GtkTreeModel
*model
;
5066 gtkconv
= PIDGIN_CONVERSATION(PURPLE_CONVERSATION(chat
));
5067 gtkchat
= gtkconv
->u
.chat
;
5069 num_users
= purple_chat_conversation_get_users_count(chat
);
5071 for (l
= users
; l
!= NULL
; l
= l
->next
) {
5072 model
= gtk_tree_view_get_model(GTK_TREE_VIEW(gtkchat
->list
));
5074 if (!gtk_tree_model_get_iter_first(GTK_TREE_MODEL(model
), &iter
))
5081 gtk_tree_model_get(GTK_TREE_MODEL(model
), &iter
,
5082 CHAT_USERS_NAME_COLUMN
, &val
, -1);
5084 if (!purple_utf8_strcasecmp((char *)l
->data
, val
)) {
5085 f
= gtk_list_store_remove(GTK_LIST_STORE(model
), &iter
);
5088 f
= gtk_tree_model_iter_next(GTK_TREE_MODEL(model
), &iter
);
5093 if ((tag
= get_buddy_tag(chat
, l
->data
, 0, FALSE
)))
5094 g_object_set(G_OBJECT(tag
), "style", PANGO_STYLE_ITALIC
, NULL
);
5095 if ((tag
= get_buddy_tag(chat
, l
->data
, PURPLE_MESSAGE_NICK
, FALSE
)))
5096 g_object_set(G_OBJECT(tag
), "style", PANGO_STYLE_ITALIC
, NULL
);
5099 g_snprintf(tmp
, sizeof(tmp
),
5100 ngettext("%d person in room", "%d people in room",
5101 num_users
), num_users
);
5103 gtk_label_set_text(GTK_LABEL(gtkchat
->count
), tmp
);
5107 pidgin_conv_chat_update_user(PurpleChatUser
*chatuser
)
5109 PurpleChatConversation
*chat
;
5110 PidginConversation
*gtkconv
;
5111 PidginChatPane
*gtkchat
;
5113 GtkTreeModel
*model
;
5118 chat
= purple_chat_user_get_chat(chatuser
);
5119 gtkconv
= PIDGIN_CONVERSATION(PURPLE_CONVERSATION(chat
));
5120 gtkchat
= gtkconv
->u
.chat
;
5122 model
= gtk_tree_view_get_model(GTK_TREE_VIEW(gtkchat
->list
));
5124 if (!gtk_tree_model_get_iter_first(GTK_TREE_MODEL(model
), &iter
))
5127 if (get_iter_from_chatuser(chatuser
, &iter
)) {
5128 GtkTreeRowReference
*ref
= purple_chat_user_get_ui_data(chatuser
);
5129 gtk_list_store_remove(GTK_LIST_STORE(model
), &iter
);
5130 gtk_tree_row_reference_free(ref
);
5131 purple_chat_user_set_ui_data(chatuser
, NULL
);
5135 add_chat_user_common(chat
, chatuser
, NULL
);
5139 pidgin_conv_has_focus(PurpleConversation
*conv
)
5141 PidginConversation
*gtkconv
= PIDGIN_CONVERSATION(conv
);
5142 PidginConvWindow
*win
;
5147 g_object_get(G_OBJECT(win
->window
), "has-toplevel-focus", &has_focus
, NULL
);
5149 if (has_focus
&& pidgin_conv_window_is_active_conversation(conv
))
5156 * Makes sure all the menu items and all the buttons are hidden/shown and
5157 * sensitive/insensitive. This is called after changing tabs and when an
5158 * account signs on or off.
5161 gray_stuff_out(PidginConversation
*gtkconv
)
5163 PidginConvWindow
*win
;
5164 PurpleConversation
*conv
= gtkconv
->active_conv
;
5165 PurpleConnection
*gc
;
5166 PurpleProtocol
*protocol
= NULL
;
5167 GdkPixbuf
*window_icon
= NULL
;
5168 // PidginWebViewButtons buttons;
5169 PurpleAccount
*account
;
5171 win
= pidgin_conv_get_window(gtkconv
);
5172 gc
= purple_conversation_get_connection(conv
);
5173 account
= purple_conversation_get_account(conv
);
5176 protocol
= purple_connection_get_protocol(gc
);
5178 if (win
->menu
->send_to
!= NULL
)
5179 update_send_to_selection(win
);
5182 * Handle hiding and showing stuff based on what type of conv this is.
5183 * Stuff that Purple IMs support in general should be shown for IM
5184 * conversations. Stuff that Purple chats support in general should be
5185 * shown for chat conversations. It doesn't matter whether the protocol
5186 * supports it or not--that only affects if the button or menu item
5187 * is sensitive or not.
5189 if (PURPLE_IS_IM_CONVERSATION(conv
)) {
5190 /* Show stuff that applies to IMs, hide stuff that applies to chats */
5192 /* Deal with menu items */
5193 gtk_action_set_visible(win
->menu
->view_log
, TRUE
);
5194 gtk_action_set_visible(win
->menu
->send_file
, TRUE
);
5195 gtk_action_set_visible(win
->menu
->get_attention
, TRUE
);
5196 gtk_action_set_visible(win
->menu
->add_pounce
, TRUE
);
5197 gtk_action_set_visible(win
->menu
->get_info
, TRUE
);
5198 gtk_action_set_visible(win
->menu
->invite
, FALSE
);
5199 gtk_action_set_visible(win
->menu
->alias
, TRUE
);
5200 if (purple_account_privacy_check(account
, purple_conversation_get_name(conv
))) {
5201 gtk_action_set_visible(win
->menu
->unblock
, FALSE
);
5202 gtk_action_set_visible(win
->menu
->block
, TRUE
);
5204 gtk_action_set_visible(win
->menu
->block
, FALSE
);
5205 gtk_action_set_visible(win
->menu
->unblock
, TRUE
);
5208 if (purple_blist_find_buddy(account
, purple_conversation_get_name(conv
)) == NULL
) {
5209 gtk_action_set_visible(win
->menu
->add
, TRUE
);
5210 gtk_action_set_visible(win
->menu
->remove
, FALSE
);
5212 gtk_action_set_visible(win
->menu
->remove
, TRUE
);
5213 gtk_action_set_visible(win
->menu
->add
, FALSE
);
5216 gtk_action_set_visible(win
->menu
->insert_link
, TRUE
);
5217 gtk_action_set_visible(win
->menu
->insert_image
, TRUE
);
5218 } else if (PURPLE_IS_CHAT_CONVERSATION(conv
)) {
5219 /* Show stuff that applies to Chats, hide stuff that applies to IMs */
5221 /* Deal with menu items */
5222 gtk_action_set_visible(win
->menu
->view_log
, TRUE
);
5223 gtk_action_set_visible(win
->menu
->send_file
, FALSE
);
5224 gtk_action_set_visible(win
->menu
->get_attention
, FALSE
);
5225 gtk_action_set_visible(win
->menu
->add_pounce
, FALSE
);
5226 gtk_action_set_visible(win
->menu
->get_info
, FALSE
);
5227 gtk_action_set_visible(win
->menu
->invite
, TRUE
);
5228 gtk_action_set_visible(win
->menu
->alias
, TRUE
);
5229 gtk_action_set_visible(win
->menu
->block
, FALSE
);
5230 gtk_action_set_visible(win
->menu
->unblock
, FALSE
);
5232 if ((account
== NULL
) || purple_blist_find_chat(account
, purple_conversation_get_name(conv
)) == NULL
) {
5233 /* If the chat is NOT in the buddy list */
5234 gtk_action_set_visible(win
->menu
->add
, TRUE
);
5235 gtk_action_set_visible(win
->menu
->remove
, FALSE
);
5237 /* If the chat IS in the buddy list */
5238 gtk_action_set_visible(win
->menu
->add
, FALSE
);
5239 gtk_action_set_visible(win
->menu
->remove
, TRUE
);
5242 gtk_action_set_visible(win
->menu
->insert_link
, TRUE
);
5243 gtk_action_set_visible(win
->menu
->insert_image
, TRUE
);
5247 * Handle graying stuff out based on whether an account is connected
5248 * and what features that account supports.
5251 (!PURPLE_IS_CHAT_CONVERSATION(conv
) ||
5252 !purple_chat_conversation_has_left(PURPLE_CHAT_CONVERSATION(conv
)) ))
5254 PurpleConnectionFlags features
= purple_conversation_get_features(conv
);
5255 /* Account is online */
5256 /* Deal with the toolbar */
5258 if (features
& PURPLE_CONNECTION_FLAG_HTML
)
5260 buttons
= PIDGIN_WEBVIEW_ALL
; /* Everything on */
5261 if (features
& PURPLE_CONNECTION_FLAG_NO_BGCOLOR
)
5262 buttons
&= ~PIDGIN_WEBVIEW_BACKCOLOR
;
5263 if (features
& PURPLE_CONNECTION_FLAG_NO_FONTSIZE
)
5265 buttons
&= ~PIDGIN_WEBVIEW_GROW
;
5266 buttons
&= ~PIDGIN_WEBVIEW_SHRINK
;
5268 if (features
& PURPLE_CONNECTION_FLAG_NO_URLDESC
)
5269 buttons
&= ~PIDGIN_WEBVIEW_LINKDESC
5271 buttons
= PIDGIN_WEBVIEW_SMILEY
| PIDGIN_WEBVIEW_IMAGE
;
5274 if (features
& PURPLE_CONNECTION_FLAG_NO_IMAGES
)
5275 buttons
&= ~PIDGIN_WEBVIEW_IMAGE
;
5277 if (features
& PURPLE_CONNECTION_FLAG_ALLOW_CUSTOM_SMILEY
)
5278 buttons
|= PIDGIN_WEBVIEW_CUSTOM_SMILEY
;
5280 buttons
&= ~PIDGIN_WEBVIEW_CUSTOM_SMILEY
;
5282 pidgin_webview_set_format_functions(PIDGIN_WEBVIEW(gtkconv
->entry
), buttons
);
5285 /* Deal with menu items */
5286 gtk_action_set_sensitive(win
->menu
->view_log
, TRUE
);
5287 gtk_action_set_sensitive(win
->menu
->add_pounce
, TRUE
);
5288 gtk_action_set_sensitive(win
->menu
->get_info
, (PURPLE_PROTOCOL_IMPLEMENTS(protocol
, SERVER
, get_info
)));
5289 gtk_action_set_sensitive(win
->menu
->invite
, (PURPLE_PROTOCOL_IMPLEMENTS(protocol
, CHAT
, invite
)));
5290 gtk_action_set_sensitive(win
->menu
->insert_link
, (features
& PURPLE_CONNECTION_FLAG_HTML
));
5291 gtk_action_set_sensitive(win
->menu
->insert_image
, !(features
& PURPLE_CONNECTION_FLAG_NO_IMAGES
));
5293 if (PURPLE_IS_IM_CONVERSATION(conv
))
5295 gboolean can_send_file
= FALSE
;
5296 const gchar
*name
= purple_conversation_get_name(conv
);
5298 if (PURPLE_IS_PROTOCOL_XFER(protocol
) &&
5299 purple_protocol_xfer_can_receive(PURPLE_PROTOCOL_XFER(protocol
), gc
, name
)
5301 can_send_file
= TRUE
;
5304 gtk_action_set_sensitive(win
->menu
->add
, (PURPLE_PROTOCOL_IMPLEMENTS(protocol
, SERVER
, add_buddy
)));
5305 gtk_action_set_sensitive(win
->menu
->remove
, (PURPLE_PROTOCOL_IMPLEMENTS(protocol
, SERVER
, remove_buddy
)));
5306 gtk_action_set_sensitive(win
->menu
->send_file
, can_send_file
);
5307 gtk_action_set_sensitive(win
->menu
->get_attention
, (PURPLE_IS_PROTOCOL_ATTENTION(protocol
)));
5308 gtk_action_set_sensitive(win
->menu
->alias
,
5309 (account
!= NULL
) &&
5310 (purple_blist_find_buddy(account
, purple_conversation_get_name(conv
)) != NULL
));
5312 else if (PURPLE_IS_CHAT_CONVERSATION(conv
))
5314 gtk_action_set_sensitive(win
->menu
->add
, (PURPLE_PROTOCOL_IMPLEMENTS(protocol
, CHAT
, join
)));
5315 gtk_action_set_sensitive(win
->menu
->remove
, (PURPLE_PROTOCOL_IMPLEMENTS(protocol
, CHAT
, join
)));
5316 gtk_action_set_sensitive(win
->menu
->alias
,
5317 (account
!= NULL
) &&
5318 (purple_blist_find_chat(account
, purple_conversation_get_name(conv
)) != NULL
));
5322 /* Account is offline */
5323 /* Or it's a chat that we've left. */
5325 /* Then deal with menu items */
5326 gtk_action_set_sensitive(win
->menu
->view_log
, TRUE
);
5327 gtk_action_set_sensitive(win
->menu
->send_file
, FALSE
);
5328 gtk_action_set_sensitive(win
->menu
->get_attention
, FALSE
);
5329 gtk_action_set_sensitive(win
->menu
->add_pounce
, TRUE
);
5330 gtk_action_set_sensitive(win
->menu
->get_info
, FALSE
);
5331 gtk_action_set_sensitive(win
->menu
->invite
, FALSE
);
5332 gtk_action_set_sensitive(win
->menu
->alias
, FALSE
);
5333 gtk_action_set_sensitive(win
->menu
->add
, FALSE
);
5334 gtk_action_set_sensitive(win
->menu
->remove
, FALSE
);
5335 gtk_action_set_sensitive(win
->menu
->insert_link
, TRUE
);
5336 gtk_action_set_sensitive(win
->menu
->insert_image
, FALSE
);
5340 * Update the window's icon
5342 if (pidgin_conv_window_is_active_conversation(conv
))
5345 if (PURPLE_IS_IM_CONVERSATION(conv
) &&
5346 (gtkconv
->u
.im
->anim
))
5348 PurpleBuddy
*buddy
= purple_blist_find_buddy(purple_conversation_get_account(conv
), purple_conversation_get_name(conv
));
5350 gdk_pixbuf_animation_get_static_image(gtkconv
->u
.im
->anim
);
5352 if (buddy
&& !PURPLE_BUDDY_IS_ONLINE(buddy
))
5353 gdk_pixbuf_saturate_and_pixelate(window_icon
, window_icon
, 0.0, FALSE
);
5355 g_object_ref(window_icon
);
5356 l
= g_list_append(l
, window_icon
);
5358 l
= pidgin_conv_get_tab_icons(conv
);
5360 gtk_window_set_icon_list(GTK_WINDOW(win
->window
), l
);
5361 if (window_icon
!= NULL
) {
5362 g_object_unref(G_OBJECT(window_icon
));
5369 pidgin_conv_update_fields(PurpleConversation
*conv
, PidginConvFields fields
)
5371 PidginConversation
*gtkconv
;
5372 PidginConvWindow
*win
;
5374 gtkconv
= PIDGIN_CONVERSATION(conv
);
5377 win
= pidgin_conv_get_window(gtkconv
);
5381 if (fields
& PIDGIN_CONV_SET_TITLE
)
5383 purple_conversation_autoset_title(conv
);
5386 if (fields
& PIDGIN_CONV_BUDDY_ICON
)
5388 if (PURPLE_IS_IM_CONVERSATION(conv
))
5389 pidgin_conv_update_buddy_icon(PURPLE_IM_CONVERSATION(conv
));
5392 if (fields
& PIDGIN_CONV_MENU
)
5394 gray_stuff_out(PIDGIN_CONVERSATION(conv
));
5395 generate_send_to_items(win
);
5396 regenerate_plugins_items(win
);
5399 if (fields
& PIDGIN_CONV_E2EE
)
5400 generate_e2ee_controls(win
);
5402 if (fields
& PIDGIN_CONV_TAB_ICON
)
5404 update_tab_icon(conv
);
5405 generate_send_to_items(win
); /* To update the icons in SendTo menu */
5408 if ((fields
& PIDGIN_CONV_TOPIC
) &&
5409 PURPLE_IS_CHAT_CONVERSATION(conv
))
5412 PidginChatPane
*gtkchat
= gtkconv
->u
.chat
;
5414 if (gtkchat
->topic_text
!= NULL
)
5416 topic
= purple_chat_conversation_get_topic(PURPLE_CHAT_CONVERSATION(conv
));
5418 gtk_entry_set_text(GTK_ENTRY(gtkchat
->topic_text
), topic
? topic
: "");
5419 gtk_widget_set_tooltip_text(gtkchat
->topic_text
,
5420 topic
? topic
: "");
5424 if ((fields
& PIDGIN_CONV_COLORIZE_TITLE
) ||
5425 (fields
& PIDGIN_CONV_SET_TITLE
) ||
5426 (fields
& PIDGIN_CONV_TOPIC
))
5429 PurpleIMConversation
*im
= NULL
;
5430 PurpleAccount
*account
= purple_conversation_get_account(conv
);
5431 PurpleBuddy
*buddy
= NULL
;
5432 char *markup
= NULL
;
5433 AtkObject
*accessibility_obj
;
5434 /* I think this is a little longer than it needs to be but I'm lazy. */
5437 if (PURPLE_IS_IM_CONVERSATION(conv
))
5438 im
= PURPLE_IM_CONVERSATION(conv
);
5440 if ((account
== NULL
) ||
5441 !purple_account_is_connected(account
) ||
5442 (PURPLE_IS_CHAT_CONVERSATION(conv
)
5443 && purple_chat_conversation_has_left(PURPLE_CHAT_CONVERSATION(conv
))))
5444 title
= g_strdup_printf("(%s)", purple_conversation_get_title(conv
));
5446 title
= g_strdup(purple_conversation_get_title(conv
));
5448 if (PURPLE_IS_IM_CONVERSATION(conv
)) {
5449 buddy
= purple_blist_find_buddy(account
, purple_conversation_get_name(conv
));
5451 markup
= pidgin_blist_get_name_markup(buddy
, FALSE
, FALSE
);
5455 } else if (PURPLE_IS_CHAT_CONVERSATION(conv
)) {
5456 const char *topic
= gtkconv
->u
.chat
->topic_text
5457 ? gtk_entry_get_text(GTK_ENTRY(gtkconv
->u
.chat
->topic_text
))
5459 const char *title
= purple_conversation_get_title(conv
);
5460 const char *name
= purple_conversation_get_name(conv
);
5462 char *topic_esc
, *unaliased
, *unaliased_esc
, *title_esc
;
5464 topic_esc
= topic
? g_markup_escape_text(topic
, -1) : NULL
;
5465 unaliased
= g_utf8_collate(title
, name
) ? g_strdup_printf("(%s)", name
) : NULL
;
5466 unaliased_esc
= unaliased
? g_markup_escape_text(unaliased
, -1) : NULL
;
5467 title_esc
= g_markup_escape_text(title
, -1);
5469 markup
= g_strdup_printf("%s%s<span size='smaller'>%s</span>%s<span color='%s' size='smaller'>%s</span>",
5471 unaliased_esc
? " " : "",
5472 unaliased_esc
? unaliased_esc
: "",
5473 topic_esc
&& *topic_esc
? "\n" : "",
5474 pidgin_get_dim_grey_string(gtkconv
->infopane
),
5475 topic_esc
? topic_esc
: "");
5480 g_free(unaliased_esc
);
5482 gtk_list_store_set(gtkconv
->infopane_model
, &(gtkconv
->infopane_iter
),
5483 CONV_TEXT_COLUMN
, markup
, -1);
5484 /* XXX seanegan Why do I have to do this? */
5485 gtk_widget_queue_draw(gtkconv
->infopane
);
5487 if (title
!= markup
)
5490 if (!gtk_widget_get_realized(gtkconv
->tab_label
))
5491 gtk_widget_realize(gtkconv
->tab_label
);
5493 accessibility_obj
= gtk_widget_get_accessible(gtkconv
->tab_cont
);
5495 purple_im_conversation_get_typing_state(im
) == PURPLE_IM_TYPING
) {
5496 atk_object_set_description(accessibility_obj
, _("Typing"));
5497 style
= "tab-label-typing";
5498 } else if (im
!= NULL
&&
5499 purple_im_conversation_get_typing_state(im
) == PURPLE_IM_TYPED
) {
5500 atk_object_set_description(accessibility_obj
, _("Stopped Typing"));
5501 style
= "tab-label-typed";
5502 } else if (gtkconv
->unseen_state
== PIDGIN_UNSEEN_NICK
) {
5503 atk_object_set_description(accessibility_obj
, _("Nick Said"));
5504 style
= "tab-label-attention";
5505 } else if (gtkconv
->unseen_state
== PIDGIN_UNSEEN_TEXT
) {
5506 atk_object_set_description(accessibility_obj
, _("Unread Messages"));
5507 if (PURPLE_IS_CHAT_CONVERSATION(gtkconv
->active_conv
))
5508 style
= "tab-label-unreadchat";
5510 style
= "tab-label-attention";
5511 } else if (gtkconv
->unseen_state
== PIDGIN_UNSEEN_EVENT
) {
5512 atk_object_set_description(accessibility_obj
, _("New Event"));
5513 style
= "tab-label-event";
5515 style
= "tab-label";
5518 gtk_widget_set_name(gtkconv
->tab_label
, style
);
5519 gtk_label_set_text(GTK_LABEL(gtkconv
->tab_label
), title
);
5520 gtk_widget_set_state_flags(gtkconv
->tab_label
, GTK_STATE_FLAG_ACTIVE
, TRUE
);
5522 if (gtkconv
->unseen_state
== PIDGIN_UNSEEN_TEXT
||
5523 gtkconv
->unseen_state
== PIDGIN_UNSEEN_NICK
||
5524 gtkconv
->unseen_state
== PIDGIN_UNSEEN_EVENT
) {
5525 PangoAttrList
*list
= pango_attr_list_new();
5526 PangoAttribute
*attr
= pango_attr_weight_new(PANGO_WEIGHT_BOLD
);
5527 attr
->start_index
= 0;
5528 attr
->end_index
= -1;
5529 pango_attr_list_insert(list
, attr
);
5530 gtk_label_set_attributes(GTK_LABEL(gtkconv
->tab_label
), list
);
5531 pango_attr_list_unref(list
);
5533 gtk_label_set_attributes(GTK_LABEL(gtkconv
->tab_label
), NULL
);
5535 if (pidgin_conv_window_is_active_conversation(conv
))
5536 update_typing_icon(gtkconv
);
5538 gtk_label_set_text(GTK_LABEL(gtkconv
->menu_label
), title
);
5539 if (pidgin_conv_window_is_active_conversation(conv
)) {
5540 const char* current_title
= gtk_window_get_title(GTK_WINDOW(win
->window
));
5541 if (current_title
== NULL
|| !purple_strequal(current_title
, title
))
5542 gtk_window_set_title(GTK_WINDOW(win
->window
), title
);
5550 pidgin_conv_updated(PurpleConversation
*conv
, PurpleConversationUpdateType type
)
5552 PidginConvFields flags
= 0;
5554 g_return_if_fail(conv
!= NULL
);
5556 if (type
== PURPLE_CONVERSATION_UPDATE_ACCOUNT
)
5558 flags
= PIDGIN_CONV_ALL
;
5560 else if (type
== PURPLE_CONVERSATION_UPDATE_TYPING
||
5561 type
== PURPLE_CONVERSATION_UPDATE_UNSEEN
||
5562 type
== PURPLE_CONVERSATION_UPDATE_TITLE
)
5564 flags
= PIDGIN_CONV_COLORIZE_TITLE
;
5566 else if (type
== PURPLE_CONVERSATION_UPDATE_TOPIC
)
5568 flags
= PIDGIN_CONV_TOPIC
;
5570 else if (type
== PURPLE_CONVERSATION_ACCOUNT_ONLINE
||
5571 type
== PURPLE_CONVERSATION_ACCOUNT_OFFLINE
)
5573 flags
= PIDGIN_CONV_MENU
| PIDGIN_CONV_TAB_ICON
| PIDGIN_CONV_SET_TITLE
;
5575 else if (type
== PURPLE_CONVERSATION_UPDATE_AWAY
)
5577 flags
= PIDGIN_CONV_TAB_ICON
;
5579 else if (type
== PURPLE_CONVERSATION_UPDATE_ADD
||
5580 type
== PURPLE_CONVERSATION_UPDATE_REMOVE
||
5581 type
== PURPLE_CONVERSATION_UPDATE_CHATLEFT
)
5583 flags
= PIDGIN_CONV_SET_TITLE
| PIDGIN_CONV_MENU
;
5585 else if (type
== PURPLE_CONVERSATION_UPDATE_ICON
)
5587 flags
= PIDGIN_CONV_BUDDY_ICON
;
5589 else if (type
== PURPLE_CONVERSATION_UPDATE_FEATURES
)
5591 flags
= PIDGIN_CONV_MENU
;
5593 else if (type
== PURPLE_CONVERSATION_UPDATE_E2EE
)
5595 flags
= PIDGIN_CONV_E2EE
| PIDGIN_CONV_MENU
;
5598 pidgin_conv_update_fields(conv
, flags
);
5602 wrote_msg_update_unseen_cb(PurpleConversation
*conv
, PurpleMessage
*msg
,
5605 PidginConversation
*gtkconv
= conv
? PIDGIN_CONVERSATION(conv
) : NULL
;
5606 PurpleMessageFlags flags
;
5607 if (conv
== NULL
|| (gtkconv
&& gtkconv
->win
!= hidden_convwin
))
5609 flags
= purple_message_get_flags(msg
);
5610 if (flags
& (PURPLE_MESSAGE_SEND
| PURPLE_MESSAGE_RECV
)) {
5611 PidginUnseenState unseen
= PIDGIN_UNSEEN_NONE
;
5613 if ((flags
& PURPLE_MESSAGE_NICK
) == PURPLE_MESSAGE_NICK
)
5614 unseen
= PIDGIN_UNSEEN_NICK
;
5615 else if (((flags
& PURPLE_MESSAGE_SYSTEM
) == PURPLE_MESSAGE_SYSTEM
) ||
5616 ((flags
& PURPLE_MESSAGE_ERROR
) == PURPLE_MESSAGE_ERROR
))
5617 unseen
= PIDGIN_UNSEEN_EVENT
;
5618 else if ((flags
& PURPLE_MESSAGE_NO_LOG
) == PURPLE_MESSAGE_NO_LOG
)
5619 unseen
= PIDGIN_UNSEEN_NO_LOG
;
5621 unseen
= PIDGIN_UNSEEN_TEXT
;
5623 conv_set_unseen(conv
, unseen
);
5627 static PurpleConversationUiOps conversation_ui_ops
=
5630 pidgin_conv_destroy
, /* destroy_conversation */
5631 NULL
, /* write_chat */
5632 NULL
, /* write_im */
5633 pidgin_conv_write_conv
, /* write_conv */
5634 pidgin_conv_chat_add_users
, /* chat_add_users */
5635 pidgin_conv_chat_rename_user
, /* chat_rename_user */
5636 pidgin_conv_chat_remove_users
, /* chat_remove_users */
5637 pidgin_conv_chat_update_user
, /* chat_update_user */
5638 pidgin_conv_present_conversation
, /* present */
5639 pidgin_conv_has_focus
, /* has_focus */
5640 NULL
, /* send_confirm */
5647 PurpleConversationUiOps
*
5648 pidgin_conversations_get_conv_ui_ops(void)
5650 return &conversation_ui_ops
;
5653 /**************************************************************************
5654 * Public conversation utility functions
5655 **************************************************************************/
5657 pidgin_conv_update_buddy_icon(PurpleIMConversation
*im
)
5659 PidginConversation
*gtkconv
;
5660 PurpleConversation
*conv
;
5661 PidginConvWindow
*win
;
5665 PurpleImage
*custom_img
= NULL
;
5666 gconstpointer data
= NULL
;
5674 int scale_width
, scale_height
;
5677 PurpleAccount
*account
;
5679 PurpleBuddyIcon
*icon
;
5681 conv
= PURPLE_CONVERSATION(im
);
5683 g_return_if_fail(conv
!= NULL
);
5684 g_return_if_fail(PIDGIN_IS_PIDGIN_CONVERSATION(conv
));
5686 gtkconv
= PIDGIN_CONVERSATION(conv
);
5688 if (conv
!= gtkconv
->active_conv
)
5691 if (!gtkconv
->u
.im
->show_icon
)
5694 account
= purple_conversation_get_account(conv
);
5696 /* Remove the current icon stuff */
5697 children
= gtk_container_get_children(GTK_CONTAINER(gtkconv
->u
.im
->icon_container
));
5699 /* We know there's only one child here. It'd be nice to shortcut to the
5700 event box, but we can't change the PidginConversation until 3.0 */
5701 event
= (GtkWidget
*)children
->data
;
5702 gtk_container_remove(GTK_CONTAINER(gtkconv
->u
.im
->icon_container
), event
);
5703 g_list_free(children
);
5706 if (gtkconv
->u
.im
->anim
!= NULL
)
5707 g_object_unref(G_OBJECT(gtkconv
->u
.im
->anim
));
5709 gtkconv
->u
.im
->anim
= NULL
;
5711 if (gtkconv
->u
.im
->icon_timer
!= 0)
5712 g_source_remove(gtkconv
->u
.im
->icon_timer
);
5714 gtkconv
->u
.im
->icon_timer
= 0;
5716 if (gtkconv
->u
.im
->iter
!= NULL
)
5717 g_object_unref(G_OBJECT(gtkconv
->u
.im
->iter
));
5719 gtkconv
->u
.im
->iter
= NULL
;
5721 if (!purple_prefs_get_bool(PIDGIN_PREFS_ROOT
"/conversations/im/show_buddy_icons"))
5724 if (purple_conversation_get_connection(conv
) == NULL
)
5727 buddy
= purple_blist_find_buddy(account
, purple_conversation_get_name(conv
));
5730 PurpleContact
*contact
= purple_buddy_get_contact(buddy
);
5732 custom_img
= purple_buddy_icons_node_find_custom_icon((PurpleBlistNode
*)contact
);
5734 /* There is a custom icon for this user */
5735 data
= purple_image_get_data(custom_img
);
5736 len
= purple_image_get_data_size(custom_img
);
5742 icon
= purple_im_conversation_get_icon(im
);
5745 gtk_widget_set_size_request(gtkconv
->u
.im
->icon_container
,
5746 -1, BUDDYICON_SIZE_MIN
);
5750 data
= purple_buddy_icon_get_data(icon
, &len
);
5753 gtk_widget_set_size_request(gtkconv
->u
.im
->icon_container
,
5754 -1, BUDDYICON_SIZE_MIN
);
5759 gtkconv
->u
.im
->anim
= pidgin_pixbuf_anim_from_data(data
, len
);
5761 g_object_unref(custom_img
);
5763 if (!gtkconv
->u
.im
->anim
) {
5764 purple_debug_error("gtkconv", "Couldn't load icon for conv %s\n",
5765 purple_conversation_get_name(conv
));
5769 if (gdk_pixbuf_animation_is_static_image(gtkconv
->u
.im
->anim
)) {
5771 gtkconv
->u
.im
->iter
= NULL
;
5772 stat
= gdk_pixbuf_animation_get_static_image(gtkconv
->u
.im
->anim
);
5773 buf
= gdk_pixbuf_add_alpha(stat
, FALSE
, 0, 0, 0);
5776 gtkconv
->u
.im
->iter
=
5777 gdk_pixbuf_animation_get_iter(gtkconv
->u
.im
->anim
, NULL
); /* LEAK */
5778 stat
= gdk_pixbuf_animation_iter_get_pixbuf(gtkconv
->u
.im
->iter
);
5779 buf
= gdk_pixbuf_add_alpha(stat
, FALSE
, 0, 0, 0);
5780 if (gtkconv
->u
.im
->animate
)
5781 start_anim(NULL
, gtkconv
);
5784 scale_width
= gdk_pixbuf_get_width(buf
);
5785 scale_height
= gdk_pixbuf_get_height(buf
);
5787 gtk_widget_get_size_request(gtkconv
->u
.im
->icon_container
, NULL
, &size
);
5788 size
= MIN(size
, MIN(scale_width
, scale_height
));
5790 /* Some sanity checks */
5791 size
= CLAMP(size
, BUDDYICON_SIZE_MIN
, BUDDYICON_SIZE_MAX
);
5792 if (scale_width
== scale_height
) {
5793 scale_width
= scale_height
= size
;
5794 } else if (scale_height
> scale_width
) {
5795 scale_width
= size
* scale_width
/ scale_height
;
5796 scale_height
= size
;
5798 scale_height
= size
* scale_height
/ scale_width
;
5801 scale
= gdk_pixbuf_scale_simple(buf
, scale_width
, scale_height
,
5802 GDK_INTERP_BILINEAR
);
5803 g_object_unref(buf
);
5804 if (pidgin_gdk_pixbuf_is_opaque(scale
))
5805 pidgin_gdk_pixbuf_make_round(scale
);
5807 event
= gtk_event_box_new();
5808 gtk_container_add(GTK_CONTAINER(gtkconv
->u
.im
->icon_container
), event
);
5809 gtk_event_box_set_visible_window(GTK_EVENT_BOX(event
), FALSE
);
5810 gtk_widget_add_events(event
,
5811 GDK_POINTER_MOTION_MASK
| GDK_LEAVE_NOTIFY_MASK
);
5812 g_signal_connect(G_OBJECT(event
), "button-press-event",
5813 G_CALLBACK(icon_menu
), gtkconv
);
5815 pidgin_tooltip_setup_for_widget(event
, gtkconv
, pidgin_conv_create_tooltip
, NULL
);
5816 gtk_widget_show(event
);
5818 gtkconv
->u
.im
->icon
= gtk_image_new_from_pixbuf(scale
);
5819 gtk_container_add(GTK_CONTAINER(event
), gtkconv
->u
.im
->icon
);
5820 gtk_widget_show(gtkconv
->u
.im
->icon
);
5822 g_object_unref(G_OBJECT(scale
));
5824 /* The buddy icon code needs badly to be fixed. */
5825 if(pidgin_conv_window_is_active_conversation(conv
))
5827 buf
= gdk_pixbuf_animation_get_static_image(gtkconv
->u
.im
->anim
);
5828 if (buddy
&& !PURPLE_BUDDY_IS_ONLINE(buddy
))
5829 gdk_pixbuf_saturate_and_pixelate(buf
, buf
, 0.0, FALSE
);
5830 gtk_window_set_icon(GTK_WINDOW(win
->window
), buf
);
5835 pidgin_conv_update_buttons_by_protocol(PurpleConversation
*conv
)
5837 PidginConvWindow
*win
;
5839 if (!PIDGIN_IS_PIDGIN_CONVERSATION(conv
))
5842 win
= PIDGIN_CONVERSATION(conv
)->win
;
5844 if (win
!= NULL
&& pidgin_conv_window_is_active_conversation(conv
))
5845 gray_stuff_out(PIDGIN_CONVERSATION(conv
));
5849 pidgin_conv_xy_to_right_infopane(PidginConvWindow
*win
, int x
, int y
)
5851 gint pane_x
, pane_y
, x_rel
;
5852 PidginConversation
*gtkconv
;
5853 GtkAllocation allocation
;
5855 gdk_window_get_origin(gtk_widget_get_window(win
->notebook
),
5858 gtkconv
= pidgin_conv_window_get_active_gtkconv(win
);
5859 gtk_widget_get_allocation(gtkconv
->infopane
, &allocation
);
5860 return (x_rel
> allocation
.x
+ allocation
.width
/ 2);
5864 pidgin_conv_get_tab_at_xy(PidginConvWindow
*win
, int x
, int y
, gboolean
*to_right
)
5866 gint nb_x
, nb_y
, x_rel
, y_rel
;
5867 GtkNotebook
*notebook
;
5868 GtkWidget
*page
, *tab
;
5869 gint i
, page_num
= -1;
5876 notebook
= GTK_NOTEBOOK(win
->notebook
);
5878 gdk_window_get_origin(gtk_widget_get_window(win
->notebook
), &nb_x
, &nb_y
);
5882 horiz
= (gtk_notebook_get_tab_pos(notebook
) == GTK_POS_TOP
||
5883 gtk_notebook_get_tab_pos(notebook
) == GTK_POS_BOTTOM
);
5885 count
= gtk_notebook_get_n_pages(GTK_NOTEBOOK(notebook
));
5887 for (i
= 0; i
< count
; i
++) {
5888 GtkAllocation allocation
;
5890 page
= gtk_notebook_get_nth_page(GTK_NOTEBOOK(notebook
), i
);
5891 tab
= gtk_notebook_get_tab_label(GTK_NOTEBOOK(notebook
), page
);
5892 gtk_widget_get_allocation(tab
, &allocation
);
5894 /* Make sure the tab is not hidden beyond an arrow */
5895 if (!gtk_widget_is_drawable(tab
) && gtk_notebook_get_show_tabs(notebook
))
5899 if (x_rel
>= allocation
.x
- PIDGIN_HIG_BOX_SPACE
&&
5900 x_rel
<= allocation
.x
+ allocation
.width
+ PIDGIN_HIG_BOX_SPACE
) {
5903 if (to_right
&& x_rel
>= allocation
.x
+ allocation
.width
/2)
5909 if (y_rel
>= allocation
.y
- PIDGIN_HIG_BOX_SPACE
&&
5910 y_rel
<= allocation
.y
+ allocation
.height
+ PIDGIN_HIG_BOX_SPACE
) {
5913 if (to_right
&& y_rel
>= allocation
.y
+ allocation
.height
/2)
5921 if (page_num
== -1) {
5922 /* Add after the last tab */
5923 page_num
= count
- 1;
5930 close_on_tabs_pref_cb(const char *name
, PurplePrefType type
,
5931 gconstpointer value
, gpointer data
)
5934 PurpleConversation
*conv
;
5935 PidginConversation
*gtkconv
;
5937 for (l
= purple_conversations_get_all(); l
!= NULL
; l
= l
->next
) {
5938 conv
= (PurpleConversation
*)l
->data
;
5940 if (!PIDGIN_IS_PIDGIN_CONVERSATION(conv
))
5943 gtkconv
= PIDGIN_CONVERSATION(conv
);
5946 gtk_widget_show(gtkconv
->close
);
5948 gtk_widget_hide(gtkconv
->close
);
5953 spellcheck_pref_cb(const char *name
, PurplePrefType type
,
5954 gconstpointer value
, gpointer data
)
5957 PurpleConversation
*conv
;
5958 PidginConversation
*gtkconv
;
5960 for (cl
= purple_conversations_get_all(); cl
!= NULL
; cl
= cl
->next
) {
5962 conv
= (PurpleConversation
*)cl
->data
;
5964 if (!PIDGIN_IS_PIDGIN_CONVERSATION(conv
))
5967 gtkconv
= PIDGIN_CONVERSATION(conv
);
5969 # warning toggle spell checking when talkatu #60 is done.
5974 tab_side_pref_cb(const char *name
, PurplePrefType type
,
5975 gconstpointer value
, gpointer data
)
5977 GList
*gtkwins
, *gtkconvs
;
5978 GtkPositionType pos
;
5979 PidginConvWindow
*gtkwin
;
5981 pos
= GPOINTER_TO_INT(value
);
5983 for (gtkwins
= pidgin_conv_windows_get_list(); gtkwins
!= NULL
; gtkwins
= gtkwins
->next
) {
5984 gtkwin
= gtkwins
->data
;
5985 gtk_notebook_set_tab_pos(GTK_NOTEBOOK(gtkwin
->notebook
), pos
&~8);
5986 for (gtkconvs
= gtkwin
->gtkconvs
; gtkconvs
!= NULL
; gtkconvs
= gtkconvs
->next
) {
5987 pidgin_conv_tab_pack(gtkwin
, gtkconvs
->data
);
5993 show_formatting_toolbar_pref_cb(const char *name
, PurplePrefType type
,
5994 gconstpointer value
, gpointer data
)
5997 PurpleConversation
*conv
;
5998 PidginConversation
*gtkconv
;
5999 PidginConvWindow
*win
;
6000 gboolean visible
= (gboolean
)GPOINTER_TO_INT(value
);
6002 for (l
= purple_conversations_get_all(); l
!= NULL
; l
= l
->next
)
6004 conv
= (PurpleConversation
*)l
->data
;
6006 if (!PIDGIN_IS_PIDGIN_CONVERSATION(conv
))
6009 gtkconv
= PIDGIN_CONVERSATION(conv
);
6012 gtk_toggle_action_set_active(
6013 GTK_TOGGLE_ACTION(win
->menu
->show_formatting_toolbar
),
6017 talkatu_editor_set_toolbar_visible(TALKATU_EDITOR(gtkconv
->editor
), visible
);
6022 animate_buddy_icons_pref_cb(const char *name
, PurplePrefType type
,
6023 gconstpointer value
, gpointer data
)
6026 PurpleConversation
*conv
;
6027 PidginConversation
*gtkconv
;
6028 PidginConvWindow
*win
;
6030 if (!purple_prefs_get_bool(PIDGIN_PREFS_ROOT
"/conversations/im/show_buddy_icons"))
6033 /* Set the "animate" flag for each icon based on the new preference */
6034 for (l
= purple_conversations_get_ims(); l
!= NULL
; l
= l
->next
) {
6035 conv
= (PurpleConversation
*)l
->data
;
6036 gtkconv
= PIDGIN_CONVERSATION(conv
);
6038 gtkconv
->u
.im
->animate
= GPOINTER_TO_INT(value
);
6041 /* Now either stop or start animation for the active conversation in each window */
6042 for (l
= pidgin_conv_windows_get_list(); l
!= NULL
; l
= l
->next
) {
6044 conv
= pidgin_conv_window_get_active_conversation(win
);
6045 pidgin_conv_update_buddy_icon(PURPLE_IM_CONVERSATION(conv
));
6050 show_buddy_icons_pref_cb(const char *name
, PurplePrefType type
,
6051 gconstpointer value
, gpointer data
)
6055 for (l
= purple_conversations_get_all(); l
!= NULL
; l
= l
->next
) {
6056 PurpleConversation
*conv
= l
->data
;
6057 if (!PIDGIN_CONVERSATION(conv
))
6059 if (GPOINTER_TO_INT(value
))
6060 gtk_widget_show(PIDGIN_CONVERSATION(conv
)->infopane_hbox
);
6062 gtk_widget_hide(PIDGIN_CONVERSATION(conv
)->infopane_hbox
);
6064 if (PURPLE_IS_IM_CONVERSATION(conv
)) {
6065 pidgin_conv_update_buddy_icon(PURPLE_IM_CONVERSATION(conv
));
6069 /* Make the tabs show/hide correctly */
6070 for (l
= pidgin_conv_windows_get_list(); l
!= NULL
; l
= l
->next
) {
6071 PidginConvWindow
*win
= l
->data
;
6072 if (pidgin_conv_window_get_gtkconv_count(win
) == 1)
6073 gtk_notebook_set_show_tabs(GTK_NOTEBOOK(win
->notebook
),
6074 GPOINTER_TO_INT(value
) == 0);
6079 show_protocol_icons_pref_cb(const char *name
, PurplePrefType type
,
6080 gconstpointer value
, gpointer data
)
6083 for (l
= purple_conversations_get_all(); l
!= NULL
; l
= l
->next
) {
6084 PurpleConversation
*conv
= l
->data
;
6085 if (PIDGIN_CONVERSATION(conv
))
6086 update_tab_icon(conv
);
6091 conv_placement_usetabs_cb(const char *name
, PurplePrefType type
,
6092 gconstpointer value
, gpointer data
)
6094 purple_prefs_trigger_callback(PIDGIN_PREFS_ROOT
"/conversations/placement");
6098 account_status_changed_cb(PurpleAccount
*account
, PurpleStatus
*oldstatus
,
6099 PurpleStatus
*newstatus
)
6102 PurpleConversation
*conv
= NULL
;
6103 PidginConversation
*gtkconv
;
6105 if(!purple_strequal(purple_prefs_get_string(PIDGIN_PREFS_ROOT
"/conversations/im/hide_new"), "away"))
6108 if(purple_status_is_available(oldstatus
) || !purple_status_is_available(newstatus
))
6111 for (l
= hidden_convwin
->gtkconvs
; l
; ) {
6115 conv
= gtkconv
->active_conv
;
6116 if (PURPLE_IS_CHAT_CONVERSATION(conv
) ||
6117 account
!= purple_conversation_get_account(conv
))
6120 pidgin_conv_attach_to_conversation(conv
);
6122 /* TODO: do we need to do anything for any other conversations that are in the same gtkconv here?
6123 * I'm a little concerned that not doing so will cause the "pending" indicator in the gtkblist not to be cleared. -DAA*/
6124 purple_conversation_update(conv
, PURPLE_CONVERSATION_UPDATE_UNSEEN
);
6129 hide_new_pref_cb(const char *name
, PurplePrefType type
,
6130 gconstpointer value
, gpointer data
)
6133 PurpleConversation
*conv
= NULL
;
6134 PidginConversation
*gtkconv
;
6135 gboolean when_away
= FALSE
;
6140 if(purple_strequal(purple_prefs_get_string(PIDGIN_PREFS_ROOT
"/conversations/im/hide_new"), "always"))
6143 if(purple_strequal(purple_prefs_get_string(PIDGIN_PREFS_ROOT
"/conversations/im/hide_new"), "away"))
6146 for (l
= hidden_convwin
->gtkconvs
; l
; )
6151 conv
= gtkconv
->active_conv
;
6153 if (PURPLE_IS_CHAT_CONVERSATION(conv
) ||
6154 gtkconv
->unseen_count
== 0 ||
6155 (when_away
&& !purple_status_is_available(
6156 purple_account_get_active_status(
6157 purple_conversation_get_account(conv
)))))
6160 pidgin_conv_attach_to_conversation(conv
);
6166 conv_placement_pref_cb(const char *name
, PurplePrefType type
,
6167 gconstpointer value
, gpointer data
)
6169 PidginConvPlacementFunc func
;
6171 if (!purple_strequal(name
, PIDGIN_PREFS_ROOT
"/conversations/placement"))
6174 func
= pidgin_conv_placement_get_fnc(value
);
6179 pidgin_conv_placement_set_current_func(func
);
6182 static PidginConversation
*
6183 get_gtkconv_with_contact(PurpleContact
*contact
)
6185 PurpleBlistNode
*node
;
6187 node
= ((PurpleBlistNode
*)contact
)->child
;
6189 for (; node
; node
= node
->next
)
6191 PurpleBuddy
*buddy
= (PurpleBuddy
*)node
;
6192 PurpleIMConversation
*im
;
6193 im
= purple_conversations_find_im_with_account(purple_buddy_get_name(buddy
), purple_buddy_get_account(buddy
));
6195 return PIDGIN_CONVERSATION(PURPLE_CONVERSATION(im
));
6201 account_signed_off_cb(PurpleConnection
*gc
, gpointer event
)
6205 for (iter
= purple_conversations_get_all(); iter
; iter
= iter
->next
)
6207 PurpleConversation
*conv
= iter
->data
;
6209 /* This seems fine in theory, but we also need to cover the
6210 * case of this account matching one of the other buddies in
6211 * one of the contacts containing the buddy corresponding to
6212 * a conversation. It's easier to just update them all. */
6213 /* if (purple_conversation_get_account(conv) == account) */
6214 pidgin_conv_update_fields(conv
, PIDGIN_CONV_TAB_ICON
|
6215 PIDGIN_CONV_MENU
| PIDGIN_CONV_COLORIZE_TITLE
);
6217 if (PURPLE_CONNECTION_IS_CONNECTED(gc
) &&
6218 PURPLE_IS_CHAT_CONVERSATION(conv
) &&
6219 purple_conversation_get_account(conv
) == purple_connection_get_account(gc
) &&
6220 g_object_get_data(G_OBJECT(conv
), "want-to-rejoin")) {
6221 GHashTable
*comps
= NULL
;
6222 PurpleChat
*chat
= purple_blist_find_chat(purple_conversation_get_account(conv
), purple_conversation_get_name(conv
));
6224 PurpleProtocol
*protocol
= purple_connection_get_protocol(gc
);
6225 comps
= purple_protocol_chat_iface_info_defaults(protocol
, gc
, purple_conversation_get_name(conv
));
6227 comps
= purple_chat_get_components(chat
);
6229 purple_serv_join_chat(gc
, comps
);
6230 if (chat
== NULL
&& comps
!= NULL
)
6231 g_hash_table_destroy(comps
);
6237 account_signing_off(PurpleConnection
*gc
)
6239 GList
*list
= purple_conversations_get_chats();
6240 PurpleAccount
*account
= purple_connection_get_account(gc
);
6242 /* We are about to sign off. See which chats we are currently in, and mark
6243 * them for rejoin on reconnect. */
6245 PurpleConversation
*conv
= list
->data
;
6246 if (!purple_chat_conversation_has_left(PURPLE_CHAT_CONVERSATION(conv
)) &&
6247 purple_conversation_get_account(conv
) == account
) {
6248 g_object_set_data(G_OBJECT(conv
), "want-to-rejoin", GINT_TO_POINTER(TRUE
));
6249 purple_conversation_write_system_message(conv
,
6250 _("The account has disconnected and you are no "
6251 "longer in this chat. You will automatically "
6252 "rejoin the chat when the account reconnects."),
6253 PURPLE_MESSAGE_NO_LOG
);
6260 update_buddy_status_changed(PurpleBuddy
*buddy
, PurpleStatus
*old
, PurpleStatus
*newstatus
)
6262 PidginConversation
*gtkconv
;
6263 PurpleConversation
*conv
;
6265 gtkconv
= get_gtkconv_with_contact(purple_buddy_get_contact(buddy
));
6268 conv
= gtkconv
->active_conv
;
6269 pidgin_conv_update_fields(conv
, PIDGIN_CONV_TAB_ICON
6270 | PIDGIN_CONV_COLORIZE_TITLE
6271 | PIDGIN_CONV_BUDDY_ICON
);
6272 if ((purple_status_is_online(old
) ^ purple_status_is_online(newstatus
)) != 0)
6273 pidgin_conv_update_fields(conv
, PIDGIN_CONV_MENU
);
6278 update_buddy_privacy_changed(PurpleBuddy
*buddy
)
6280 PidginConversation
*gtkconv
;
6281 PurpleConversation
*conv
;
6283 gtkconv
= get_gtkconv_with_contact(purple_buddy_get_contact(buddy
));
6285 conv
= gtkconv
->active_conv
;
6286 pidgin_conv_update_fields(conv
, PIDGIN_CONV_TAB_ICON
| PIDGIN_CONV_MENU
);
6291 update_buddy_idle_changed(PurpleBuddy
*buddy
, gboolean old
, gboolean newidle
)
6293 PurpleIMConversation
*im
;
6295 im
= purple_conversations_find_im_with_account(purple_buddy_get_name(buddy
), purple_buddy_get_account(buddy
));
6297 pidgin_conv_update_fields(PURPLE_CONVERSATION(im
), PIDGIN_CONV_TAB_ICON
);
6301 update_buddy_icon(PurpleBuddy
*buddy
)
6303 PurpleIMConversation
*im
;
6305 im
= purple_conversations_find_im_with_account(purple_buddy_get_name(buddy
), purple_buddy_get_account(buddy
));
6307 pidgin_conv_update_fields(PURPLE_CONVERSATION(im
), PIDGIN_CONV_BUDDY_ICON
);
6311 update_buddy_sign(PurpleBuddy
*buddy
, const char *which
)
6313 PurplePresence
*presence
;
6314 PurpleStatus
*on
, *off
;
6316 presence
= purple_buddy_get_presence(buddy
);
6319 off
= purple_presence_get_status(presence
, "offline");
6320 on
= purple_presence_get_status(presence
, "available");
6322 if (*(which
+1) == 'f')
6323 update_buddy_status_changed(buddy
, on
, off
);
6325 update_buddy_status_changed(buddy
, off
, on
);
6329 update_conversation_switched(PurpleConversation
*conv
)
6331 pidgin_conv_update_fields(conv
, PIDGIN_CONV_TAB_ICON
|
6332 PIDGIN_CONV_SET_TITLE
| PIDGIN_CONV_MENU
|
6333 PIDGIN_CONV_BUDDY_ICON
| PIDGIN_CONV_E2EE
);
6337 update_buddy_typing(PurpleAccount
*account
, const char *who
)
6339 PurpleConversation
*conv
;
6340 PidginConversation
*gtkconv
;
6342 conv
= PURPLE_CONVERSATION(purple_conversations_find_im_with_account(who
, account
));
6346 gtkconv
= PIDGIN_CONVERSATION(conv
);
6347 if (gtkconv
&& gtkconv
->active_conv
== conv
)
6348 pidgin_conv_update_fields(conv
, PIDGIN_CONV_COLORIZE_TITLE
);
6352 update_chat(PurpleChatConversation
*chat
)
6354 pidgin_conv_update_fields(PURPLE_CONVERSATION(chat
), PIDGIN_CONV_TOPIC
|
6355 PIDGIN_CONV_MENU
| PIDGIN_CONV_SET_TITLE
);
6359 update_chat_topic(PurpleChatConversation
*chat
, const char *old
, const char *new)
6361 pidgin_conv_update_fields(PURPLE_CONVERSATION(chat
), PIDGIN_CONV_TOPIC
);
6364 /* Message history stuff */
6366 /* Compare two PurpleMessages, according to time in ascending order. */
6368 message_compare(gconstpointer p1
, gconstpointer p2
)
6370 const PurpleMessage
*m1
= p1
, *m2
= p2
;
6371 return (purple_message_get_time(m1
) > purple_message_get_time(m2
));
6374 /* Adds some message history to the gtkconv. This happens in a idle-callback. */
6376 add_message_history_to_gtkconv(gpointer data
)
6378 PidginConversation
*gtkconv
= data
;
6380 int timer
= gtkconv
->attach_timer
;
6381 time_t when
= GPOINTER_TO_INT(g_object_get_data(G_OBJECT(gtkconv
->editor
), "attach-start-time"));
6382 gboolean im
= (PURPLE_IS_IM_CONVERSATION(gtkconv
->active_conv
));
6384 gtkconv
->attach_timer
= 0;
6385 while (gtkconv
->attach_current
&& count
< ADD_MESSAGE_HISTORY_AT_ONCE
) {
6386 PurpleMessage
*msg
= gtkconv
->attach_current
->data
;
6387 if (!im
&& when
&& (guint64
)when
< purple_message_get_time(msg
)) {
6388 g_object_set_data(G_OBJECT(gtkconv
->editor
), "attach-start-time", NULL
);
6390 /* XXX: should it be gtkconv->active_conv? */
6391 pidgin_conv_write_conv(gtkconv
->active_conv
, msg
);
6393 gtkconv
->attach_current
= g_list_delete_link(gtkconv
->attach_current
, gtkconv
->attach_current
);
6395 gtkconv
->attach_current
= gtkconv
->attach_current
->prev
;
6399 gtkconv
->attach_timer
= timer
;
6400 if (gtkconv
->attach_current
)
6403 g_source_remove(gtkconv
->attach_timer
);
6404 gtkconv
->attach_timer
= 0;
6406 /* Print any message that was sent while the old history was being added back. */
6408 GList
*iter
= gtkconv
->convs
;
6409 for (; iter
; iter
= iter
->next
) {
6410 PurpleConversation
*conv
= iter
->data
;
6411 GList
*history
= purple_conversation_get_message_history(conv
);
6412 for (; history
; history
= history
->next
) {
6413 PurpleMessage
*msg
= history
->data
;
6414 if (purple_message_get_time(msg
) > (guint64
)when
)
6415 msgs
= g_list_prepend(msgs
, msg
);
6418 msgs
= g_list_sort(msgs
, message_compare
);
6419 for (; msgs
; msgs
= g_list_delete_link(msgs
, msgs
)) {
6420 PurpleMessage
*msg
= msgs
->data
;
6421 /* XXX: see above - should it be active_conv? */
6422 pidgin_conv_write_conv(gtkconv
->active_conv
, msg
);
6424 g_object_set_data(G_OBJECT(gtkconv
->editor
), "attach-start-time", NULL
);
6427 g_object_set_data(G_OBJECT(gtkconv
->editor
), "attach-start-time", NULL
);
6428 purple_signal_emit(pidgin_conversations_get_handle(),
6429 "conversation-displayed", gtkconv
);
6434 pidgin_conv_attach(PurpleConversation
*conv
)
6437 g_object_set_data(G_OBJECT(conv
), "unseen-count", NULL
);
6438 g_object_set_data(G_OBJECT(conv
), "unseen-state", NULL
);
6439 purple_conversation_set_ui_ops(conv
, pidgin_conversations_get_conv_ui_ops());
6440 if (!PIDGIN_CONVERSATION(conv
))
6441 private_gtkconv_new(conv
, FALSE
);
6442 timer
= GPOINTER_TO_INT(g_object_get_data(G_OBJECT(conv
), "close-timer"));
6444 g_source_remove(timer
);
6445 g_object_set_data(G_OBJECT(conv
), "close-timer", NULL
);
6449 gboolean
pidgin_conv_attach_to_conversation(PurpleConversation
*conv
)
6452 PidginConversation
*gtkconv
;
6454 if (PIDGIN_IS_PIDGIN_CONVERSATION(conv
)) {
6455 /* This is pretty much always the case now. */
6456 gtkconv
= PIDGIN_CONVERSATION(conv
);
6457 if (gtkconv
->win
!= hidden_convwin
)
6459 pidgin_conv_window_remove_gtkconv(hidden_convwin
, gtkconv
);
6460 pidgin_conv_placement_place(gtkconv
);
6461 purple_signal_emit(pidgin_conversations_get_handle(),
6462 "conversation-displayed", gtkconv
);
6463 list
= gtkconv
->convs
;
6465 pidgin_conv_attach(list
->data
);
6471 pidgin_conv_attach(conv
);
6472 gtkconv
= PIDGIN_CONVERSATION(conv
);
6474 list
= purple_conversation_get_message_history(conv
);
6476 if (PURPLE_IS_IM_CONVERSATION(conv
)) {
6478 list
= g_list_copy(list
);
6479 for (convs
= purple_conversations_get_ims(); convs
; convs
= convs
->next
)
6480 if (convs
->data
!= conv
&&
6481 pidgin_conv_find_gtkconv(convs
->data
) == gtkconv
) {
6482 pidgin_conv_attach(convs
->data
);
6483 list
= g_list_concat(list
, g_list_copy(purple_conversation_get_message_history(convs
->data
)));
6485 list
= g_list_sort(list
, message_compare
);
6486 gtkconv
->attach_current
= list
;
6487 list
= g_list_last(list
);
6488 } else if (PURPLE_IS_CHAT_CONVERSATION(conv
)) {
6489 gtkconv
->attach_current
= g_list_last(list
);
6492 g_object_set_data(G_OBJECT(gtkconv
->editor
), "attach-start-time",
6493 GINT_TO_POINTER(purple_message_get_time(list
->data
)));
6494 gtkconv
->attach_timer
= g_idle_add(add_message_history_to_gtkconv
, gtkconv
);
6496 purple_signal_emit(pidgin_conversations_get_handle(),
6497 "conversation-displayed", gtkconv
);
6500 if (PURPLE_IS_CHAT_CONVERSATION(conv
)) {
6502 PurpleChatConversation
*chat
= PURPLE_CHAT_CONVERSATION(conv
);
6503 pidgin_conv_update_fields(conv
, PIDGIN_CONV_TOPIC
);
6504 users
= purple_chat_conversation_get_users(chat
);
6505 pidgin_conv_chat_add_users(chat
, users
, TRUE
);
6513 pidgin_conversations_get_handle(void)
6521 pidgin_conversations_pre_uninit(void);
6524 pidgin_conversations_init(void)
6526 void *handle
= pidgin_conversations_get_handle();
6527 void *blist_handle
= purple_blist_get_handle();
6529 e2ee_stock
= g_hash_table_new_full(g_str_hash
, g_str_equal
,
6530 g_free
, g_object_unref
);
6533 purple_prefs_add_none(PIDGIN_PREFS_ROOT
"/conversations");
6534 purple_prefs_add_bool(PIDGIN_PREFS_ROOT
"/conversations/use_smooth_scrolling", TRUE
);
6535 purple_prefs_add_bool(PIDGIN_PREFS_ROOT
"/conversations/close_on_tabs", TRUE
);
6536 purple_prefs_add_bool(PIDGIN_PREFS_ROOT
"/conversations/send_bold", FALSE
);
6537 purple_prefs_add_bool(PIDGIN_PREFS_ROOT
"/conversations/send_italic", FALSE
);
6538 purple_prefs_add_bool(PIDGIN_PREFS_ROOT
"/conversations/send_underline", FALSE
);
6539 purple_prefs_add_bool(PIDGIN_PREFS_ROOT
"/conversations/send_strike", FALSE
);
6540 purple_prefs_add_bool(PIDGIN_PREFS_ROOT
"/conversations/spellcheck", TRUE
);
6541 purple_prefs_add_bool(PIDGIN_PREFS_ROOT
"/conversations/show_incoming_formatting", TRUE
);
6542 /* TODO: it's about *remote* smileys, not local ones */
6543 purple_prefs_add_bool(PIDGIN_PREFS_ROOT
"/conversations/resize_custom_smileys", TRUE
);
6544 purple_prefs_add_int(PIDGIN_PREFS_ROOT
"/conversations/custom_smileys_size", 96);
6545 purple_prefs_add_int(PIDGIN_PREFS_ROOT
"/conversations/minimum_entry_lines", 2);
6547 purple_prefs_add_bool(PIDGIN_PREFS_ROOT
"/conversations/show_formatting_toolbar", TRUE
);
6549 purple_prefs_add_string(PIDGIN_PREFS_ROOT
"/conversations/placement", "last");
6550 purple_prefs_add_int(PIDGIN_PREFS_ROOT
"/conversations/placement_number", 1);
6551 purple_prefs_add_string(PIDGIN_PREFS_ROOT
"/conversations/bgcolor", "");
6552 purple_prefs_add_string(PIDGIN_PREFS_ROOT
"/conversations/fgcolor", "");
6553 purple_prefs_add_string(PIDGIN_PREFS_ROOT
"/conversations/font_face", "");
6554 purple_prefs_add_int(PIDGIN_PREFS_ROOT
"/conversations/font_size", 3);
6555 purple_prefs_add_bool(PIDGIN_PREFS_ROOT
"/conversations/tabs", TRUE
);
6556 purple_prefs_add_int(PIDGIN_PREFS_ROOT
"/conversations/tab_side", GTK_POS_TOP
);
6557 purple_prefs_add_int(PIDGIN_PREFS_ROOT
"/conversations/scrollback_lines", 4000);
6560 purple_prefs_add_bool(PIDGIN_PREFS_ROOT
"/conversations/use_theme_font", TRUE
);
6561 purple_prefs_add_string(PIDGIN_PREFS_ROOT
"/conversations/custom_font", "");
6564 /* Conversations -> Chat */
6565 purple_prefs_add_none(PIDGIN_PREFS_ROOT
"/conversations/chat");
6566 purple_prefs_add_int(PIDGIN_PREFS_ROOT
"/conversations/chat/entry_height", 54);
6567 purple_prefs_add_int(PIDGIN_PREFS_ROOT
"/conversations/chat/userlist_width", 80);
6568 purple_prefs_add_int(PIDGIN_PREFS_ROOT
"/conversations/chat/x", 0);
6569 purple_prefs_add_int(PIDGIN_PREFS_ROOT
"/conversations/chat/y", 0);
6570 purple_prefs_add_int(PIDGIN_PREFS_ROOT
"/conversations/chat/width", 340);
6571 purple_prefs_add_int(PIDGIN_PREFS_ROOT
"/conversations/chat/height", 390);
6573 /* Conversations -> IM */
6574 purple_prefs_add_none(PIDGIN_PREFS_ROOT
"/conversations/im");
6575 purple_prefs_add_int(PIDGIN_PREFS_ROOT
"/conversations/im/x", 0);
6576 purple_prefs_add_int(PIDGIN_PREFS_ROOT
"/conversations/im/y", 0);
6577 purple_prefs_add_int(PIDGIN_PREFS_ROOT
"/conversations/im/width", 340);
6578 purple_prefs_add_int(PIDGIN_PREFS_ROOT
"/conversations/im/height", 390);
6580 purple_prefs_add_bool(PIDGIN_PREFS_ROOT
"/conversations/im/animate_buddy_icons", TRUE
);
6582 purple_prefs_add_int(PIDGIN_PREFS_ROOT
"/conversations/im/entry_height", 54);
6583 purple_prefs_add_bool(PIDGIN_PREFS_ROOT
"/conversations/im/show_buddy_icons", TRUE
);
6585 purple_prefs_add_string(PIDGIN_PREFS_ROOT
"/conversations/im/hide_new", "never");
6586 purple_prefs_add_bool(PIDGIN_PREFS_ROOT
"/conversations/im/close_immediately", TRUE
);
6589 purple_prefs_add_bool(PIDGIN_PREFS_ROOT
"/win32/minimize_new_convs", FALSE
);
6592 /* Connect callbacks. */
6593 purple_prefs_connect_callback(handle
, PIDGIN_PREFS_ROOT
"/conversations/close_on_tabs",
6594 close_on_tabs_pref_cb
, NULL
);
6595 purple_prefs_connect_callback(handle
, PIDGIN_PREFS_ROOT
"/conversations/show_formatting_toolbar",
6596 show_formatting_toolbar_pref_cb
, NULL
);
6597 purple_prefs_connect_callback(handle
, PIDGIN_PREFS_ROOT
"/conversations/spellcheck",
6598 spellcheck_pref_cb
, NULL
);
6599 purple_prefs_connect_callback(handle
, PIDGIN_PREFS_ROOT
"/conversations/tab_side",
6600 tab_side_pref_cb
, NULL
);
6602 purple_prefs_connect_callback(handle
, PIDGIN_PREFS_ROOT
"/conversations/tabs",
6603 conv_placement_usetabs_cb
, NULL
);
6605 purple_prefs_connect_callback(handle
, PIDGIN_PREFS_ROOT
"/conversations/placement",
6606 conv_placement_pref_cb
, NULL
);
6607 purple_prefs_trigger_callback(PIDGIN_PREFS_ROOT
"/conversations/placement");
6609 purple_prefs_connect_callback(handle
, PIDGIN_PREFS_ROOT
"/conversations/minimum_entry_lines",
6610 minimum_entry_lines_pref_cb
, NULL
);
6613 purple_prefs_connect_callback(handle
, PIDGIN_PREFS_ROOT
"/conversations/im/animate_buddy_icons",
6614 animate_buddy_icons_pref_cb
, NULL
);
6615 purple_prefs_connect_callback(handle
, PIDGIN_PREFS_ROOT
"/conversations/im/show_buddy_icons",
6616 show_buddy_icons_pref_cb
, NULL
);
6617 purple_prefs_connect_callback(handle
, PIDGIN_PREFS_ROOT
"/blist/show_protocol_icons",
6618 show_protocol_icons_pref_cb
, NULL
);
6619 purple_prefs_connect_callback(handle
, PIDGIN_PREFS_ROOT
"/conversations/im/hide_new",
6620 hide_new_pref_cb
, NULL
);
6622 /**********************************************************************
6624 **********************************************************************/
6625 purple_signal_register(handle
, "conversation-dragging",
6626 purple_marshal_VOID__POINTER_POINTER
, G_TYPE_NONE
, 2,
6627 G_TYPE_POINTER
, /* pointer to a (PidginConvWindow *) */
6628 G_TYPE_POINTER
); /* pointer to a (PidginConvWindow *) */
6630 purple_signal_register(handle
, "conversation-timestamp",
6631 #if SIZEOF_TIME_T == 4
6632 purple_marshal_POINTER__POINTER_INT_BOOLEAN
,
6633 #elif SIZEOF_TIME_T == 8
6634 purple_marshal_POINTER__POINTER_INT64_BOOLEAN
,
6636 #error Unkown size of time_t
6638 G_TYPE_STRING
, 3, PURPLE_TYPE_CONVERSATION
,
6639 #if SIZEOF_TIME_T == 4
6641 #elif SIZEOF_TIME_T == 8
6644 # error Unknown size of time_t
6648 purple_signal_register(handle
, "displaying-im-msg",
6649 purple_marshal_BOOLEAN__POINTER_POINTER
,
6650 G_TYPE_BOOLEAN
, 2, PURPLE_TYPE_CONVERSATION
, PURPLE_TYPE_MESSAGE
);
6652 purple_signal_register(handle
, "displayed-im-msg",
6653 purple_marshal_VOID__POINTER_POINTER
, G_TYPE_NONE
, 2,
6654 PURPLE_TYPE_CONVERSATION
, PURPLE_TYPE_MESSAGE
);
6656 purple_signal_register(handle
, "displaying-chat-msg",
6657 purple_marshal_BOOLEAN__POINTER_POINTER
,
6658 G_TYPE_BOOLEAN
, 2, PURPLE_TYPE_CONVERSATION
, PURPLE_TYPE_MESSAGE
);
6660 purple_signal_register(handle
, "displayed-chat-msg",
6661 purple_marshal_VOID__POINTER_POINTER
, G_TYPE_NONE
, 2,
6662 PURPLE_TYPE_CONVERSATION
, PURPLE_TYPE_MESSAGE
);
6664 purple_signal_register(handle
, "conversation-switched",
6665 purple_marshal_VOID__POINTER
, G_TYPE_NONE
, 1,
6666 PURPLE_TYPE_CONVERSATION
);
6668 purple_signal_register(handle
, "conversation-hiding",
6669 purple_marshal_VOID__POINTER
, G_TYPE_NONE
, 1,
6670 G_TYPE_POINTER
); /* (PidginConversation *) */
6672 purple_signal_register(handle
, "conversation-displayed",
6673 purple_marshal_VOID__POINTER
, G_TYPE_NONE
, 1,
6674 G_TYPE_POINTER
); /* (PidginConversation *) */
6676 purple_signal_register(handle
, "chat-nick-autocomplete",
6677 purple_marshal_BOOLEAN__POINTER_BOOLEAN
,
6678 G_TYPE_BOOLEAN
, 1, PURPLE_TYPE_CONVERSATION
);
6680 purple_signal_register(handle
, "chat-nick-clicked",
6681 purple_marshal_BOOLEAN__POINTER_POINTER_UINT
,
6682 G_TYPE_BOOLEAN
, 3, PURPLE_TYPE_CONVERSATION
,
6683 G_TYPE_STRING
, G_TYPE_UINT
);
6685 purple_signal_register(handle
, "conversation-window-created",
6686 purple_marshal_VOID__POINTER
, G_TYPE_NONE
, 1,
6687 G_TYPE_POINTER
); /* (PidginConvWindow *) */
6690 /**********************************************************************
6692 **********************************************************************/
6693 purple_cmd_register("say", "S", PURPLE_CMD_P_DEFAULT
,
6694 PURPLE_CMD_FLAG_CHAT
| PURPLE_CMD_FLAG_IM
, NULL
,
6695 say_command_cb
, _("say <message>: Send a message normally as if you weren't using a command."), NULL
);
6696 purple_cmd_register("me", "S", PURPLE_CMD_P_DEFAULT
,
6697 PURPLE_CMD_FLAG_CHAT
| PURPLE_CMD_FLAG_IM
, NULL
,
6698 me_command_cb
, _("me <action>: Send an IRC style action to a buddy or chat."), NULL
);
6699 purple_cmd_register("debug", "w", PURPLE_CMD_P_DEFAULT
,
6700 PURPLE_CMD_FLAG_CHAT
| PURPLE_CMD_FLAG_IM
, NULL
,
6701 debug_command_cb
, _("debug <option>: Send various debug information to the current conversation."), NULL
);
6702 purple_cmd_register("clear", "", PURPLE_CMD_P_DEFAULT
,
6703 PURPLE_CMD_FLAG_CHAT
| PURPLE_CMD_FLAG_IM
, NULL
,
6704 clear_command_cb
, _("clear: Clears the conversation scrollback."), NULL
);
6705 purple_cmd_register("clearall", "", PURPLE_CMD_P_DEFAULT
,
6706 PURPLE_CMD_FLAG_CHAT
| PURPLE_CMD_FLAG_IM
, NULL
,
6707 clearall_command_cb
, _("clear: Clears all conversation scrollbacks."), NULL
);
6708 purple_cmd_register("help", "w", PURPLE_CMD_P_DEFAULT
,
6709 PURPLE_CMD_FLAG_CHAT
| PURPLE_CMD_FLAG_IM
| PURPLE_CMD_FLAG_ALLOW_WRONG_ARGS
, NULL
,
6710 help_command_cb
, _("help <command>: Help on a specific command."), NULL
);
6712 /**********************************************************************
6714 **********************************************************************/
6716 purple_signal_connect(purple_connections_get_handle(), "signed-on", handle
,
6717 G_CALLBACK(account_signed_off_cb
),
6718 GINT_TO_POINTER(PURPLE_CONVERSATION_ACCOUNT_ONLINE
));
6719 purple_signal_connect(purple_connections_get_handle(), "signed-off", handle
,
6720 G_CALLBACK(account_signed_off_cb
),
6721 GINT_TO_POINTER(PURPLE_CONVERSATION_ACCOUNT_OFFLINE
));
6722 purple_signal_connect(purple_connections_get_handle(), "signing-off", handle
,
6723 G_CALLBACK(account_signing_off
), NULL
);
6725 purple_signal_connect(purple_conversations_get_handle(), "writing-im-msg",
6726 handle
, G_CALLBACK(writing_msg
), NULL
);
6727 purple_signal_connect(purple_conversations_get_handle(), "writing-chat-msg",
6728 handle
, G_CALLBACK(writing_msg
), NULL
);
6729 purple_signal_connect(purple_conversations_get_handle(), "received-im-msg",
6730 handle
, G_CALLBACK(received_im_msg_cb
), NULL
);
6731 purple_signal_connect(purple_conversations_get_handle(), "cleared-message-history",
6732 handle
, G_CALLBACK(clear_conversation_scrollback_cb
), NULL
);
6734 purple_signal_connect(purple_conversations_get_handle(), "deleting-chat-user",
6735 handle
, G_CALLBACK(deleting_chat_user_cb
), NULL
);
6737 purple_conversations_set_ui_ops(&conversation_ui_ops
);
6739 hidden_convwin
= pidgin_conv_window_new();
6740 window_list
= g_list_remove(window_list
, hidden_convwin
);
6742 purple_signal_connect(purple_accounts_get_handle(), "account-status-changed",
6743 handle
, PURPLE_CALLBACK(account_status_changed_cb
), NULL
);
6745 purple_signal_connect_priority(purple_get_core(), "quitting", handle
,
6746 PURPLE_CALLBACK(pidgin_conversations_pre_uninit
), NULL
, PURPLE_SIGNAL_PRIORITY_HIGHEST
);
6748 /* Callbacks to update a conversation */
6749 purple_signal_connect(blist_handle
, "blist-node-added", handle
,
6750 G_CALLBACK(buddy_update_cb
), NULL
);
6751 purple_signal_connect(blist_handle
, "blist-node-removed", handle
,
6752 G_CALLBACK(buddy_update_cb
), NULL
);
6753 purple_signal_connect(blist_handle
, "buddy-signed-on",
6754 handle
, PURPLE_CALLBACK(update_buddy_sign
), "on");
6755 purple_signal_connect(blist_handle
, "buddy-signed-off",
6756 handle
, PURPLE_CALLBACK(update_buddy_sign
), "off");
6757 purple_signal_connect(blist_handle
, "buddy-status-changed",
6758 handle
, PURPLE_CALLBACK(update_buddy_status_changed
), NULL
);
6759 purple_signal_connect(blist_handle
, "buddy-privacy-changed",
6760 handle
, PURPLE_CALLBACK(update_buddy_privacy_changed
), NULL
);
6761 purple_signal_connect(blist_handle
, "buddy-idle-changed",
6762 handle
, PURPLE_CALLBACK(update_buddy_idle_changed
), NULL
);
6763 purple_signal_connect(blist_handle
, "buddy-icon-changed",
6764 handle
, PURPLE_CALLBACK(update_buddy_icon
), NULL
);
6765 purple_signal_connect(purple_conversations_get_handle(), "buddy-typing",
6766 handle
, PURPLE_CALLBACK(update_buddy_typing
), NULL
);
6767 purple_signal_connect(purple_conversations_get_handle(), "buddy-typing-stopped",
6768 handle
, PURPLE_CALLBACK(update_buddy_typing
), NULL
);
6769 purple_signal_connect(pidgin_conversations_get_handle(), "conversation-switched",
6770 handle
, PURPLE_CALLBACK(update_conversation_switched
), NULL
);
6771 purple_signal_connect(purple_conversations_get_handle(), "chat-left", handle
,
6772 PURPLE_CALLBACK(update_chat
), NULL
);
6773 purple_signal_connect(purple_conversations_get_handle(), "chat-joined", handle
,
6774 PURPLE_CALLBACK(update_chat
), NULL
);
6775 purple_signal_connect(purple_conversations_get_handle(), "chat-topic-changed", handle
,
6776 PURPLE_CALLBACK(update_chat_topic
), NULL
);
6777 purple_signal_connect_priority(purple_conversations_get_handle(), "conversation-updated", handle
,
6778 PURPLE_CALLBACK(pidgin_conv_updated
), NULL
,
6779 PURPLE_SIGNAL_PRIORITY_LOWEST
);
6780 purple_signal_connect(purple_conversations_get_handle(), "wrote-im-msg", handle
,
6781 PURPLE_CALLBACK(wrote_msg_update_unseen_cb
), NULL
);
6782 purple_signal_connect(purple_conversations_get_handle(), "wrote-chat-msg", handle
,
6783 PURPLE_CALLBACK(wrote_msg_update_unseen_cb
), NULL
);
6787 pidgin_conversations_pre_uninit(void)
6789 g_hash_table_destroy(e2ee_stock
);
6793 /* Invalidate the first tab color set */
6794 static gboolean tab_color_fuse
= TRUE
;
6797 pidgin_conversations_set_tab_colors(void)
6799 /* Set default tab colors */
6800 GString
*str
= g_string_new(NULL
);
6801 GtkSettings
*settings
= gtk_settings_get_default();
6802 GtkStyle
*parent
= gtk_rc_get_style_by_paths(settings
, "tab-container.tab-label*", NULL
, G_TYPE_NONE
), *now
;
6804 const char *stylename
;
6805 const char *labelname
;
6808 {"pidgin_tab_label_typing_default", "tab-label-typing", "#4e9a06"},
6809 {"pidgin_tab_label_typed_default", "tab-label-typed", "#c4a000"},
6810 {"pidgin_tab_label_attention_default", "tab-label-attention", "#006aff"},
6811 {"pidgin_tab_label_unreadchat_default", "tab-label-unreadchat", "#cc0000"},
6812 {"pidgin_tab_label_event_default", "tab-label-event", "#888a85"},
6817 if(tab_color_fuse
) {
6818 tab_color_fuse
= FALSE
;
6822 for (iter
= 0; styles
[iter
].stylename
; iter
++) {
6823 now
= gtk_rc_get_style_by_paths(settings
, styles
[iter
].labelname
, NULL
, G_TYPE_NONE
);
6824 if (parent
== now
||
6825 (parent
&& now
&& parent
->rc_style
== now
->rc_style
)) {
6829 gdk_rgba_parse(&color
, styles
[iter
].color
);
6830 pidgin_style_adjust_contrast(gtk_widget_get_default_style(), &color
);
6832 color_str
= gdk_rgba_to_string(&color
);
6833 g_string_append_printf(str
, "style \"%s\" {\n"
6834 "fg[ACTIVE] = \"%s\"\n"
6836 "widget \"*%s\" style \"%s\"\n",
6837 styles
[iter
].stylename
,
6839 styles
[iter
].labelname
, styles
[iter
].stylename
);
6843 gtk_rc_parse_string(str
->str
);
6844 g_string_free(str
, TRUE
);
6845 gtk_rc_reset_styles(settings
);
6849 pidgin_conversations_uninit(void)
6851 purple_prefs_disconnect_by_handle(pidgin_conversations_get_handle());
6852 purple_signals_disconnect_by_handle(pidgin_conversations_get_handle());
6853 purple_signals_unregister_by_instance(pidgin_conversations_get_handle());
6856 /**************************************************************************
6857 * PidginConversation GBoxed code
6858 **************************************************************************/
6859 static PidginConversation
*
6860 pidgin_conversation_ref(PidginConversation
*gtkconv
)
6862 g_return_val_if_fail(gtkconv
!= NULL
, NULL
);
6864 gtkconv
->box_count
++;
6870 pidgin_conversation_unref(PidginConversation
*gtkconv
)
6872 g_return_if_fail(gtkconv
!= NULL
);
6873 g_return_if_fail(gtkconv
->box_count
>= 0);
6875 if (!gtkconv
->box_count
--)
6876 pidgin_conv_destroy(gtkconv
->active_conv
);
6880 pidgin_conversation_get_type(void)
6882 static GType type
= 0;
6885 type
= g_boxed_type_register_static("PidginConversation",
6886 (GBoxedCopyFunc
)pidgin_conversation_ref
,
6887 (GBoxedFreeFunc
)pidgin_conversation_unref
);
6908 /* down here is where gtkconvwin.c ought to start. except they share like every freaking function,
6909 * and touch each others' private members all day long */
6913 * Pidgin is the legal property of its developers, whose names are too numerous
6914 * to list here. Please refer to the COPYRIGHT file distributed with this
6915 * source distribution.
6917 * This program is free software; you can redistribute it and/or modify
6918 * it under the terms of the GNU General Public License as published by
6919 * the Free Software Foundation; either version 2 of the License, or
6920 * (at your option) any later version.
6922 * This program is distributed in the hope that it will be useful,
6923 * but WITHOUT ANY WARRANTY; without even the implied warranty of
6924 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
6925 * GNU General Public License for more details.
6927 * You should have received a copy of the GNU General Public License
6928 * along with this program; if not, write to the Free Software
6929 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA
6932 #include "internal.h"
6936 #include <gdk/gdkkeysyms.h>
6938 #include "account.h"
6943 #include "protocol.h"
6944 #include "request.h"
6947 #include "gtkdnd-hints.h"
6948 #include "gtkblist.h"
6949 #include "gtkconv.h"
6950 #include "gtkdialogs.h"
6951 #include "gtkmenutray.h"
6952 #include "gtkpounce.h"
6953 #include "gtkprefs.h"
6954 #include "gtkprivacy.h"
6955 #include "gtkutils.h"
6956 #include "pidginstock.h"
6959 do_close(GtkWidget
*w
, int resp
, PidginConvWindow
*win
)
6961 gtk_widget_destroy(warn_close_dialog
);
6962 warn_close_dialog
= NULL
;
6964 if (resp
== GTK_RESPONSE_OK
)
6965 pidgin_conv_window_destroy(win
);
6969 build_warn_close_dialog(PidginConvWindow
*gtkwin
)
6971 GtkWidget
*label
, *vbox
, *hbox
, *img
;
6973 g_return_if_fail(warn_close_dialog
== NULL
);
6975 warn_close_dialog
= gtk_dialog_new_with_buttons(_("Confirm close"),
6976 GTK_WINDOW(gtkwin
->window
), GTK_DIALOG_MODAL
,
6977 GTK_STOCK_CANCEL
, GTK_RESPONSE_CANCEL
,
6978 GTK_STOCK_CLOSE
, GTK_RESPONSE_OK
, NULL
);
6980 gtk_dialog_set_default_response(GTK_DIALOG(warn_close_dialog
),
6983 gtk_container_set_border_width(GTK_CONTAINER(warn_close_dialog
),
6985 gtk_window_set_resizable(GTK_WINDOW(warn_close_dialog
), FALSE
);
6987 /* Setup the outside spacing. */
6988 vbox
= gtk_dialog_get_content_area(GTK_DIALOG(warn_close_dialog
));
6990 gtk_box_set_spacing(GTK_BOX(vbox
), 12);
6991 gtk_container_set_border_width(GTK_CONTAINER(vbox
), 6);
6993 img
= gtk_image_new_from_icon_name("dialog-warning",
6994 GTK_ICON_SIZE_DIALOG
);
6996 /* Setup the inner hbox and put the dialog's icon in it. */
6997 hbox
= gtk_box_new(GTK_ORIENTATION_HORIZONTAL
, 12);
6998 gtk_container_add(GTK_CONTAINER(vbox
), hbox
);
6999 gtk_box_pack_start(GTK_BOX(hbox
), img
, FALSE
, FALSE
, 0);
7000 gtk_widget_set_halign(img
, GTK_ALIGN_START
);
7001 gtk_widget_set_valign(img
, GTK_ALIGN_START
);
7003 /* Setup the right vbox. */
7004 vbox
= gtk_box_new(GTK_ORIENTATION_VERTICAL
, 12);
7005 gtk_container_add(GTK_CONTAINER(hbox
), vbox
);
7007 label
= gtk_label_new(_("You have unread messages. Are you sure you want to close the window?"));
7008 gtk_widget_set_size_request(label
, 350, -1);
7009 gtk_label_set_line_wrap(GTK_LABEL(label
), TRUE
);
7010 gtk_label_set_xalign(GTK_LABEL(label
), 0);
7011 gtk_label_set_yalign(GTK_LABEL(label
), 0);
7012 gtk_box_pack_start(GTK_BOX(vbox
), label
, FALSE
, FALSE
, 0);
7014 /* Connect the signals. */
7015 g_signal_connect(G_OBJECT(warn_close_dialog
), "response",
7016 G_CALLBACK(do_close
), gtkwin
);
7020 /**************************************************************************
7022 **************************************************************************/
7025 close_win_cb(GtkWidget
*w
, GdkEventAny
*e
, gpointer d
)
7027 PidginConvWindow
*win
= d
;
7030 /* If there are unread messages then show a warning dialog */
7031 for (l
= pidgin_conv_window_get_gtkconvs(win
);
7032 l
!= NULL
; l
= l
->next
)
7034 PidginConversation
*gtkconv
= l
->data
;
7035 if (PURPLE_IS_IM_CONVERSATION(gtkconv
->active_conv
) &&
7036 gtkconv
->unseen_state
>= PIDGIN_UNSEEN_TEXT
)
7038 build_warn_close_dialog(win
);
7039 gtk_widget_show_all(warn_close_dialog
);
7045 pidgin_conv_window_destroy(win
);
7051 conv_set_unseen(PurpleConversation
*conv
, PidginUnseenState state
)
7053 int unseen_count
= 0;
7054 PidginUnseenState unseen_state
= PIDGIN_UNSEEN_NONE
;
7056 if(g_object_get_data(G_OBJECT(conv
), "unseen-count"))
7057 unseen_count
= GPOINTER_TO_INT(g_object_get_data(G_OBJECT(conv
), "unseen-count"));
7059 if(g_object_get_data(G_OBJECT(conv
), "unseen-state"))
7060 unseen_state
= GPOINTER_TO_INT(g_object_get_data(G_OBJECT(conv
), "unseen-state"));
7062 if (state
== PIDGIN_UNSEEN_NONE
)
7065 unseen_state
= PIDGIN_UNSEEN_NONE
;
7069 if (state
>= PIDGIN_UNSEEN_TEXT
)
7072 if (state
> unseen_state
)
7073 unseen_state
= state
;
7076 g_object_set_data(G_OBJECT(conv
), "unseen-count", GINT_TO_POINTER(unseen_count
));
7077 g_object_set_data(G_OBJECT(conv
), "unseen-state", GINT_TO_POINTER(unseen_state
));
7079 purple_conversation_update(conv
, PURPLE_CONVERSATION_UPDATE_UNSEEN
);
7083 gtkconv_set_unseen(PidginConversation
*gtkconv
, PidginUnseenState state
)
7085 if (state
== PIDGIN_UNSEEN_NONE
)
7087 gtkconv
->unseen_count
= 0;
7088 gtkconv
->unseen_state
= PIDGIN_UNSEEN_NONE
;
7092 if (state
>= PIDGIN_UNSEEN_TEXT
)
7093 gtkconv
->unseen_count
++;
7095 if (state
> gtkconv
->unseen_state
)
7096 gtkconv
->unseen_state
= state
;
7099 g_object_set_data(G_OBJECT(gtkconv
->active_conv
), "unseen-count", GINT_TO_POINTER(gtkconv
->unseen_count
));
7100 g_object_set_data(G_OBJECT(gtkconv
->active_conv
), "unseen-state", GINT_TO_POINTER(gtkconv
->unseen_state
));
7102 purple_conversation_update(gtkconv
->active_conv
, PURPLE_CONVERSATION_UPDATE_UNSEEN
);
7106 * When a conversation window is focused, we know the user
7107 * has looked at it so we know there are no longer unseen
7111 focus_win_cb(GtkWidget
*w
, GdkEventFocus
*e
, gpointer d
)
7113 PidginConvWindow
*win
= d
;
7114 PidginConversation
*gtkconv
= pidgin_conv_window_get_active_gtkconv(win
);
7117 gtkconv_set_unseen(gtkconv
, PIDGIN_UNSEEN_NONE
);
7123 notebook_init_grab(PidginConvWindow
*gtkwin
, GtkWidget
*widget
, GdkEvent
*event
)
7125 static GdkCursor
*cursor
= NULL
;
7128 gtkwin
->in_drag
= TRUE
;
7130 if (gtkwin
->drag_leave_signal
) {
7131 g_signal_handler_disconnect(G_OBJECT(widget
),
7132 gtkwin
->drag_leave_signal
);
7133 gtkwin
->drag_leave_signal
= 0;
7136 if (cursor
== NULL
) {
7137 GdkDisplay
*display
= gtk_widget_get_display(gtkwin
->notebook
);
7138 cursor
= gdk_cursor_new_for_display(display
, GDK_FLEUR
);
7141 /* Grab the pointer */
7142 gtk_grab_add(gtkwin
->notebook
);
7143 device
= gdk_event_get_device(event
);
7144 if (!gdk_display_device_is_grabbed(gdk_device_get_display(device
), device
))
7145 gdk_device_grab(device
, gtk_widget_get_window(gtkwin
->notebook
),
7146 GDK_OWNERSHIP_WINDOW
, FALSE
,
7147 GDK_BUTTON1_MOTION_MASK
| GDK_BUTTON_RELEASE_MASK
,
7148 cursor
, gdk_event_get_time(event
));
7152 notebook_motion_cb(GtkWidget
*widget
, GdkEventButton
*e
, PidginConvWindow
*win
)
7156 * Make sure the user moved the mouse far enough for the
7157 * drag to be initiated.
7159 if (win
->in_predrag
) {
7160 if (e
->x_root
< win
->drag_min_x
||
7161 e
->x_root
>= win
->drag_max_x
||
7162 e
->y_root
< win
->drag_min_y
||
7163 e
->y_root
>= win
->drag_max_y
) {
7165 win
->in_predrag
= FALSE
;
7166 notebook_init_grab(win
, widget
, (GdkEvent
*)e
);
7169 else { /* Otherwise, draw the arrows. */
7170 PidginConvWindow
*dest_win
;
7171 GtkNotebook
*dest_notebook
;
7174 gboolean horiz_tabs
= FALSE
;
7175 gboolean to_right
= FALSE
;
7177 /* Get the window that the cursor is over. */
7178 dest_win
= pidgin_conv_window_get_at_event((GdkEvent
*)e
);
7180 if (dest_win
== NULL
) {
7181 pidgin_dnd_hints_hide_all();
7186 dest_notebook
= GTK_NOTEBOOK(dest_win
->notebook
);
7188 if (gtk_notebook_get_show_tabs(dest_notebook
)) {
7189 page_num
= pidgin_conv_get_tab_at_xy(dest_win
,
7190 e
->x_root
, e
->y_root
, &to_right
);
7191 to_right
= to_right
&& (win
!= dest_win
);
7192 tab
= pidgin_conv_window_get_gtkconv_at_index(dest_win
, page_num
)->tabby
;
7195 to_right
= pidgin_conv_xy_to_right_infopane(dest_win
, e
->x_root
, e
->y_root
);
7196 tab
= pidgin_conv_window_get_gtkconv_at_index(dest_win
, page_num
)->infopane_hbox
;
7199 if (gtk_notebook_get_tab_pos(dest_notebook
) == GTK_POS_TOP
||
7200 gtk_notebook_get_tab_pos(dest_notebook
) == GTK_POS_BOTTOM
) {
7204 if (gtk_notebook_get_show_tabs(dest_notebook
) == FALSE
&& win
== dest_win
)
7206 /* dragging a tab from a single-tabbed window over its own window */
7207 pidgin_dnd_hints_hide_all();
7209 } else if (horiz_tabs
) {
7210 if (((gpointer
)win
== (gpointer
)dest_win
&& win
->drag_tab
< page_num
) || to_right
) {
7211 pidgin_dnd_hints_show_relative(HINT_ARROW_DOWN
, tab
, HINT_POSITION_RIGHT
, HINT_POSITION_TOP
);
7212 pidgin_dnd_hints_show_relative(HINT_ARROW_UP
, tab
, HINT_POSITION_RIGHT
, HINT_POSITION_BOTTOM
);
7214 pidgin_dnd_hints_show_relative(HINT_ARROW_DOWN
, tab
, HINT_POSITION_LEFT
, HINT_POSITION_TOP
);
7215 pidgin_dnd_hints_show_relative(HINT_ARROW_UP
, tab
, HINT_POSITION_LEFT
, HINT_POSITION_BOTTOM
);
7218 if (((gpointer
)win
== (gpointer
)dest_win
&& win
->drag_tab
< page_num
) || to_right
) {
7219 pidgin_dnd_hints_show_relative(HINT_ARROW_RIGHT
, tab
, HINT_POSITION_LEFT
, HINT_POSITION_BOTTOM
);
7220 pidgin_dnd_hints_show_relative(HINT_ARROW_LEFT
, tab
, HINT_POSITION_RIGHT
, HINT_POSITION_BOTTOM
);
7222 pidgin_dnd_hints_show_relative(HINT_ARROW_RIGHT
, tab
, HINT_POSITION_LEFT
, HINT_POSITION_TOP
);
7223 pidgin_dnd_hints_show_relative(HINT_ARROW_LEFT
, tab
, HINT_POSITION_RIGHT
, HINT_POSITION_TOP
);
7232 notebook_leave_cb(GtkWidget
*widget
, GdkEventCrossing
*e
, PidginConvWindow
*win
)
7237 if (e
->x_root
< win
->drag_min_x
||
7238 e
->x_root
>= win
->drag_max_x
||
7239 e
->y_root
< win
->drag_min_y
||
7240 e
->y_root
>= win
->drag_max_y
) {
7242 win
->in_predrag
= FALSE
;
7243 notebook_init_grab(win
, widget
, (GdkEvent
*)e
);
7254 infopane_press_cb(GtkWidget
*widget
, GdkEventButton
*e
, PidginConversation
*gtkconv
)
7256 if (e
->type
== GDK_2BUTTON_PRESS
&& e
->button
== GDK_BUTTON_PRIMARY
) {
7257 if (infopane_entry_activate(gtkconv
))
7261 if (e
->type
!= GDK_BUTTON_PRESS
)
7264 if (e
->button
== GDK_BUTTON_PRIMARY
) {
7266 GtkAllocation allocation
;
7268 gtk_widget_get_allocation(gtkconv
->infopane_hbox
, &allocation
);
7270 if (gtkconv
->win
->in_drag
)
7273 gtkconv
->win
->in_predrag
= TRUE
;
7274 gtkconv
->win
->drag_tab
= gtk_notebook_page_num(GTK_NOTEBOOK(gtkconv
->win
->notebook
), gtkconv
->tab_cont
);
7276 gdk_window_get_origin(gtk_widget_get_window(gtkconv
->infopane_hbox
), &nb_x
, &nb_y
);
7278 gtkconv
->win
->drag_min_x
= allocation
.x
+ nb_x
;
7279 gtkconv
->win
->drag_min_y
= allocation
.y
+ nb_y
;
7280 gtkconv
->win
->drag_max_x
= allocation
.width
+ gtkconv
->win
->drag_min_x
;
7281 gtkconv
->win
->drag_max_y
= allocation
.height
+ gtkconv
->win
->drag_min_y
;
7283 gtkconv
->win
->drag_motion_signal
= g_signal_connect(G_OBJECT(gtkconv
->win
->notebook
), "motion_notify_event",
7284 G_CALLBACK(notebook_motion_cb
), gtkconv
->win
);
7285 gtkconv
->win
->drag_leave_signal
= g_signal_connect(G_OBJECT(gtkconv
->win
->notebook
), "leave_notify_event",
7286 G_CALLBACK(notebook_leave_cb
), gtkconv
->win
);
7290 if (gdk_event_triggers_context_menu((GdkEvent
*)e
)) {
7291 /* Right click was pressed. Popup the context menu. */
7292 GtkWidget
*menu
= gtk_menu_new(), *sub
;
7293 gboolean populated
= populate_menu_with_options(menu
, gtkconv
, TRUE
);
7295 sub
= gtk_menu_item_get_submenu(GTK_MENU_ITEM(gtkconv
->win
->menu
->send_to
));
7296 if (sub
&& gtk_widget_is_sensitive(gtkconv
->win
->menu
->send_to
)) {
7297 GtkWidget
*item
= gtk_menu_item_new_with_mnemonic(_("S_end To"));
7299 pidgin_separator(menu
);
7300 gtk_menu_shell_append(GTK_MENU_SHELL(menu
), item
);
7301 gtk_menu_item_set_submenu(GTK_MENU_ITEM(item
), sub
);
7302 gtk_widget_show(item
);
7303 gtk_widget_show_all(sub
);
7304 } else if (!populated
) {
7305 gtk_widget_destroy(menu
);
7309 gtk_widget_show_all(menu
);
7310 gtk_menu_popup_at_pointer(GTK_MENU(menu
), (GdkEvent
*)e
);
7317 notebook_press_cb(GtkWidget
*widget
, GdkEventButton
*e
, PidginConvWindow
*win
)
7323 GtkAllocation allocation
;
7325 if (e
->button
== GDK_BUTTON_MIDDLE
&& e
->type
== GDK_BUTTON_PRESS
) {
7326 PidginConversation
*gtkconv
;
7327 tab_clicked
= pidgin_conv_get_tab_at_xy(win
, e
->x_root
, e
->y_root
, NULL
);
7329 if (tab_clicked
== -1)
7332 gtkconv
= pidgin_conv_window_get_gtkconv_at_index(win
, tab_clicked
);
7333 close_conv_cb(NULL
, gtkconv
);
7338 if (e
->button
!= GDK_BUTTON_PRIMARY
|| e
->type
!= GDK_BUTTON_PRESS
)
7343 purple_debug(PURPLE_DEBUG_WARNING
, "gtkconv",
7344 "Already in the middle of a window drag at tab_press_cb\n");
7349 * Make sure a tab was actually clicked. The arrow buttons
7352 tab_clicked
= pidgin_conv_get_tab_at_xy(win
, e
->x_root
, e
->y_root
, NULL
);
7354 if (tab_clicked
== -1)
7358 * Get the relative position of the press event, with regards to
7359 * the position of the notebook.
7361 gdk_window_get_origin(gtk_widget_get_window(win
->notebook
), &nb_x
, &nb_y
);
7363 /* Reset the min/max x/y */
7364 win
->drag_min_x
= 0;
7365 win
->drag_min_y
= 0;
7366 win
->drag_max_x
= 0;
7367 win
->drag_max_y
= 0;
7369 /* Find out which tab was dragged. */
7370 page
= gtk_notebook_get_nth_page(GTK_NOTEBOOK(win
->notebook
), tab_clicked
);
7371 tab
= gtk_notebook_get_tab_label(GTK_NOTEBOOK(win
->notebook
), page
);
7373 gtk_widget_get_allocation(tab
, &allocation
);
7375 win
->drag_min_x
= allocation
.x
+ nb_x
;
7376 win
->drag_min_y
= allocation
.y
+ nb_y
;
7377 win
->drag_max_x
= allocation
.width
+ win
->drag_min_x
;
7378 win
->drag_max_y
= allocation
.height
+ win
->drag_min_y
;
7380 /* Make sure the click occurred in the tab. */
7381 if (e
->x_root
< win
->drag_min_x
||
7382 e
->x_root
>= win
->drag_max_x
||
7383 e
->y_root
< win
->drag_min_y
||
7384 e
->y_root
>= win
->drag_max_y
) {
7389 win
->in_predrag
= TRUE
;
7390 win
->drag_tab
= tab_clicked
;
7392 /* Connect the new motion signals. */
7393 win
->drag_motion_signal
=
7394 g_signal_connect(G_OBJECT(widget
), "motion_notify_event",
7395 G_CALLBACK(notebook_motion_cb
), win
);
7397 win
->drag_leave_signal
=
7398 g_signal_connect(G_OBJECT(widget
), "leave_notify_event",
7399 G_CALLBACK(notebook_leave_cb
), win
);
7405 notebook_release_cb(GtkWidget
*widget
, GdkEventButton
*e
, PidginConvWindow
*win
)
7407 PidginConvWindow
*dest_win
;
7408 GtkNotebook
*dest_notebook
;
7409 PidginConversation
*active_gtkconv
;
7410 PidginConversation
*gtkconv
;
7411 gint dest_page_num
= 0;
7412 gboolean new_window
= FALSE
;
7413 gboolean to_right
= FALSE
;
7417 * Don't check to make sure that the event's window matches the
7418 * widget's, because we may be getting an event passed on from the
7421 if (e
->button
!= GDK_BUTTON_PRIMARY
&& e
->type
!= GDK_BUTTON_RELEASE
)
7424 device
= gdk_event_get_device((GdkEvent
*)e
);
7425 if (gdk_display_device_is_grabbed(gdk_device_get_display(device
), device
)) {
7426 gdk_device_ungrab(device
, gdk_event_get_time((GdkEvent
*)e
));
7427 gtk_grab_remove(widget
);
7430 if (!win
->in_predrag
&& !win
->in_drag
)
7433 /* Disconnect the motion signal. */
7434 if (win
->drag_motion_signal
) {
7435 g_signal_handler_disconnect(G_OBJECT(widget
),
7436 win
->drag_motion_signal
);
7438 win
->drag_motion_signal
= 0;
7442 * If we're in a pre-drag, we'll also need to disconnect the leave
7445 if (win
->in_predrag
) {
7446 win
->in_predrag
= FALSE
;
7448 if (win
->drag_leave_signal
) {
7449 g_signal_handler_disconnect(G_OBJECT(widget
),
7450 win
->drag_leave_signal
);
7452 win
->drag_leave_signal
= 0;
7456 /* If we're not in drag... */
7457 /* We're perfectly normal people! */
7461 win
->in_drag
= FALSE
;
7463 pidgin_dnd_hints_hide_all();
7465 dest_win
= pidgin_conv_window_get_at_event((GdkEvent
*)e
);
7467 active_gtkconv
= pidgin_conv_window_get_active_gtkconv(win
);
7469 if (dest_win
== NULL
) {
7470 /* If the current window doesn't have any other conversations,
7471 * there isn't much point transferring the conv to a new window. */
7472 if (pidgin_conv_window_get_gtkconv_count(win
) > 1) {
7473 /* Make a new window to stick this to. */
7474 dest_win
= pidgin_conv_window_new();
7479 if (dest_win
== NULL
)
7482 purple_signal_emit(pidgin_conversations_get_handle(),
7483 "conversation-dragging", win
, dest_win
);
7485 /* Get the destination page number. */
7487 dest_notebook
= GTK_NOTEBOOK(dest_win
->notebook
);
7488 if (gtk_notebook_get_show_tabs(dest_notebook
)) {
7489 dest_page_num
= pidgin_conv_get_tab_at_xy(dest_win
,
7490 e
->x_root
, e
->y_root
, &to_right
);
7493 to_right
= pidgin_conv_xy_to_right_infopane(dest_win
, e
->x_root
, e
->y_root
);
7497 gtkconv
= pidgin_conv_window_get_gtkconv_at_index(win
, win
->drag_tab
);
7499 if (win
== dest_win
) {
7500 gtk_notebook_reorder_child(GTK_NOTEBOOK(win
->notebook
), gtkconv
->tab_cont
, dest_page_num
);
7502 pidgin_conv_window_remove_gtkconv(win
, gtkconv
);
7503 pidgin_conv_window_add_gtkconv(dest_win
, gtkconv
);
7504 gtk_notebook_reorder_child(GTK_NOTEBOOK(dest_win
->notebook
), gtkconv
->tab_cont
, dest_page_num
+ to_right
);
7505 pidgin_conv_window_switch_gtkconv(dest_win
, gtkconv
);
7507 gint win_width
, win_height
;
7509 gtk_window_get_size(GTK_WINDOW(dest_win
->window
),
7510 &win_width
, &win_height
);
7511 #ifdef _WIN32 /* only override window manager placement on Windows */
7512 gtk_window_move(GTK_WINDOW(dest_win
->window
),
7513 e
->x_root
- (win_width
/ 2),
7514 e
->y_root
- (win_height
/ 2));
7517 pidgin_conv_window_show(dest_win
);
7521 gtk_widget_grab_focus(active_gtkconv
->editor
);
7528 before_switch_conv_cb(GtkNotebook
*notebook
, GtkWidget
*page
, gint page_num
,
7531 PidginConvWindow
*win
;
7532 PurpleConversation
*conv
;
7533 PidginConversation
*gtkconv
;
7536 conv
= pidgin_conv_window_get_active_conversation(win
);
7538 g_return_if_fail(conv
!= NULL
);
7540 if (!PURPLE_IS_IM_CONVERSATION(conv
))
7543 gtkconv
= PIDGIN_CONVERSATION(conv
);
7545 if (gtkconv
->u
.im
->typing_timer
!= 0) {
7546 g_source_remove(gtkconv
->u
.im
->typing_timer
);
7547 gtkconv
->u
.im
->typing_timer
= 0;
7550 stop_anim(NULL
, gtkconv
);
7554 close_window(GtkWidget
*w
, PidginConvWindow
*win
)
7556 close_win_cb(w
, NULL
, win
);
7560 detach_tab_cb(GtkWidget
*w
, PidginConvWindow
*win
)
7562 PidginConvWindow
*new_window
;
7563 PidginConversation
*gtkconv
;
7565 gtkconv
= win
->clicked_tab
;
7570 /* Nothing to do if there's only one tab in the window */
7571 if (pidgin_conv_window_get_gtkconv_count(win
) == 1)
7574 pidgin_conv_window_remove_gtkconv(win
, gtkconv
);
7576 new_window
= pidgin_conv_window_new();
7577 pidgin_conv_window_add_gtkconv(new_window
, gtkconv
);
7578 pidgin_conv_window_show(new_window
);
7582 close_others_cb(GtkWidget
*w
, PidginConvWindow
*win
)
7585 PidginConversation
*gtkconv
;
7587 gtkconv
= win
->clicked_tab
;
7592 for (iter
= pidgin_conv_window_get_gtkconvs(win
); iter
; )
7594 PidginConversation
*gconv
= iter
->data
;
7597 if (gconv
!= gtkconv
)
7599 close_conv_cb(NULL
, gconv
);
7605 close_tab_cb(GtkWidget
*w
, PidginConvWindow
*win
)
7607 PidginConversation
*gtkconv
;
7609 gtkconv
= win
->clicked_tab
;
7612 close_conv_cb(NULL
, gtkconv
);
7616 notebook_menu_switch_cb(GtkWidget
*item
, GtkWidget
*child
)
7618 GtkNotebook
*notebook
;
7621 notebook
= GTK_NOTEBOOK(gtk_widget_get_parent(child
));
7622 index
= gtk_notebook_page_num(notebook
, child
);
7623 gtk_notebook_set_current_page(notebook
, index
);
7627 notebook_menu_update_label_cb(GtkWidget
*child
, GParamSpec
*pspec
,
7628 GtkNotebook
*notebook
)
7633 item
= g_object_get_data(G_OBJECT(child
), "popup-menu-item");
7634 label
= gtk_bin_get_child(GTK_BIN(item
));
7636 gtk_container_remove(GTK_CONTAINER(item
), label
);
7638 label
= gtk_notebook_get_menu_label(notebook
, child
);
7640 gtk_widget_show(label
);
7641 gtk_container_add(GTK_CONTAINER(item
), label
);
7642 gtk_widget_show(item
);
7644 gtk_widget_hide(item
);
7649 notebook_add_tab_to_menu_cb(GtkNotebook
*notebook
, GtkWidget
*child
,
7650 guint page_num
, PidginConvWindow
*win
)
7655 item
= gtk_menu_item_new();
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
);
7663 g_signal_connect(child
, "child-notify::menu-label",
7664 G_CALLBACK(notebook_menu_update_label_cb
), notebook
);
7665 g_signal_connect(item
, "activate",
7666 G_CALLBACK(notebook_menu_switch_cb
), child
);
7667 g_object_set_data(G_OBJECT(child
), "popup-menu-item", item
);
7669 gtk_menu_shell_insert(GTK_MENU_SHELL(win
->notebook_menu
), item
, page_num
);
7673 notebook_remove_tab_from_menu_cb(GtkNotebook
*notebook
, GtkWidget
*child
,
7674 guint page_num
, PidginConvWindow
*win
)
7678 /* Disconnecting the "child-notify::menu-label" signal. */
7679 g_signal_handlers_disconnect_by_data(child
, notebook
);
7681 item
= g_object_get_data(G_OBJECT(child
), "popup-menu-item");
7682 gtk_container_remove(GTK_CONTAINER(win
->notebook_menu
), item
);
7687 notebook_reorder_tab_in_menu_cb(GtkNotebook
*notebook
, GtkWidget
*child
,
7688 guint page_num
, PidginConvWindow
*win
)
7692 item
= g_object_get_data(G_OBJECT(child
), "popup-menu-item");
7693 gtk_menu_reorder_child(GTK_MENU(win
->notebook_menu
), item
, page_num
);
7697 notebook_right_click_menu_cb(GtkNotebook
*notebook
, GdkEventButton
*event
,
7698 PidginConvWindow
*win
)
7701 PidginConversation
*gtkconv
;
7703 if (!gdk_event_triggers_context_menu((GdkEvent
*)event
))
7706 gtkconv
= pidgin_conv_window_get_gtkconv_at_index(win
,
7707 pidgin_conv_get_tab_at_xy(win
, event
->x_root
, event
->y_root
, NULL
));
7709 win
->clicked_tab
= gtkconv
;
7711 menu
= win
->notebook_menu
;
7713 gtk_menu_popup_at_pointer(GTK_MENU(menu
), (GdkEvent
*)event
);
7719 remove_edit_entry(PidginConversation
*gtkconv
, GtkWidget
*entry
)
7721 g_signal_handlers_disconnect_matched(G_OBJECT(entry
), G_SIGNAL_MATCH_DATA
,
7722 0, 0, NULL
, NULL
, gtkconv
);
7723 gtk_widget_show(gtkconv
->infopane
);
7724 gtk_widget_grab_focus(gtkconv
->editor
);
7725 gtk_widget_destroy(entry
);
7729 alias_focus_cb(GtkWidget
*widget
, GdkEventFocus
*event
, gpointer user_data
)
7731 remove_edit_entry(user_data
, widget
);
7736 alias_key_press_cb(GtkWidget
*widget
, GdkEventKey
*event
, gpointer user_data
)
7738 if (event
->keyval
== GDK_KEY_Escape
) {
7739 remove_edit_entry(user_data
, widget
);
7746 alias_cb(GtkEntry
*entry
, gpointer user_data
)
7748 PidginConversation
*gtkconv
;
7749 PurpleConversation
*conv
;
7750 PurpleAccount
*account
;
7753 gtkconv
= (PidginConversation
*)user_data
;
7754 if (gtkconv
== NULL
) {
7757 conv
= gtkconv
->active_conv
;
7758 account
= purple_conversation_get_account(conv
);
7759 name
= purple_conversation_get_name(conv
);
7761 if (PURPLE_IS_IM_CONVERSATION(conv
)) {
7763 buddy
= purple_blist_find_buddy(account
, name
);
7764 if (buddy
!= NULL
) {
7765 purple_buddy_set_local_alias(buddy
, gtk_entry_get_text(entry
));
7767 purple_serv_alias_buddy(buddy
);
7768 } else if (PURPLE_IS_CHAT_CONVERSATION(conv
)) {
7769 gtk_entry_set_text(GTK_ENTRY(gtkconv
->u
.chat
->topic_text
), gtk_entry_get_text(entry
));
7770 topic_callback(NULL
, gtkconv
);
7772 remove_edit_entry(user_data
, GTK_WIDGET(entry
));
7776 infopane_entry_activate(PidginConversation
*gtkconv
)
7778 GtkWidget
*entry
= NULL
;
7779 PurpleConversation
*conv
= gtkconv
->active_conv
;
7780 const char *text
= NULL
;
7782 if (!gtk_widget_get_visible(gtkconv
->infopane
)) {
7783 /* There's already an entry for alias. Let's not create another one. */
7787 if (!purple_account_is_connected(purple_conversation_get_account(gtkconv
->active_conv
))) {
7788 /* Do not allow aliasing someone on a disconnected account. */
7792 if (PURPLE_IS_IM_CONVERSATION(conv
)) {
7793 PurpleBuddy
*buddy
= purple_blist_find_buddy(purple_conversation_get_account(gtkconv
->active_conv
), purple_conversation_get_name(gtkconv
->active_conv
));
7795 /* This buddy isn't in your buddy list, so we can't alias him */
7798 text
= purple_buddy_get_contact_alias(buddy
);
7799 } else if (PURPLE_IS_CHAT_CONVERSATION(conv
)) {
7800 PurpleConnection
*gc
;
7801 PurpleProtocol
*protocol
= NULL
;
7803 gc
= purple_conversation_get_connection(conv
);
7805 protocol
= purple_connection_get_protocol(gc
);
7806 if (protocol
&& !PURPLE_PROTOCOL_IMPLEMENTS(protocol
, CHAT
, set_topic
))
7807 /* This protocol doesn't support setting the chat room topic */
7810 text
= purple_chat_conversation_get_topic(PURPLE_CHAT_CONVERSATION(conv
));
7814 entry
= gtk_entry_new();
7815 gtk_entry_set_has_frame(GTK_ENTRY(entry
), FALSE
);
7816 gtk_entry_set_width_chars(GTK_ENTRY(entry
), 10);
7817 gtk_entry_set_alignment(GTK_ENTRY(entry
), 0.5);
7819 gtk_box_pack_start(GTK_BOX(gtkconv
->infopane_hbox
), entry
, TRUE
, TRUE
, 0);
7820 /* after the tab label */
7821 gtk_box_reorder_child(GTK_BOX(gtkconv
->infopane_hbox
), entry
, 0);
7823 g_signal_connect(G_OBJECT(entry
), "activate", G_CALLBACK(alias_cb
), gtkconv
);
7824 g_signal_connect(G_OBJECT(entry
), "focus-out-event", G_CALLBACK(alias_focus_cb
), gtkconv
);
7825 g_signal_connect(G_OBJECT(entry
), "key-press-event", G_CALLBACK(alias_key_press_cb
), gtkconv
);
7828 gtk_entry_set_text(GTK_ENTRY(entry
), text
);
7829 gtk_widget_show(entry
);
7830 gtk_widget_hide(gtkconv
->infopane
);
7831 gtk_widget_grab_focus(entry
);
7837 window_keypress_cb(GtkWidget
*widget
, GdkEventKey
*event
, PidginConvWindow
*win
)
7839 PidginConversation
*gtkconv
= pidgin_conv_window_get_active_gtkconv(win
);
7841 return conv_keypress_common(gtkconv
, event
);
7845 switch_conv_cb(GtkNotebook
*notebook
, GtkWidget
*page
, gint page_num
,
7848 PidginConvWindow
*win
;
7849 PurpleConversation
*conv
;
7850 PidginConversation
*gtkconv
;
7851 const char *sound_method
;
7854 gtkconv
= pidgin_conv_window_get_gtkconv_at_index(win
, page_num
);
7855 conv
= gtkconv
->active_conv
;
7857 g_return_if_fail(conv
!= NULL
);
7859 /* clear unseen flag if conversation is not hidden */
7860 if(!pidgin_conv_is_hidden(gtkconv
)) {
7861 gtkconv_set_unseen(gtkconv
, PIDGIN_UNSEEN_NONE
);
7864 /* Update the menubar */
7866 gtk_toggle_action_set_active(GTK_TOGGLE_ACTION(gtkconv
->win
->menu
->logging
),
7867 purple_conversation_is_logging(conv
));
7869 generate_send_to_items(win
);
7870 generate_e2ee_controls(win
);
7871 regenerate_options_items(win
);
7872 regenerate_plugins_items(win
);
7874 pidgin_conv_switch_active_conversation(conv
);
7876 sound_method
= purple_prefs_get_string(PIDGIN_PREFS_ROOT
"/sound/method");
7877 if (!purple_strequal(sound_method
, "none"))
7878 gtk_toggle_action_set_active(GTK_TOGGLE_ACTION(win
->menu
->sounds
),
7879 gtkconv
->make_sound
);
7881 gtk_toggle_action_set_active(GTK_TOGGLE_ACTION(win
->menu
->show_formatting_toolbar
),
7882 purple_prefs_get_bool(PIDGIN_PREFS_ROOT
"/conversations/show_formatting_toolbar"));
7885 * We pause icons when they are not visible. If this icon should
7886 * be animated then start it back up again.
7888 if (PURPLE_IS_IM_CONVERSATION(conv
) &&
7889 (gtkconv
->u
.im
->animate
))
7890 start_anim(NULL
, gtkconv
);
7892 purple_signal_emit(pidgin_conversations_get_handle(), "conversation-switched", conv
);
7895 /**************************************************************************
7897 **************************************************************************/
7900 pidgin_conv_windows_get_list()
7906 make_status_icon_list(const char *stock
, GtkWidget
*w
)
7909 l
= g_list_append(l
,
7910 gtk_widget_render_icon(w
, stock
,
7911 gtk_icon_size_from_name(PIDGIN_ICON_SIZE_TANGO_EXTRA_SMALL
), "GtkWindow"));
7912 l
= g_list_append(l
,
7913 gtk_widget_render_icon(w
, stock
,
7914 gtk_icon_size_from_name(PIDGIN_ICON_SIZE_TANGO_SMALL
), "GtkWindow"));
7915 l
= g_list_append(l
,
7916 gtk_widget_render_icon(w
, stock
,
7917 gtk_icon_size_from_name(PIDGIN_ICON_SIZE_TANGO_MEDIUM
), "GtkWindow"));
7918 l
= g_list_append(l
,
7919 gtk_widget_render_icon(w
, stock
,
7920 gtk_icon_size_from_name(PIDGIN_ICON_SIZE_TANGO_LARGE
), "GtkWindow"));
7925 create_icon_lists(GtkWidget
*w
)
7927 available_list
= make_status_icon_list(PIDGIN_STOCK_STATUS_AVAILABLE
, w
);
7928 busy_list
= make_status_icon_list(PIDGIN_STOCK_STATUS_BUSY
, w
);
7929 xa_list
= make_status_icon_list(PIDGIN_STOCK_STATUS_XA
, w
);
7930 offline_list
= make_status_icon_list(PIDGIN_STOCK_STATUS_OFFLINE
, w
);
7931 away_list
= make_status_icon_list(PIDGIN_STOCK_STATUS_AWAY
, w
);
7932 protocol_lists
= g_hash_table_new(g_str_hash
, g_str_equal
);
7936 plugin_changed_cb(PurplePlugin
*p
, gpointer data
)
7938 regenerate_plugins_items(data
);
7941 static gboolean
gtk_conv_configure_cb(GtkWidget
*w
, GdkEventConfigure
*event
, gpointer data
) {
7944 if (gtk_widget_get_visible(w
))
7945 gtk_window_get_position(GTK_WINDOW(w
), &x
, &y
);
7947 return FALSE
; /* carry on normally */
7949 /* Workaround for GTK+ bug # 169811 - "configure_event" is fired
7950 * when the window is being maximized */
7951 if (gdk_window_get_state(gtk_widget_get_window(w
)) & GDK_WINDOW_STATE_MAXIMIZED
)
7954 /* don't save off-screen positioning */
7955 if (x
+ event
->width
< 0 ||
7956 y
+ event
->height
< 0 ||
7957 x
> gdk_screen_width() ||
7958 y
> gdk_screen_height())
7959 return FALSE
; /* carry on normally */
7961 /* store the position */
7962 purple_prefs_set_int(PIDGIN_PREFS_ROOT
"/conversations/im/x", x
);
7963 purple_prefs_set_int(PIDGIN_PREFS_ROOT
"/conversations/im/y", y
);
7964 purple_prefs_set_int(PIDGIN_PREFS_ROOT
"/conversations/im/width", event
->width
);
7965 purple_prefs_set_int(PIDGIN_PREFS_ROOT
"/conversations/im/height", event
->height
);
7967 /* continue to handle event normally */
7973 pidgin_conv_set_position_size(PidginConvWindow
*win
, int conv_x
, int conv_y
,
7974 int conv_width
, int conv_height
)
7976 /* if the window exists, is hidden, we're saving positions, and the
7977 * position is sane... */
7978 if (win
&& win
->window
&&
7979 !gtk_widget_get_visible(win
->window
) && conv_width
!= 0) {
7981 #ifdef _WIN32 /* only override window manager placement on Windows */
7982 /* ...check position is on screen... */
7983 if (conv_x
>= gdk_screen_width())
7984 conv_x
= gdk_screen_width() - 100;
7985 else if (conv_x
+ conv_width
< 0)
7988 if (conv_y
>= gdk_screen_height())
7989 conv_y
= gdk_screen_height() - 100;
7990 else if (conv_y
+ conv_height
< 0)
7993 /* ...and move it back. */
7994 gtk_window_move(GTK_WINDOW(win
->window
), conv_x
, conv_y
);
7996 gtk_window_resize(GTK_WINDOW(win
->window
), conv_width
, conv_height
);
8001 pidgin_conv_restore_position(PidginConvWindow
*win
) {
8002 pidgin_conv_set_position_size(win
,
8003 purple_prefs_get_int(PIDGIN_PREFS_ROOT
"/conversations/im/x"),
8004 purple_prefs_get_int(PIDGIN_PREFS_ROOT
"/conversations/im/y"),
8005 purple_prefs_get_int(PIDGIN_PREFS_ROOT
"/conversations/im/width"),
8006 purple_prefs_get_int(PIDGIN_PREFS_ROOT
"/conversations/im/height"));
8010 pidgin_conv_window_new()
8012 PidginConvWindow
*win
;
8013 GtkPositionType pos
;
8014 GtkWidget
*testidea
;
8018 GdkModifierType state
;
8020 win
= g_malloc0(sizeof(PidginConvWindow
));
8021 win
->menu
= g_malloc0(sizeof(PidginConvWindowMenu
));
8023 window_list
= g_list_append(window_list
, win
);
8025 /* Create the window. */
8026 win
->window
= pidgin_create_window(NULL
, 0, "conversation", TRUE
);
8027 /*_pidgin_widget_set_accessible_name(win->window, "Conversations");*/
8028 if (!gtk_get_current_event_state(&state
))
8029 gtk_window_set_focus_on_map(GTK_WINDOW(win
->window
), FALSE
);
8031 /* Etan: I really think this entire function call should happen only
8032 * when we are on Windows but I was informed that back before we used
8033 * to save the window position we stored the window size, so I'm
8034 * leaving it for now. */
8035 #if TRUE || defined(_WIN32)
8036 pidgin_conv_restore_position(win
);
8039 if (available_list
== NULL
) {
8040 create_icon_lists(win
->window
);
8043 g_signal_connect(G_OBJECT(win
->window
), "delete_event",
8044 G_CALLBACK(close_win_cb
), win
);
8045 g_signal_connect(G_OBJECT(win
->window
), "focus_in_event",
8046 G_CALLBACK(focus_win_cb
), win
);
8048 /* Intercept keystrokes from the menu items */
8049 g_signal_connect(G_OBJECT(win
->window
), "key_press_event",
8050 G_CALLBACK(window_keypress_cb
), win
);
8053 /* Create the notebook. */
8054 win
->notebook
= gtk_notebook_new();
8056 pos
= purple_prefs_get_int(PIDGIN_PREFS_ROOT
"/conversations/tab_side");
8058 gtk_notebook_set_tab_pos(GTK_NOTEBOOK(win
->notebook
), pos
);
8059 gtk_notebook_set_scrollable(GTK_NOTEBOOK(win
->notebook
), TRUE
);
8060 gtk_notebook_set_show_tabs(GTK_NOTEBOOK(win
->notebook
), FALSE
);
8061 gtk_notebook_set_show_border(GTK_NOTEBOOK(win
->notebook
), TRUE
);
8063 menu
= win
->notebook_menu
= gtk_menu_new();
8065 pidgin_separator(GTK_WIDGET(menu
));
8067 item
= gtk_menu_item_new_with_label(_("Close other tabs"));
8068 gtk_widget_show(item
);
8069 gtk_menu_shell_append(GTK_MENU_SHELL(menu
), item
);
8070 g_signal_connect(G_OBJECT(item
), "activate",
8071 G_CALLBACK(close_others_cb
), win
);
8073 item
= gtk_menu_item_new_with_label(_("Close all tabs"));
8074 gtk_widget_show(item
);
8075 gtk_menu_shell_append(GTK_MENU_SHELL(menu
), item
);
8076 g_signal_connect(G_OBJECT(item
), "activate",
8077 G_CALLBACK(close_window
), win
);
8079 pidgin_separator(menu
);
8081 item
= gtk_menu_item_new_with_label(_("Detach this tab"));
8082 gtk_widget_show(item
);
8083 gtk_menu_shell_append(GTK_MENU_SHELL(menu
), item
);
8084 g_signal_connect(G_OBJECT(item
), "activate",
8085 G_CALLBACK(detach_tab_cb
), win
);
8087 item
= gtk_menu_item_new_with_label(_("Close this tab"));
8088 gtk_widget_show(item
);
8089 gtk_menu_shell_append(GTK_MENU_SHELL(menu
), item
);
8090 g_signal_connect(G_OBJECT(item
), "activate",
8091 G_CALLBACK(close_tab_cb
), win
);
8093 g_signal_connect(G_OBJECT(win
->notebook
), "page-added",
8094 G_CALLBACK(notebook_add_tab_to_menu_cb
), win
);
8095 g_signal_connect(G_OBJECT(win
->notebook
), "page-removed",
8096 G_CALLBACK(notebook_remove_tab_from_menu_cb
), win
);
8097 g_signal_connect(G_OBJECT(win
->notebook
), "page-reordered",
8098 G_CALLBACK(notebook_reorder_tab_in_menu_cb
), win
);
8100 g_signal_connect(G_OBJECT(win
->notebook
), "button-press-event",
8101 G_CALLBACK(notebook_right_click_menu_cb
), win
);
8103 gtk_widget_show(win
->notebook
);
8105 g_signal_connect(G_OBJECT(win
->notebook
), "switch_page",
8106 G_CALLBACK(before_switch_conv_cb
), win
);
8107 g_signal_connect_after(G_OBJECT(win
->notebook
), "switch_page",
8108 G_CALLBACK(switch_conv_cb
), win
);
8110 /* Setup the tab drag and drop signals. */
8111 gtk_widget_add_events(win
->notebook
,
8112 GDK_BUTTON1_MOTION_MASK
| GDK_LEAVE_NOTIFY_MASK
);
8113 g_signal_connect(G_OBJECT(win
->notebook
), "button_press_event",
8114 G_CALLBACK(notebook_press_cb
), win
);
8115 g_signal_connect(G_OBJECT(win
->notebook
), "button_release_event",
8116 G_CALLBACK(notebook_release_cb
), win
);
8118 testidea
= gtk_box_new(GTK_ORIENTATION_VERTICAL
, 0);
8120 /* Setup the menubar. */
8121 menubar
= setup_menubar(win
);
8122 gtk_box_pack_start(GTK_BOX(testidea
), menubar
, FALSE
, TRUE
, 0);
8124 gtk_box_pack_start(GTK_BOX(testidea
), win
->notebook
, TRUE
, TRUE
, 0);
8126 gtk_container_add(GTK_CONTAINER(win
->window
), testidea
);
8128 gtk_widget_show(testidea
);
8130 /* Update the plugin actions when plugins are (un)loaded */
8131 purple_signal_connect(purple_plugins_get_handle(), "plugin-load",
8132 win
, PURPLE_CALLBACK(plugin_changed_cb
), win
);
8133 purple_signal_connect(purple_plugins_get_handle(), "plugin-unload",
8134 win
, PURPLE_CALLBACK(plugin_changed_cb
), win
);
8138 g_signal_connect(G_OBJECT(win
->window
), "show",
8139 G_CALLBACK(winpidgin_ensure_onscreen
), win
->window
);
8141 if (purple_prefs_get_bool(PIDGIN_PREFS_ROOT
"/win32/minimize_new_convs")
8142 && !gtk_get_current_event_state(&state
))
8143 gtk_window_iconify(GTK_WINDOW(win
->window
));
8146 purple_signal_emit(pidgin_conversations_get_handle(),
8147 "conversation-window-created", win
);
8150 pidgin_conversations_set_tab_colors();
8156 pidgin_conv_window_destroy(PidginConvWindow
*win
)
8158 if (win
->gtkconvs
) {
8159 GList
*iter
= win
->gtkconvs
;
8162 PidginConversation
*gtkconv
= iter
->data
;
8164 close_conv_cb(NULL
, gtkconv
);
8169 purple_prefs_disconnect_by_handle(win
);
8170 window_list
= g_list_remove(window_list
, win
);
8172 gtk_widget_destroy(win
->notebook_menu
);
8173 gtk_widget_destroy(win
->window
);
8175 g_object_unref(G_OBJECT(win
->menu
->ui
));
8177 purple_notify_close_with_handle(win
);
8178 purple_signals_disconnect_by_handle(win
);
8185 pidgin_conv_window_show(PidginConvWindow
*win
)
8187 gtk_widget_show(win
->window
);
8191 pidgin_conv_window_hide(PidginConvWindow
*win
)
8193 gtk_widget_hide(win
->window
);
8197 pidgin_conv_window_raise(PidginConvWindow
*win
)
8199 gdk_window_raise(GDK_WINDOW(gtk_widget_get_window(win
->window
)));
8203 pidgin_conv_window_switch_gtkconv(PidginConvWindow
*win
, PidginConversation
*gtkconv
)
8205 gtk_notebook_set_current_page(GTK_NOTEBOOK(win
->notebook
),
8206 gtk_notebook_page_num(GTK_NOTEBOOK(win
->notebook
),
8207 gtkconv
->tab_cont
));
8211 gtkconv_tab_set_tip(GtkWidget
*widget
, GdkEventCrossing
*event
, PidginConversation
*gtkconv
)
8213 /* PANGO_VERSION_CHECK macro was introduced in 1.15. So we need this double check. */
8214 #ifndef PANGO_VERSION_CHECK
8215 #define pango_layout_is_ellipsized(l) TRUE
8216 #elif !PANGO_VERSION_CHECK(1,16,0)
8217 #define pango_layout_is_ellipsized(l) TRUE
8219 PangoLayout
*layout
;
8221 layout
= gtk_label_get_layout(GTK_LABEL(gtkconv
->tab_label
));
8222 if (pango_layout_is_ellipsized(layout
))
8223 gtk_widget_set_tooltip_text(widget
, gtk_label_get_text(GTK_LABEL(gtkconv
->tab_label
)));
8225 gtk_widget_set_tooltip_text(widget
, NULL
);
8231 set_default_tab_colors(GtkWidget
*widget
)
8234 GtkCssProvider
*provider
;
8235 GError
*error
= NULL
;
8239 const char *labelname
;
8242 {"tab-label-typing", "#4e9a06"},
8243 {"tab-label-typed", "#c4a000"},
8244 {"tab-label-attention", "#006aff"},
8245 {"tab-label-unreadchat", "#cc0000"},
8246 {"tab-label-event", "#888a85"},
8250 str
= g_string_new(NULL
);
8252 for (iter
= 0; styles
[iter
].labelname
; iter
++) {
8253 g_string_append_printf(str
,
8257 styles
[iter
].labelname
,
8258 styles
[iter
].color
);
8261 provider
= gtk_css_provider_new();
8263 gtk_css_provider_load_from_data(provider
, str
->str
, str
->len
, &error
);
8265 gtk_style_context_add_provider(gtk_widget_get_style_context(widget
),
8266 GTK_STYLE_PROVIDER(provider
),
8267 GTK_STYLE_PROVIDER_PRIORITY_APPLICATION
);
8270 g_error_free(error
);
8271 g_string_free(str
, TRUE
);
8275 pidgin_conv_window_add_gtkconv(PidginConvWindow
*win
, PidginConversation
*gtkconv
)
8277 PurpleConversation
*conv
= gtkconv
->active_conv
;
8278 PidginConversation
*focus_gtkconv
;
8279 GtkWidget
*tab_cont
= gtkconv
->tab_cont
;
8280 const gchar
*tmp_lab
;
8282 win
->gtkconvs
= g_list_append(win
->gtkconvs
, gtkconv
);
8285 if (win
->gtkconvs
&& win
->gtkconvs
->next
&& win
->gtkconvs
->next
->next
== NULL
)
8286 pidgin_conv_tab_pack(win
, ((PidginConversation
*)win
->gtkconvs
->data
));
8290 gtkconv
->close
= pidgin_create_small_button(gtk_label_new("×"));
8291 gtk_widget_set_tooltip_text(gtkconv
->close
, _("Close conversation"));
8293 g_signal_connect(gtkconv
->close
, "clicked", G_CALLBACK (close_conv_cb
), gtkconv
);
8296 gtkconv
->icon
= gtk_image_new();
8297 gtkconv
->menu_icon
= gtk_image_new();
8298 g_object_set(G_OBJECT(gtkconv
->icon
),
8299 "icon-size", gtk_icon_size_from_name(PIDGIN_ICON_SIZE_TANGO_MICROSCOPIC
),
8301 g_object_set(G_OBJECT(gtkconv
->menu_icon
),
8302 "icon-size", gtk_icon_size_from_name(PIDGIN_ICON_SIZE_TANGO_MICROSCOPIC
),
8304 gtk_widget_show(gtkconv
->icon
);
8305 update_tab_icon(conv
);
8308 gtkconv
->tab_label
= gtk_label_new(tmp_lab
= purple_conversation_get_title(conv
));
8309 set_default_tab_colors(gtkconv
->tab_label
);
8310 gtk_widget_set_name(gtkconv
->tab_label
, "tab-label");
8312 gtkconv
->menu_tabby
= gtk_box_new(GTK_ORIENTATION_HORIZONTAL
, PIDGIN_HIG_BOX_SPACE
);
8313 gtkconv
->menu_label
= gtk_label_new(tmp_lab
);
8314 gtk_box_pack_start(GTK_BOX(gtkconv
->menu_tabby
), gtkconv
->menu_icon
, FALSE
, FALSE
, 0);
8316 gtk_widget_show_all(gtkconv
->menu_icon
);
8318 gtk_box_pack_start(GTK_BOX(gtkconv
->menu_tabby
), gtkconv
->menu_label
, TRUE
, TRUE
, 0);
8319 gtk_widget_show(gtkconv
->menu_label
);
8320 gtk_label_set_xalign(GTK_LABEL(gtkconv
->menu_label
), 0);
8321 gtk_label_set_yalign(GTK_LABEL(gtkconv
->menu_label
), 0);
8323 gtk_widget_show(gtkconv
->menu_tabby
);
8325 if (PURPLE_IS_IM_CONVERSATION(conv
))
8326 pidgin_conv_update_buddy_icon(PURPLE_IM_CONVERSATION(conv
));
8328 /* Build and set conversations tab */
8329 pidgin_conv_tab_pack(win
, gtkconv
);
8331 gtk_notebook_set_menu_label(GTK_NOTEBOOK(win
->notebook
), tab_cont
, gtkconv
->menu_tabby
);
8333 gtk_widget_show(tab_cont
);
8335 if (pidgin_conv_window_get_gtkconv_count(win
) == 1) {
8336 /* Er, bug in notebooks? Switch to the page manually. */
8337 gtk_notebook_set_current_page(GTK_NOTEBOOK(win
->notebook
), 0);
8339 gtk_notebook_set_show_tabs(GTK_NOTEBOOK(win
->notebook
), TRUE
);
8342 focus_gtkconv
= g_list_nth_data(pidgin_conv_window_get_gtkconvs(win
),
8343 gtk_notebook_get_current_page(GTK_NOTEBOOK(win
->notebook
)));
8344 gtk_widget_grab_focus(focus_gtkconv
->editor
);
8346 if (pidgin_conv_window_get_gtkconv_count(win
) == 1)
8347 update_send_to_selection(win
);
8351 pidgin_conv_tab_pack(PidginConvWindow
*win
, PidginConversation
*gtkconv
)
8353 gboolean tabs_side
= FALSE
;
8355 GtkWidget
*first
, *third
, *ebox
, *parent
;
8357 if (purple_prefs_get_int(PIDGIN_PREFS_ROOT
"/conversations/tab_side") == GTK_POS_LEFT
||
8358 purple_prefs_get_int(PIDGIN_PREFS_ROOT
"/conversations/tab_side") == GTK_POS_RIGHT
)
8360 else if (purple_prefs_get_int(PIDGIN_PREFS_ROOT
"/conversations/tab_side") == (GTK_POS_LEFT
|8))
8362 else if (purple_prefs_get_int(PIDGIN_PREFS_ROOT
"/conversations/tab_side") == (GTK_POS_RIGHT
|8))
8366 g_object_set(G_OBJECT(gtkconv
->tab_label
), "ellipsize", PANGO_ELLIPSIZE_END
, NULL
);
8367 gtk_label_set_width_chars(GTK_LABEL(gtkconv
->tab_label
), 4);
8369 g_object_set(G_OBJECT(gtkconv
->tab_label
), "ellipsize", PANGO_ELLIPSIZE_NONE
, NULL
);
8370 gtk_label_set_width_chars(GTK_LABEL(gtkconv
->tab_label
), -1);
8374 gtk_label_set_width_chars(
8375 GTK_LABEL(gtkconv
->tab_label
),
8376 MIN(g_utf8_strlen(gtk_label_get_text(GTK_LABEL(gtkconv
->tab_label
)), -1), 12)
8380 gtk_label_set_angle(GTK_LABEL(gtkconv
->tab_label
), angle
);
8383 gtkconv
->tabby
= gtk_box_new(GTK_ORIENTATION_VERTICAL
, PIDGIN_HIG_BOX_SPACE
);
8385 gtkconv
->tabby
= gtk_box_new(GTK_ORIENTATION_HORIZONTAL
, PIDGIN_HIG_BOX_SPACE
);
8386 gtk_widget_set_name(gtkconv
->tabby
, "tab-container");
8388 /* select the correct ordering for verticle tabs */
8390 first
= gtkconv
->close
;
8391 third
= gtkconv
->icon
;
8393 first
= gtkconv
->icon
;
8394 third
= gtkconv
->close
;
8397 ebox
= gtk_event_box_new();
8398 gtk_event_box_set_visible_window(GTK_EVENT_BOX(ebox
), FALSE
);
8399 gtk_container_add(GTK_CONTAINER(ebox
), gtkconv
->tabby
);
8400 g_signal_connect(G_OBJECT(ebox
), "enter-notify-event",
8401 G_CALLBACK(gtkconv_tab_set_tip
), gtkconv
);
8403 parent
= gtk_widget_get_parent(gtkconv
->tab_label
);
8404 if (parent
!= NULL
) {
8405 /* reparent old widgets on preference changes */
8406 g_object_ref(first
);
8407 g_object_ref(gtkconv
->tab_label
);
8408 g_object_ref(third
);
8409 gtk_container_remove(GTK_CONTAINER(parent
), first
);
8410 gtk_container_remove(GTK_CONTAINER(parent
), gtkconv
->tab_label
);
8411 gtk_container_remove(GTK_CONTAINER(parent
), third
);
8414 gtk_box_pack_start(GTK_BOX(gtkconv
->tabby
), first
, FALSE
, FALSE
, 0);
8415 gtk_box_pack_start(GTK_BOX(gtkconv
->tabby
), gtkconv
->tab_label
, TRUE
, TRUE
, 0);
8416 gtk_box_pack_start(GTK_BOX(gtkconv
->tabby
), third
, FALSE
, FALSE
, 0);
8418 if (parent
== NULL
) {
8419 /* Add this pane to the conversation's notebook. */
8420 gtk_notebook_append_page(GTK_NOTEBOOK(win
->notebook
), gtkconv
->tab_cont
, ebox
);
8422 /* reparent old widgets on preference changes */
8423 g_object_unref(first
);
8424 g_object_unref(gtkconv
->tab_label
);
8425 g_object_unref(third
);
8427 /* Reset the tabs label to the new version */
8428 gtk_notebook_set_tab_label(GTK_NOTEBOOK(win
->notebook
), gtkconv
->tab_cont
, ebox
);
8431 gtk_container_child_set(GTK_CONTAINER(win
->notebook
), gtkconv
->tab_cont
,
8432 "tab-expand", !tabs_side
&& !angle
,
8433 "tab-fill", TRUE
, NULL
);
8435 if (pidgin_conv_window_get_gtkconv_count(win
) == 1)
8436 gtk_notebook_set_show_tabs(GTK_NOTEBOOK(win
->notebook
),
8437 purple_prefs_get_bool(PIDGIN_PREFS_ROOT
"/conversations/tabs") &&
8438 (!purple_prefs_get_bool(PIDGIN_PREFS_ROOT
"/conversations/im/show_buddy_icons") ||
8439 purple_prefs_get_int(PIDGIN_PREFS_ROOT
"/conversations/tab_side") != GTK_POS_TOP
));
8441 /* show the widgets */
8442 /* gtk_widget_show(gtkconv->icon); */
8443 gtk_widget_show(gtkconv
->tab_label
);
8444 if (purple_prefs_get_bool(PIDGIN_PREFS_ROOT
"/conversations/close_on_tabs"))
8445 gtk_widget_show(gtkconv
->close
);
8446 gtk_widget_show(gtkconv
->tabby
);
8447 gtk_widget_show(ebox
);
8451 pidgin_conv_window_remove_gtkconv(PidginConvWindow
*win
, PidginConversation
*gtkconv
)
8455 index
= gtk_notebook_page_num(GTK_NOTEBOOK(win
->notebook
), gtkconv
->tab_cont
);
8457 g_object_ref_sink(G_OBJECT(gtkconv
->tab_cont
));
8459 gtk_notebook_remove_page(GTK_NOTEBOOK(win
->notebook
), index
);
8461 win
->gtkconvs
= g_list_remove(win
->gtkconvs
, gtkconv
);
8463 g_signal_handlers_disconnect_matched(win
->window
, G_SIGNAL_MATCH_DATA
,
8464 0, 0, NULL
, NULL
, gtkconv
);
8466 if (win
->gtkconvs
&& win
->gtkconvs
->next
== NULL
)
8467 pidgin_conv_tab_pack(win
, win
->gtkconvs
->data
);
8469 if (!win
->gtkconvs
&& win
!= hidden_convwin
)
8470 pidgin_conv_window_destroy(win
);
8473 PidginConversation
*
8474 pidgin_conv_window_get_gtkconv_at_index(const PidginConvWindow
*win
, int index
)
8476 GtkWidget
*tab_cont
;
8480 tab_cont
= gtk_notebook_get_nth_page(GTK_NOTEBOOK(win
->notebook
), index
);
8481 return tab_cont
? g_object_get_data(G_OBJECT(tab_cont
), "PidginConversation") : NULL
;
8484 PidginConversation
*
8485 pidgin_conv_window_get_active_gtkconv(const PidginConvWindow
*win
)
8488 GtkWidget
*tab_cont
;
8490 index
= gtk_notebook_get_current_page(GTK_NOTEBOOK(win
->notebook
));
8493 tab_cont
= gtk_notebook_get_nth_page(GTK_NOTEBOOK(win
->notebook
), index
);
8496 return g_object_get_data(G_OBJECT(tab_cont
), "PidginConversation");
8500 PurpleConversation
*
8501 pidgin_conv_window_get_active_conversation(const PidginConvWindow
*win
)
8503 PidginConversation
*gtkconv
;
8505 gtkconv
= pidgin_conv_window_get_active_gtkconv(win
);
8506 return gtkconv
? gtkconv
->active_conv
: NULL
;
8510 pidgin_conv_window_is_active_conversation(const PurpleConversation
*conv
)
8512 return conv
== pidgin_conv_window_get_active_conversation(PIDGIN_CONVERSATION(conv
)->win
);
8516 pidgin_conv_window_has_focus(PidginConvWindow
*win
)
8518 gboolean has_focus
= FALSE
;
8520 g_object_get(G_OBJECT(win
->window
), "has-toplevel-focus", &has_focus
, NULL
);
8526 pidgin_conv_window_get_at_event(GdkEvent
*event
)
8528 PidginConvWindow
*win
;
8533 gdkwin
= gdk_device_get_window_at_position(gdk_event_get_device(event
),
8537 gdkwin
= gdk_window_get_toplevel(gdkwin
);
8539 for (l
= pidgin_conv_windows_get_list(); l
!= NULL
; l
= l
->next
) {
8542 if (gdkwin
== gtk_widget_get_window(win
->window
))
8550 pidgin_conv_window_get_gtkconvs(PidginConvWindow
*win
)
8552 return win
->gtkconvs
;
8556 pidgin_conv_window_get_gtkconv_count(PidginConvWindow
*win
)
8558 return g_list_length(win
->gtkconvs
);
8562 pidgin_conv_window_first_im(void)
8564 GList
*wins
, *convs
;
8565 PidginConvWindow
*win
;
8566 PidginConversation
*conv
;
8568 for (wins
= pidgin_conv_windows_get_list(); wins
!= NULL
; wins
= wins
->next
) {
8571 for (convs
= win
->gtkconvs
;
8573 convs
= convs
->next
) {
8577 if (PURPLE_IS_IM_CONVERSATION(conv
->active_conv
))
8586 pidgin_conv_window_last_im(void)
8588 GList
*wins
, *convs
;
8589 PidginConvWindow
*win
;
8590 PidginConversation
*conv
;
8592 for (wins
= g_list_last(pidgin_conv_windows_get_list());
8594 wins
= wins
->prev
) {
8598 for (convs
= win
->gtkconvs
;
8600 convs
= convs
->next
) {
8604 if (PURPLE_IS_IM_CONVERSATION(conv
->active_conv
))
8613 pidgin_conv_window_first_chat(void)
8615 GList
*wins
, *convs
;
8616 PidginConvWindow
*win
;
8617 PidginConversation
*conv
;
8619 for (wins
= pidgin_conv_windows_get_list(); wins
!= NULL
; wins
= wins
->next
) {
8622 for (convs
= win
->gtkconvs
;
8624 convs
= convs
->next
) {
8628 if (PURPLE_IS_CHAT_CONVERSATION(conv
->active_conv
))
8637 pidgin_conv_window_last_chat(void)
8639 GList
*wins
, *convs
;
8640 PidginConvWindow
*win
;
8641 PidginConversation
*conv
;
8643 for (wins
= g_list_last(pidgin_conv_windows_get_list());
8645 wins
= wins
->prev
) {
8649 for (convs
= win
->gtkconvs
;
8651 convs
= convs
->next
) {
8655 if (PURPLE_IS_CHAT_CONVERSATION(conv
->active_conv
))
8664 /**************************************************************************
8665 * Conversation placement functions
8666 **************************************************************************/
8671 PidginConvPlacementFunc fnc
;
8673 } ConvPlacementData
;
8675 static GList
*conv_placement_fncs
= NULL
;
8676 static PidginConvPlacementFunc place_conv
= NULL
;
8678 /* This one places conversations in the last made window. */
8680 conv_placement_last_created_win(PidginConversation
*conv
)
8682 PidginConvWindow
*win
;
8684 GList
*l
= g_list_last(pidgin_conv_windows_get_list());
8685 win
= l
? l
->data
: NULL
;;
8688 win
= pidgin_conv_window_new();
8690 g_signal_connect(G_OBJECT(win
->window
), "configure_event",
8691 G_CALLBACK(gtk_conv_configure_cb
), NULL
);
8693 pidgin_conv_window_add_gtkconv(win
, conv
);
8694 pidgin_conv_window_show(win
);
8696 pidgin_conv_window_add_gtkconv(win
, conv
);
8700 /* This one places conversations in the last made window of the same type. */
8702 conv_placement_last_created_win_type_configured_cb(GtkWidget
*w
,
8703 GdkEventConfigure
*event
, PidginConversation
*conv
)
8708 if (gtk_widget_get_visible(w
))
8709 gtk_window_get_position(GTK_WINDOW(w
), &x
, &y
);
8711 return FALSE
; /* carry on normally */
8713 /* Workaround for GTK+ bug # 169811 - "configure_event" is fired
8714 * when the window is being maximized */
8715 if (gdk_window_get_state(gtk_widget_get_window(w
)) & GDK_WINDOW_STATE_MAXIMIZED
)
8718 /* don't save off-screen positioning */
8719 if (x
+ event
->width
< 0 ||
8720 y
+ event
->height
< 0 ||
8721 x
> gdk_screen_width() ||
8722 y
> gdk_screen_height())
8723 return FALSE
; /* carry on normally */
8725 for (all
= conv
->convs
; all
!= NULL
; all
= all
->next
) {
8726 if (PURPLE_IS_IM_CONVERSATION(conv
->active_conv
) != PURPLE_IS_IM_CONVERSATION(all
->data
)) {
8727 /* this window has different types of conversation, don't save */
8732 if (PURPLE_IS_IM_CONVERSATION(conv
->active_conv
)) {
8733 purple_prefs_set_int(PIDGIN_PREFS_ROOT
"/conversations/im/x", x
);
8734 purple_prefs_set_int(PIDGIN_PREFS_ROOT
"/conversations/im/y", y
);
8735 purple_prefs_set_int(PIDGIN_PREFS_ROOT
"/conversations/im/width", event
->width
);
8736 purple_prefs_set_int(PIDGIN_PREFS_ROOT
"/conversations/im/height", event
->height
);
8737 } else if (PURPLE_IS_CHAT_CONVERSATION(conv
->active_conv
)) {
8738 purple_prefs_set_int(PIDGIN_PREFS_ROOT
"/conversations/chat/x", x
);
8739 purple_prefs_set_int(PIDGIN_PREFS_ROOT
"/conversations/chat/y", y
);
8740 purple_prefs_set_int(PIDGIN_PREFS_ROOT
"/conversations/chat/width", event
->width
);
8741 purple_prefs_set_int(PIDGIN_PREFS_ROOT
"/conversations/chat/height", event
->height
);
8748 conv_placement_last_created_win_type(PidginConversation
*conv
)
8750 PidginConvWindow
*win
;
8752 if (PURPLE_IS_IM_CONVERSATION(conv
->active_conv
))
8753 win
= pidgin_conv_window_last_im();
8755 win
= pidgin_conv_window_last_chat();
8758 win
= pidgin_conv_window_new();
8760 if (PURPLE_IS_IM_CONVERSATION(conv
->active_conv
) ||
8761 purple_prefs_get_int(PIDGIN_PREFS_ROOT
"/conversations/chat/width") == 0) {
8762 pidgin_conv_set_position_size(win
,
8763 purple_prefs_get_int(PIDGIN_PREFS_ROOT
"/conversations/im/x"),
8764 purple_prefs_get_int(PIDGIN_PREFS_ROOT
"/conversations/im/y"),
8765 purple_prefs_get_int(PIDGIN_PREFS_ROOT
"/conversations/im/width"),
8766 purple_prefs_get_int(PIDGIN_PREFS_ROOT
"/conversations/im/height"));
8767 } else if (PURPLE_IS_CHAT_CONVERSATION(conv
->active_conv
)) {
8768 pidgin_conv_set_position_size(win
,
8769 purple_prefs_get_int(PIDGIN_PREFS_ROOT
"/conversations/chat/x"),
8770 purple_prefs_get_int(PIDGIN_PREFS_ROOT
"/conversations/chat/y"),
8771 purple_prefs_get_int(PIDGIN_PREFS_ROOT
"/conversations/chat/width"),
8772 purple_prefs_get_int(PIDGIN_PREFS_ROOT
"/conversations/chat/height"));
8775 pidgin_conv_window_add_gtkconv(win
, conv
);
8776 pidgin_conv_window_show(win
);
8778 g_signal_connect(G_OBJECT(win
->window
), "configure_event",
8779 G_CALLBACK(conv_placement_last_created_win_type_configured_cb
), conv
);
8781 pidgin_conv_window_add_gtkconv(win
, conv
);
8784 /* This one places each conversation in its own window. */
8786 conv_placement_new_window(PidginConversation
*conv
)
8788 PidginConvWindow
*win
;
8790 win
= pidgin_conv_window_new();
8792 g_signal_connect(G_OBJECT(win
->window
), "configure_event",
8793 G_CALLBACK(gtk_conv_configure_cb
), NULL
);
8795 pidgin_conv_window_add_gtkconv(win
, conv
);
8797 pidgin_conv_window_show(win
);
8800 static PurpleGroup
*
8801 conv_get_group(PidginConversation
*conv
)
8803 PurpleGroup
*group
= NULL
;
8805 if (PURPLE_IS_IM_CONVERSATION(conv
->active_conv
)) {
8808 buddy
= purple_blist_find_buddy(purple_conversation_get_account(conv
->active_conv
),
8809 purple_conversation_get_name(conv
->active_conv
));
8812 group
= purple_buddy_get_group(buddy
);
8814 } else if (PURPLE_IS_CHAT_CONVERSATION(conv
->active_conv
)) {
8817 chat
= purple_blist_find_chat(purple_conversation_get_account(conv
->active_conv
),
8818 purple_conversation_get_name(conv
->active_conv
));
8821 group
= purple_chat_get_group(chat
);
8828 * This groups things by, well, group. Buddies from groups will always be
8829 * grouped together, and a buddy from a group not belonging to any currently
8830 * open windows will get a new window.
8833 conv_placement_by_group(PidginConversation
*conv
)
8835 PurpleGroup
*group
= NULL
;
8838 group
= conv_get_group(conv
);
8840 /* Go through the list of IMs and find one with this group. */
8841 for (wl
= pidgin_conv_windows_get_list(); wl
!= NULL
; wl
= wl
->next
) {
8842 PidginConvWindow
*win2
;
8843 PidginConversation
*conv2
;
8844 PurpleGroup
*group2
= NULL
;
8848 for (cl
= win2
->gtkconvs
;
8853 group2
= conv_get_group(conv2
);
8855 if (group
== group2
) {
8856 pidgin_conv_window_add_gtkconv(win2
, conv
);
8863 /* Make a new window. */
8864 conv_placement_new_window(conv
);
8867 /* This groups things by account. Otherwise, the same semantics as above */
8869 conv_placement_by_account(PidginConversation
*conv
)
8871 GList
*wins
, *convs
;
8872 PurpleAccount
*account
;
8874 account
= purple_conversation_get_account(conv
->active_conv
);
8876 /* Go through the list of IMs and find one with this group. */
8877 for (wins
= pidgin_conv_windows_get_list(); wins
!= NULL
; wins
= wins
->next
) {
8878 PidginConvWindow
*win2
;
8879 PidginConversation
*conv2
;
8883 for (convs
= win2
->gtkconvs
;
8885 convs
= convs
->next
) {
8886 conv2
= convs
->data
;
8888 if (account
== purple_conversation_get_account(conv2
->active_conv
)) {
8889 pidgin_conv_window_add_gtkconv(win2
, conv
);
8895 /* Make a new window. */
8896 conv_placement_new_window(conv
);
8899 static ConvPlacementData
*
8900 get_conv_placement_data(const char *id
)
8902 ConvPlacementData
*data
= NULL
;
8905 for (n
= conv_placement_fncs
; n
; n
= n
->next
) {
8907 if (purple_strequal(data
->id
, id
))
8915 add_conv_placement_fnc(const char *id
, const char *name
,
8916 PidginConvPlacementFunc fnc
)
8918 ConvPlacementData
*data
;
8920 data
= g_new(ConvPlacementData
, 1);
8922 data
->id
= g_strdup(id
);
8923 data
->name
= g_strdup(name
);
8926 conv_placement_fncs
= g_list_append(conv_placement_fncs
, data
);
8930 ensure_default_funcs(void)
8932 if (conv_placement_fncs
== NULL
) {
8933 add_conv_placement_fnc("last", _("Last created window"),
8934 conv_placement_last_created_win
);
8935 add_conv_placement_fnc("im_chat", _("Separate IM and Chat windows"),
8936 conv_placement_last_created_win_type
);
8937 add_conv_placement_fnc("new", _("New window"),
8938 conv_placement_new_window
);
8939 add_conv_placement_fnc("group", _("By group"),
8940 conv_placement_by_group
);
8941 add_conv_placement_fnc("account", _("By account"),
8942 conv_placement_by_account
);
8947 pidgin_conv_placement_get_options(void)
8949 GList
*n
, *list
= NULL
;
8950 ConvPlacementData
*data
;
8952 ensure_default_funcs();
8954 for (n
= conv_placement_fncs
; n
; n
= n
->next
) {
8956 list
= g_list_append(list
, data
->name
);
8957 list
= g_list_append(list
, data
->id
);
8965 pidgin_conv_placement_add_fnc(const char *id
, const char *name
,
8966 PidginConvPlacementFunc fnc
)
8968 g_return_if_fail(id
!= NULL
);
8969 g_return_if_fail(name
!= NULL
);
8970 g_return_if_fail(fnc
!= NULL
);
8972 ensure_default_funcs();
8974 add_conv_placement_fnc(id
, name
, fnc
);
8978 pidgin_conv_placement_remove_fnc(const char *id
)
8980 ConvPlacementData
*data
= get_conv_placement_data(id
);
8985 conv_placement_fncs
= g_list_remove(conv_placement_fncs
, data
);
8993 pidgin_conv_placement_get_name(const char *id
)
8995 ConvPlacementData
*data
;
8997 ensure_default_funcs();
8999 data
= get_conv_placement_data(id
);
9007 PidginConvPlacementFunc
9008 pidgin_conv_placement_get_fnc(const char *id
)
9010 ConvPlacementData
*data
;
9012 ensure_default_funcs();
9014 data
= get_conv_placement_data(id
);
9023 pidgin_conv_placement_set_current_func(PidginConvPlacementFunc func
)
9025 g_return_if_fail(func
!= NULL
);
9027 /* If tabs are enabled, set the function, otherwise, NULL it out. */
9028 if (purple_prefs_get_bool(PIDGIN_PREFS_ROOT
"/conversations/tabs"))
9034 PidginConvPlacementFunc
9035 pidgin_conv_placement_get_current_func(void)
9041 pidgin_conv_placement_place(PidginConversation
*gtkconv
)
9044 place_conv(gtkconv
);
9046 conv_placement_new_window(gtkconv
);
9050 pidgin_conv_is_hidden(PidginConversation
*gtkconv
)
9052 g_return_val_if_fail(gtkconv
!= NULL
, FALSE
);
9054 return (gtkconv
->win
== hidden_convwin
);
9058 gdouble
luminance(GdkRGBA color
)
9062 gdouble cutoff
= 0.03928, scale
= 12.92;
9063 gdouble a
= 0.055, d
= 1.055, p
= 2.2;
9069 r
= (rr
> cutoff
) ? pow((rr
+a
)/d
, p
) : rr
/scale
;
9070 g
= (gg
> cutoff
) ? pow((gg
+a
)/d
, p
) : gg
/scale
;
9071 b
= (bb
> cutoff
) ? pow((bb
+a
)/d
, p
) : bb
/scale
;
9073 return (r
*0.2126 + g
*0.7152 + b
*0.0722);
9076 /* Algorithm from https://www.w3.org/TR/2008/REC-WCAG20-20081211/relative-luminance.xml */
9078 color_is_visible(GdkRGBA foreground
, GdkRGBA background
, gdouble min_contrast_ratio
)
9080 gdouble lfg
, lbg
, lmin
, lmax
;
9081 gdouble luminosity_ratio
;
9084 lfg
= luminance(foreground
);
9085 lbg
= luminance(background
);
9088 lmax
= lfg
, lmin
= lbg
;
9090 lmax
= lbg
, lmin
= lfg
;
9092 nr
= lmax
+ 0.05, dr
= lmin
- 0.05;
9093 if (dr
< 0.005 && dr
> -0.005)
9096 luminosity_ratio
= nr
/dr
;
9097 if ( luminosity_ratio
< 0)
9098 luminosity_ratio
*= -1.0;
9099 return (luminosity_ratio
> min_contrast_ratio
);
9104 generate_nick_colors(guint numcolors
, GdkRGBA background
)
9107 GArray
*colors
= g_array_new(FALSE
, FALSE
, sizeof(GdkRGBA
));
9108 GdkRGBA nick_highlight
;
9110 time_t breakout_time
;
9112 gdk_rgba_parse(&nick_highlight
, DEFAULT_HIGHLIGHT_COLOR
);
9113 gdk_rgba_parse(&send_color
, DEFAULT_SEND_COLOR
);
9115 pidgin_style_adjust_contrast(NULL
, &nick_highlight
);
9116 pidgin_style_adjust_contrast(NULL
, &send_color
);
9118 srand(background
.red
* 65535 + background
.green
* 65535 + background
.blue
* 65535 + 1);
9120 breakout_time
= time(NULL
) + 3;
9122 /* first we look through the list of "good" colors: colors that differ from every other color in the
9123 * list. only some of them will differ from the background color though. lets see if we can find
9124 * numcolors of them that do
9126 while (i
< numcolors
&& j
< PIDGIN_NUM_NICK_SEED_COLORS
&& time(NULL
) < breakout_time
)
9128 GdkRGBA color
= nick_seed_colors
[j
];
9130 if (color_is_visible(color
, background
, MIN_LUMINANCE_CONTRAST_RATIO
) &&
9131 color_is_visible(color
, nick_highlight
, MIN_LUMINANCE_CONTRAST_RATIO
) &&
9132 color_is_visible(color
, send_color
, MIN_LUMINANCE_CONTRAST_RATIO
))
9134 g_array_append_val(colors
, color
);
9140 /* we might not have found numcolors in the last loop. if we did, we'll never enter this one.
9141 * if we did not, lets just find some colors that don't conflict with the background. its
9142 * expensive to find colors that not only don't conflict with the background, but also do not
9143 * conflict with each other.
9145 while(i
< numcolors
&& time(NULL
) < breakout_time
)
9147 GdkRGBA color
= {g_random_double_range(0, 1), g_random_double_range(0, 1), g_random_double_range(0, 1), 1};
9149 if (color_is_visible(color
, background
, MIN_LUMINANCE_CONTRAST_RATIO
) &&
9150 color_is_visible(color
, nick_highlight
, MIN_LUMINANCE_CONTRAST_RATIO
) &&
9151 color_is_visible(color
, send_color
, MIN_LUMINANCE_CONTRAST_RATIO
))
9153 g_array_append_val(colors
, color
);
9158 if (i
< numcolors
) {
9159 purple_debug_warning("gtkconv", "Unable to generate enough random colors before timeout. %u colors found.\n", i
);
9163 /* To remove errors caused by an empty array. */
9164 GdkRGBA color
= {0.5, 0.5, 0.5, 1.0};
9165 g_array_append_val(colors
, color
);
9171 /**************************************************************************
9172 * PidginConvWindow GBoxed code
9173 **************************************************************************/
9174 static PidginConvWindow
*
9175 pidgin_conv_window_ref(PidginConvWindow
*win
)
9177 g_return_val_if_fail(win
!= NULL
, NULL
);
9185 pidgin_conv_window_unref(PidginConvWindow
*win
)
9187 g_return_if_fail(win
!= NULL
);
9188 g_return_if_fail(win
->box_count
>= 0);
9190 if (!win
->box_count
--)
9191 pidgin_conv_window_destroy(win
);
9195 pidgin_conv_window_get_type(void)
9197 static GType type
= 0;
9200 type
= g_boxed_type_register_static("PidginConvWindow",
9201 (GBoxedCopyFunc
)pidgin_conv_window_ref
,
9202 (GBoxedFreeFunc
)pidgin_conv_window_unref
);