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 GtkWidget
*view
= NULL
;
1810 PidginConversation
*gtkconv
= data
;
1812 /* If we have a valid key for the conversation display, then exit */
1813 if ((event
->state
& GDK_CONTROL_MASK
) ||
1814 (event
->keyval
== GDK_KEY_F6
) ||
1815 (event
->keyval
== GDK_KEY_F10
) ||
1816 (event
->keyval
== GDK_KEY_Menu
) ||
1817 (event
->keyval
== GDK_KEY_Shift_L
) ||
1818 (event
->keyval
== GDK_KEY_Shift_R
) ||
1819 (event
->keyval
== GDK_KEY_Control_L
) ||
1820 (event
->keyval
== GDK_KEY_Control_R
) ||
1821 (event
->keyval
== GDK_KEY_Escape
) ||
1822 (event
->keyval
== GDK_KEY_Up
) ||
1823 (event
->keyval
== GDK_KEY_Down
) ||
1824 (event
->keyval
== GDK_KEY_Left
) ||
1825 (event
->keyval
== GDK_KEY_Right
) ||
1826 (event
->keyval
== GDK_KEY_Page_Up
) ||
1827 (event
->keyval
== GDK_KEY_KP_Page_Up
) ||
1828 (event
->keyval
== GDK_KEY_Page_Down
) ||
1829 (event
->keyval
== GDK_KEY_KP_Page_Down
) ||
1830 (event
->keyval
== GDK_KEY_Home
) ||
1831 (event
->keyval
== GDK_KEY_End
) ||
1832 (event
->keyval
== GDK_KEY_Tab
) ||
1833 (event
->keyval
== GDK_KEY_KP_Tab
) ||
1834 (event
->keyval
== GDK_KEY_ISO_Left_Tab
))
1836 if (event
->type
== GDK_KEY_PRESS
)
1837 return conv_keypress_common(gtkconv
, event
);
1841 view
= talkatu_editor_get_view(TALKATU_EDITOR(gtkconv
->editor
));
1842 gtk_widget_grab_focus(view
);
1843 gtk_widget_event(view
, (GdkEvent
*)event
);
1849 regenerate_options_items(PidginConvWindow
*win
);
1852 pidgin_conv_switch_active_conversation(PurpleConversation
*conv
)
1854 PidginConversation
*gtkconv
;
1855 PurpleConversation
*old_conv
;
1856 PurpleConnectionFlags features
;
1858 g_return_if_fail(conv
!= NULL
);
1860 gtkconv
= PIDGIN_CONVERSATION(conv
);
1861 old_conv
= gtkconv
->active_conv
;
1863 purple_debug_info("gtkconv", "setting active conversation on toolbar %p\n",
1866 if (old_conv
== conv
)
1869 purple_conversation_close_logs(old_conv
);
1870 gtkconv
->active_conv
= conv
;
1872 purple_conversation_set_logging(conv
,
1873 gtk_toggle_action_get_active(GTK_TOGGLE_ACTION(gtkconv
->win
->menu
->logging
)));
1875 purple_signal_emit(pidgin_conversations_get_handle(), "conversation-switched", conv
);
1877 gray_stuff_out(gtkconv
);
1878 update_typing_icon(gtkconv
);
1879 g_object_set_data(G_OBJECT(gtkconv
->entry
), "transient_buddy", NULL
);
1880 regenerate_options_items(gtkconv
->win
);
1882 gtk_window_set_title(GTK_WINDOW(gtkconv
->win
->window
),
1883 gtk_label_get_text(GTK_LABEL(gtkconv
->tab_label
)));
1887 menu_conv_sel_send_cb(GObject
*m
, gpointer data
)
1889 PurpleAccount
*account
= g_object_get_data(m
, "purple_account");
1890 gchar
*name
= g_object_get_data(m
, "purple_buddy_name");
1891 PurpleIMConversation
*im
;
1893 if (gtk_check_menu_item_get_active((GtkCheckMenuItem
*) m
) == FALSE
)
1896 im
= purple_im_conversation_new(account
, name
);
1897 pidgin_conv_switch_active_conversation(PURPLE_CONVERSATION(im
));
1900 /**************************************************************************
1901 * A bunch of buddy icon functions
1902 **************************************************************************/
1904 static GList
*get_protocol_icon_list(PurpleAccount
*account
)
1907 PurpleProtocol
*protocol
=
1908 purple_protocols_find(purple_account_get_protocol_id(account
));
1909 const char *protoname
= purple_protocol_class_list_icon(protocol
, account
, NULL
);
1910 l
= g_hash_table_lookup(protocol_lists
, protoname
);
1914 l
= g_list_prepend(l
, pidgin_create_protocol_icon(account
, PIDGIN_PROTOCOL_ICON_LARGE
));
1915 l
= g_list_prepend(l
, pidgin_create_protocol_icon(account
, PIDGIN_PROTOCOL_ICON_MEDIUM
));
1916 l
= g_list_prepend(l
, pidgin_create_protocol_icon(account
, PIDGIN_PROTOCOL_ICON_SMALL
));
1918 g_hash_table_insert(protocol_lists
, g_strdup(protoname
), l
);
1923 pidgin_conv_get_tab_icons(PurpleConversation
*conv
)
1925 PurpleAccount
*account
= NULL
;
1926 const char *name
= NULL
;
1928 g_return_val_if_fail(conv
!= NULL
, NULL
);
1930 account
= purple_conversation_get_account(conv
);
1931 name
= purple_conversation_get_name(conv
);
1933 g_return_val_if_fail(account
!= NULL
, NULL
);
1934 g_return_val_if_fail(name
!= NULL
, NULL
);
1936 /* Use the buddy icon, if possible */
1937 if (PURPLE_IS_IM_CONVERSATION(conv
)) {
1938 PurpleBuddy
*b
= purple_blist_find_buddy(account
, name
);
1941 p
= purple_buddy_get_presence(b
);
1942 if (purple_presence_is_status_primitive_active(p
, PURPLE_STATUS_AWAY
))
1944 if (purple_presence_is_status_primitive_active(p
, PURPLE_STATUS_UNAVAILABLE
))
1946 if (purple_presence_is_status_primitive_active(p
, PURPLE_STATUS_EXTENDED_AWAY
))
1948 if (purple_presence_is_status_primitive_active(p
, PURPLE_STATUS_OFFLINE
))
1949 return offline_list
;
1951 return available_list
;
1955 return get_protocol_icon_list(account
);
1959 pidgin_conv_get_icon_stock(PurpleConversation
*conv
)
1961 PurpleAccount
*account
= NULL
;
1962 const char *stock
= NULL
;
1964 g_return_val_if_fail(conv
!= NULL
, NULL
);
1966 account
= purple_conversation_get_account(conv
);
1967 g_return_val_if_fail(account
!= NULL
, NULL
);
1969 /* Use the buddy icon, if possible */
1970 if (PURPLE_IS_IM_CONVERSATION(conv
)) {
1971 const char *name
= NULL
;
1973 name
= purple_conversation_get_name(conv
);
1974 b
= purple_blist_find_buddy(account
, name
);
1976 PurplePresence
*p
= purple_buddy_get_presence(b
);
1977 PurpleStatus
*active
= purple_presence_get_active_status(p
);
1978 PurpleStatusType
*type
= purple_status_get_status_type(active
);
1979 PurpleStatusPrimitive prim
= purple_status_type_get_primitive(type
);
1980 stock
= pidgin_stock_id_from_status_primitive(prim
);
1982 stock
= PIDGIN_STOCK_STATUS_PERSON
;
1985 stock
= PIDGIN_STOCK_STATUS_CHAT
;
1992 pidgin_conv_get_icon(PurpleConversation
*conv
, GtkWidget
*parent
, const char *icon_size
)
1994 PurpleAccount
*account
= NULL
;
1995 const char *name
= NULL
;
1996 const char *stock
= NULL
;
1997 GdkPixbuf
*status
= NULL
;
2000 g_return_val_if_fail(conv
!= NULL
, NULL
);
2002 account
= purple_conversation_get_account(conv
);
2003 name
= purple_conversation_get_name(conv
);
2005 g_return_val_if_fail(account
!= NULL
, NULL
);
2006 g_return_val_if_fail(name
!= NULL
, NULL
);
2008 /* Use the buddy icon, if possible */
2009 if (PURPLE_IS_IM_CONVERSATION(conv
)) {
2010 PurpleBuddy
*b
= purple_blist_find_buddy(account
, name
);
2012 /* I hate this hack. It fixes a bug where the pending message icon
2013 * displays in the conv tab even though it shouldn't.
2014 * A better solution would be great. */
2015 purple_blist_update_node(NULL
, PURPLE_BLIST_NODE(b
));
2019 stock
= pidgin_conv_get_icon_stock(conv
);
2020 size
= gtk_icon_size_from_name(icon_size
);
2021 status
= gtk_widget_render_icon (parent
, stock
, size
, "GtkWidget");
2026 pidgin_conv_get_tab_icon(PurpleConversation
*conv
, gboolean small_icon
)
2028 const char *icon_size
= small_icon
? PIDGIN_ICON_SIZE_TANGO_MICROSCOPIC
: PIDGIN_ICON_SIZE_TANGO_EXTRA_SMALL
;
2029 return pidgin_conv_get_icon(conv
, PIDGIN_CONVERSATION(conv
)->icon
, icon_size
);
2034 update_tab_icon(PurpleConversation
*conv
)
2036 PidginConversation
*gtkconv
;
2037 PidginConvWindow
*win
;
2039 GdkPixbuf
*emblem
= NULL
;
2040 const char *status
= NULL
;
2041 const char *infopane_status
= NULL
;
2043 g_return_if_fail(conv
!= NULL
);
2045 gtkconv
= PIDGIN_CONVERSATION(conv
);
2047 if (conv
!= gtkconv
->active_conv
)
2050 status
= infopane_status
= pidgin_conv_get_icon_stock(conv
);
2052 if (PURPLE_IS_IM_CONVERSATION(conv
)) {
2053 PurpleBuddy
*b
= purple_blist_find_buddy(purple_conversation_get_account(conv
), purple_conversation_get_name(conv
));
2055 emblem
= pidgin_blist_get_emblem((PurpleBlistNode
*)b
);
2058 g_return_if_fail(status
!= NULL
);
2060 g_object_set(G_OBJECT(gtkconv
->icon
), "stock", status
, NULL
);
2061 g_object_set(G_OBJECT(gtkconv
->menu_icon
), "stock", status
, NULL
);
2063 gtk_list_store_set(GTK_LIST_STORE(gtkconv
->infopane_model
),
2064 &(gtkconv
->infopane_iter
),
2065 CONV_ICON_COLUMN
, infopane_status
, -1);
2067 gtk_list_store_set(GTK_LIST_STORE(gtkconv
->infopane_model
),
2068 &(gtkconv
->infopane_iter
),
2069 CONV_EMBLEM_COLUMN
, emblem
, -1);
2071 g_object_unref(emblem
);
2073 if (purple_prefs_get_bool(PIDGIN_PREFS_ROOT
"/blist/show_protocol_icons")) {
2074 emblem
= pidgin_create_protocol_icon(purple_conversation_get_account(gtkconv
->active_conv
), PIDGIN_PROTOCOL_ICON_SMALL
);
2079 gtk_list_store_set(GTK_LIST_STORE(gtkconv
->infopane_model
),
2080 &(gtkconv
->infopane_iter
),
2081 CONV_PROTOCOL_ICON_COLUMN
, emblem
, -1);
2083 g_object_unref(emblem
);
2085 /* XXX seanegan Why do I have to do this? */
2086 gtk_widget_queue_resize(gtkconv
->infopane
);
2087 gtk_widget_queue_draw(gtkconv
->infopane
);
2089 if (pidgin_conv_window_is_active_conversation(conv
) &&
2090 (!PURPLE_IS_IM_CONVERSATION(conv
) || gtkconv
->u
.im
->anim
== NULL
))
2092 l
= pidgin_conv_get_tab_icons(conv
);
2094 gtk_window_set_icon_list(GTK_WINDOW(win
->window
), l
);
2099 redraw_icon(gpointer data
)
2101 PidginConversation
*gtkconv
= (PidginConversation
*)data
;
2102 PurpleConversation
*conv
= gtkconv
->active_conv
;
2103 PurpleAccount
*account
;
2108 int scale_width
, scale_height
;
2111 gtkconv
= PIDGIN_CONVERSATION(conv
);
2112 account
= purple_conversation_get_account(conv
);
2114 if (!(account
&& purple_account_get_connection(account
))) {
2115 gtkconv
->u
.im
->icon_timer
= 0;
2119 gdk_pixbuf_animation_iter_advance(gtkconv
->u
.im
->iter
, NULL
);
2120 buf
= gdk_pixbuf_animation_iter_get_pixbuf(gtkconv
->u
.im
->iter
);
2122 scale_width
= gdk_pixbuf_get_width(buf
);
2123 scale_height
= gdk_pixbuf_get_height(buf
);
2125 gtk_widget_get_size_request(gtkconv
->u
.im
->icon_container
, NULL
, &size
);
2126 size
= MIN(size
, MIN(scale_width
, scale_height
));
2127 size
= CLAMP(size
, BUDDYICON_SIZE_MIN
, BUDDYICON_SIZE_MAX
);
2129 if (scale_width
== scale_height
) {
2130 scale_width
= scale_height
= size
;
2131 } else if (scale_height
> scale_width
) {
2132 scale_width
= size
* scale_width
/ scale_height
;
2133 scale_height
= size
;
2135 scale_height
= size
* scale_height
/ scale_width
;
2139 scale
= gdk_pixbuf_scale_simple(buf
, scale_width
, scale_height
,
2140 GDK_INTERP_BILINEAR
);
2141 if (pidgin_gdk_pixbuf_is_opaque(scale
))
2142 pidgin_gdk_pixbuf_make_round(scale
);
2144 gtk_image_set_from_pixbuf(GTK_IMAGE(gtkconv
->u
.im
->icon
), scale
);
2145 g_object_unref(G_OBJECT(scale
));
2146 gtk_widget_queue_draw(gtkconv
->u
.im
->icon
);
2148 delay
= gdk_pixbuf_animation_iter_get_delay_time(gtkconv
->u
.im
->iter
);
2153 gtkconv
->u
.im
->icon_timer
= g_timeout_add(delay
, redraw_icon
, gtkconv
);
2159 start_anim(GtkWidget
*widget
, PidginConversation
*gtkconv
)
2163 if (gtkconv
->u
.im
->anim
== NULL
)
2166 if (gtkconv
->u
.im
->icon_timer
!= 0)
2169 if (gdk_pixbuf_animation_is_static_image(gtkconv
->u
.im
->anim
))
2172 delay
= gdk_pixbuf_animation_iter_get_delay_time(gtkconv
->u
.im
->iter
);
2177 gtkconv
->u
.im
->icon_timer
= g_timeout_add(delay
, redraw_icon
, gtkconv
);
2181 remove_icon(GtkWidget
*widget
, PidginConversation
*gtkconv
)
2185 PurpleConversation
*conv
= gtkconv
->active_conv
;
2187 g_return_if_fail(conv
!= NULL
);
2189 gtk_widget_set_size_request(gtkconv
->u
.im
->icon_container
, -1, BUDDYICON_SIZE_MIN
);
2190 children
= gtk_container_get_children(GTK_CONTAINER(gtkconv
->u
.im
->icon_container
));
2192 /* We know there's only one child here. It'd be nice to shortcut to the
2193 event box, but we can't change the PidginConversation until 3.0 */
2194 event
= (GtkWidget
*)children
->data
;
2195 gtk_container_remove(GTK_CONTAINER(gtkconv
->u
.im
->icon_container
), event
);
2196 g_list_free(children
);
2199 if (gtkconv
->u
.im
->anim
!= NULL
)
2200 g_object_unref(G_OBJECT(gtkconv
->u
.im
->anim
));
2202 if (gtkconv
->u
.im
->icon_timer
!= 0)
2203 g_source_remove(gtkconv
->u
.im
->icon_timer
);
2205 if (gtkconv
->u
.im
->iter
!= NULL
)
2206 g_object_unref(G_OBJECT(gtkconv
->u
.im
->iter
));
2208 gtkconv
->u
.im
->icon_timer
= 0;
2209 gtkconv
->u
.im
->icon
= NULL
;
2210 gtkconv
->u
.im
->anim
= NULL
;
2211 gtkconv
->u
.im
->iter
= NULL
;
2212 gtkconv
->u
.im
->show_icon
= FALSE
;
2216 saveicon_writefile_cb(void *user_data
, const char *filename
)
2218 PidginConversation
*gtkconv
= (PidginConversation
*)user_data
;
2219 PurpleIMConversation
*im
= PURPLE_IM_CONVERSATION(gtkconv
->active_conv
);
2220 PurpleBuddyIcon
*icon
;
2224 icon
= purple_im_conversation_get_icon(im
);
2225 data
= purple_buddy_icon_get_data(icon
, &len
);
2227 if ((len
<= 0) || (data
== NULL
) || !purple_util_write_data_to_file_absolute(filename
, data
, len
)) {
2228 purple_notify_error(gtkconv
, NULL
, _("Unable to save icon file to disk."), NULL
, NULL
);
2233 custom_icon_sel_cb(const char *filename
, gpointer data
)
2238 PurpleContact
*contact
;
2239 PidginConversation
*gtkconv
= data
;
2240 PurpleConversation
*conv
= gtkconv
->active_conv
;
2241 PurpleAccount
*account
= purple_conversation_get_account(conv
);
2243 name
= purple_conversation_get_name(conv
);
2244 buddy
= purple_blist_find_buddy(account
, name
);
2246 purple_debug_info("custom-icon", "You can only set custom icons for people on your buddylist.\n");
2249 contact
= purple_buddy_get_contact(buddy
);
2251 purple_buddy_icons_node_set_custom_icon_from_file((PurpleBlistNode
*)contact
, filename
);
2256 set_custom_icon_cb(GtkWidget
*widget
, PidginConversation
*gtkconv
)
2258 GtkWidget
*win
= pidgin_buddy_icon_chooser_new(GTK_WINDOW(gtkconv
->win
->window
),
2259 custom_icon_sel_cb
, gtkconv
);
2260 gtk_widget_show_all(win
);
2264 change_size_cb(GtkWidget
*widget
, PidginConversation
*gtkconv
)
2267 PurpleConversation
*conv
= gtkconv
->active_conv
;
2270 gtk_widget_get_size_request(gtkconv
->u
.im
->icon_container
, NULL
, &size
);
2272 if (size
== BUDDYICON_SIZE_MAX
) {
2273 size
= BUDDYICON_SIZE_MIN
;
2275 size
= BUDDYICON_SIZE_MAX
;
2278 gtk_widget_set_size_request(gtkconv
->u
.im
->icon_container
, -1, size
);
2279 pidgin_conv_update_buddy_icon(PURPLE_IM_CONVERSATION(conv
));
2281 buddies
= purple_blist_find_buddies(purple_conversation_get_account(conv
),
2282 purple_conversation_get_name(conv
));
2283 for (; buddies
; buddies
= g_slist_delete_link(buddies
, buddies
)) {
2284 PurpleBuddy
*buddy
= buddies
->data
;
2285 PurpleContact
*contact
= purple_buddy_get_contact(buddy
);
2286 purple_blist_node_set_int((PurpleBlistNode
*)contact
, "pidgin-infopane-iconsize", size
);
2291 remove_custom_icon_cb(GtkWidget
*widget
, PidginConversation
*gtkconv
)
2295 PurpleAccount
*account
;
2296 PurpleContact
*contact
;
2297 PurpleConversation
*conv
= gtkconv
->active_conv
;
2299 account
= purple_conversation_get_account(conv
);
2300 name
= purple_conversation_get_name(conv
);
2301 buddy
= purple_blist_find_buddy(account
, name
);
2305 contact
= purple_buddy_get_contact(buddy
);
2307 purple_buddy_icons_node_set_custom_icon_from_file((PurpleBlistNode
*)contact
, NULL
);
2311 icon_menu_save_cb(GtkWidget
*widget
, PidginConversation
*gtkconv
)
2313 PurpleConversation
*conv
= gtkconv
->active_conv
;
2317 g_return_if_fail(conv
!= NULL
);
2319 ext
= purple_buddy_icon_get_extension(purple_im_conversation_get_icon(PURPLE_IM_CONVERSATION(conv
)));
2321 buf
= g_strdup_printf("%s.%s", purple_normalize(purple_conversation_get_account(conv
), purple_conversation_get_name(conv
)), ext
);
2323 purple_request_file(gtkconv
, _("Save Icon"), buf
, TRUE
,
2324 G_CALLBACK(saveicon_writefile_cb
), NULL
,
2325 purple_request_cpar_from_conversation(conv
), gtkconv
);
2331 stop_anim(GtkWidget
*widget
, PidginConversation
*gtkconv
)
2333 if (gtkconv
->u
.im
->icon_timer
!= 0)
2334 g_source_remove(gtkconv
->u
.im
->icon_timer
);
2336 gtkconv
->u
.im
->icon_timer
= 0;
2341 toggle_icon_animate_cb(GtkWidget
*w
, PidginConversation
*gtkconv
)
2343 gtkconv
->u
.im
->animate
=
2344 gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(w
));
2346 if (gtkconv
->u
.im
->animate
)
2347 start_anim(NULL
, gtkconv
);
2349 stop_anim(NULL
, gtkconv
);
2353 icon_menu(GtkWidget
*widget
, GdkEventButton
*e
, PidginConversation
*gtkconv
)
2355 static GtkWidget
*menu
= NULL
;
2356 PurpleConversation
*conv
;
2359 if (e
->button
== GDK_BUTTON_PRIMARY
&& e
->type
== GDK_BUTTON_PRESS
) {
2360 change_size_cb(NULL
, gtkconv
);
2364 if (!gdk_event_triggers_context_menu((GdkEvent
*)e
)) {
2369 * If a menu already exists, destroy it before creating a new one,
2370 * thus freeing-up the memory it occupied.
2373 gtk_widget_destroy(menu
);
2375 menu
= gtk_menu_new();
2377 if (gtkconv
->u
.im
->anim
&&
2378 !(gdk_pixbuf_animation_is_static_image(gtkconv
->u
.im
->anim
)))
2380 pidgin_new_check_item(menu
, _("Animate"),
2381 G_CALLBACK(toggle_icon_animate_cb
), gtkconv
,
2382 gtkconv
->u
.im
->icon_timer
);
2385 pidgin_new_menu_item(menu
, _("Hide Icon"), NULL
,
2386 G_CALLBACK(remove_icon
), gtkconv
);
2388 pidgin_new_menu_item(menu
, _("Save Icon As..."), GTK_STOCK_SAVE_AS
,
2389 G_CALLBACK(icon_menu_save_cb
), gtkconv
);
2391 pidgin_new_menu_item(menu
, _("Set Custom Icon..."), NULL
,
2392 G_CALLBACK(set_custom_icon_cb
), gtkconv
);
2394 pidgin_new_menu_item(menu
, _("Change Size"), NULL
,
2395 G_CALLBACK(change_size_cb
), gtkconv
);
2397 /* Is there a custom icon for this person? */
2398 conv
= gtkconv
->active_conv
;
2399 buddy
= purple_blist_find_buddy(purple_conversation_get_account(conv
),
2400 purple_conversation_get_name(conv
));
2403 PurpleContact
*contact
= purple_buddy_get_contact(buddy
);
2404 if (contact
&& purple_buddy_icons_node_has_custom_icon((PurpleBlistNode
*)contact
))
2406 pidgin_new_menu_item(menu
, _("Remove Custom Icon"),
2407 NULL
, G_CALLBACK(remove_custom_icon_cb
),
2412 gtk_menu_popup_at_pointer(GTK_MENU(menu
), (GdkEvent
*)e
);
2417 /**************************************************************************
2418 * End of the bunch of buddy icon functions
2419 **************************************************************************/
2421 pidgin_conv_present_conversation(PurpleConversation
*conv
)
2423 PidginConversation
*gtkconv
;
2424 GdkModifierType state
;
2426 pidgin_conv_attach_to_conversation(conv
);
2427 gtkconv
= PIDGIN_CONVERSATION(conv
);
2429 pidgin_conv_switch_active_conversation(conv
);
2430 /* Switch the tab only if the user initiated the event by pressing
2431 * a button or hitting a key. */
2432 if (gtk_get_current_event_state(&state
))
2433 pidgin_conv_window_switch_gtkconv(gtkconv
->win
, gtkconv
);
2434 gtk_window_present(GTK_WINDOW(gtkconv
->win
->window
));
2438 pidgin_conversations_get_unseen(GList
*l
,
2439 PidginUnseenState min_state
,
2440 gboolean hidden_only
,
2446 for (; l
!= NULL
&& (max_count
== 0 || c
< max_count
); l
= l
->next
) {
2447 PurpleConversation
*conv
= (PurpleConversation
*)l
->data
;
2448 PidginConversation
*gtkconv
= PIDGIN_CONVERSATION(conv
);
2450 if(gtkconv
== NULL
|| gtkconv
->active_conv
!= conv
)
2453 if (gtkconv
->unseen_state
>= min_state
2455 (hidden_only
&& gtkconv
->win
== hidden_convwin
))) {
2457 r
= g_list_prepend(r
, conv
);
2466 pidgin_conversations_get_unseen_all(PidginUnseenState min_state
,
2467 gboolean hidden_only
,
2470 return pidgin_conversations_get_unseen(purple_conversations_get_all(),
2471 min_state
, hidden_only
, max_count
);
2475 pidgin_conversations_get_unseen_ims(PidginUnseenState min_state
,
2476 gboolean hidden_only
,
2479 return pidgin_conversations_get_unseen(purple_conversations_get_ims(),
2480 min_state
, hidden_only
, max_count
);
2484 pidgin_conversations_get_unseen_chats(PidginUnseenState min_state
,
2485 gboolean hidden_only
,
2488 return pidgin_conversations_get_unseen(purple_conversations_get_chats(),
2489 min_state
, hidden_only
, max_count
);
2493 unseen_conv_menu_cb(GtkMenuItem
*item
, PurpleConversation
*conv
)
2495 g_return_if_fail(conv
!= NULL
);
2496 pidgin_conv_present_conversation(conv
);
2500 unseen_all_conv_menu_cb(GtkMenuItem
*item
, GList
*list
)
2502 g_return_if_fail(list
!= NULL
);
2503 /* Do not free the list from here. It will be freed from the
2504 * 'destroy' callback on the menuitem. */
2506 pidgin_conv_present_conversation(list
->data
);
2512 pidgin_conversations_fill_menu(GtkWidget
*menu
, GList
*convs
)
2517 g_return_val_if_fail(menu
!= NULL
, 0);
2518 g_return_val_if_fail(convs
!= NULL
, 0);
2520 for (l
= convs
; l
!= NULL
; l
= l
->next
) {
2521 PurpleConversation
*conv
= (PurpleConversation
*)l
->data
;
2522 PidginConversation
*gtkconv
= PIDGIN_CONVERSATION(conv
);
2524 GtkWidget
*icon
= gtk_image_new_from_stock(pidgin_conv_get_icon_stock(conv
),
2525 gtk_icon_size_from_name(PIDGIN_ICON_SIZE_TANGO_MICROSCOPIC
));
2527 gchar
*text
= g_strdup_printf("%s (%d)",
2528 gtk_label_get_text(GTK_LABEL(gtkconv
->tab_label
)),
2529 gtkconv
->unseen_count
);
2531 item
= gtk_image_menu_item_new_with_label(text
);
2532 gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(item
), icon
);
2533 g_signal_connect(G_OBJECT(item
), "activate", G_CALLBACK(unseen_conv_menu_cb
), conv
);
2534 gtk_menu_shell_append(GTK_MENU_SHELL(menu
), item
);
2540 /* There are more than one conversation. Add an option to show all conversations. */
2542 GList
*list
= g_list_copy(convs
);
2544 pidgin_separator(menu
);
2546 item
= gtk_menu_item_new_with_label(_("Show All"));
2547 g_signal_connect(G_OBJECT(item
), "activate", G_CALLBACK(unseen_all_conv_menu_cb
), list
);
2548 g_signal_connect_swapped(G_OBJECT(item
), "destroy", G_CALLBACK(g_list_free
), list
);
2549 gtk_menu_shell_append(GTK_MENU_SHELL(menu
), item
);
2556 pidgin_conv_get_window(PidginConversation
*gtkconv
)
2558 g_return_val_if_fail(gtkconv
!= NULL
, NULL
);
2559 return gtkconv
->win
;
2562 static GtkActionEntry menu_entries
[] =
2563 /* TODO: fill out tooltips... */
2565 /* Conversation menu */
2566 { "ConversationMenu", NULL
, N_("_Conversation"), NULL
, NULL
, NULL
},
2567 { "NewInstantMessage", PIDGIN_STOCK_TOOLBAR_MESSAGE_NEW
, N_("New Instant _Message..."), "<control>M", NULL
, G_CALLBACK(menu_new_conv_cb
) },
2568 { "JoinAChat", PIDGIN_STOCK_CHAT
, N_("Join a _Chat..."), NULL
, NULL
, G_CALLBACK(menu_join_chat_cb
) },
2569 { "Find", GTK_STOCK_FIND
, N_("_Find..."), NULL
, NULL
, G_CALLBACK(menu_find_cb
) },
2570 { "ViewLog", NULL
, N_("View _Log"), NULL
, NULL
, G_CALLBACK(menu_view_log_cb
) },
2571 { "SaveAs", GTK_STOCK_SAVE_AS
, N_("_Save As..."), NULL
, NULL
, G_CALLBACK(menu_save_as_cb
) },
2572 { "ClearScrollback", GTK_STOCK_CLEAR
, N_("Clea_r Scrollback"), "<control>L", NULL
, G_CALLBACK(menu_clear_cb
) },
2575 { "MediaMenu", NULL
, N_("M_edia"), NULL
, NULL
, NULL
},
2576 { "AudioCall", PIDGIN_STOCK_TOOLBAR_AUDIO_CALL
, N_("_Audio Call"), NULL
, NULL
, G_CALLBACK(menu_initiate_media_call_cb
) },
2577 { "VideoCall", PIDGIN_STOCK_TOOLBAR_VIDEO_CALL
, N_("_Video Call"), NULL
, NULL
, G_CALLBACK(menu_initiate_media_call_cb
) },
2578 { "AudioVideoCall", PIDGIN_STOCK_TOOLBAR_VIDEO_CALL
, N_("Audio/Video _Call"), NULL
, NULL
, G_CALLBACK(menu_initiate_media_call_cb
) },
2581 { "SendFile", PIDGIN_STOCK_TOOLBAR_SEND_FILE
, N_("Se_nd File..."), NULL
, NULL
, G_CALLBACK(menu_send_file_cb
) },
2582 { "GetAttention", PIDGIN_STOCK_TOOLBAR_SEND_ATTENTION
, N_("Get _Attention"), NULL
, NULL
, G_CALLBACK(menu_get_attention_cb
) },
2583 { "AddBuddyPounce", NULL
, N_("Add Buddy _Pounce..."), NULL
, NULL
, G_CALLBACK(menu_add_pounce_cb
) },
2584 { "GetInfo", PIDGIN_STOCK_TOOLBAR_USER_INFO
, N_("_Get Info"), "<control>O", NULL
, G_CALLBACK(menu_get_info_cb
) },
2585 { "Invite", NULL
, N_("In_vite..."), NULL
, NULL
, G_CALLBACK(menu_invite_cb
) },
2586 { "MoreMenu", NULL
, N_("M_ore"), NULL
, NULL
, NULL
},
2587 { "Alias", NULL
, N_("Al_ias..."), NULL
, NULL
, G_CALLBACK(menu_alias_cb
) },
2588 { "Block", PIDGIN_STOCK_TOOLBAR_BLOCK
, N_("_Block..."), NULL
, NULL
, G_CALLBACK(menu_block_cb
) },
2589 { "Unblock", PIDGIN_STOCK_TOOLBAR_UNBLOCK
, N_("_Unblock..."), NULL
, NULL
, G_CALLBACK(menu_unblock_cb
) },
2590 { "Add", GTK_STOCK_ADD
, N_("_Add..."), NULL
, NULL
, G_CALLBACK(menu_add_remove_cb
) },
2591 { "Remove", GTK_STOCK_REMOVE
, N_("_Remove..."), NULL
, NULL
, G_CALLBACK(menu_add_remove_cb
) },
2592 { "InsertLink", PIDGIN_STOCK_TOOLBAR_INSERT_LINK
, N_("Insert Lin_k..."), NULL
, NULL
, NULL
},
2593 { "InsertImage", PIDGIN_STOCK_TOOLBAR_INSERT_IMAGE
, N_("Insert Imag_e..."), NULL
, NULL
, NULL
},
2594 { "Close", GTK_STOCK_CLOSE
, N_("_Close"), "<control>W", NULL
, G_CALLBACK(menu_close_conv_cb
) },
2597 { "OptionsMenu", NULL
, N_("_Options"), NULL
, NULL
, NULL
},
2601 static const GtkToggleActionEntry menu_toggle_entries
[] = {
2602 { "EnableLogging", NULL
, N_("Enable _Logging"), NULL
, NULL
, G_CALLBACK(menu_logging_cb
), FALSE
},
2603 { "EnableSounds", NULL
, N_("Enable _Sounds"), NULL
, NULL
, G_CALLBACK(menu_sounds_cb
), FALSE
},
2604 { "ShowFormattingToolbars", NULL
, N_("Show Formatting _Toolbars"), NULL
, NULL
, G_CALLBACK(menu_toolbar_cb
), FALSE
},
2607 static const char *conversation_menu
=
2609 "<menubar name='Conversation'>"
2610 "<menu action='ConversationMenu'>"
2611 "<menuitem action='NewInstantMessage'/>"
2612 "<menuitem action='JoinAChat'/>"
2614 "<menuitem action='Find'/>"
2615 "<menuitem action='ViewLog'/>"
2616 "<menuitem action='SaveAs'/>"
2617 "<menuitem action='ClearScrollback'/>"
2620 "<menu action='MediaMenu'>"
2621 "<menuitem action='AudioCall'/>"
2622 "<menuitem action='VideoCall'/>"
2623 "<menuitem action='AudioVideoCall'/>"
2626 "<menuitem action='SendFile'/>"
2627 "<menuitem action='GetAttention'/>"
2628 "<menuitem action='AddBuddyPounce'/>"
2629 "<menuitem action='GetInfo'/>"
2630 "<menuitem action='Invite'/>"
2631 "<menu action='MoreMenu'/>"
2633 "<menuitem action='Alias'/>"
2634 "<menuitem action='Block'/>"
2635 "<menuitem action='Unblock'/>"
2636 "<menuitem action='Add'/>"
2637 "<menuitem action='Remove'/>"
2639 "<menuitem action='InsertLink'/>"
2640 "<menuitem action='InsertImage'/>"
2642 "<menuitem action='Close'/>"
2644 "<menu action='OptionsMenu'>"
2645 "<menuitem action='EnableLogging'/>"
2646 "<menuitem action='EnableSounds'/>"
2648 "<menuitem action='ShowFormattingToolbars'/>"
2654 sound_method_pref_changed_cb(const char *name
, PurplePrefType type
,
2655 gconstpointer value
, gpointer data
)
2657 PidginConvWindow
*win
= data
;
2658 const char *method
= value
;
2660 if (purple_strequal(method
, "none"))
2662 gtk_toggle_action_set_active(GTK_TOGGLE_ACTION(win
->menu
->sounds
),
2664 gtk_action_set_sensitive(win
->menu
->sounds
, FALSE
);
2668 PidginConversation
*gtkconv
= pidgin_conv_window_get_active_gtkconv(win
);
2670 if (gtkconv
!= NULL
)
2671 gtk_toggle_action_set_active(GTK_TOGGLE_ACTION(win
->menu
->sounds
),
2672 gtkconv
->make_sound
);
2673 gtk_action_set_sensitive(win
->menu
->sounds
, TRUE
);
2677 /* Returns TRUE if some items were added to the menu, FALSE otherwise */
2679 populate_menu_with_options(GtkWidget
*menu
, PidginConversation
*gtkconv
, gboolean all
)
2682 PurpleConversation
*conv
;
2683 PurpleAccount
*account
;
2684 PurpleBlistNode
*node
= NULL
;
2685 PurpleChat
*chat
= NULL
;
2686 PurpleBuddy
*buddy
= NULL
;
2689 conv
= gtkconv
->active_conv
;
2690 account
= purple_conversation_get_account(conv
);
2692 if (PURPLE_IS_CHAT_CONVERSATION(conv
)) {
2693 chat
= purple_blist_find_chat(account
, purple_conversation_get_name(conv
));
2695 if ((chat
== NULL
) && (gtkconv
->history
!= NULL
)) {
2696 chat
= g_object_get_data(G_OBJECT(gtkconv
->history
), "transient_chat");
2699 if ((chat
== NULL
) && (gtkconv
->history
!= NULL
)) {
2700 GHashTable
*components
;
2701 PurpleAccount
*account
= purple_conversation_get_account(conv
);
2702 PurpleProtocol
*protocol
=
2703 purple_protocols_find(purple_account_get_protocol_id(account
));
2704 if (purple_account_get_connection(account
) != NULL
&&
2705 PURPLE_PROTOCOL_IMPLEMENTS(protocol
, CHAT
, info_defaults
)) {
2706 components
= purple_protocol_chat_iface_info_defaults(protocol
, purple_account_get_connection(account
),
2707 purple_conversation_get_name(conv
));
2709 components
= g_hash_table_new_full(g_str_hash
, g_str_equal
,
2711 g_hash_table_replace(components
, g_strdup("channel"),
2712 g_strdup(purple_conversation_get_name(conv
)));
2714 chat
= purple_chat_new(account
, NULL
, components
);
2715 purple_blist_node_set_transient((PurpleBlistNode
*)chat
, TRUE
);
2716 g_object_set_data_full(G_OBJECT(gtkconv
->history
), "transient_chat",
2717 chat
, (GDestroyNotify
)purple_blist_remove_chat
);
2720 if (!purple_account_is_connected(account
))
2723 buddy
= purple_blist_find_buddy(account
, purple_conversation_get_name(conv
));
2724 if (!buddy
&& gtkconv
->history
) {
2725 buddy
= g_object_get_data(G_OBJECT(gtkconv
->history
), "transient_buddy");
2728 buddy
= purple_buddy_new(account
, purple_conversation_get_name(conv
), NULL
);
2729 purple_blist_node_set_transient((PurpleBlistNode
*)buddy
, TRUE
);
2730 g_object_set_data_full(G_OBJECT(gtkconv
->history
), "transient_buddy",
2731 buddy
, (GDestroyNotify
)g_object_unref
);
2737 node
= (PurpleBlistNode
*)chat
;
2739 node
= (PurpleBlistNode
*)buddy
;
2741 /* Now add the stuff */
2744 pidgin_blist_make_buddy_menu(menu
, buddy
, TRUE
);
2749 if (purple_account_is_connected(account
))
2750 pidgin_append_blist_node_proto_menu(menu
, purple_account_get_connection(account
), node
);
2751 pidgin_append_blist_node_extended_menu(menu
, node
);
2754 if ((list
= gtk_container_get_children(GTK_CONTAINER(menu
))) == NULL
) {
2764 regenerate_media_items(PidginConvWindow
*win
)
2767 PurpleAccount
*account
;
2768 PurpleConversation
*conv
;
2770 conv
= pidgin_conv_window_get_active_conversation(win
);
2773 purple_debug_error("gtkconv", "couldn't get active conversation"
2774 " when regenerating media items\n");
2778 account
= purple_conversation_get_account(conv
);
2780 if (account
== NULL
) {
2781 purple_debug_error("gtkconv", "couldn't get account when"
2782 " regenerating media items\n");
2787 * Check if account support voice and/or calls, and
2788 * if the current buddy supports it.
2790 if (account
!= NULL
&& PURPLE_IS_IM_CONVERSATION(conv
)) {
2791 PurpleMediaCaps caps
=
2792 purple_protocol_get_media_caps(account
,
2793 purple_conversation_get_name(conv
));
2795 gtk_action_set_sensitive(win
->menu
->audio_call
,
2796 caps
& PURPLE_MEDIA_CAPS_AUDIO
2798 gtk_action_set_sensitive(win
->menu
->video_call
,
2799 caps
& PURPLE_MEDIA_CAPS_VIDEO
2801 gtk_action_set_sensitive(win
->menu
->audio_video_call
,
2802 caps
& PURPLE_MEDIA_CAPS_AUDIO_VIDEO
2804 } else if (PURPLE_IS_CHAT_CONVERSATION(conv
)) {
2805 /* for now, don't care about chats... */
2806 gtk_action_set_sensitive(win
->menu
->audio_call
, FALSE
);
2807 gtk_action_set_sensitive(win
->menu
->video_call
, FALSE
);
2808 gtk_action_set_sensitive(win
->menu
->audio_video_call
, FALSE
);
2810 gtk_action_set_sensitive(win
->menu
->audio_call
, FALSE
);
2811 gtk_action_set_sensitive(win
->menu
->video_call
, FALSE
);
2812 gtk_action_set_sensitive(win
->menu
->audio_video_call
, FALSE
);
2818 regenerate_attention_items(PidginConvWindow
*win
)
2820 GtkWidget
*attention
;
2822 PurpleConversation
*conv
;
2823 PurpleConnection
*pc
;
2824 PurpleProtocol
*protocol
= NULL
;
2827 conv
= pidgin_conv_window_get_active_conversation(win
);
2831 attention
= gtk_ui_manager_get_widget(win
->menu
->ui
,
2832 "/Conversation/ConversationMenu/GetAttention");
2834 /* Remove the previous entries */
2835 gtk_menu_item_set_submenu(GTK_MENU_ITEM(attention
), NULL
);
2837 pc
= purple_conversation_get_connection(conv
);
2839 protocol
= purple_connection_get_protocol(pc
);
2841 if (protocol
&& PURPLE_IS_PROTOCOL_ATTENTION(protocol
)) {
2842 list
= purple_protocol_attention_get_types(PURPLE_PROTOCOL_ATTENTION(protocol
), purple_connection_get_account(pc
));
2844 /* Multiple attention types */
2845 if (list
&& list
->next
) {
2848 menu
= gtk_menu_new();
2850 PurpleAttentionType
*type
;
2851 GtkWidget
*menuitem
;
2855 menuitem
= gtk_menu_item_new_with_label(purple_attention_type_get_name(type
));
2856 g_object_set_data(G_OBJECT(menuitem
), "index", GINT_TO_POINTER(index
));
2857 g_signal_connect(G_OBJECT(menuitem
), "activate",
2858 G_CALLBACK(menu_get_attention_cb
),
2860 gtk_menu_shell_append(GTK_MENU_SHELL(menu
), menuitem
);
2863 list
= g_list_delete_link(list
, list
);
2866 gtk_menu_item_set_submenu(GTK_MENU_ITEM(attention
), menu
);
2867 gtk_widget_show_all(menu
);
2873 regenerate_options_items(PidginConvWindow
*win
)
2876 PidginConversation
*gtkconv
;
2878 GtkWidget
*more_menu
;
2880 gtkconv
= pidgin_conv_window_get_active_gtkconv(win
);
2881 more_menu
= gtk_ui_manager_get_widget(win
->menu
->ui
,
2882 "/Conversation/ConversationMenu/MoreMenu");
2883 gtk_widget_show(more_menu
);
2884 menu
= gtk_menu_item_get_submenu(GTK_MENU_ITEM(more_menu
));
2886 /* Remove the previous entries */
2887 for (list
= gtk_container_get_children(GTK_CONTAINER(menu
)); list
; )
2889 GtkWidget
*w
= list
->data
;
2890 list
= g_list_delete_link(list
, list
);
2891 gtk_widget_destroy(w
);
2894 if (!populate_menu_with_options(menu
, gtkconv
, FALSE
))
2896 GtkWidget
*item
= gtk_menu_item_new_with_label(_("No actions available"));
2897 gtk_menu_shell_append(GTK_MENU_SHELL(menu
), item
);
2898 gtk_widget_set_sensitive(item
, FALSE
);
2901 gtk_widget_show_all(menu
);
2905 remove_from_list(GtkWidget
*widget
, PidginConvWindow
*win
)
2907 GList
*list
= g_object_get_data(G_OBJECT(win
->window
), "plugin-actions");
2908 list
= g_list_remove(list
, widget
);
2909 g_object_set_data(G_OBJECT(win
->window
), "plugin-actions", list
);
2913 regenerate_plugins_items(PidginConvWindow
*win
)
2915 GList
*action_items
;
2918 PidginConversation
*gtkconv
;
2919 PurpleConversation
*conv
;
2922 if (win
->window
== NULL
|| win
== hidden_convwin
)
2925 gtkconv
= pidgin_conv_window_get_active_gtkconv(win
);
2926 if (gtkconv
== NULL
)
2929 conv
= gtkconv
->active_conv
;
2930 action_items
= g_object_get_data(G_OBJECT(win
->window
), "plugin-actions");
2932 /* Remove the old menuitems */
2933 while (action_items
) {
2934 g_signal_handlers_disconnect_by_func(G_OBJECT(action_items
->data
),
2935 G_CALLBACK(remove_from_list
), win
);
2936 gtk_widget_destroy(action_items
->data
);
2937 action_items
= g_list_delete_link(action_items
, action_items
);
2940 item
= gtk_ui_manager_get_widget(win
->menu
->ui
, "/Conversation/OptionsMenu");
2941 menu
= gtk_menu_item_get_submenu(GTK_MENU_ITEM(item
));
2943 list
= purple_conversation_get_extended_menu(conv
);
2945 action_items
= g_list_prepend(NULL
, (item
= pidgin_separator(menu
)));
2946 g_signal_connect(G_OBJECT(item
), "destroy", G_CALLBACK(remove_from_list
), win
);
2949 for(; list
; list
= g_list_delete_link(list
, list
)) {
2950 PurpleActionMenu
*act
= (PurpleActionMenu
*) list
->data
;
2951 item
= pidgin_append_menu_action(menu
, act
, conv
);
2952 action_items
= g_list_prepend(action_items
, item
);
2953 gtk_widget_show_all(item
);
2954 g_signal_connect(G_OBJECT(item
), "destroy", G_CALLBACK(remove_from_list
), win
);
2956 g_object_set_data(G_OBJECT(win
->window
), "plugin-actions", action_items
);
2959 static void menubar_activated(GtkWidget
*item
, gpointer data
)
2961 PidginConvWindow
*win
= data
;
2962 regenerate_media_items(win
);
2963 regenerate_options_items(win
);
2964 regenerate_plugins_items(win
);
2965 regenerate_attention_items(win
);
2967 /* The following are to make sure the 'More' submenu is not regenerated every time
2968 * the focus shifts from 'Conversations' to some other menu and back. */
2969 g_signal_handlers_block_by_func(G_OBJECT(item
), G_CALLBACK(menubar_activated
), data
);
2970 g_signal_connect(G_OBJECT(win
->menu
->menubar
), "deactivate", G_CALLBACK(focus_out_from_menubar
), data
);
2974 focus_out_from_menubar(GtkWidget
*wid
, PidginConvWindow
*win
)
2976 /* The menubar has been deactivated. Make sure the 'More' submenu is regenerated next time
2977 * the 'Conversation' menu pops up. */
2978 GtkWidget
*menuitem
= gtk_ui_manager_get_widget(win
->menu
->ui
, "/Conversation/ConversationMenu");
2979 g_signal_handlers_unblock_by_func(G_OBJECT(menuitem
), G_CALLBACK(menubar_activated
), win
);
2980 g_signal_handlers_disconnect_by_func(G_OBJECT(win
->menu
->menubar
),
2981 G_CALLBACK(focus_out_from_menubar
), win
);
2985 setup_menubar(PidginConvWindow
*win
)
2987 GtkAccelGroup
*accel_group
;
2989 GtkActionGroup
*action_group
;
2991 GtkWidget
*menuitem
;
2993 action_group
= gtk_action_group_new("ConversationActions");
2994 gtk_action_group_set_translation_domain(action_group
, PACKAGE
);
2995 gtk_action_group_add_actions(action_group
,
2997 G_N_ELEMENTS(menu_entries
),
2999 gtk_action_group_add_toggle_actions(action_group
,
3000 menu_toggle_entries
,
3001 G_N_ELEMENTS(menu_toggle_entries
),
3004 win
->menu
->ui
= gtk_ui_manager_new();
3005 gtk_ui_manager_insert_action_group(win
->menu
->ui
, action_group
, 0);
3007 accel_group
= gtk_ui_manager_get_accel_group(win
->menu
->ui
);
3008 gtk_window_add_accel_group(GTK_WINDOW(win
->window
), accel_group
);
3009 g_signal_connect(G_OBJECT(accel_group
), "accel-changed",
3010 G_CALLBACK(pidgin_save_accels_cb
), NULL
);
3013 if (!gtk_ui_manager_add_ui_from_string(win
->menu
->ui
, conversation_menu
, -1, &error
))
3015 g_message("building menus failed: %s", error
->message
);
3016 g_error_free(error
);
3020 win
->menu
->menubar
=
3021 gtk_ui_manager_get_widget(win
->menu
->ui
, "/Conversation");
3023 /* Make sure the 'Conversation ⇨ More' menuitems are regenerated whenever
3024 * the 'Conversation' menu pops up because the entries can change after the
3025 * conversation is created. */
3026 menuitem
= gtk_ui_manager_get_widget(win
->menu
->ui
, "/Conversation/ConversationMenu");
3027 g_signal_connect(G_OBJECT(menuitem
), "activate", G_CALLBACK(menubar_activated
), win
);
3029 win
->menu
->view_log
=
3030 gtk_ui_manager_get_action(win
->menu
->ui
,
3031 "/Conversation/ConversationMenu/ViewLog");
3034 win
->menu
->audio_call
=
3035 gtk_ui_manager_get_action(win
->menu
->ui
,
3036 "/Conversation/ConversationMenu/MediaMenu/AudioCall");
3037 win
->menu
->video_call
=
3038 gtk_ui_manager_get_action(win
->menu
->ui
,
3039 "/Conversation/ConversationMenu/MediaMenu/VideoCall");
3040 win
->menu
->audio_video_call
=
3041 gtk_ui_manager_get_action(win
->menu
->ui
,
3042 "/Conversation/ConversationMenu/MediaMenu/AudioVideoCall");
3047 win
->menu
->send_file
=
3048 gtk_ui_manager_get_action(win
->menu
->ui
,
3049 "/Conversation/ConversationMenu/SendFile");
3051 win
->menu
->get_attention
=
3052 gtk_ui_manager_get_action(win
->menu
->ui
,
3053 "/Conversation/ConversationMenu/GetAttention");
3055 win
->menu
->add_pounce
=
3056 gtk_ui_manager_get_action(win
->menu
->ui
,
3057 "/Conversation/ConversationMenu/AddBuddyPounce");
3061 win
->menu
->get_info
=
3062 gtk_ui_manager_get_action(win
->menu
->ui
,
3063 "/Conversation/ConversationMenu/GetInfo");
3066 gtk_ui_manager_get_action(win
->menu
->ui
,
3067 "/Conversation/ConversationMenu/Invite");
3072 gtk_ui_manager_get_action(win
->menu
->ui
,
3073 "/Conversation/ConversationMenu/Alias");
3076 gtk_ui_manager_get_action(win
->menu
->ui
,
3077 "/Conversation/ConversationMenu/Block");
3079 win
->menu
->unblock
=
3080 gtk_ui_manager_get_action(win
->menu
->ui
,
3081 "/Conversation/ConversationMenu/Unblock");
3084 gtk_ui_manager_get_action(win
->menu
->ui
,
3085 "/Conversation/ConversationMenu/Add");
3088 gtk_ui_manager_get_action(win
->menu
->ui
,
3089 "/Conversation/ConversationMenu/Remove");
3093 win
->menu
->insert_link
=
3094 gtk_ui_manager_get_action(win
->menu
->ui
,
3095 "/Conversation/ConversationMenu/InsertLink");
3097 win
->menu
->insert_image
=
3098 gtk_ui_manager_get_action(win
->menu
->ui
,
3099 "/Conversation/ConversationMenu/InsertImage");
3103 win
->menu
->logging
=
3104 gtk_ui_manager_get_action(win
->menu
->ui
,
3105 "/Conversation/OptionsMenu/EnableLogging");
3107 gtk_ui_manager_get_action(win
->menu
->ui
,
3108 "/Conversation/OptionsMenu/EnableSounds");
3109 method
= purple_prefs_get_string(PIDGIN_PREFS_ROOT
"/sound/method");
3110 if (purple_strequal(method
, "none"))
3112 gtk_toggle_action_set_active(GTK_TOGGLE_ACTION(win
->menu
->sounds
),
3114 gtk_action_set_sensitive(win
->menu
->sounds
, FALSE
);
3116 purple_prefs_connect_callback(win
, PIDGIN_PREFS_ROOT
"/sound/method",
3117 sound_method_pref_changed_cb
, win
);
3119 win
->menu
->show_formatting_toolbar
=
3120 gtk_ui_manager_get_action(win
->menu
->ui
,
3121 "/Conversation/OptionsMenu/ShowFormattingToolbars");
3123 win
->menu
->tray
= pidgin_menu_tray_new();
3124 gtk_menu_shell_append(GTK_MENU_SHELL(win
->menu
->menubar
),
3126 gtk_widget_show(win
->menu
->tray
);
3128 gtk_widget_show(win
->menu
->menubar
);
3130 return win
->menu
->menubar
;
3134 /**************************************************************************
3136 **************************************************************************/
3139 got_typing_keypress(PidginConversation
*gtkconv
, gboolean first
)
3141 PurpleConversation
*conv
= gtkconv
->active_conv
;
3142 PurpleIMConversation
*im
;
3145 * We know we got something, so we at least have to make sure we don't
3146 * send PURPLE_IM_TYPED any time soon.
3149 im
= PURPLE_IM_CONVERSATION(conv
);
3151 purple_im_conversation_stop_send_typed_timeout(im
);
3152 purple_im_conversation_start_send_typed_timeout(im
);
3154 /* Check if we need to send another PURPLE_IM_TYPING message */
3155 if (first
|| (purple_im_conversation_get_type_again(im
) != 0 &&
3156 time(NULL
) > purple_im_conversation_get_type_again(im
)))
3158 unsigned int timeout
;
3159 timeout
= purple_serv_send_typing(purple_conversation_get_connection(conv
),
3160 purple_conversation_get_name(conv
),
3162 purple_im_conversation_set_type_again(im
, timeout
);
3168 typing_animation(gpointer data
) {
3169 PidginConversation
*gtkconv
= data
;
3170 PidginConvWindow
*gtkwin
= gtkconv
->win
;
3171 const char *stock_id
= NULL
;
3173 if(gtkconv
!= pidgin_conv_window_get_active_gtkconv(gtkwin
)) {
3177 switch (rand() % 5) {
3179 stock_id
= PIDGIN_STOCK_ANIMATION_TYPING0
;
3182 stock_id
= PIDGIN_STOCK_ANIMATION_TYPING1
;
3185 stock_id
= PIDGIN_STOCK_ANIMATION_TYPING2
;
3188 stock_id
= PIDGIN_STOCK_ANIMATION_TYPING3
;
3191 stock_id
= PIDGIN_STOCK_ANIMATION_TYPING4
;
3194 if (gtkwin
->menu
->typing_icon
== NULL
) {
3195 gtkwin
->menu
->typing_icon
= gtk_image_new_from_stock(stock_id
, GTK_ICON_SIZE_MENU
);
3196 pidgin_menu_tray_append(PIDGIN_MENU_TRAY(gtkwin
->menu
->tray
),
3197 gtkwin
->menu
->typing_icon
,
3198 _("User is typing..."));
3200 gtk_image_set_from_stock(GTK_IMAGE(gtkwin
->menu
->typing_icon
), stock_id
, GTK_ICON_SIZE_MENU
);
3202 gtk_widget_show(gtkwin
->menu
->typing_icon
);
3208 update_typing_message(PidginConversation
*gtkconv
, const char *message
)
3210 /* TODO WEBKIT: this is not handled at all */
3212 GtkTextBuffer
*buffer
;
3213 GtkTextMark
*stmark
, *enmark
;
3215 if (g_object_get_data(G_OBJECT(gtkconv
->imhtml
), "disable-typing-notification"))
3218 buffer
= gtk_text_view_get_buffer(GTK_TEXT_VIEW(gtkconv
->imhtml
));
3219 stmark
= gtk_text_buffer_get_mark(buffer
, "typing-notification-start");
3220 enmark
= gtk_text_buffer_get_mark(buffer
, "typing-notification-end");
3221 if (stmark
&& enmark
) {
3222 GtkTextIter start
, end
;
3223 gtk_text_buffer_get_iter_at_mark(buffer
, &start
, stmark
);
3224 gtk_text_buffer_get_iter_at_mark(buffer
, &end
, enmark
);
3225 gtk_text_buffer_delete_mark(buffer
, stmark
);
3226 gtk_text_buffer_delete_mark(buffer
, enmark
);
3227 gtk_text_buffer_delete(buffer
, &start
, &end
);
3228 } else if (message
&& *message
== '\n' && message
[1] == ' ' && message
[2] == '\0')
3233 message
= "\n "; /* The blank space is required to avoid a GTK+/Pango bug */
3238 gtk_text_buffer_get_end_iter(buffer
, &iter
);
3239 gtk_text_buffer_create_mark(buffer
, "typing-notification-start", &iter
, TRUE
);
3240 gtk_text_buffer_insert_with_tags_by_name(buffer
, &iter
, message
, -1, "TYPING-NOTIFICATION", NULL
);
3241 gtk_text_buffer_get_end_iter(buffer
, &iter
);
3242 gtk_text_buffer_create_mark(buffer
, "typing-notification-end", &iter
, TRUE
);
3248 update_typing_icon(PidginConversation
*gtkconv
)
3250 PurpleIMConversation
*im
;
3251 char *message
= NULL
;
3253 if (!PURPLE_IS_IM_CONVERSATION(gtkconv
->active_conv
))
3256 im
= PURPLE_IM_CONVERSATION(gtkconv
->active_conv
);
3258 if (purple_im_conversation_get_typing_state(im
) == PURPLE_IM_NOT_TYPING
) {
3260 update_typing_message(gtkconv
, NULL
);
3262 update_typing_message(gtkconv
, "\n ");
3267 if (purple_im_conversation_get_typing_state(im
) == PURPLE_IM_TYPING
) {
3268 message
= g_strdup_printf(_("\n%s is typing..."), purple_conversation_get_title(PURPLE_CONVERSATION(im
)));
3270 message
= g_strdup_printf(_("\n%s has stopped typing"), purple_conversation_get_title(PURPLE_CONVERSATION(im
)));
3273 update_typing_message(gtkconv
, message
);
3278 update_send_to_selection(PidginConvWindow
*win
)
3280 PurpleAccount
*account
;
3281 PurpleConversation
*conv
;
3286 conv
= pidgin_conv_window_get_active_conversation(win
);
3291 account
= purple_conversation_get_account(conv
);
3293 if (account
== NULL
)
3296 if (win
->menu
->send_to
== NULL
)
3299 if (!(b
= purple_blist_find_buddy(account
, purple_conversation_get_name(conv
))))
3302 gtk_widget_show(win
->menu
->send_to
);
3304 menu
= gtk_menu_item_get_submenu(GTK_MENU_ITEM(win
->menu
->send_to
));
3306 for (child
= gtk_container_get_children(GTK_CONTAINER(menu
));
3308 child
= g_list_delete_link(child
, child
)) {
3310 GtkWidget
*item
= child
->data
;
3311 PurpleBuddy
*item_buddy
;
3312 PurpleAccount
*item_account
= g_object_get_data(G_OBJECT(item
), "purple_account");
3313 gchar
*buddy_name
= g_object_get_data(G_OBJECT(item
),
3314 "purple_buddy_name");
3315 item_buddy
= purple_blist_find_buddy(item_account
, buddy_name
);
3317 if (b
== item_buddy
) {
3318 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(item
), TRUE
);
3328 send_to_item_enter_notify_cb(GtkWidget
*menuitem
, GdkEventCrossing
*event
, GtkWidget
*label
)
3330 gtk_widget_set_sensitive(GTK_WIDGET(label
), TRUE
);
3335 send_to_item_leave_notify_cb(GtkWidget
*menuitem
, GdkEventCrossing
*event
, GtkWidget
*label
)
3337 gtk_widget_set_sensitive(GTK_WIDGET(label
), FALSE
);
3342 e2ee_state_to_gtkimage(PurpleE2eeState
*state
)
3346 img
= _pidgin_e2ee_stock_icon_get(
3347 purple_e2ee_state_get_stock_icon(state
));
3351 return gtk_image_new_from_pixbuf(pidgin_pixbuf_from_image(img
));
3355 create_sendto_item(GtkWidget
*menu
, GtkSizeGroup
*sg
, GSList
**group
,
3356 PurpleBuddy
*buddy
, PurpleAccount
*account
, const char *name
,
3357 gboolean e2ee_enabled
)
3362 GtkWidget
*e2ee_image
= NULL
;
3363 GtkWidget
*menuitem
;
3367 /* Create a pixmap for the protocol icon. */
3368 pixbuf
= pidgin_create_protocol_icon(account
, PIDGIN_PROTOCOL_ICON_SMALL
);
3370 /* Now convert it to GtkImage */
3372 image
= gtk_image_new();
3375 image
= gtk_image_new_from_pixbuf(pixbuf
);
3376 g_object_unref(G_OBJECT(pixbuf
));
3380 PurpleIMConversation
*im
;
3381 PurpleE2eeState
*state
= NULL
;
3383 im
= purple_conversations_find_im_with_account(
3384 purple_buddy_get_name(buddy
), purple_buddy_get_account(buddy
));
3386 state
= purple_conversation_get_e2ee_state(PURPLE_CONVERSATION(im
));
3388 e2ee_image
= e2ee_state_to_gtkimage(state
);
3390 e2ee_image
= gtk_image_new();
3393 gtk_size_group_add_widget(sg
, image
);
3395 /* Make our menu item */
3396 text
= g_strdup_printf("%s (%s)", name
, purple_account_get_name_for_display(account
));
3397 menuitem
= gtk_radio_menu_item_new_with_label(*group
, text
);
3399 *group
= gtk_radio_menu_item_get_group(GTK_RADIO_MENU_ITEM(menuitem
));
3401 /* Do some evil, see some evil, speak some evil. */
3402 box
= gtk_box_new(GTK_ORIENTATION_HORIZONTAL
, 0);
3404 label
= gtk_bin_get_child(GTK_BIN(menuitem
));
3405 g_object_ref(label
);
3406 gtk_container_remove(GTK_CONTAINER(menuitem
), label
);
3408 gtk_box_pack_start(GTK_BOX(box
), image
, FALSE
, FALSE
, 0);
3410 gtk_box_pack_start(GTK_BOX(box
), label
, TRUE
, TRUE
, 4);
3412 gtk_box_pack_start(GTK_BOX(box
), e2ee_image
, FALSE
, FALSE
, 0);
3414 if (buddy
!= NULL
&&
3415 !purple_presence_is_online(purple_buddy_get_presence(buddy
)))
3417 gtk_widget_set_sensitive(label
, FALSE
);
3419 /* Set the label sensitive when the menuitem is highlighted and
3420 * insensitive again when the mouse leaves it. This way, it
3421 * doesn't appear weird from the highlighting of the embossed
3422 * (insensitive style) text.*/
3423 g_signal_connect(menuitem
, "enter-notify-event",
3424 G_CALLBACK(send_to_item_enter_notify_cb
), label
);
3425 g_signal_connect(menuitem
, "leave-notify-event",
3426 G_CALLBACK(send_to_item_leave_notify_cb
), label
);
3429 g_object_unref(label
);
3431 gtk_container_add(GTK_CONTAINER(menuitem
), box
);
3433 gtk_widget_show(label
);
3434 gtk_widget_show(image
);
3436 gtk_widget_show(e2ee_image
);
3437 gtk_widget_show(box
);
3439 /* Set our data and callbacks. */
3440 g_object_set_data(G_OBJECT(menuitem
), "purple_account", account
);
3441 g_object_set_data_full(G_OBJECT(menuitem
), "purple_buddy_name", g_strdup(name
), g_free
);
3443 g_signal_connect(G_OBJECT(menuitem
), "activate",
3444 G_CALLBACK(menu_conv_sel_send_cb
), NULL
);
3446 gtk_widget_show(menuitem
);
3447 gtk_menu_shell_append(GTK_MENU_SHELL(menu
), menuitem
);
3451 compare_buddy_presence(PurplePresence
*p1
, PurplePresence
*p2
)
3453 /* This is necessary because multiple PurpleBuddy's don't share the same
3454 * PurplePresence anymore.
3456 PurpleBuddy
*b1
= purple_buddy_presence_get_buddy(PURPLE_BUDDY_PRESENCE(p1
));
3457 PurpleBuddy
*b2
= purple_buddy_presence_get_buddy(PURPLE_BUDDY_PRESENCE(p2
));
3458 if (purple_buddy_get_account(b1
) == purple_buddy_get_account(b2
) &&
3459 purple_strequal(purple_buddy_get_name(b1
), purple_buddy_get_name(b2
)))
3465 generate_send_to_items(PidginConvWindow
*win
)
3468 GSList
*group
= NULL
;
3469 GtkSizeGroup
*sg
= gtk_size_group_new(GTK_SIZE_GROUP_HORIZONTAL
);
3470 PidginConversation
*gtkconv
;
3473 g_return_if_fail(win
!= NULL
);
3475 gtkconv
= pidgin_conv_window_get_active_gtkconv(win
);
3477 g_return_if_fail(gtkconv
!= NULL
);
3479 if (win
->menu
->send_to
!= NULL
)
3480 gtk_widget_destroy(win
->menu
->send_to
);
3482 /* Build the Send To menu */
3483 win
->menu
->send_to
= gtk_menu_item_new_with_mnemonic(_("S_end To"));
3484 gtk_widget_show(win
->menu
->send_to
);
3486 menu
= gtk_menu_new();
3487 gtk_menu_shell_insert(GTK_MENU_SHELL(win
->menu
->menubar
),
3488 win
->menu
->send_to
, 2);
3489 gtk_menu_item_set_submenu(GTK_MENU_ITEM(win
->menu
->send_to
), menu
);
3491 gtk_widget_show(menu
);
3493 if (PURPLE_IS_IM_CONVERSATION(gtkconv
->active_conv
)) {
3494 buds
= purple_blist_find_buddies(purple_conversation_get_account(gtkconv
->active_conv
), purple_conversation_get_name(gtkconv
->active_conv
));
3498 /* The user isn't on the buddy list. So we don't create any sendto menu. */
3502 gboolean e2ee_enabled
= FALSE
;
3503 GList
*list
= NULL
, *iter
;
3504 for (l
= buds
; l
!= NULL
; l
= l
->next
)
3506 PurpleBlistNode
*node
;
3508 node
= PURPLE_BLIST_NODE(purple_buddy_get_contact(PURPLE_BUDDY(l
->data
)));
3510 for (node
= node
->child
; node
!= NULL
; node
= node
->next
)
3512 PurpleBuddy
*buddy
= (PurpleBuddy
*)node
;
3513 PurpleAccount
*account
;
3514 PurpleIMConversation
*im
;
3516 if (!PURPLE_IS_BUDDY(node
))
3519 im
= purple_conversations_find_im_with_account(purple_buddy_get_name(buddy
), purple_buddy_get_account(buddy
));
3520 if (im
&& purple_conversation_get_e2ee_state(PURPLE_CONVERSATION(im
)) != NULL
)
3521 e2ee_enabled
= TRUE
;
3523 account
= purple_buddy_get_account(buddy
);
3524 /* TODO WEBKIT: (I'm not actually sure if this is webkit-related --Mark Doliner) */
3525 if (purple_account_is_connected(account
) /*|| account == purple_conversation_get_account(gtkconv->active_conv)*/)
3527 /* Use the PurplePresence to get unique buddies. */
3528 PurplePresence
*presence
= purple_buddy_get_presence(buddy
);
3529 if (!g_list_find_custom(list
, presence
, (GCompareFunc
)compare_buddy_presence
))
3530 list
= g_list_prepend(list
, presence
);
3535 /* Create the sendto menu only if it has more than one item to show */
3536 if (list
&& list
->next
) {
3537 /* Loop over the list backwards so we get the items in the right order,
3538 * since we did a g_list_prepend() earlier. */
3539 for (iter
= g_list_last(list
); iter
!= NULL
; iter
= iter
->prev
) {
3540 PurplePresence
*pre
= iter
->data
;
3541 PurpleBuddy
*buddy
= purple_buddy_presence_get_buddy(PURPLE_BUDDY_PRESENCE(pre
));
3542 create_sendto_item(menu
, sg
, &group
, buddy
,
3543 purple_buddy_get_account(buddy
), purple_buddy_get_name(buddy
), e2ee_enabled
);
3553 gtk_widget_show(win
->menu
->send_to
);
3554 /* TODO: This should never be insensitive. Possibly hidden or not. */
3556 gtk_widget_set_sensitive(win
->menu
->send_to
, FALSE
);
3557 update_send_to_selection(win
);
3561 _pidgin_e2ee_stock_icon_get(const gchar
*stock_name
)
3563 gchar filename
[100], *path
;
3566 /* core is quitting */
3567 if (e2ee_stock
== NULL
)
3570 if (g_hash_table_lookup_extended(e2ee_stock
, stock_name
, NULL
, (gpointer
*)&image
))
3573 g_snprintf(filename
, sizeof(filename
), "e2ee-%s.png", stock_name
);
3574 path
= g_build_filename(PURPLE_DATADIR
, "pidgin", "icons",
3575 "hicolor", "16x16", "status", filename
, NULL
);
3576 image
= purple_image_new_from_file(path
, NULL
);
3579 g_hash_table_insert(e2ee_stock
, g_strdup(stock_name
), image
);
3584 generate_e2ee_controls(PidginConvWindow
*win
)
3586 PidginConversation
*gtkconv
;
3587 PurpleConversation
*conv
;
3588 PurpleE2eeState
*state
;
3589 PurpleE2eeProvider
*provider
;
3591 GList
*menu_actions
, *it
;
3592 GtkWidget
*e2ee_image
;
3594 gtkconv
= pidgin_conv_window_get_active_gtkconv(win
);
3595 g_return_if_fail(gtkconv
!= NULL
);
3597 conv
= gtkconv
->active_conv
;
3598 g_return_if_fail(conv
!= NULL
);
3600 if (win
->menu
->e2ee
!= NULL
) {
3601 gtk_widget_destroy(win
->menu
->e2ee
);
3602 win
->menu
->e2ee
= NULL
;
3605 provider
= purple_e2ee_provider_get_main();
3606 state
= purple_conversation_get_e2ee_state(conv
);
3607 if (state
== NULL
|| provider
== NULL
)
3609 if (purple_e2ee_state_get_provider(state
) != provider
)
3612 win
->menu
->e2ee
= gtk_image_menu_item_new_with_label(
3613 purple_e2ee_provider_get_name(provider
));
3615 menu
= gtk_menu_new();
3616 gtk_menu_shell_insert(GTK_MENU_SHELL(win
->menu
->menubar
),
3617 win
->menu
->e2ee
, 3);
3618 gtk_menu_item_set_submenu(GTK_MENU_ITEM(win
->menu
->e2ee
), menu
);
3620 e2ee_image
= e2ee_state_to_gtkimage(state
);
3622 gtk_image_menu_item_set_image(
3623 GTK_IMAGE_MENU_ITEM(win
->menu
->e2ee
), e2ee_image
);
3626 gtk_widget_set_tooltip_text(win
->menu
->e2ee
,
3627 purple_e2ee_state_get_name(state
));
3629 menu_actions
= purple_e2ee_provider_get_conv_menu_actions(provider
, conv
);
3630 for (it
= menu_actions
; it
; it
= g_list_next(it
)) {
3631 PurpleActionMenu
*action
= it
->data
;
3633 gtk_widget_show_all(
3634 pidgin_append_menu_action(menu
, action
, conv
));
3636 g_list_free(menu_actions
);
3638 gtk_widget_show(win
->menu
->e2ee
);
3639 gtk_widget_show(menu
);
3643 get_chat_user_status_icon(PurpleChatConversation
*chat
, const char *name
, PurpleChatUserFlags flags
)
3645 const char *image
= NULL
;
3647 if (flags
& PURPLE_CHAT_USER_FOUNDER
) {
3648 image
= PIDGIN_STOCK_STATUS_FOUNDER
;
3649 } else if (flags
& PURPLE_CHAT_USER_OP
) {
3650 image
= PIDGIN_STOCK_STATUS_OPERATOR
;
3651 } else if (flags
& PURPLE_CHAT_USER_HALFOP
) {
3652 image
= PIDGIN_STOCK_STATUS_HALFOP
;
3653 } else if (flags
& PURPLE_CHAT_USER_VOICE
) {
3654 image
= PIDGIN_STOCK_STATUS_VOICE
;
3655 } else if ((!flags
) && purple_chat_conversation_is_ignored_user(chat
, name
)) {
3656 image
= PIDGIN_STOCK_STATUS_IGNORED
;
3664 deleting_chat_user_cb(PurpleChatUser
*cb
)
3666 GtkTreeRowReference
*ref
= purple_chat_user_get_ui_data(cb
);
3669 gtk_tree_row_reference_free(ref
);
3670 purple_chat_user_set_ui_data(cb
, NULL
);
3675 add_chat_user_common(PurpleChatConversation
*chat
, PurpleChatUser
*cb
, const char *old_name
)
3677 PidginConversation
*gtkconv
;
3678 PurpleConversation
*conv
;
3679 PidginChatPane
*gtkchat
;
3680 PurpleConnection
*gc
;
3681 PurpleProtocol
*protocol
;
3684 GtkTreePath
*newpath
;
3687 gboolean is_me
= FALSE
;
3689 const gchar
*name
, *alias
;
3690 gchar
*tmp
, *alias_key
;
3691 PurpleChatUserFlags flags
;
3692 GdkRGBA
*color
= NULL
;
3694 alias
= purple_chat_user_get_alias(cb
);
3695 name
= purple_chat_user_get_name(cb
);
3696 flags
= purple_chat_user_get_flags(cb
);
3698 conv
= PURPLE_CONVERSATION(chat
);
3699 gtkconv
= PIDGIN_CONVERSATION(conv
);
3700 gtkchat
= gtkconv
->u
.chat
;
3701 gc
= purple_conversation_get_connection(conv
);
3703 if (!gc
|| !(protocol
= purple_connection_get_protocol(gc
)))
3706 tm
= gtk_tree_view_get_model(GTK_TREE_VIEW(gtkchat
->list
));
3707 ls
= GTK_LIST_STORE(tm
);
3709 stock
= get_chat_user_status_icon(chat
, name
, flags
);
3711 if (purple_strequal(purple_chat_conversation_get_nick(chat
), purple_normalize(purple_conversation_get_account(conv
), old_name
!= NULL
? old_name
: name
)))
3714 is_buddy
= purple_chat_user_is_buddy(cb
);
3716 tmp
= g_utf8_casefold(alias
, -1);
3717 alias_key
= g_utf8_collate_key(tmp
, -1);
3722 /* TODO WEBKIT: No tags in webkit stuff, yet. */
3723 GtkTextTag
*tag
= gtk_text_tag_table_lookup(
3724 gtk_text_buffer_get_tag_table(GTK_IMHTML(gtkconv
->webview
)->text_buffer
),
3726 g_object_get(tag
, "foreground-rgba", &color
, NULL
);
3730 if ((tag
= get_buddy_tag(chat
, name
, 0, FALSE
)))
3731 g_object_set(G_OBJECT(tag
), "style", PANGO_STYLE_NORMAL
, NULL
);
3732 if ((tag
= get_buddy_tag(chat
, name
, PURPLE_MESSAGE_NICK
, FALSE
)))
3733 g_object_set(G_OBJECT(tag
), "style", PANGO_STYLE_NORMAL
, NULL
);
3734 color
= (GdkRGBA
*)get_nick_color(gtkconv
, name
);
3737 gtk_list_store_insert_with_values(ls
, &iter
,
3739 * The GTK docs are mute about the effects of the "row" value for performance.
3740 * X-Chat hardcodes their value to 0 (prepend) and -1 (append), so we will too.
3741 * It *might* be faster to search the gtk_list_store and set row accurately,
3742 * but no one in #gtk+ seems to know anything about it either.
3743 * Inserting in the "wrong" location has no visible ill effects. - F.P.
3746 CHAT_USERS_ICON_STOCK_COLUMN
, stock
,
3747 CHAT_USERS_ALIAS_COLUMN
, alias
,
3748 CHAT_USERS_ALIAS_KEY_COLUMN
, alias_key
,
3749 CHAT_USERS_NAME_COLUMN
, name
,
3750 CHAT_USERS_FLAGS_COLUMN
, flags
,
3751 CHAT_USERS_COLOR_COLUMN
, color
,
3752 CHAT_USERS_WEIGHT_COLUMN
, is_buddy
? PANGO_WEIGHT_BOLD
: PANGO_WEIGHT_NORMAL
,
3755 if (purple_chat_user_get_ui_data(cb
)) {
3756 GtkTreeRowReference
*ref
= purple_chat_user_get_ui_data(cb
);
3757 gtk_tree_row_reference_free(ref
);
3760 newpath
= gtk_tree_model_get_path(tm
, &iter
);
3761 purple_chat_user_set_ui_data(cb
, gtk_tree_row_reference_new(tm
, newpath
));
3762 gtk_tree_path_free(newpath
);
3766 gdk_rgba_free(color
);
3771 static void topic_callback(GtkWidget
*w
, PidginConversation
*gtkconv
)
3773 PurpleProtocol
*protocol
= NULL
;
3774 PurpleConnection
*gc
;
3775 PurpleConversation
*conv
= gtkconv
->active_conv
;
3776 PidginChatPane
*gtkchat
;
3778 const char *current_topic
;
3780 gc
= purple_conversation_get_connection(conv
);
3782 if(!gc
|| !(protocol
= purple_connection_get_protocol(gc
)))
3785 if(!PURPLE_PROTOCOL_IMPLEMENTS(protocol
, CHAT
, set_topic
))
3788 gtkconv
= PIDGIN_CONVERSATION(conv
);
3789 gtkchat
= gtkconv
->u
.chat
;
3790 new_topic
= g_strdup(gtk_entry_get_text(GTK_ENTRY(gtkchat
->topic_text
)));
3791 current_topic
= purple_chat_conversation_get_topic(PURPLE_CHAT_CONVERSATION(conv
));
3793 if(current_topic
&& !g_utf8_collate(new_topic
, current_topic
)){
3799 gtk_entry_set_text(GTK_ENTRY(gtkchat
->topic_text
), current_topic
);
3801 gtk_entry_set_text(GTK_ENTRY(gtkchat
->topic_text
), "");
3803 purple_protocol_chat_iface_set_topic(protocol
, gc
, purple_chat_conversation_get_id(PURPLE_CHAT_CONVERSATION(conv
)),
3810 sort_chat_users(GtkTreeModel
*model
, GtkTreeIter
*a
, GtkTreeIter
*b
, gpointer userdata
)
3812 PurpleChatUserFlags f1
= 0, f2
= 0;
3813 char *user1
= NULL
, *user2
= NULL
;
3814 gboolean buddy1
= FALSE
, buddy2
= FALSE
;
3817 gtk_tree_model_get(model
, a
,
3818 CHAT_USERS_ALIAS_KEY_COLUMN
, &user1
,
3819 CHAT_USERS_FLAGS_COLUMN
, &f1
,
3820 CHAT_USERS_WEIGHT_COLUMN
, &buddy1
,
3822 gtk_tree_model_get(model
, b
,
3823 CHAT_USERS_ALIAS_KEY_COLUMN
, &user2
,
3824 CHAT_USERS_FLAGS_COLUMN
, &f2
,
3825 CHAT_USERS_WEIGHT_COLUMN
, &buddy2
,
3828 /* Only sort by membership levels */
3829 f1
&= PURPLE_CHAT_USER_VOICE
| PURPLE_CHAT_USER_HALFOP
| PURPLE_CHAT_USER_OP
|
3830 PURPLE_CHAT_USER_FOUNDER
;
3831 f2
&= PURPLE_CHAT_USER_VOICE
| PURPLE_CHAT_USER_HALFOP
| PURPLE_CHAT_USER_OP
|
3832 PURPLE_CHAT_USER_FOUNDER
;
3834 ret
= g_strcmp0(user1
, user2
);
3836 if (user1
!= NULL
&& user2
!= NULL
) {
3838 /* sort more important users first */
3839 ret
= (f1
> f2
) ? -1 : 1;
3840 } else if (buddy1
!= buddy2
) {
3841 ret
= (buddy1
> buddy2
) ? -1 : 1;
3852 update_chat_alias(PurpleBuddy
*buddy
, PurpleChatConversation
*chat
, PurpleConnection
*gc
, PurpleProtocol
*protocol
)
3854 PidginConversation
*gtkconv
= PIDGIN_CONVERSATION(PURPLE_CONVERSATION(chat
));
3855 PurpleAccount
*account
= purple_conversation_get_account(PURPLE_CONVERSATION(chat
));
3856 GtkTreeModel
*model
;
3857 char *normalized_name
;
3861 g_return_if_fail(buddy
!= NULL
);
3862 g_return_if_fail(chat
!= NULL
);
3864 /* This is safe because this callback is only used in chats, not IMs. */
3865 model
= gtk_tree_view_get_model(GTK_TREE_VIEW(gtkconv
->u
.chat
->list
));
3867 if (!gtk_tree_model_get_iter_first(GTK_TREE_MODEL(model
), &iter
))
3870 normalized_name
= g_strdup(purple_normalize(account
, purple_buddy_get_name(buddy
)));
3875 gtk_tree_model_get(model
, &iter
, CHAT_USERS_NAME_COLUMN
, &name
, -1);
3877 if (purple_strequal(normalized_name
, purple_normalize(account
, name
))) {
3878 const char *alias
= name
;
3880 char *alias_key
= NULL
;
3881 PurpleBuddy
*buddy2
;
3883 if (!purple_strequal(purple_chat_conversation_get_nick(chat
), purple_normalize(account
, name
))) {
3884 /* This user is not me, so look into updating the alias. */
3886 if ((buddy2
= purple_blist_find_buddy(account
, name
)) != NULL
) {
3887 alias
= purple_buddy_get_contact_alias(buddy2
);
3890 tmp
= g_utf8_casefold(alias
, -1);
3891 alias_key
= g_utf8_collate_key(tmp
, -1);
3894 gtk_list_store_set(GTK_LIST_STORE(model
), &iter
,
3895 CHAT_USERS_ALIAS_COLUMN
, alias
,
3896 CHAT_USERS_ALIAS_KEY_COLUMN
, alias_key
,
3904 f
= gtk_tree_model_iter_next(model
, &iter
);
3909 g_free(normalized_name
);
3913 blist_node_aliased_cb(PurpleBlistNode
*node
, const char *old_alias
, PurpleChatConversation
*chat
)
3915 PurpleConnection
*gc
;
3916 PurpleProtocol
*protocol
;
3917 PurpleConversation
*conv
= PURPLE_CONVERSATION(chat
);
3919 g_return_if_fail(node
!= NULL
);
3920 g_return_if_fail(conv
!= NULL
);
3922 gc
= purple_conversation_get_connection(conv
);
3923 g_return_if_fail(gc
!= NULL
);
3924 g_return_if_fail(purple_connection_get_protocol(gc
) != NULL
);
3925 protocol
= purple_connection_get_protocol(gc
);
3927 if (purple_protocol_get_options(protocol
) & OPT_PROTO_UNIQUE_CHATNAME
)
3930 if (PURPLE_IS_CONTACT(node
))
3932 PurpleBlistNode
*bnode
;
3934 for(bnode
= node
->child
; bnode
; bnode
= bnode
->next
) {
3936 if(!PURPLE_IS_BUDDY(bnode
))
3939 update_chat_alias((PurpleBuddy
*)bnode
, chat
, gc
, protocol
);
3942 else if (PURPLE_IS_BUDDY(node
))
3943 update_chat_alias((PurpleBuddy
*)node
, chat
, gc
, protocol
);
3944 else if (PURPLE_IS_CHAT(node
) &&
3945 purple_conversation_get_account(conv
) == purple_chat_get_account((PurpleChat
*)node
))
3947 if (old_alias
== NULL
|| g_utf8_collate(old_alias
, purple_conversation_get_title(conv
)) == 0)
3948 pidgin_conv_update_fields(conv
, PIDGIN_CONV_SET_TITLE
);
3953 buddy_cb_common(PurpleBuddy
*buddy
, PurpleChatConversation
*chat
, gboolean is_buddy
)
3955 GtkTreeModel
*model
;
3956 char *normalized_name
;
3958 GtkTextTag
*texttag
;
3959 PurpleConversation
*conv
= PURPLE_CONVERSATION(chat
);
3962 g_return_if_fail(buddy
!= NULL
);
3963 g_return_if_fail(conv
!= NULL
);
3965 /* Do nothing if the buddy does not belong to the conv's account */
3966 if (purple_buddy_get_account(buddy
) != purple_conversation_get_account(conv
))
3969 /* This is safe because this callback is only used in chats, not IMs. */
3970 model
= gtk_tree_view_get_model(GTK_TREE_VIEW(PIDGIN_CONVERSATION(conv
)->u
.chat
->list
));
3972 if (!gtk_tree_model_get_iter_first(GTK_TREE_MODEL(model
), &iter
))
3975 normalized_name
= g_strdup(purple_normalize(purple_conversation_get_account(conv
), purple_buddy_get_name(buddy
)));
3980 gtk_tree_model_get(model
, &iter
, CHAT_USERS_NAME_COLUMN
, &name
, -1);
3982 if (purple_strequal(normalized_name
, purple_normalize(purple_conversation_get_account(conv
), name
))) {
3983 gtk_list_store_set(GTK_LIST_STORE(model
), &iter
,
3984 CHAT_USERS_WEIGHT_COLUMN
, is_buddy
? PANGO_WEIGHT_BOLD
: PANGO_WEIGHT_NORMAL
, -1);
3989 f
= gtk_tree_model_iter_next(model
, &iter
);
3994 g_free(normalized_name
);
3996 blist_node_aliased_cb((PurpleBlistNode
*)buddy
, NULL
, chat
);
3998 texttag
= get_buddy_tag(chat
, purple_buddy_get_name(buddy
), 0, FALSE
); /* XXX: do we want the normalized name? */
4000 g_object_set(texttag
, "weight", is_buddy
? PANGO_WEIGHT_BOLD
: PANGO_WEIGHT_NORMAL
, NULL
);
4005 buddy_added_cb(PurpleBlistNode
*node
, PurpleChatConversation
*chat
)
4007 if (!PURPLE_IS_BUDDY(node
))
4010 buddy_cb_common(PURPLE_BUDDY(node
), chat
, TRUE
);
4014 buddy_removed_cb(PurpleBlistNode
*node
, PurpleChatConversation
*chat
)
4016 if (!PURPLE_IS_BUDDY(node
))
4019 /* If there's another buddy for the same "dude" on the list, do nothing. */
4020 if (purple_blist_find_buddy(purple_buddy_get_account(PURPLE_BUDDY(node
)),
4021 purple_buddy_get_name(PURPLE_BUDDY(node
))) != NULL
)
4024 buddy_cb_common(PURPLE_BUDDY(node
), chat
, FALSE
);
4028 minimum_entry_lines_pref_cb(const char *name
,
4029 PurplePrefType type
,
4030 gconstpointer value
,
4033 GList
*l
= purple_conversations_get_all();
4034 PurpleConversation
*conv
;
4038 conv
= (PurpleConversation
*)l
->data
;
4040 if (PIDGIN_IS_PIDGIN_CONVERSATION(conv
))
4041 resize_webview_cb(PIDGIN_CONVERSATION(conv
));
4048 setup_chat_topic(PidginConversation
*gtkconv
, GtkWidget
*vbox
)
4050 PurpleConversation
*conv
= gtkconv
->active_conv
;
4051 PurpleConnection
*gc
= purple_conversation_get_connection(conv
);
4052 PurpleProtocol
*protocol
= purple_connection_get_protocol(gc
);
4053 if (purple_protocol_get_options(protocol
) & OPT_PROTO_CHAT_TOPIC
)
4055 GtkWidget
*hbox
, *label
;
4056 PidginChatPane
*gtkchat
= gtkconv
->u
.chat
;
4058 hbox
= gtk_box_new(GTK_ORIENTATION_HORIZONTAL
, PIDGIN_HIG_BOX_SPACE
);
4059 gtk_box_pack_start(GTK_BOX(vbox
), hbox
, FALSE
, FALSE
, 0);
4061 label
= gtk_label_new(_("Topic:"));
4062 gtk_box_pack_start(GTK_BOX(hbox
), label
, FALSE
, FALSE
, 0);
4064 gtkchat
->topic_text
= gtk_entry_new();
4065 gtk_widget_set_size_request(gtkchat
->topic_text
, -1, BUDDYICON_SIZE_MIN
);
4067 if(!PURPLE_PROTOCOL_IMPLEMENTS(protocol
, CHAT
, set_topic
)) {
4068 gtk_editable_set_editable(GTK_EDITABLE(gtkchat
->topic_text
), FALSE
);
4070 g_signal_connect(G_OBJECT(gtkchat
->topic_text
), "activate",
4071 G_CALLBACK(topic_callback
), gtkconv
);
4074 gtk_box_pack_start(GTK_BOX(hbox
), gtkchat
->topic_text
, TRUE
, TRUE
, 0);
4075 g_signal_connect(G_OBJECT(gtkchat
->topic_text
), "key_press_event",
4076 G_CALLBACK(entry_key_press_cb
), gtkconv
);
4081 pidgin_conv_userlist_create_tooltip(GtkWidget
*tipwindow
, GtkTreePath
*path
,
4082 gpointer userdata
, int *w
, int *h
)
4084 PidginConversation
*gtkconv
= userdata
;
4086 GtkTreeModel
*model
= gtk_tree_view_get_model(GTK_TREE_VIEW(gtkconv
->u
.chat
->list
));
4087 PurpleConversation
*conv
= gtkconv
->active_conv
;
4088 PurpleBlistNode
*node
;
4089 PurpleProtocol
*protocol
;
4090 PurpleAccount
*account
= purple_conversation_get_account(conv
);
4093 if (purple_account_get_connection(account
) == NULL
)
4096 if (!gtk_tree_model_get_iter(GTK_TREE_MODEL(model
), &iter
, path
))
4099 gtk_tree_model_get(GTK_TREE_MODEL(model
), &iter
, CHAT_USERS_NAME_COLUMN
, &who
, -1);
4101 protocol
= purple_connection_get_protocol(purple_account_get_connection(account
));
4102 node
= (PurpleBlistNode
*)(purple_blist_find_buddy(purple_conversation_get_account(conv
), who
));
4103 if (node
&& protocol
&& (purple_protocol_get_options(protocol
) & OPT_PROTO_UNIQUE_CHATNAME
))
4104 pidgin_blist_draw_tooltip(node
, gtkconv
->infopane
);
4111 setup_chat_userlist(PidginConversation
*gtkconv
, GtkWidget
*hpaned
)
4113 PidginChatPane
*gtkchat
= gtkconv
->u
.chat
;
4114 GtkWidget
*lbox
, *list
;
4116 GtkCellRenderer
*rend
;
4117 GtkTreeViewColumn
*col
;
4119 void *blist_handle
= purple_blist_get_handle();
4120 PurpleConversation
*conv
= gtkconv
->active_conv
;
4122 /* Build the right pane. */
4123 lbox
= gtk_box_new(GTK_ORIENTATION_VERTICAL
, PIDGIN_HIG_BOX_SPACE
);
4124 gtk_paned_pack2(GTK_PANED(hpaned
), lbox
, FALSE
, TRUE
);
4125 gtk_widget_show(lbox
);
4127 /* Setup the label telling how many people are in the room. */
4128 gtkchat
->count
= gtk_label_new(_("0 people in room"));
4129 gtk_label_set_ellipsize(GTK_LABEL(gtkchat
->count
), PANGO_ELLIPSIZE_END
);
4130 gtk_box_pack_start(GTK_BOX(lbox
), gtkchat
->count
, FALSE
, FALSE
, 0);
4131 gtk_widget_show(gtkchat
->count
);
4133 /* Setup the list of users. */
4135 ls
= gtk_list_store_new(CHAT_USERS_COLUMNS
, GDK_TYPE_PIXBUF
, G_TYPE_STRING
,
4136 G_TYPE_STRING
, G_TYPE_STRING
, G_TYPE_INT
,
4137 GDK_TYPE_RGBA
, G_TYPE_INT
, G_TYPE_STRING
);
4138 gtk_tree_sortable_set_sort_func(GTK_TREE_SORTABLE(ls
), CHAT_USERS_ALIAS_KEY_COLUMN
,
4139 sort_chat_users
, NULL
, NULL
);
4141 list
= gtk_tree_view_new_with_model(GTK_TREE_MODEL(ls
));
4143 /* Allow a user to specify gtkrc settings for the chat userlist only */
4144 gtk_widget_set_name(list
, "pidgin_conv_userlist");
4146 rend
= gtk_cell_renderer_pixbuf_new();
4147 g_object_set(G_OBJECT(rend
),
4148 "stock-size", gtk_icon_size_from_name(PIDGIN_ICON_SIZE_TANGO_EXTRA_SMALL
),
4150 col
= gtk_tree_view_column_new_with_attributes(NULL
, rend
,
4151 "stock-id", CHAT_USERS_ICON_STOCK_COLUMN
, NULL
);
4152 gtk_tree_view_column_set_sizing(col
, GTK_TREE_VIEW_COLUMN_AUTOSIZE
);
4153 gtk_tree_view_append_column(GTK_TREE_VIEW(list
), col
);
4154 ul_width
= purple_prefs_get_int(PIDGIN_PREFS_ROOT
"/conversations/chat/userlist_width");
4155 gtk_widget_set_size_request(lbox
, ul_width
, -1);
4157 /* Hack to prevent completely collapsed userlist coming back with a 1 pixel width.
4158 * I would have liked to use the GtkPaned "max-position", but for some reason that didn't work */
4160 gtk_paned_set_position(GTK_PANED(hpaned
), 999999);
4162 g_signal_connect(G_OBJECT(list
), "button_press_event",
4163 G_CALLBACK(right_click_chat_cb
), gtkconv
);
4164 g_signal_connect(G_OBJECT(list
), "row-activated",
4165 G_CALLBACK(activate_list_cb
), gtkconv
);
4166 g_signal_connect(G_OBJECT(list
), "popup-menu",
4167 G_CALLBACK(gtkconv_chat_popup_menu_cb
), gtkconv
);
4168 g_signal_connect(G_OBJECT(lbox
), "size-allocate", G_CALLBACK(lbox_size_allocate_cb
), gtkconv
);
4170 pidgin_tooltip_setup_for_treeview(list
, gtkconv
,
4171 pidgin_conv_userlist_create_tooltip
, NULL
);
4173 rend
= gtk_cell_renderer_text_new();
4175 "foreground-set", TRUE
,
4178 g_object_set(G_OBJECT(rend
), "editable", TRUE
, NULL
);
4180 col
= gtk_tree_view_column_new_with_attributes(NULL
, rend
,
4181 "text", CHAT_USERS_ALIAS_COLUMN
,
4182 "foreground-rgba", CHAT_USERS_COLOR_COLUMN
,
4183 "weight", CHAT_USERS_WEIGHT_COLUMN
,
4186 purple_signal_connect(blist_handle
, "blist-node-added",
4187 gtkchat
, PURPLE_CALLBACK(buddy_added_cb
), conv
);
4188 purple_signal_connect(blist_handle
, "blist-node-removed",
4189 gtkchat
, PURPLE_CALLBACK(buddy_removed_cb
), conv
);
4190 purple_signal_connect(blist_handle
, "blist-node-aliased",
4191 gtkchat
, PURPLE_CALLBACK(blist_node_aliased_cb
), conv
);
4193 gtk_tree_view_column_set_expand(col
, TRUE
);
4194 g_object_set(rend
, "ellipsize", PANGO_ELLIPSIZE_END
, NULL
);
4196 gtk_tree_view_append_column(GTK_TREE_VIEW(list
), col
);
4198 gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(list
), FALSE
);
4199 gtk_widget_show(list
);
4201 gtkchat
->list
= list
;
4203 gtk_box_pack_start(GTK_BOX(lbox
),
4204 pidgin_make_scrollable(list
, GTK_POLICY_AUTOMATIC
, GTK_POLICY_AUTOMATIC
, GTK_SHADOW_IN
, -1, -1),
4209 pidgin_conv_create_tooltip(GtkWidget
*tipwindow
, gpointer userdata
, int *w
, int *h
)
4211 PurpleBlistNode
*node
= NULL
;
4212 PurpleConversation
*conv
;
4213 PidginConversation
*gtkconv
= userdata
;
4215 conv
= gtkconv
->active_conv
;
4216 if (PURPLE_IS_CHAT_CONVERSATION(conv
)) {
4217 node
= (PurpleBlistNode
*)(purple_blist_find_chat(purple_conversation_get_account(conv
), purple_conversation_get_name(conv
)));
4219 node
= g_object_get_data(G_OBJECT(gtkconv
->history
), "transient_chat");
4221 node
= (PurpleBlistNode
*)(purple_blist_find_buddy(purple_conversation_get_account(conv
), purple_conversation_get_name(conv
)));
4223 /* Using the transient blist nodes to show the tooltip doesn't quite work yet. */
4225 node
= g_object_get_data(G_OBJECT(gtkconv
->webview
), "transient_buddy");
4230 pidgin_blist_draw_tooltip(node
, gtkconv
->infopane
);
4235 setup_common_pane(PidginConversation
*gtkconv
)
4237 GtkWidget
*vbox
, *sw
, *event_box
, *view
;
4238 GtkCellRenderer
*rend
;
4240 PurpleConversation
*conv
= gtkconv
->active_conv
;
4242 gboolean chat
= PURPLE_IS_CHAT_CONVERSATION(conv
);
4243 int buddyicon_size
= 0;
4245 /* Setup the top part of the pane */
4246 vbox
= gtk_box_new(GTK_ORIENTATION_VERTICAL
, PIDGIN_HIG_BOX_SPACE
);
4247 gtk_widget_show(vbox
);
4249 /* Setup the info pane */
4250 event_box
= gtk_event_box_new();
4251 gtk_event_box_set_visible_window(GTK_EVENT_BOX(event_box
), FALSE
);
4252 gtk_widget_show(event_box
);
4253 gtkconv
->infopane_hbox
= gtk_box_new(GTK_ORIENTATION_HORIZONTAL
, 0);
4254 gtk_box_pack_start(GTK_BOX(vbox
), event_box
, FALSE
, FALSE
, 0);
4255 gtk_container_add(GTK_CONTAINER(event_box
), gtkconv
->infopane_hbox
);
4256 gtk_widget_show(gtkconv
->infopane_hbox
);
4257 gtk_widget_add_events(event_box
,
4258 GDK_POINTER_MOTION_MASK
| GDK_LEAVE_NOTIFY_MASK
);
4259 g_signal_connect(G_OBJECT(event_box
), "button-press-event",
4260 G_CALLBACK(infopane_press_cb
), gtkconv
);
4262 pidgin_tooltip_setup_for_widget(event_box
, gtkconv
,
4263 pidgin_conv_create_tooltip
, NULL
);
4265 gtkconv
->infopane
= gtk_cell_view_new();
4266 gtkconv
->infopane_model
= gtk_list_store_new(CONV_NUM_COLUMNS
, G_TYPE_STRING
, G_TYPE_STRING
, GDK_TYPE_PIXBUF
, GDK_TYPE_PIXBUF
);
4267 gtk_cell_view_set_model(GTK_CELL_VIEW(gtkconv
->infopane
),
4268 GTK_TREE_MODEL(gtkconv
->infopane_model
));
4269 g_object_unref(gtkconv
->infopane_model
);
4270 gtk_list_store_append(gtkconv
->infopane_model
, &(gtkconv
->infopane_iter
));
4271 gtk_box_pack_start(GTK_BOX(gtkconv
->infopane_hbox
), gtkconv
->infopane
, TRUE
, TRUE
, 0);
4272 path
= gtk_tree_path_new_from_string("0");
4273 gtk_cell_view_set_displayed_row(GTK_CELL_VIEW(gtkconv
->infopane
), path
);
4274 gtk_tree_path_free(path
);
4277 /* This empty widget is used to ensure that the infopane is consistently
4278 sized for chat windows. The correct fix is to put an icon in the chat
4279 window as well, because that would make "Set Custom Icon" consistent
4280 for both the buddy list and the chat window, but PidginConversation
4281 is pretty much stuck until 3.0. */
4282 GtkWidget
*sizing_vbox
;
4283 sizing_vbox
= gtk_box_new(GTK_ORIENTATION_VERTICAL
, 0);
4284 gtk_widget_set_size_request(sizing_vbox
, -1, BUDDYICON_SIZE_MIN
);
4285 gtk_box_pack_start(GTK_BOX(gtkconv
->infopane_hbox
), sizing_vbox
, FALSE
, FALSE
, 0);
4286 gtk_widget_show(sizing_vbox
);
4289 gtkconv
->u
.im
->icon_container
= gtk_box_new(GTK_ORIENTATION_VERTICAL
, 0);
4291 if ((buddy
= purple_blist_find_buddy(purple_conversation_get_account(conv
),
4292 purple_conversation_get_name(conv
))) != NULL
) {
4293 PurpleContact
*contact
= purple_buddy_get_contact(buddy
);
4295 buddyicon_size
= purple_blist_node_get_int((PurpleBlistNode
*)contact
, "pidgin-infopane-iconsize");
4298 buddyicon_size
= CLAMP(buddyicon_size
, BUDDYICON_SIZE_MIN
, BUDDYICON_SIZE_MAX
);
4299 gtk_widget_set_size_request(gtkconv
->u
.im
->icon_container
, -1, buddyicon_size
);
4301 gtk_box_pack_start(GTK_BOX(gtkconv
->infopane_hbox
),
4302 gtkconv
->u
.im
->icon_container
, FALSE
, FALSE
, 0);
4304 gtk_widget_show(gtkconv
->u
.im
->icon_container
);
4307 gtk_widget_show(gtkconv
->infopane
);
4309 rend
= gtk_cell_renderer_pixbuf_new();
4310 gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(gtkconv
->infopane
), rend
, FALSE
);
4311 gtk_cell_layout_set_attributes(GTK_CELL_LAYOUT(gtkconv
->infopane
), rend
, "stock-id", CONV_ICON_COLUMN
, NULL
);
4312 g_object_set(rend
, "xalign", 0.0, "xpad", 6, "ypad", 0,
4313 "stock-size", gtk_icon_size_from_name(PIDGIN_ICON_SIZE_TANGO_EXTRA_SMALL
),
4316 rend
= gtk_cell_renderer_text_new();
4317 gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(gtkconv
->infopane
), rend
, TRUE
);
4318 gtk_cell_layout_set_attributes(GTK_CELL_LAYOUT(gtkconv
->infopane
), rend
, "markup", CONV_TEXT_COLUMN
, NULL
);
4319 g_object_set(rend
, "ypad", 0, "yalign", 0.5, NULL
);
4321 g_object_set(rend
, "ellipsize", PANGO_ELLIPSIZE_END
, NULL
);
4323 rend
= gtk_cell_renderer_pixbuf_new();
4324 gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(gtkconv
->infopane
), rend
, FALSE
);
4325 gtk_cell_layout_set_attributes(GTK_CELL_LAYOUT(gtkconv
->infopane
), rend
, "pixbuf", CONV_PROTOCOL_ICON_COLUMN
, NULL
);
4326 g_object_set(rend
, "xalign", 0.0, "xpad", 3, "ypad", 0, NULL
);
4328 rend
= gtk_cell_renderer_pixbuf_new();
4329 gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(gtkconv
->infopane
), rend
, FALSE
);
4330 gtk_cell_layout_set_attributes(GTK_CELL_LAYOUT(gtkconv
->infopane
), rend
, "pixbuf", CONV_EMBLEM_COLUMN
, NULL
);
4331 g_object_set(rend
, "xalign", 0.0, "xpad", 6, "ypad", 0, NULL
);
4333 /* Setup the history widget */
4334 sw
= gtk_scrolled_window_new(NULL
, NULL
);
4335 gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(sw
), GTK_SHADOW_IN
);
4336 gtk_scrolled_window_set_policy(
4337 GTK_SCROLLED_WINDOW(sw
),
4342 gtkconv
->history_buffer
= talkatu_history_buffer_new();
4343 gtkconv
->history
= talkatu_history_new();
4344 gtk_text_view_set_buffer(GTK_TEXT_VIEW(gtkconv
->history
), gtkconv
->history_buffer
);
4345 gtk_text_view_set_wrap_mode(GTK_TEXT_VIEW(gtkconv
->history
), GTK_WRAP_WORD
);
4346 gtk_container_add(GTK_CONTAINER(sw
), gtkconv
->history
);
4352 setup_chat_topic(gtkconv
, vbox
);
4354 /* Add the talkatu history */
4355 hpaned
= gtk_paned_new(GTK_ORIENTATION_HORIZONTAL
);
4356 gtk_box_pack_start(GTK_BOX(vbox
), hpaned
, TRUE
, TRUE
, 0);
4357 gtk_widget_show(hpaned
);
4358 gtk_paned_pack1(GTK_PANED(hpaned
), sw
, TRUE
, TRUE
);
4360 /* Now add the userlist */
4361 setup_chat_userlist(gtkconv
, hpaned
);
4363 gtk_box_pack_start(GTK_BOX(vbox
), sw
, TRUE
, TRUE
, 0);
4365 gtk_widget_show_all(sw
);
4367 g_object_set_data(G_OBJECT(gtkconv
->history
), "gtkconv", gtkconv
);
4369 g_signal_connect(G_OBJECT(gtkconv
->history
), "key_press_event",
4370 G_CALLBACK(refocus_entry_cb
), gtkconv
);
4371 g_signal_connect(G_OBJECT(gtkconv
->history
), "key_release_event",
4372 G_CALLBACK(refocus_entry_cb
), gtkconv
);
4374 gtkconv
->lower_hbox
= gtk_box_new(GTK_ORIENTATION_HORIZONTAL
, PIDGIN_HIG_BOX_SPACE
);
4375 gtk_box_pack_start(GTK_BOX(vbox
), gtkconv
->lower_hbox
, FALSE
, FALSE
, 0);
4376 gtk_widget_show(gtkconv
->lower_hbox
);
4378 /* Setup the entry widget and all signals */
4379 gtkconv
->editor
= talkatu_editor_new();
4380 talkatu_editor_set_buffer(TALKATU_EDITOR(gtkconv
->editor
), talkatu_html_buffer_new());
4381 gtk_box_pack_start(GTK_BOX(gtkconv
->lower_hbox
), gtkconv
->editor
, TRUE
, TRUE
, 0);
4383 view
= talkatu_editor_get_view(TALKATU_EDITOR(gtkconv
->editor
));
4384 gtk_widget_set_name(view
, "pidgin_conv_entry");
4385 talkatu_view_set_send_binding(TALKATU_VIEW(view
), TALKATU_VIEW_SEND_BINDING_RETURN
| TALKATU_VIEW_SEND_BINDING_KP_ENTER
);
4389 G_CALLBACK(send_cb
),
4394 /* For sending typing notifications for IMs */
4395 gtkconv
->u
.im
->typing_timer
= 0;
4396 gtkconv
->u
.im
->animate
= purple_prefs_get_bool(PIDGIN_PREFS_ROOT
"/conversations/im/animate_buddy_icons");
4397 gtkconv
->u
.im
->show_icon
= TRUE
;
4403 static PidginConversation
*
4404 pidgin_conv_find_gtkconv(PurpleConversation
* conv
)
4406 PurpleBuddy
*bud
= purple_blist_find_buddy(purple_conversation_get_account(conv
), purple_conversation_get_name(conv
));
4408 PurpleBlistNode
*cn
, *bn
;
4413 if (!(c
= purple_buddy_get_contact(bud
)))
4416 cn
= PURPLE_BLIST_NODE(c
);
4417 for (bn
= purple_blist_node_get_first_child(cn
); bn
; bn
= purple_blist_node_get_sibling_next(bn
)) {
4418 PurpleBuddy
*b
= PURPLE_BUDDY(bn
);
4419 PurpleIMConversation
*im
;
4420 if ((im
= purple_conversations_find_im_with_account(purple_buddy_get_name(b
), purple_buddy_get_account(b
)))) {
4421 if (PIDGIN_CONVERSATION(PURPLE_CONVERSATION(im
)))
4422 return PIDGIN_CONVERSATION(PURPLE_CONVERSATION(im
));
4430 buddy_update_cb(PurpleBlistNode
*bnode
, gpointer null
)
4434 g_return_if_fail(bnode
);
4435 if (!PURPLE_IS_BUDDY(bnode
))
4438 for (list
= pidgin_conv_windows_get_list(); list
; list
= list
->next
)
4440 PidginConvWindow
*win
= list
->data
;
4441 PurpleConversation
*conv
= pidgin_conv_window_get_active_conversation(win
);
4443 if (!PURPLE_IS_IM_CONVERSATION(conv
))
4446 pidgin_conv_update_fields(conv
, PIDGIN_CONV_MENU
);
4451 ignore_middle_click(GtkWidget
*widget
, GdkEventButton
*e
, gpointer null
)
4453 /* A click on the pane is propagated to the notebook containing the pane.
4454 * So if Stu accidentally aims high and middle clicks on the pane-handle,
4455 * it causes a conversation tab to close. Let's stop that from happening.
4457 if (e
->button
== GDK_BUTTON_MIDDLE
&& e
->type
== GDK_BUTTON_PRESS
)
4462 /**************************************************************************
4463 * Conversation UI operations
4464 **************************************************************************/
4466 private_gtkconv_new(PurpleConversation
*conv
, gboolean hidden
)
4468 PidginConversation
*gtkconv
;
4469 GtkWidget
*pane
= NULL
;
4470 GtkWidget
*tab_cont
;
4471 PurpleBlistNode
*convnode
;
4472 GtkTargetList
*targets
;
4474 if (PURPLE_IS_IM_CONVERSATION(conv
) && (gtkconv
= pidgin_conv_find_gtkconv(conv
))) {
4475 purple_conversation_set_ui_data(conv
, gtkconv
);
4476 if (!g_list_find(gtkconv
->convs
, conv
))
4477 gtkconv
->convs
= g_list_prepend(gtkconv
->convs
, conv
);
4478 pidgin_conv_switch_active_conversation(conv
);
4482 gtkconv
= g_new0(PidginConversation
, 1);
4483 purple_conversation_set_ui_data(conv
, gtkconv
);
4484 gtkconv
->active_conv
= conv
;
4485 gtkconv
->convs
= g_list_prepend(gtkconv
->convs
, conv
);
4486 gtkconv
->send_history
= g_list_append(NULL
, NULL
);
4488 /* Setup some initial variables. */
4489 gtkconv
->unseen_state
= PIDGIN_UNSEEN_NONE
;
4490 gtkconv
->unseen_count
= 0;
4491 gtkconv
->last_flags
= 0;
4493 if (PURPLE_IS_IM_CONVERSATION(conv
)) {
4494 gtkconv
->u
.im
= g_malloc0(sizeof(PidginImPane
));
4495 } else if (PURPLE_IS_CHAT_CONVERSATION(conv
)) {
4496 gtkconv
->u
.chat
= g_malloc0(sizeof(PidginChatPane
));
4498 pane
= setup_common_pane(gtkconv
);
4501 if (PURPLE_IS_CHAT_CONVERSATION(conv
))
4502 g_free(gtkconv
->u
.chat
);
4503 else if (PURPLE_IS_IM_CONVERSATION(conv
))
4504 g_free(gtkconv
->u
.im
);
4507 purple_conversation_set_ui_data(conv
, NULL
);
4511 g_signal_connect(G_OBJECT(pane
), "button_press_event",
4512 G_CALLBACK(ignore_middle_click
), NULL
);
4514 /* Setup the container for the tab. */
4515 gtkconv
->tab_cont
= tab_cont
= gtk_box_new(GTK_ORIENTATION_VERTICAL
, PIDGIN_HIG_BOX_SPACE
);
4516 g_object_set_data(G_OBJECT(tab_cont
), "PidginConversation", gtkconv
);
4517 gtk_container_set_border_width(GTK_CONTAINER(tab_cont
), PIDGIN_HIG_BOX_SPACE
);
4518 gtk_box_pack_start(GTK_BOX(tab_cont
), pane
, TRUE
, TRUE
, 0);
4519 gtk_widget_show(pane
);
4521 convnode
= get_conversation_blist_node(conv
);
4522 if (convnode
== NULL
|| !purple_blist_node_get_bool(convnode
, "gtk-mute-sound"))
4523 gtkconv
->make_sound
= TRUE
;
4525 if (convnode
!= NULL
&& purple_blist_node_has_setting(convnode
, "enable-logging")) {
4526 gboolean logging
= purple_blist_node_get_bool(convnode
, "enable-logging");
4527 purple_conversation_set_logging(conv
, logging
);
4530 talkatu_editor_set_toolbar_visible(
4531 TALKATU_EDITOR(gtkconv
->editor
),
4532 purple_prefs_get_bool(PIDGIN_PREFS_ROOT
"/conversations/show_formatting_toolbar")
4535 if (purple_prefs_get_bool(PIDGIN_PREFS_ROOT
"/conversations/im/show_buddy_icons"))
4536 gtk_widget_show(gtkconv
->infopane_hbox
);
4538 gtk_widget_hide(gtkconv
->infopane_hbox
);
4541 g_signal_connect_swapped(G_OBJECT(pane
), "focus",
4542 G_CALLBACK(gtk_widget_grab_focus
),
4546 pidgin_conv_window_add_gtkconv(hidden_convwin
, gtkconv
);
4548 pidgin_conv_placement_place(gtkconv
);
4550 if (generated_nick_colors
== NULL
) {
4553 color
= gtk_widget_get_style(gtkconv
->history
)->base
[GTK_STATE_NORMAL
];
4554 rgba
.red
= color
.red
/ 65535.0;
4555 rgba
.green
= color
.green
/ 65535.0;
4556 rgba
.blue
= color
.blue
/ 65535.0;
4558 generated_nick_colors
= generate_nick_colors(NICK_COLOR_GENERATE_COUNT
, rgba
);
4561 gtkconv
->nick_colors
= g_array_ref(generated_nick_colors
);
4565 pidgin_conv_new_hidden(PurpleConversation
*conv
)
4567 private_gtkconv_new(conv
, TRUE
);
4571 pidgin_conv_new(PurpleConversation
*conv
)
4573 private_gtkconv_new(conv
, FALSE
);
4574 if (PIDGIN_IS_PIDGIN_CONVERSATION(conv
))
4575 purple_signal_emit(pidgin_conversations_get_handle(),
4576 "conversation-displayed", PIDGIN_CONVERSATION(conv
));
4580 received_im_msg_cb(PurpleAccount
*account
, char *sender
, char *message
,
4581 PurpleConversation
*conv
, PurpleMessageFlags flags
)
4583 PurpleConversationUiOps
*ui_ops
= pidgin_conversations_get_conv_ui_ops();
4584 gboolean hide
= FALSE
;
4587 /* create hidden conv if hide_new pref is always */
4588 if (purple_strequal(purple_prefs_get_string(PIDGIN_PREFS_ROOT
"/conversations/im/hide_new"), "always"))
4591 /* create hidden conv if hide_new pref is away and account is away */
4592 if (purple_strequal(purple_prefs_get_string(PIDGIN_PREFS_ROOT
"/conversations/im/hide_new"), "away") &&
4593 !purple_status_is_available(purple_account_get_active_status(account
)))
4596 if (conv
&& PIDGIN_IS_PIDGIN_CONVERSATION(conv
) && !hide
) {
4597 PidginConversation
*gtkconv
= PIDGIN_CONVERSATION(conv
);
4598 if (gtkconv
->win
== hidden_convwin
) {
4599 pidgin_conv_attach_to_conversation(gtkconv
->active_conv
);
4605 ui_ops
->create_conversation
= pidgin_conv_new_hidden
;
4606 purple_im_conversation_new(account
, sender
);
4607 ui_ops
->create_conversation
= pidgin_conv_new
;
4610 /* Somebody wants to keep this conversation around, so don't time it out */
4612 timer
= GPOINTER_TO_INT(g_object_get_data(G_OBJECT(conv
), "close-timer"));
4614 g_source_remove(timer
);
4615 g_object_set_data(G_OBJECT(conv
), "close-timer", GINT_TO_POINTER(0));
4621 pidgin_conv_destroy(PurpleConversation
*conv
)
4623 PidginConversation
*gtkconv
= PIDGIN_CONVERSATION(conv
);
4625 gtkconv
->convs
= g_list_remove(gtkconv
->convs
, conv
);
4626 /* Don't destroy ourselves until all our convos are gone */
4627 if (gtkconv
->convs
) {
4628 /* Make sure the destroyed conversation is not the active one */
4629 if (gtkconv
->active_conv
== conv
) {
4630 gtkconv
->active_conv
= gtkconv
->convs
->data
;
4631 purple_conversation_update(gtkconv
->active_conv
, PURPLE_CONVERSATION_UPDATE_FEATURES
);
4636 pidgin_conv_window_remove_gtkconv(gtkconv
->win
, gtkconv
);
4638 /* If the "Save Conversation" or "Save Icon" dialogs are open then close them */
4639 purple_request_close_with_handle(gtkconv
);
4640 purple_notify_close_with_handle(gtkconv
);
4642 gtk_widget_destroy(gtkconv
->tab_cont
);
4643 g_object_unref(gtkconv
->tab_cont
);
4645 if (PURPLE_IS_IM_CONVERSATION(conv
)) {
4646 if (gtkconv
->u
.im
->icon_timer
!= 0)
4647 g_source_remove(gtkconv
->u
.im
->icon_timer
);
4649 if (gtkconv
->u
.im
->anim
!= NULL
)
4650 g_object_unref(G_OBJECT(gtkconv
->u
.im
->anim
));
4652 if (gtkconv
->u
.im
->typing_timer
!= 0)
4653 g_source_remove(gtkconv
->u
.im
->typing_timer
);
4655 g_free(gtkconv
->u
.im
);
4656 } else if (PURPLE_IS_CHAT_CONVERSATION(conv
)) {
4657 purple_signals_disconnect_by_handle(gtkconv
->u
.chat
);
4658 g_free(gtkconv
->u
.chat
);
4661 gtkconv
->send_history
= g_list_first(gtkconv
->send_history
);
4662 g_list_foreach(gtkconv
->send_history
, (GFunc
)g_free
, NULL
);
4663 g_list_free(gtkconv
->send_history
);
4665 if (gtkconv
->attach_timer
) {
4666 g_source_remove(gtkconv
->attach_timer
);
4669 g_array_unref(gtkconv
->nick_colors
);
4676 get_text_tag_color(GtkTextTag
*tag
)
4678 GdkRGBA
*color
= NULL
;
4679 gboolean set
= FALSE
;
4680 static char colcode
[] = "#XXXXXX";
4682 g_object_get(G_OBJECT(tag
), "foreground-set", &set
, "foreground-rgba", &color
, NULL
);
4684 g_snprintf(colcode
, sizeof(colcode
), "#%02x%02x%02x",
4685 (unsigned int)(color
->red
* 255),
4686 (unsigned int)(color
->green
* 255),
4687 (unsigned int)(color
->blue
* 255));
4691 gdk_rgba_free(color
);
4695 /* The callback for an event on a link tag. */
4696 static gboolean
buddytag_event(GtkTextTag
*tag
, GObject
*imhtml
,
4697 GdkEvent
*event
, GtkTextIter
*arg2
, gpointer data
)
4699 if (event
->type
== GDK_BUTTON_PRESS
4700 || event
->type
== GDK_2BUTTON_PRESS
) {
4701 GdkEventButton
*btn_event
= (GdkEventButton
*) event
;
4702 PurpleConversation
*conv
= data
;
4706 g_object_get(G_OBJECT(tag
), "name", &name
, NULL
);
4708 /* strlen("BUDDY " or "HILIT ") == 6 */
4709 g_return_val_if_fail((name
!= NULL
) && (strlen(name
) > 6), FALSE
);
4711 buddyname
= name
+ 6;
4713 /* emit chat-nick-clicked signal */
4714 if (event
->type
== GDK_BUTTON_PRESS
) {
4715 gint plugin_return
= GPOINTER_TO_INT(purple_signal_emit_return_1(
4716 pidgin_conversations_get_handle(), "chat-nick-clicked",
4717 data
, buddyname
, btn_event
->button
));
4718 if (plugin_return
) {
4724 if (btn_event
->button
== GDK_BUTTON_PRIMARY
&& event
->type
== GDK_2BUTTON_PRESS
) {
4725 chat_do_im(PIDGIN_CONVERSATION(conv
), buddyname
);
4729 } else if (btn_event
->button
== GDK_BUTTON_MIDDLE
&& event
->type
== GDK_2BUTTON_PRESS
) {
4730 chat_do_info(PIDGIN_CONVERSATION(conv
), buddyname
);
4734 } else if (gdk_event_triggers_context_menu(event
)) {
4735 GtkTextIter start
, end
;
4737 /* we shouldn't display the popup
4738 * if the user has selected something: */
4739 if (!gtk_text_buffer_get_selection_bounds(
4740 gtk_text_iter_get_buffer(arg2
),
4742 GtkWidget
*menu
= NULL
;
4743 PurpleConnection
*gc
=
4744 purple_conversation_get_connection(conv
);
4746 menu
= create_chat_menu(conv
, buddyname
, gc
);
4747 gtk_menu_popup_at_pointer(GTK_MENU(menu
), event
);
4751 /* Don't propagate the event any further */
4763 static GtkTextTag
*get_buddy_tag(PurpleChatConversation
*chat
, const char *who
, PurpleMessageFlags flag
,
4768 PidginConversation
*gtkconv
= PIDGIN_CONVERSATION(conv
);
4769 GtkTextTag
*buddytag
;
4771 gboolean highlight
= (flag
& PURPLE_MESSAGE_NICK
);
4772 GtkTextBuffer
*buffer
= GTK_IMHTML(gtkconv
->imhtml
)->text_buffer
;
4774 str
= g_strdup_printf(highlight
? "HILIT %s" : "BUDDY %s", who
);
4776 buddytag
= gtk_text_tag_table_lookup(
4777 gtk_text_buffer_get_tag_table(buffer
), str
);
4779 if (buddytag
== NULL
&& create
) {
4781 buddytag
= gtk_text_buffer_create_tag(buffer
, str
,
4782 "foreground", get_text_tag_color(gtk_text_tag_table_lookup(
4783 gtk_text_buffer_get_tag_table(buffer
), "highlight-name")),
4784 "weight", PANGO_WEIGHT_BOLD
,
4787 buddytag
= gtk_text_buffer_create_tag(
4789 "foreground-rgba", get_nick_color(gtkconv
, who
),
4790 "weight", purple_blist_find_buddy(purple_conversation_get_account(conv
), who
) ? PANGO_WEIGHT_BOLD
: PANGO_WEIGHT_NORMAL
,
4793 g_object_set_data(G_OBJECT(buddytag
), "cursor", "");
4794 g_signal_connect(G_OBJECT(buddytag
), "event",
4795 G_CALLBACK(buddytag_event
), conv
);
4806 writing_msg(PurpleConversation
*conv
, PurpleMessage
*msg
, gpointer _unused
)
4808 PidginConversation
*gtkconv
;
4810 g_return_val_if_fail(msg
!= NULL
, FALSE
);
4812 if (!(purple_message_get_flags(msg
) & PURPLE_MESSAGE_ACTIVE_ONLY
))
4815 g_return_val_if_fail(conv
!= NULL
, FALSE
);
4816 gtkconv
= PIDGIN_CONVERSATION(conv
);
4817 g_return_val_if_fail(gtkconv
!= NULL
, FALSE
);
4819 if (conv
== gtkconv
->active_conv
)
4822 purple_debug_info("gtkconv",
4823 "Suppressing message for an inactive conversation");
4829 pidgin_conv_write_conv(PurpleConversation
*conv
, PurpleMessage
*pmsg
)
4831 PidginMessage
*pidgin_msg
= NULL
;
4832 PurpleMessageFlags flags
;
4833 PidginConversation
*gtkconv
;
4834 PurpleConnection
*gc
;
4835 PurpleAccount
*account
;
4836 gboolean plugin_return
;
4838 g_return_if_fail(conv
!= NULL
);
4839 gtkconv
= PIDGIN_CONVERSATION(conv
);
4840 g_return_if_fail(gtkconv
!= NULL
);
4841 flags
= purple_message_get_flags(pmsg
);
4844 if (gtkconv
->attach_timer
) {
4845 /* We are currently in the process of filling up the buffer with the message
4846 * history of the conversation. So we do not need to add the message here.
4847 * Instead, this message will be added to the message-list, which in turn will
4848 * be processed and displayed by the attach-callback.
4853 if (conv
!= gtkconv
->active_conv
)
4855 /* Set the active conversation to the one that just messaged us. */
4856 /* TODO: consider not doing this if the account is offline or something */
4857 if (flags
& (PURPLE_MESSAGE_SEND
| PURPLE_MESSAGE_RECV
))
4858 pidgin_conv_switch_active_conversation(conv
);
4862 account
= purple_conversation_get_account(conv
);
4863 g_return_if_fail(account
!= NULL
);
4864 gc
= purple_account_get_connection(account
);
4865 g_return_if_fail(gc
!= NULL
|| !(flags
& (PURPLE_MESSAGE_SEND
| PURPLE_MESSAGE_RECV
)));
4867 plugin_return
= GPOINTER_TO_INT(purple_signal_emit_return_1(
4868 pidgin_conversations_get_handle(),
4869 (PURPLE_IS_IM_CONVERSATION(conv
) ? "displaying-im-msg" : "displaying-chat-msg"),
4876 pidgin_msg
= pidgin_message_new(pmsg
);
4877 talkatu_history_buffer_write_message(
4878 TALKATU_HISTORY_BUFFER(gtkconv
->history_buffer
),
4879 TALKATU_MESSAGE(pidgin_msg
)
4882 /* Tab highlighting stuff */
4883 if (!(flags
& PURPLE_MESSAGE_SEND
) && !pidgin_conv_has_focus(conv
))
4885 PidginUnseenState unseen
= PIDGIN_UNSEEN_NONE
;
4887 if ((flags
& PURPLE_MESSAGE_NICK
) == PURPLE_MESSAGE_NICK
)
4888 unseen
= PIDGIN_UNSEEN_NICK
;
4889 else if (((flags
& PURPLE_MESSAGE_SYSTEM
) == PURPLE_MESSAGE_SYSTEM
) ||
4890 ((flags
& PURPLE_MESSAGE_ERROR
) == PURPLE_MESSAGE_ERROR
))
4891 unseen
= PIDGIN_UNSEEN_EVENT
;
4892 else if ((flags
& PURPLE_MESSAGE_NO_LOG
) == PURPLE_MESSAGE_NO_LOG
)
4893 unseen
= PIDGIN_UNSEEN_NO_LOG
;
4895 unseen
= PIDGIN_UNSEEN_TEXT
;
4897 gtkconv_set_unseen(gtkconv
, unseen
);
4900 /* on rejoin only request message history from after this message */
4901 if (flags
& (PURPLE_MESSAGE_SEND
| PURPLE_MESSAGE_RECV
) &&
4902 PURPLE_IS_CHAT_CONVERSATION(conv
)) {
4903 PurpleChat
*chat
= purple_blist_find_chat(
4904 purple_conversation_get_account(conv
),
4905 purple_conversation_get_name(conv
));
4907 GHashTable
*comps
= purple_chat_get_components(chat
);
4908 time_t now
, history_since
, prev_history_since
= 0;
4909 struct tm
*history_since_tm
;
4910 const char *history_since_s
, *prev_history_since_s
;
4912 history_since
= purple_message_get_time(pmsg
) + 1;
4914 prev_history_since_s
= g_hash_table_lookup(comps
,
4916 if (prev_history_since_s
!= NULL
)
4917 prev_history_since
= purple_str_to_time(
4918 prev_history_since_s
, TRUE
, NULL
, NULL
,
4922 /* in case of incorrectly stored timestamps */
4923 if (prev_history_since
> now
)
4924 prev_history_since
= now
;
4925 /* in case of delayed messages */
4926 if (history_since
< prev_history_since
)
4927 history_since
= prev_history_since
;
4929 history_since_tm
= gmtime(&history_since
);
4930 history_since_s
= purple_utf8_strftime(
4931 "%Y-%m-%dT%H:%M:%SZ", history_since_tm
);
4932 if (!purple_strequal(prev_history_since_s
,
4934 g_hash_table_replace(comps
,
4935 g_strdup("history_since"),
4936 g_strdup(history_since_s
));
4940 purple_signal_emit(pidgin_conversations_get_handle(),
4941 (PURPLE_IS_IM_CONVERSATION(conv
) ? "displayed-im-msg" : "displayed-chat-msg"),
4943 update_typing_message(gtkconv
, NULL
);
4946 static gboolean
get_iter_from_chatuser(PurpleChatUser
*cb
, GtkTreeIter
*iter
)
4948 GtkTreeRowReference
*ref
;
4950 GtkTreeModel
*model
;
4952 g_return_val_if_fail(cb
!= NULL
, FALSE
);
4954 ref
= purple_chat_user_get_ui_data(cb
);
4958 if ((path
= gtk_tree_row_reference_get_path(ref
)) == NULL
)
4961 model
= gtk_tree_row_reference_get_model(ref
);
4962 if (!gtk_tree_model_get_iter(GTK_TREE_MODEL(model
), iter
, path
)) {
4963 gtk_tree_path_free(path
);
4967 gtk_tree_path_free(path
);
4972 pidgin_conv_chat_add_users(PurpleChatConversation
*chat
, GList
*cbuddies
, gboolean new_arrivals
)
4974 PidginConversation
*gtkconv
;
4975 PidginChatPane
*gtkchat
;
4982 gtkconv
= PIDGIN_CONVERSATION(PURPLE_CONVERSATION(chat
));
4983 gtkchat
= gtkconv
->u
.chat
;
4985 num_users
= purple_chat_conversation_get_users_count(chat
);
4987 g_snprintf(tmp
, sizeof(tmp
),
4988 ngettext("%d person in room", "%d people in room",
4992 gtk_label_set_text(GTK_LABEL(gtkchat
->count
), tmp
);
4994 ls
= GTK_LIST_STORE(gtk_tree_view_get_model(GTK_TREE_VIEW(gtkchat
->list
)));
4996 gtk_tree_sortable_set_sort_column_id(GTK_TREE_SORTABLE(ls
), GTK_TREE_SORTABLE_UNSORTED_SORT_COLUMN_ID
,
4997 GTK_TREE_SORTABLE_UNSORTED_SORT_COLUMN_ID
);
5001 add_chat_user_common(chat
, (PurpleChatUser
*)l
->data
, NULL
);
5005 /* Currently GTK+ maintains our sorted list after it's in the tree.
5006 * This may change if it turns out we can manage it faster ourselves.
5008 gtk_tree_sortable_set_sort_column_id(GTK_TREE_SORTABLE(ls
), CHAT_USERS_ALIAS_KEY_COLUMN
,
5009 GTK_SORT_ASCENDING
);
5013 pidgin_conv_chat_rename_user(PurpleChatConversation
*chat
, const char *old_name
,
5014 const char *new_name
, const char *new_alias
)
5016 PidginConversation
*gtkconv
;
5017 PidginChatPane
*gtkchat
;
5018 PurpleChatUser
*old_chatuser
, *new_chatuser
;
5020 GtkTreeModel
*model
;
5023 gtkconv
= PIDGIN_CONVERSATION(PURPLE_CONVERSATION(chat
));
5024 gtkchat
= gtkconv
->u
.chat
;
5026 model
= gtk_tree_view_get_model(GTK_TREE_VIEW(gtkchat
->list
));
5028 if (!gtk_tree_model_get_iter_first(GTK_TREE_MODEL(model
), &iter
))
5031 if ((tag
= get_buddy_tag(chat
, old_name
, 0, FALSE
)))
5032 g_object_set(G_OBJECT(tag
), "style", PANGO_STYLE_ITALIC
, NULL
);
5033 if ((tag
= get_buddy_tag(chat
, old_name
, PURPLE_MESSAGE_NICK
, FALSE
)))
5034 g_object_set(G_OBJECT(tag
), "style", PANGO_STYLE_ITALIC
, NULL
);
5036 old_chatuser
= purple_chat_conversation_find_user(chat
, old_name
);
5040 if (get_iter_from_chatuser(old_chatuser
, &iter
)) {
5041 GtkTreeRowReference
*ref
= purple_chat_user_get_ui_data(old_chatuser
);
5043 gtk_list_store_remove(GTK_LIST_STORE(model
), &iter
);
5044 gtk_tree_row_reference_free(ref
);
5045 purple_chat_user_set_ui_data(old_chatuser
, NULL
);
5048 g_return_if_fail(new_alias
!= NULL
);
5050 new_chatuser
= purple_chat_conversation_find_user(chat
, new_name
);
5052 add_chat_user_common(chat
, new_chatuser
, old_name
);
5056 pidgin_conv_chat_remove_users(PurpleChatConversation
*chat
, GList
*users
)
5058 PidginConversation
*gtkconv
;
5059 PidginChatPane
*gtkchat
;
5061 GtkTreeModel
*model
;
5068 gtkconv
= PIDGIN_CONVERSATION(PURPLE_CONVERSATION(chat
));
5069 gtkchat
= gtkconv
->u
.chat
;
5071 num_users
= purple_chat_conversation_get_users_count(chat
);
5073 for (l
= users
; l
!= NULL
; l
= l
->next
) {
5074 model
= gtk_tree_view_get_model(GTK_TREE_VIEW(gtkchat
->list
));
5076 if (!gtk_tree_model_get_iter_first(GTK_TREE_MODEL(model
), &iter
))
5083 gtk_tree_model_get(GTK_TREE_MODEL(model
), &iter
,
5084 CHAT_USERS_NAME_COLUMN
, &val
, -1);
5086 if (!purple_utf8_strcasecmp((char *)l
->data
, val
)) {
5087 f
= gtk_list_store_remove(GTK_LIST_STORE(model
), &iter
);
5090 f
= gtk_tree_model_iter_next(GTK_TREE_MODEL(model
), &iter
);
5095 if ((tag
= get_buddy_tag(chat
, l
->data
, 0, FALSE
)))
5096 g_object_set(G_OBJECT(tag
), "style", PANGO_STYLE_ITALIC
, NULL
);
5097 if ((tag
= get_buddy_tag(chat
, l
->data
, PURPLE_MESSAGE_NICK
, FALSE
)))
5098 g_object_set(G_OBJECT(tag
), "style", PANGO_STYLE_ITALIC
, NULL
);
5101 g_snprintf(tmp
, sizeof(tmp
),
5102 ngettext("%d person in room", "%d people in room",
5103 num_users
), num_users
);
5105 gtk_label_set_text(GTK_LABEL(gtkchat
->count
), tmp
);
5109 pidgin_conv_chat_update_user(PurpleChatUser
*chatuser
)
5111 PurpleChatConversation
*chat
;
5112 PidginConversation
*gtkconv
;
5113 PidginChatPane
*gtkchat
;
5115 GtkTreeModel
*model
;
5120 chat
= purple_chat_user_get_chat(chatuser
);
5121 gtkconv
= PIDGIN_CONVERSATION(PURPLE_CONVERSATION(chat
));
5122 gtkchat
= gtkconv
->u
.chat
;
5124 model
= gtk_tree_view_get_model(GTK_TREE_VIEW(gtkchat
->list
));
5126 if (!gtk_tree_model_get_iter_first(GTK_TREE_MODEL(model
), &iter
))
5129 if (get_iter_from_chatuser(chatuser
, &iter
)) {
5130 GtkTreeRowReference
*ref
= purple_chat_user_get_ui_data(chatuser
);
5131 gtk_list_store_remove(GTK_LIST_STORE(model
), &iter
);
5132 gtk_tree_row_reference_free(ref
);
5133 purple_chat_user_set_ui_data(chatuser
, NULL
);
5137 add_chat_user_common(chat
, chatuser
, NULL
);
5141 pidgin_conv_has_focus(PurpleConversation
*conv
)
5143 PidginConversation
*gtkconv
= PIDGIN_CONVERSATION(conv
);
5144 PidginConvWindow
*win
;
5149 g_object_get(G_OBJECT(win
->window
), "has-toplevel-focus", &has_focus
, NULL
);
5151 if (has_focus
&& pidgin_conv_window_is_active_conversation(conv
))
5158 * Makes sure all the menu items and all the buttons are hidden/shown and
5159 * sensitive/insensitive. This is called after changing tabs and when an
5160 * account signs on or off.
5163 gray_stuff_out(PidginConversation
*gtkconv
)
5165 PidginConvWindow
*win
;
5166 PurpleConversation
*conv
= gtkconv
->active_conv
;
5167 PurpleConnection
*gc
;
5168 PurpleProtocol
*protocol
= NULL
;
5169 GdkPixbuf
*window_icon
= NULL
;
5170 // PidginWebViewButtons buttons;
5171 PurpleAccount
*account
;
5173 win
= pidgin_conv_get_window(gtkconv
);
5174 gc
= purple_conversation_get_connection(conv
);
5175 account
= purple_conversation_get_account(conv
);
5178 protocol
= purple_connection_get_protocol(gc
);
5180 if (win
->menu
->send_to
!= NULL
)
5181 update_send_to_selection(win
);
5184 * Handle hiding and showing stuff based on what type of conv this is.
5185 * Stuff that Purple IMs support in general should be shown for IM
5186 * conversations. Stuff that Purple chats support in general should be
5187 * shown for chat conversations. It doesn't matter whether the protocol
5188 * supports it or not--that only affects if the button or menu item
5189 * is sensitive or not.
5191 if (PURPLE_IS_IM_CONVERSATION(conv
)) {
5192 /* Show stuff that applies to IMs, hide stuff that applies to chats */
5194 /* Deal with menu items */
5195 gtk_action_set_visible(win
->menu
->view_log
, TRUE
);
5196 gtk_action_set_visible(win
->menu
->send_file
, TRUE
);
5197 gtk_action_set_visible(win
->menu
->get_attention
, TRUE
);
5198 gtk_action_set_visible(win
->menu
->add_pounce
, TRUE
);
5199 gtk_action_set_visible(win
->menu
->get_info
, TRUE
);
5200 gtk_action_set_visible(win
->menu
->invite
, FALSE
);
5201 gtk_action_set_visible(win
->menu
->alias
, TRUE
);
5202 if (purple_account_privacy_check(account
, purple_conversation_get_name(conv
))) {
5203 gtk_action_set_visible(win
->menu
->unblock
, FALSE
);
5204 gtk_action_set_visible(win
->menu
->block
, TRUE
);
5206 gtk_action_set_visible(win
->menu
->block
, FALSE
);
5207 gtk_action_set_visible(win
->menu
->unblock
, TRUE
);
5210 if (purple_blist_find_buddy(account
, purple_conversation_get_name(conv
)) == NULL
) {
5211 gtk_action_set_visible(win
->menu
->add
, TRUE
);
5212 gtk_action_set_visible(win
->menu
->remove
, FALSE
);
5214 gtk_action_set_visible(win
->menu
->remove
, TRUE
);
5215 gtk_action_set_visible(win
->menu
->add
, FALSE
);
5218 gtk_action_set_visible(win
->menu
->insert_link
, TRUE
);
5219 gtk_action_set_visible(win
->menu
->insert_image
, TRUE
);
5220 } else if (PURPLE_IS_CHAT_CONVERSATION(conv
)) {
5221 /* Show stuff that applies to Chats, hide stuff that applies to IMs */
5223 /* Deal with menu items */
5224 gtk_action_set_visible(win
->menu
->view_log
, TRUE
);
5225 gtk_action_set_visible(win
->menu
->send_file
, FALSE
);
5226 gtk_action_set_visible(win
->menu
->get_attention
, FALSE
);
5227 gtk_action_set_visible(win
->menu
->add_pounce
, FALSE
);
5228 gtk_action_set_visible(win
->menu
->get_info
, FALSE
);
5229 gtk_action_set_visible(win
->menu
->invite
, TRUE
);
5230 gtk_action_set_visible(win
->menu
->alias
, TRUE
);
5231 gtk_action_set_visible(win
->menu
->block
, FALSE
);
5232 gtk_action_set_visible(win
->menu
->unblock
, FALSE
);
5234 if ((account
== NULL
) || purple_blist_find_chat(account
, purple_conversation_get_name(conv
)) == NULL
) {
5235 /* If the chat is NOT in the buddy list */
5236 gtk_action_set_visible(win
->menu
->add
, TRUE
);
5237 gtk_action_set_visible(win
->menu
->remove
, FALSE
);
5239 /* If the chat IS in the buddy list */
5240 gtk_action_set_visible(win
->menu
->add
, FALSE
);
5241 gtk_action_set_visible(win
->menu
->remove
, TRUE
);
5244 gtk_action_set_visible(win
->menu
->insert_link
, TRUE
);
5245 gtk_action_set_visible(win
->menu
->insert_image
, TRUE
);
5249 * Handle graying stuff out based on whether an account is connected
5250 * and what features that account supports.
5253 (!PURPLE_IS_CHAT_CONVERSATION(conv
) ||
5254 !purple_chat_conversation_has_left(PURPLE_CHAT_CONVERSATION(conv
)) ))
5256 PurpleConnectionFlags features
= purple_conversation_get_features(conv
);
5257 /* Account is online */
5258 /* Deal with the toolbar */
5260 if (features
& PURPLE_CONNECTION_FLAG_HTML
)
5262 buttons
= PIDGIN_WEBVIEW_ALL
; /* Everything on */
5263 if (features
& PURPLE_CONNECTION_FLAG_NO_BGCOLOR
)
5264 buttons
&= ~PIDGIN_WEBVIEW_BACKCOLOR
;
5265 if (features
& PURPLE_CONNECTION_FLAG_NO_FONTSIZE
)
5267 buttons
&= ~PIDGIN_WEBVIEW_GROW
;
5268 buttons
&= ~PIDGIN_WEBVIEW_SHRINK
;
5270 if (features
& PURPLE_CONNECTION_FLAG_NO_URLDESC
)
5271 buttons
&= ~PIDGIN_WEBVIEW_LINKDESC
5273 buttons
= PIDGIN_WEBVIEW_SMILEY
| PIDGIN_WEBVIEW_IMAGE
;
5276 if (features
& PURPLE_CONNECTION_FLAG_NO_IMAGES
)
5277 buttons
&= ~PIDGIN_WEBVIEW_IMAGE
;
5279 if (features
& PURPLE_CONNECTION_FLAG_ALLOW_CUSTOM_SMILEY
)
5280 buttons
|= PIDGIN_WEBVIEW_CUSTOM_SMILEY
;
5282 buttons
&= ~PIDGIN_WEBVIEW_CUSTOM_SMILEY
;
5284 pidgin_webview_set_format_functions(PIDGIN_WEBVIEW(gtkconv
->entry
), buttons
);
5287 /* Deal with menu items */
5288 gtk_action_set_sensitive(win
->menu
->view_log
, TRUE
);
5289 gtk_action_set_sensitive(win
->menu
->add_pounce
, TRUE
);
5290 gtk_action_set_sensitive(win
->menu
->get_info
, (PURPLE_PROTOCOL_IMPLEMENTS(protocol
, SERVER
, get_info
)));
5291 gtk_action_set_sensitive(win
->menu
->invite
, (PURPLE_PROTOCOL_IMPLEMENTS(protocol
, CHAT
, invite
)));
5292 gtk_action_set_sensitive(win
->menu
->insert_link
, (features
& PURPLE_CONNECTION_FLAG_HTML
));
5293 gtk_action_set_sensitive(win
->menu
->insert_image
, !(features
& PURPLE_CONNECTION_FLAG_NO_IMAGES
));
5295 if (PURPLE_IS_IM_CONVERSATION(conv
))
5297 gboolean can_send_file
= FALSE
;
5298 const gchar
*name
= purple_conversation_get_name(conv
);
5300 if (PURPLE_IS_PROTOCOL_XFER(protocol
) &&
5301 purple_protocol_xfer_can_receive(PURPLE_PROTOCOL_XFER(protocol
), gc
, name
)
5303 can_send_file
= TRUE
;
5306 gtk_action_set_sensitive(win
->menu
->add
, (PURPLE_PROTOCOL_IMPLEMENTS(protocol
, SERVER
, add_buddy
)));
5307 gtk_action_set_sensitive(win
->menu
->remove
, (PURPLE_PROTOCOL_IMPLEMENTS(protocol
, SERVER
, remove_buddy
)));
5308 gtk_action_set_sensitive(win
->menu
->send_file
, can_send_file
);
5309 gtk_action_set_sensitive(win
->menu
->get_attention
, (PURPLE_IS_PROTOCOL_ATTENTION(protocol
)));
5310 gtk_action_set_sensitive(win
->menu
->alias
,
5311 (account
!= NULL
) &&
5312 (purple_blist_find_buddy(account
, purple_conversation_get_name(conv
)) != NULL
));
5314 else if (PURPLE_IS_CHAT_CONVERSATION(conv
))
5316 gtk_action_set_sensitive(win
->menu
->add
, (PURPLE_PROTOCOL_IMPLEMENTS(protocol
, CHAT
, join
)));
5317 gtk_action_set_sensitive(win
->menu
->remove
, (PURPLE_PROTOCOL_IMPLEMENTS(protocol
, CHAT
, join
)));
5318 gtk_action_set_sensitive(win
->menu
->alias
,
5319 (account
!= NULL
) &&
5320 (purple_blist_find_chat(account
, purple_conversation_get_name(conv
)) != NULL
));
5324 /* Account is offline */
5325 /* Or it's a chat that we've left. */
5327 /* Then deal with menu items */
5328 gtk_action_set_sensitive(win
->menu
->view_log
, TRUE
);
5329 gtk_action_set_sensitive(win
->menu
->send_file
, FALSE
);
5330 gtk_action_set_sensitive(win
->menu
->get_attention
, FALSE
);
5331 gtk_action_set_sensitive(win
->menu
->add_pounce
, TRUE
);
5332 gtk_action_set_sensitive(win
->menu
->get_info
, FALSE
);
5333 gtk_action_set_sensitive(win
->menu
->invite
, FALSE
);
5334 gtk_action_set_sensitive(win
->menu
->alias
, FALSE
);
5335 gtk_action_set_sensitive(win
->menu
->add
, FALSE
);
5336 gtk_action_set_sensitive(win
->menu
->remove
, FALSE
);
5337 gtk_action_set_sensitive(win
->menu
->insert_link
, TRUE
);
5338 gtk_action_set_sensitive(win
->menu
->insert_image
, FALSE
);
5342 * Update the window's icon
5344 if (pidgin_conv_window_is_active_conversation(conv
))
5347 if (PURPLE_IS_IM_CONVERSATION(conv
) &&
5348 (gtkconv
->u
.im
->anim
))
5350 PurpleBuddy
*buddy
= purple_blist_find_buddy(purple_conversation_get_account(conv
), purple_conversation_get_name(conv
));
5352 gdk_pixbuf_animation_get_static_image(gtkconv
->u
.im
->anim
);
5354 if (buddy
&& !PURPLE_BUDDY_IS_ONLINE(buddy
))
5355 gdk_pixbuf_saturate_and_pixelate(window_icon
, window_icon
, 0.0, FALSE
);
5357 g_object_ref(window_icon
);
5358 l
= g_list_append(l
, window_icon
);
5360 l
= pidgin_conv_get_tab_icons(conv
);
5362 gtk_window_set_icon_list(GTK_WINDOW(win
->window
), l
);
5363 if (window_icon
!= NULL
) {
5364 g_object_unref(G_OBJECT(window_icon
));
5371 pidgin_conv_update_fields(PurpleConversation
*conv
, PidginConvFields fields
)
5373 PidginConversation
*gtkconv
;
5374 PidginConvWindow
*win
;
5376 gtkconv
= PIDGIN_CONVERSATION(conv
);
5379 win
= pidgin_conv_get_window(gtkconv
);
5383 if (fields
& PIDGIN_CONV_SET_TITLE
)
5385 purple_conversation_autoset_title(conv
);
5388 if (fields
& PIDGIN_CONV_BUDDY_ICON
)
5390 if (PURPLE_IS_IM_CONVERSATION(conv
))
5391 pidgin_conv_update_buddy_icon(PURPLE_IM_CONVERSATION(conv
));
5394 if (fields
& PIDGIN_CONV_MENU
)
5396 gray_stuff_out(PIDGIN_CONVERSATION(conv
));
5397 generate_send_to_items(win
);
5398 regenerate_plugins_items(win
);
5401 if (fields
& PIDGIN_CONV_E2EE
)
5402 generate_e2ee_controls(win
);
5404 if (fields
& PIDGIN_CONV_TAB_ICON
)
5406 update_tab_icon(conv
);
5407 generate_send_to_items(win
); /* To update the icons in SendTo menu */
5410 if ((fields
& PIDGIN_CONV_TOPIC
) &&
5411 PURPLE_IS_CHAT_CONVERSATION(conv
))
5414 PidginChatPane
*gtkchat
= gtkconv
->u
.chat
;
5416 if (gtkchat
->topic_text
!= NULL
)
5418 topic
= purple_chat_conversation_get_topic(PURPLE_CHAT_CONVERSATION(conv
));
5420 gtk_entry_set_text(GTK_ENTRY(gtkchat
->topic_text
), topic
? topic
: "");
5421 gtk_widget_set_tooltip_text(gtkchat
->topic_text
,
5422 topic
? topic
: "");
5426 if ((fields
& PIDGIN_CONV_COLORIZE_TITLE
) ||
5427 (fields
& PIDGIN_CONV_SET_TITLE
) ||
5428 (fields
& PIDGIN_CONV_TOPIC
))
5431 PurpleIMConversation
*im
= NULL
;
5432 PurpleAccount
*account
= purple_conversation_get_account(conv
);
5433 PurpleBuddy
*buddy
= NULL
;
5434 char *markup
= NULL
;
5435 AtkObject
*accessibility_obj
;
5436 /* I think this is a little longer than it needs to be but I'm lazy. */
5439 if (PURPLE_IS_IM_CONVERSATION(conv
))
5440 im
= PURPLE_IM_CONVERSATION(conv
);
5442 if ((account
== NULL
) ||
5443 !purple_account_is_connected(account
) ||
5444 (PURPLE_IS_CHAT_CONVERSATION(conv
)
5445 && purple_chat_conversation_has_left(PURPLE_CHAT_CONVERSATION(conv
))))
5446 title
= g_strdup_printf("(%s)", purple_conversation_get_title(conv
));
5448 title
= g_strdup(purple_conversation_get_title(conv
));
5450 if (PURPLE_IS_IM_CONVERSATION(conv
)) {
5451 buddy
= purple_blist_find_buddy(account
, purple_conversation_get_name(conv
));
5453 markup
= pidgin_blist_get_name_markup(buddy
, FALSE
, FALSE
);
5457 } else if (PURPLE_IS_CHAT_CONVERSATION(conv
)) {
5458 const char *topic
= gtkconv
->u
.chat
->topic_text
5459 ? gtk_entry_get_text(GTK_ENTRY(gtkconv
->u
.chat
->topic_text
))
5461 const char *title
= purple_conversation_get_title(conv
);
5462 const char *name
= purple_conversation_get_name(conv
);
5464 char *topic_esc
, *unaliased
, *unaliased_esc
, *title_esc
;
5466 topic_esc
= topic
? g_markup_escape_text(topic
, -1) : NULL
;
5467 unaliased
= g_utf8_collate(title
, name
) ? g_strdup_printf("(%s)", name
) : NULL
;
5468 unaliased_esc
= unaliased
? g_markup_escape_text(unaliased
, -1) : NULL
;
5469 title_esc
= g_markup_escape_text(title
, -1);
5471 markup
= g_strdup_printf("%s%s<span size='smaller'>%s</span>%s<span color='%s' size='smaller'>%s</span>",
5473 unaliased_esc
? " " : "",
5474 unaliased_esc
? unaliased_esc
: "",
5475 topic_esc
&& *topic_esc
? "\n" : "",
5476 pidgin_get_dim_grey_string(gtkconv
->infopane
),
5477 topic_esc
? topic_esc
: "");
5482 g_free(unaliased_esc
);
5484 gtk_list_store_set(gtkconv
->infopane_model
, &(gtkconv
->infopane_iter
),
5485 CONV_TEXT_COLUMN
, markup
, -1);
5486 /* XXX seanegan Why do I have to do this? */
5487 gtk_widget_queue_draw(gtkconv
->infopane
);
5489 if (title
!= markup
)
5492 if (!gtk_widget_get_realized(gtkconv
->tab_label
))
5493 gtk_widget_realize(gtkconv
->tab_label
);
5495 accessibility_obj
= gtk_widget_get_accessible(gtkconv
->tab_cont
);
5497 purple_im_conversation_get_typing_state(im
) == PURPLE_IM_TYPING
) {
5498 atk_object_set_description(accessibility_obj
, _("Typing"));
5499 style
= "tab-label-typing";
5500 } else if (im
!= NULL
&&
5501 purple_im_conversation_get_typing_state(im
) == PURPLE_IM_TYPED
) {
5502 atk_object_set_description(accessibility_obj
, _("Stopped Typing"));
5503 style
= "tab-label-typed";
5504 } else if (gtkconv
->unseen_state
== PIDGIN_UNSEEN_NICK
) {
5505 atk_object_set_description(accessibility_obj
, _("Nick Said"));
5506 style
= "tab-label-attention";
5507 } else if (gtkconv
->unseen_state
== PIDGIN_UNSEEN_TEXT
) {
5508 atk_object_set_description(accessibility_obj
, _("Unread Messages"));
5509 if (PURPLE_IS_CHAT_CONVERSATION(gtkconv
->active_conv
))
5510 style
= "tab-label-unreadchat";
5512 style
= "tab-label-attention";
5513 } else if (gtkconv
->unseen_state
== PIDGIN_UNSEEN_EVENT
) {
5514 atk_object_set_description(accessibility_obj
, _("New Event"));
5515 style
= "tab-label-event";
5517 style
= "tab-label";
5520 gtk_widget_set_name(gtkconv
->tab_label
, style
);
5521 gtk_label_set_text(GTK_LABEL(gtkconv
->tab_label
), title
);
5522 gtk_widget_set_state_flags(gtkconv
->tab_label
, GTK_STATE_FLAG_ACTIVE
, TRUE
);
5524 if (gtkconv
->unseen_state
== PIDGIN_UNSEEN_TEXT
||
5525 gtkconv
->unseen_state
== PIDGIN_UNSEEN_NICK
||
5526 gtkconv
->unseen_state
== PIDGIN_UNSEEN_EVENT
) {
5527 PangoAttrList
*list
= pango_attr_list_new();
5528 PangoAttribute
*attr
= pango_attr_weight_new(PANGO_WEIGHT_BOLD
);
5529 attr
->start_index
= 0;
5530 attr
->end_index
= -1;
5531 pango_attr_list_insert(list
, attr
);
5532 gtk_label_set_attributes(GTK_LABEL(gtkconv
->tab_label
), list
);
5533 pango_attr_list_unref(list
);
5535 gtk_label_set_attributes(GTK_LABEL(gtkconv
->tab_label
), NULL
);
5537 if (pidgin_conv_window_is_active_conversation(conv
))
5538 update_typing_icon(gtkconv
);
5540 gtk_label_set_text(GTK_LABEL(gtkconv
->menu_label
), title
);
5541 if (pidgin_conv_window_is_active_conversation(conv
)) {
5542 const char* current_title
= gtk_window_get_title(GTK_WINDOW(win
->window
));
5543 if (current_title
== NULL
|| !purple_strequal(current_title
, title
))
5544 gtk_window_set_title(GTK_WINDOW(win
->window
), title
);
5552 pidgin_conv_updated(PurpleConversation
*conv
, PurpleConversationUpdateType type
)
5554 PidginConvFields flags
= 0;
5556 g_return_if_fail(conv
!= NULL
);
5558 if (type
== PURPLE_CONVERSATION_UPDATE_ACCOUNT
)
5560 flags
= PIDGIN_CONV_ALL
;
5562 else if (type
== PURPLE_CONVERSATION_UPDATE_TYPING
||
5563 type
== PURPLE_CONVERSATION_UPDATE_UNSEEN
||
5564 type
== PURPLE_CONVERSATION_UPDATE_TITLE
)
5566 flags
= PIDGIN_CONV_COLORIZE_TITLE
;
5568 else if (type
== PURPLE_CONVERSATION_UPDATE_TOPIC
)
5570 flags
= PIDGIN_CONV_TOPIC
;
5572 else if (type
== PURPLE_CONVERSATION_ACCOUNT_ONLINE
||
5573 type
== PURPLE_CONVERSATION_ACCOUNT_OFFLINE
)
5575 flags
= PIDGIN_CONV_MENU
| PIDGIN_CONV_TAB_ICON
| PIDGIN_CONV_SET_TITLE
;
5577 else if (type
== PURPLE_CONVERSATION_UPDATE_AWAY
)
5579 flags
= PIDGIN_CONV_TAB_ICON
;
5581 else if (type
== PURPLE_CONVERSATION_UPDATE_ADD
||
5582 type
== PURPLE_CONVERSATION_UPDATE_REMOVE
||
5583 type
== PURPLE_CONVERSATION_UPDATE_CHATLEFT
)
5585 flags
= PIDGIN_CONV_SET_TITLE
| PIDGIN_CONV_MENU
;
5587 else if (type
== PURPLE_CONVERSATION_UPDATE_ICON
)
5589 flags
= PIDGIN_CONV_BUDDY_ICON
;
5591 else if (type
== PURPLE_CONVERSATION_UPDATE_FEATURES
)
5593 flags
= PIDGIN_CONV_MENU
;
5595 else if (type
== PURPLE_CONVERSATION_UPDATE_E2EE
)
5597 flags
= PIDGIN_CONV_E2EE
| PIDGIN_CONV_MENU
;
5600 pidgin_conv_update_fields(conv
, flags
);
5604 wrote_msg_update_unseen_cb(PurpleConversation
*conv
, PurpleMessage
*msg
,
5607 PidginConversation
*gtkconv
= conv
? PIDGIN_CONVERSATION(conv
) : NULL
;
5608 PurpleMessageFlags flags
;
5609 if (conv
== NULL
|| (gtkconv
&& gtkconv
->win
!= hidden_convwin
))
5611 flags
= purple_message_get_flags(msg
);
5612 if (flags
& (PURPLE_MESSAGE_SEND
| PURPLE_MESSAGE_RECV
)) {
5613 PidginUnseenState unseen
= PIDGIN_UNSEEN_NONE
;
5615 if ((flags
& PURPLE_MESSAGE_NICK
) == PURPLE_MESSAGE_NICK
)
5616 unseen
= PIDGIN_UNSEEN_NICK
;
5617 else if (((flags
& PURPLE_MESSAGE_SYSTEM
) == PURPLE_MESSAGE_SYSTEM
) ||
5618 ((flags
& PURPLE_MESSAGE_ERROR
) == PURPLE_MESSAGE_ERROR
))
5619 unseen
= PIDGIN_UNSEEN_EVENT
;
5620 else if ((flags
& PURPLE_MESSAGE_NO_LOG
) == PURPLE_MESSAGE_NO_LOG
)
5621 unseen
= PIDGIN_UNSEEN_NO_LOG
;
5623 unseen
= PIDGIN_UNSEEN_TEXT
;
5625 conv_set_unseen(conv
, unseen
);
5629 static PurpleConversationUiOps conversation_ui_ops
=
5632 pidgin_conv_destroy
, /* destroy_conversation */
5633 NULL
, /* write_chat */
5634 NULL
, /* write_im */
5635 pidgin_conv_write_conv
, /* write_conv */
5636 pidgin_conv_chat_add_users
, /* chat_add_users */
5637 pidgin_conv_chat_rename_user
, /* chat_rename_user */
5638 pidgin_conv_chat_remove_users
, /* chat_remove_users */
5639 pidgin_conv_chat_update_user
, /* chat_update_user */
5640 pidgin_conv_present_conversation
, /* present */
5641 pidgin_conv_has_focus
, /* has_focus */
5642 NULL
, /* send_confirm */
5649 PurpleConversationUiOps
*
5650 pidgin_conversations_get_conv_ui_ops(void)
5652 return &conversation_ui_ops
;
5655 /**************************************************************************
5656 * Public conversation utility functions
5657 **************************************************************************/
5659 pidgin_conv_update_buddy_icon(PurpleIMConversation
*im
)
5661 PidginConversation
*gtkconv
;
5662 PurpleConversation
*conv
;
5663 PidginConvWindow
*win
;
5667 PurpleImage
*custom_img
= NULL
;
5668 gconstpointer data
= NULL
;
5676 int scale_width
, scale_height
;
5679 PurpleAccount
*account
;
5681 PurpleBuddyIcon
*icon
;
5683 conv
= PURPLE_CONVERSATION(im
);
5685 g_return_if_fail(conv
!= NULL
);
5686 g_return_if_fail(PIDGIN_IS_PIDGIN_CONVERSATION(conv
));
5688 gtkconv
= PIDGIN_CONVERSATION(conv
);
5690 if (conv
!= gtkconv
->active_conv
)
5693 if (!gtkconv
->u
.im
->show_icon
)
5696 account
= purple_conversation_get_account(conv
);
5698 /* Remove the current icon stuff */
5699 children
= gtk_container_get_children(GTK_CONTAINER(gtkconv
->u
.im
->icon_container
));
5701 /* We know there's only one child here. It'd be nice to shortcut to the
5702 event box, but we can't change the PidginConversation until 3.0 */
5703 event
= (GtkWidget
*)children
->data
;
5704 gtk_container_remove(GTK_CONTAINER(gtkconv
->u
.im
->icon_container
), event
);
5705 g_list_free(children
);
5708 if (gtkconv
->u
.im
->anim
!= NULL
)
5709 g_object_unref(G_OBJECT(gtkconv
->u
.im
->anim
));
5711 gtkconv
->u
.im
->anim
= NULL
;
5713 if (gtkconv
->u
.im
->icon_timer
!= 0)
5714 g_source_remove(gtkconv
->u
.im
->icon_timer
);
5716 gtkconv
->u
.im
->icon_timer
= 0;
5718 if (gtkconv
->u
.im
->iter
!= NULL
)
5719 g_object_unref(G_OBJECT(gtkconv
->u
.im
->iter
));
5721 gtkconv
->u
.im
->iter
= NULL
;
5723 if (!purple_prefs_get_bool(PIDGIN_PREFS_ROOT
"/conversations/im/show_buddy_icons"))
5726 if (purple_conversation_get_connection(conv
) == NULL
)
5729 buddy
= purple_blist_find_buddy(account
, purple_conversation_get_name(conv
));
5732 PurpleContact
*contact
= purple_buddy_get_contact(buddy
);
5734 custom_img
= purple_buddy_icons_node_find_custom_icon((PurpleBlistNode
*)contact
);
5736 /* There is a custom icon for this user */
5737 data
= purple_image_get_data(custom_img
);
5738 len
= purple_image_get_data_size(custom_img
);
5744 icon
= purple_im_conversation_get_icon(im
);
5747 gtk_widget_set_size_request(gtkconv
->u
.im
->icon_container
,
5748 -1, BUDDYICON_SIZE_MIN
);
5752 data
= purple_buddy_icon_get_data(icon
, &len
);
5755 gtk_widget_set_size_request(gtkconv
->u
.im
->icon_container
,
5756 -1, BUDDYICON_SIZE_MIN
);
5761 gtkconv
->u
.im
->anim
= pidgin_pixbuf_anim_from_data(data
, len
);
5763 g_object_unref(custom_img
);
5765 if (!gtkconv
->u
.im
->anim
) {
5766 purple_debug_error("gtkconv", "Couldn't load icon for conv %s\n",
5767 purple_conversation_get_name(conv
));
5771 if (gdk_pixbuf_animation_is_static_image(gtkconv
->u
.im
->anim
)) {
5773 gtkconv
->u
.im
->iter
= NULL
;
5774 stat
= gdk_pixbuf_animation_get_static_image(gtkconv
->u
.im
->anim
);
5775 buf
= gdk_pixbuf_add_alpha(stat
, FALSE
, 0, 0, 0);
5778 gtkconv
->u
.im
->iter
=
5779 gdk_pixbuf_animation_get_iter(gtkconv
->u
.im
->anim
, NULL
); /* LEAK */
5780 stat
= gdk_pixbuf_animation_iter_get_pixbuf(gtkconv
->u
.im
->iter
);
5781 buf
= gdk_pixbuf_add_alpha(stat
, FALSE
, 0, 0, 0);
5782 if (gtkconv
->u
.im
->animate
)
5783 start_anim(NULL
, gtkconv
);
5786 scale_width
= gdk_pixbuf_get_width(buf
);
5787 scale_height
= gdk_pixbuf_get_height(buf
);
5789 gtk_widget_get_size_request(gtkconv
->u
.im
->icon_container
, NULL
, &size
);
5790 size
= MIN(size
, MIN(scale_width
, scale_height
));
5792 /* Some sanity checks */
5793 size
= CLAMP(size
, BUDDYICON_SIZE_MIN
, BUDDYICON_SIZE_MAX
);
5794 if (scale_width
== scale_height
) {
5795 scale_width
= scale_height
= size
;
5796 } else if (scale_height
> scale_width
) {
5797 scale_width
= size
* scale_width
/ scale_height
;
5798 scale_height
= size
;
5800 scale_height
= size
* scale_height
/ scale_width
;
5803 scale
= gdk_pixbuf_scale_simple(buf
, scale_width
, scale_height
,
5804 GDK_INTERP_BILINEAR
);
5805 g_object_unref(buf
);
5806 if (pidgin_gdk_pixbuf_is_opaque(scale
))
5807 pidgin_gdk_pixbuf_make_round(scale
);
5809 event
= gtk_event_box_new();
5810 gtk_container_add(GTK_CONTAINER(gtkconv
->u
.im
->icon_container
), event
);
5811 gtk_event_box_set_visible_window(GTK_EVENT_BOX(event
), FALSE
);
5812 gtk_widget_add_events(event
,
5813 GDK_POINTER_MOTION_MASK
| GDK_LEAVE_NOTIFY_MASK
);
5814 g_signal_connect(G_OBJECT(event
), "button-press-event",
5815 G_CALLBACK(icon_menu
), gtkconv
);
5817 pidgin_tooltip_setup_for_widget(event
, gtkconv
, pidgin_conv_create_tooltip
, NULL
);
5818 gtk_widget_show(event
);
5820 gtkconv
->u
.im
->icon
= gtk_image_new_from_pixbuf(scale
);
5821 gtk_container_add(GTK_CONTAINER(event
), gtkconv
->u
.im
->icon
);
5822 gtk_widget_show(gtkconv
->u
.im
->icon
);
5824 g_object_unref(G_OBJECT(scale
));
5826 /* The buddy icon code needs badly to be fixed. */
5827 if(pidgin_conv_window_is_active_conversation(conv
))
5829 buf
= gdk_pixbuf_animation_get_static_image(gtkconv
->u
.im
->anim
);
5830 if (buddy
&& !PURPLE_BUDDY_IS_ONLINE(buddy
))
5831 gdk_pixbuf_saturate_and_pixelate(buf
, buf
, 0.0, FALSE
);
5832 gtk_window_set_icon(GTK_WINDOW(win
->window
), buf
);
5837 pidgin_conv_update_buttons_by_protocol(PurpleConversation
*conv
)
5839 PidginConvWindow
*win
;
5841 if (!PIDGIN_IS_PIDGIN_CONVERSATION(conv
))
5844 win
= PIDGIN_CONVERSATION(conv
)->win
;
5846 if (win
!= NULL
&& pidgin_conv_window_is_active_conversation(conv
))
5847 gray_stuff_out(PIDGIN_CONVERSATION(conv
));
5851 pidgin_conv_xy_to_right_infopane(PidginConvWindow
*win
, int x
, int y
)
5853 gint pane_x
, pane_y
, x_rel
;
5854 PidginConversation
*gtkconv
;
5855 GtkAllocation allocation
;
5857 gdk_window_get_origin(gtk_widget_get_window(win
->notebook
),
5860 gtkconv
= pidgin_conv_window_get_active_gtkconv(win
);
5861 gtk_widget_get_allocation(gtkconv
->infopane
, &allocation
);
5862 return (x_rel
> allocation
.x
+ allocation
.width
/ 2);
5866 pidgin_conv_get_tab_at_xy(PidginConvWindow
*win
, int x
, int y
, gboolean
*to_right
)
5868 gint nb_x
, nb_y
, x_rel
, y_rel
;
5869 GtkNotebook
*notebook
;
5870 GtkWidget
*page
, *tab
;
5871 gint i
, page_num
= -1;
5878 notebook
= GTK_NOTEBOOK(win
->notebook
);
5880 gdk_window_get_origin(gtk_widget_get_window(win
->notebook
), &nb_x
, &nb_y
);
5884 horiz
= (gtk_notebook_get_tab_pos(notebook
) == GTK_POS_TOP
||
5885 gtk_notebook_get_tab_pos(notebook
) == GTK_POS_BOTTOM
);
5887 count
= gtk_notebook_get_n_pages(GTK_NOTEBOOK(notebook
));
5889 for (i
= 0; i
< count
; i
++) {
5890 GtkAllocation allocation
;
5892 page
= gtk_notebook_get_nth_page(GTK_NOTEBOOK(notebook
), i
);
5893 tab
= gtk_notebook_get_tab_label(GTK_NOTEBOOK(notebook
), page
);
5894 gtk_widget_get_allocation(tab
, &allocation
);
5896 /* Make sure the tab is not hidden beyond an arrow */
5897 if (!gtk_widget_is_drawable(tab
) && gtk_notebook_get_show_tabs(notebook
))
5901 if (x_rel
>= allocation
.x
- PIDGIN_HIG_BOX_SPACE
&&
5902 x_rel
<= allocation
.x
+ allocation
.width
+ PIDGIN_HIG_BOX_SPACE
) {
5905 if (to_right
&& x_rel
>= allocation
.x
+ allocation
.width
/2)
5911 if (y_rel
>= allocation
.y
- PIDGIN_HIG_BOX_SPACE
&&
5912 y_rel
<= allocation
.y
+ allocation
.height
+ PIDGIN_HIG_BOX_SPACE
) {
5915 if (to_right
&& y_rel
>= allocation
.y
+ allocation
.height
/2)
5923 if (page_num
== -1) {
5924 /* Add after the last tab */
5925 page_num
= count
- 1;
5932 close_on_tabs_pref_cb(const char *name
, PurplePrefType type
,
5933 gconstpointer value
, gpointer data
)
5936 PurpleConversation
*conv
;
5937 PidginConversation
*gtkconv
;
5939 for (l
= purple_conversations_get_all(); l
!= NULL
; l
= l
->next
) {
5940 conv
= (PurpleConversation
*)l
->data
;
5942 if (!PIDGIN_IS_PIDGIN_CONVERSATION(conv
))
5945 gtkconv
= PIDGIN_CONVERSATION(conv
);
5948 gtk_widget_show(gtkconv
->close
);
5950 gtk_widget_hide(gtkconv
->close
);
5955 spellcheck_pref_cb(const char *name
, PurplePrefType type
,
5956 gconstpointer value
, gpointer data
)
5959 PurpleConversation
*conv
;
5960 PidginConversation
*gtkconv
;
5962 for (cl
= purple_conversations_get_all(); cl
!= NULL
; cl
= cl
->next
) {
5964 conv
= (PurpleConversation
*)cl
->data
;
5966 if (!PIDGIN_IS_PIDGIN_CONVERSATION(conv
))
5969 gtkconv
= PIDGIN_CONVERSATION(conv
);
5971 # warning toggle spell checking when talkatu #60 is done.
5976 tab_side_pref_cb(const char *name
, PurplePrefType type
,
5977 gconstpointer value
, gpointer data
)
5979 GList
*gtkwins
, *gtkconvs
;
5980 GtkPositionType pos
;
5981 PidginConvWindow
*gtkwin
;
5983 pos
= GPOINTER_TO_INT(value
);
5985 for (gtkwins
= pidgin_conv_windows_get_list(); gtkwins
!= NULL
; gtkwins
= gtkwins
->next
) {
5986 gtkwin
= gtkwins
->data
;
5987 gtk_notebook_set_tab_pos(GTK_NOTEBOOK(gtkwin
->notebook
), pos
&~8);
5988 for (gtkconvs
= gtkwin
->gtkconvs
; gtkconvs
!= NULL
; gtkconvs
= gtkconvs
->next
) {
5989 pidgin_conv_tab_pack(gtkwin
, gtkconvs
->data
);
5995 show_formatting_toolbar_pref_cb(const char *name
, PurplePrefType type
,
5996 gconstpointer value
, gpointer data
)
5999 PurpleConversation
*conv
;
6000 PidginConversation
*gtkconv
;
6001 PidginConvWindow
*win
;
6002 gboolean visible
= (gboolean
)GPOINTER_TO_INT(value
);
6004 for (l
= purple_conversations_get_all(); l
!= NULL
; l
= l
->next
)
6006 conv
= (PurpleConversation
*)l
->data
;
6008 if (!PIDGIN_IS_PIDGIN_CONVERSATION(conv
))
6011 gtkconv
= PIDGIN_CONVERSATION(conv
);
6014 gtk_toggle_action_set_active(
6015 GTK_TOGGLE_ACTION(win
->menu
->show_formatting_toolbar
),
6019 talkatu_editor_set_toolbar_visible(TALKATU_EDITOR(gtkconv
->editor
), visible
);
6024 animate_buddy_icons_pref_cb(const char *name
, PurplePrefType type
,
6025 gconstpointer value
, gpointer data
)
6028 PurpleConversation
*conv
;
6029 PidginConversation
*gtkconv
;
6030 PidginConvWindow
*win
;
6032 if (!purple_prefs_get_bool(PIDGIN_PREFS_ROOT
"/conversations/im/show_buddy_icons"))
6035 /* Set the "animate" flag for each icon based on the new preference */
6036 for (l
= purple_conversations_get_ims(); l
!= NULL
; l
= l
->next
) {
6037 conv
= (PurpleConversation
*)l
->data
;
6038 gtkconv
= PIDGIN_CONVERSATION(conv
);
6040 gtkconv
->u
.im
->animate
= GPOINTER_TO_INT(value
);
6043 /* Now either stop or start animation for the active conversation in each window */
6044 for (l
= pidgin_conv_windows_get_list(); l
!= NULL
; l
= l
->next
) {
6046 conv
= pidgin_conv_window_get_active_conversation(win
);
6047 pidgin_conv_update_buddy_icon(PURPLE_IM_CONVERSATION(conv
));
6052 show_buddy_icons_pref_cb(const char *name
, PurplePrefType type
,
6053 gconstpointer value
, gpointer data
)
6057 for (l
= purple_conversations_get_all(); l
!= NULL
; l
= l
->next
) {
6058 PurpleConversation
*conv
= l
->data
;
6059 if (!PIDGIN_CONVERSATION(conv
))
6061 if (GPOINTER_TO_INT(value
))
6062 gtk_widget_show(PIDGIN_CONVERSATION(conv
)->infopane_hbox
);
6064 gtk_widget_hide(PIDGIN_CONVERSATION(conv
)->infopane_hbox
);
6066 if (PURPLE_IS_IM_CONVERSATION(conv
)) {
6067 pidgin_conv_update_buddy_icon(PURPLE_IM_CONVERSATION(conv
));
6071 /* Make the tabs show/hide correctly */
6072 for (l
= pidgin_conv_windows_get_list(); l
!= NULL
; l
= l
->next
) {
6073 PidginConvWindow
*win
= l
->data
;
6074 if (pidgin_conv_window_get_gtkconv_count(win
) == 1)
6075 gtk_notebook_set_show_tabs(GTK_NOTEBOOK(win
->notebook
),
6076 GPOINTER_TO_INT(value
) == 0);
6081 show_protocol_icons_pref_cb(const char *name
, PurplePrefType type
,
6082 gconstpointer value
, gpointer data
)
6085 for (l
= purple_conversations_get_all(); l
!= NULL
; l
= l
->next
) {
6086 PurpleConversation
*conv
= l
->data
;
6087 if (PIDGIN_CONVERSATION(conv
))
6088 update_tab_icon(conv
);
6093 conv_placement_usetabs_cb(const char *name
, PurplePrefType type
,
6094 gconstpointer value
, gpointer data
)
6096 purple_prefs_trigger_callback(PIDGIN_PREFS_ROOT
"/conversations/placement");
6100 account_status_changed_cb(PurpleAccount
*account
, PurpleStatus
*oldstatus
,
6101 PurpleStatus
*newstatus
)
6104 PurpleConversation
*conv
= NULL
;
6105 PidginConversation
*gtkconv
;
6107 if(!purple_strequal(purple_prefs_get_string(PIDGIN_PREFS_ROOT
"/conversations/im/hide_new"), "away"))
6110 if(purple_status_is_available(oldstatus
) || !purple_status_is_available(newstatus
))
6113 for (l
= hidden_convwin
->gtkconvs
; l
; ) {
6117 conv
= gtkconv
->active_conv
;
6118 if (PURPLE_IS_CHAT_CONVERSATION(conv
) ||
6119 account
!= purple_conversation_get_account(conv
))
6122 pidgin_conv_attach_to_conversation(conv
);
6124 /* TODO: do we need to do anything for any other conversations that are in the same gtkconv here?
6125 * I'm a little concerned that not doing so will cause the "pending" indicator in the gtkblist not to be cleared. -DAA*/
6126 purple_conversation_update(conv
, PURPLE_CONVERSATION_UPDATE_UNSEEN
);
6131 hide_new_pref_cb(const char *name
, PurplePrefType type
,
6132 gconstpointer value
, gpointer data
)
6135 PurpleConversation
*conv
= NULL
;
6136 PidginConversation
*gtkconv
;
6137 gboolean when_away
= FALSE
;
6142 if(purple_strequal(purple_prefs_get_string(PIDGIN_PREFS_ROOT
"/conversations/im/hide_new"), "always"))
6145 if(purple_strequal(purple_prefs_get_string(PIDGIN_PREFS_ROOT
"/conversations/im/hide_new"), "away"))
6148 for (l
= hidden_convwin
->gtkconvs
; l
; )
6153 conv
= gtkconv
->active_conv
;
6155 if (PURPLE_IS_CHAT_CONVERSATION(conv
) ||
6156 gtkconv
->unseen_count
== 0 ||
6157 (when_away
&& !purple_status_is_available(
6158 purple_account_get_active_status(
6159 purple_conversation_get_account(conv
)))))
6162 pidgin_conv_attach_to_conversation(conv
);
6168 conv_placement_pref_cb(const char *name
, PurplePrefType type
,
6169 gconstpointer value
, gpointer data
)
6171 PidginConvPlacementFunc func
;
6173 if (!purple_strequal(name
, PIDGIN_PREFS_ROOT
"/conversations/placement"))
6176 func
= pidgin_conv_placement_get_fnc(value
);
6181 pidgin_conv_placement_set_current_func(func
);
6184 static PidginConversation
*
6185 get_gtkconv_with_contact(PurpleContact
*contact
)
6187 PurpleBlistNode
*node
;
6189 node
= ((PurpleBlistNode
*)contact
)->child
;
6191 for (; node
; node
= node
->next
)
6193 PurpleBuddy
*buddy
= (PurpleBuddy
*)node
;
6194 PurpleIMConversation
*im
;
6195 im
= purple_conversations_find_im_with_account(purple_buddy_get_name(buddy
), purple_buddy_get_account(buddy
));
6197 return PIDGIN_CONVERSATION(PURPLE_CONVERSATION(im
));
6203 account_signed_off_cb(PurpleConnection
*gc
, gpointer event
)
6207 for (iter
= purple_conversations_get_all(); iter
; iter
= iter
->next
)
6209 PurpleConversation
*conv
= iter
->data
;
6211 /* This seems fine in theory, but we also need to cover the
6212 * case of this account matching one of the other buddies in
6213 * one of the contacts containing the buddy corresponding to
6214 * a conversation. It's easier to just update them all. */
6215 /* if (purple_conversation_get_account(conv) == account) */
6216 pidgin_conv_update_fields(conv
, PIDGIN_CONV_TAB_ICON
|
6217 PIDGIN_CONV_MENU
| PIDGIN_CONV_COLORIZE_TITLE
);
6219 if (PURPLE_CONNECTION_IS_CONNECTED(gc
) &&
6220 PURPLE_IS_CHAT_CONVERSATION(conv
) &&
6221 purple_conversation_get_account(conv
) == purple_connection_get_account(gc
) &&
6222 g_object_get_data(G_OBJECT(conv
), "want-to-rejoin")) {
6223 GHashTable
*comps
= NULL
;
6224 PurpleChat
*chat
= purple_blist_find_chat(purple_conversation_get_account(conv
), purple_conversation_get_name(conv
));
6226 PurpleProtocol
*protocol
= purple_connection_get_protocol(gc
);
6227 comps
= purple_protocol_chat_iface_info_defaults(protocol
, gc
, purple_conversation_get_name(conv
));
6229 comps
= purple_chat_get_components(chat
);
6231 purple_serv_join_chat(gc
, comps
);
6232 if (chat
== NULL
&& comps
!= NULL
)
6233 g_hash_table_destroy(comps
);
6239 account_signing_off(PurpleConnection
*gc
)
6241 GList
*list
= purple_conversations_get_chats();
6242 PurpleAccount
*account
= purple_connection_get_account(gc
);
6244 /* We are about to sign off. See which chats we are currently in, and mark
6245 * them for rejoin on reconnect. */
6247 PurpleConversation
*conv
= list
->data
;
6248 if (!purple_chat_conversation_has_left(PURPLE_CHAT_CONVERSATION(conv
)) &&
6249 purple_conversation_get_account(conv
) == account
) {
6250 g_object_set_data(G_OBJECT(conv
), "want-to-rejoin", GINT_TO_POINTER(TRUE
));
6251 purple_conversation_write_system_message(conv
,
6252 _("The account has disconnected and you are no "
6253 "longer in this chat. You will automatically "
6254 "rejoin the chat when the account reconnects."),
6255 PURPLE_MESSAGE_NO_LOG
);
6262 update_buddy_status_changed(PurpleBuddy
*buddy
, PurpleStatus
*old
, PurpleStatus
*newstatus
)
6264 PidginConversation
*gtkconv
;
6265 PurpleConversation
*conv
;
6267 gtkconv
= get_gtkconv_with_contact(purple_buddy_get_contact(buddy
));
6270 conv
= gtkconv
->active_conv
;
6271 pidgin_conv_update_fields(conv
, PIDGIN_CONV_TAB_ICON
6272 | PIDGIN_CONV_COLORIZE_TITLE
6273 | PIDGIN_CONV_BUDDY_ICON
);
6274 if ((purple_status_is_online(old
) ^ purple_status_is_online(newstatus
)) != 0)
6275 pidgin_conv_update_fields(conv
, PIDGIN_CONV_MENU
);
6280 update_buddy_privacy_changed(PurpleBuddy
*buddy
)
6282 PidginConversation
*gtkconv
;
6283 PurpleConversation
*conv
;
6285 gtkconv
= get_gtkconv_with_contact(purple_buddy_get_contact(buddy
));
6287 conv
= gtkconv
->active_conv
;
6288 pidgin_conv_update_fields(conv
, PIDGIN_CONV_TAB_ICON
| PIDGIN_CONV_MENU
);
6293 update_buddy_idle_changed(PurpleBuddy
*buddy
, gboolean old
, gboolean newidle
)
6295 PurpleIMConversation
*im
;
6297 im
= purple_conversations_find_im_with_account(purple_buddy_get_name(buddy
), purple_buddy_get_account(buddy
));
6299 pidgin_conv_update_fields(PURPLE_CONVERSATION(im
), PIDGIN_CONV_TAB_ICON
);
6303 update_buddy_icon(PurpleBuddy
*buddy
)
6305 PurpleIMConversation
*im
;
6307 im
= purple_conversations_find_im_with_account(purple_buddy_get_name(buddy
), purple_buddy_get_account(buddy
));
6309 pidgin_conv_update_fields(PURPLE_CONVERSATION(im
), PIDGIN_CONV_BUDDY_ICON
);
6313 update_buddy_sign(PurpleBuddy
*buddy
, const char *which
)
6315 PurplePresence
*presence
;
6316 PurpleStatus
*on
, *off
;
6318 presence
= purple_buddy_get_presence(buddy
);
6321 off
= purple_presence_get_status(presence
, "offline");
6322 on
= purple_presence_get_status(presence
, "available");
6324 if (*(which
+1) == 'f')
6325 update_buddy_status_changed(buddy
, on
, off
);
6327 update_buddy_status_changed(buddy
, off
, on
);
6331 update_conversation_switched(PurpleConversation
*conv
)
6333 pidgin_conv_update_fields(conv
, PIDGIN_CONV_TAB_ICON
|
6334 PIDGIN_CONV_SET_TITLE
| PIDGIN_CONV_MENU
|
6335 PIDGIN_CONV_BUDDY_ICON
| PIDGIN_CONV_E2EE
);
6339 update_buddy_typing(PurpleAccount
*account
, const char *who
)
6341 PurpleConversation
*conv
;
6342 PidginConversation
*gtkconv
;
6344 conv
= PURPLE_CONVERSATION(purple_conversations_find_im_with_account(who
, account
));
6348 gtkconv
= PIDGIN_CONVERSATION(conv
);
6349 if (gtkconv
&& gtkconv
->active_conv
== conv
)
6350 pidgin_conv_update_fields(conv
, PIDGIN_CONV_COLORIZE_TITLE
);
6354 update_chat(PurpleChatConversation
*chat
)
6356 pidgin_conv_update_fields(PURPLE_CONVERSATION(chat
), PIDGIN_CONV_TOPIC
|
6357 PIDGIN_CONV_MENU
| PIDGIN_CONV_SET_TITLE
);
6361 update_chat_topic(PurpleChatConversation
*chat
, const char *old
, const char *new)
6363 pidgin_conv_update_fields(PURPLE_CONVERSATION(chat
), PIDGIN_CONV_TOPIC
);
6366 /* Message history stuff */
6368 /* Compare two PurpleMessages, according to time in ascending order. */
6370 message_compare(PurpleMessage
*m1
, PurpleMessage
*m2
)
6372 guint64 t1
= purple_message_get_time(m1
), t2
= purple_message_get_time(m2
);
6373 return (t1
> t2
) - (t1
< t2
);
6376 /* Adds some message history to the gtkconv. This happens in a idle-callback. */
6378 add_message_history_to_gtkconv(gpointer data
)
6380 PidginConversation
*gtkconv
= data
;
6382 int timer
= gtkconv
->attach_timer
;
6383 time_t when
= GPOINTER_TO_INT(g_object_get_data(G_OBJECT(gtkconv
->editor
), "attach-start-time"));
6384 gboolean im
= (PURPLE_IS_IM_CONVERSATION(gtkconv
->active_conv
));
6386 gtkconv
->attach_timer
= 0;
6387 while (gtkconv
->attach_current
&& count
< ADD_MESSAGE_HISTORY_AT_ONCE
) {
6388 PurpleMessage
*msg
= gtkconv
->attach_current
->data
;
6389 if (!im
&& when
&& (guint64
)when
< purple_message_get_time(msg
)) {
6390 g_object_set_data(G_OBJECT(gtkconv
->editor
), "attach-start-time", NULL
);
6392 /* XXX: should it be gtkconv->active_conv? */
6393 pidgin_conv_write_conv(gtkconv
->active_conv
, msg
);
6395 gtkconv
->attach_current
= g_list_delete_link(gtkconv
->attach_current
, gtkconv
->attach_current
);
6397 gtkconv
->attach_current
= gtkconv
->attach_current
->prev
;
6401 gtkconv
->attach_timer
= timer
;
6402 if (gtkconv
->attach_current
)
6405 g_source_remove(gtkconv
->attach_timer
);
6406 gtkconv
->attach_timer
= 0;
6408 /* Print any message that was sent while the old history was being added back. */
6410 GList
*iter
= gtkconv
->convs
;
6411 for (; iter
; iter
= iter
->next
) {
6412 PurpleConversation
*conv
= iter
->data
;
6413 GList
*history
= purple_conversation_get_message_history(conv
);
6414 for (; history
; history
= history
->next
) {
6415 PurpleMessage
*msg
= history
->data
;
6416 if (purple_message_get_time(msg
) > (guint64
)when
)
6417 msgs
= g_list_prepend(msgs
, msg
);
6420 msgs
= g_list_sort(msgs
, (GCompareFunc
)message_compare
);
6421 for (; msgs
; msgs
= g_list_delete_link(msgs
, msgs
)) {
6422 PurpleMessage
*msg
= msgs
->data
;
6423 /* XXX: see above - should it be active_conv? */
6424 pidgin_conv_write_conv(gtkconv
->active_conv
, msg
);
6426 g_object_set_data(G_OBJECT(gtkconv
->editor
), "attach-start-time", NULL
);
6429 g_object_set_data(G_OBJECT(gtkconv
->editor
), "attach-start-time", NULL
);
6430 purple_signal_emit(pidgin_conversations_get_handle(),
6431 "conversation-displayed", gtkconv
);
6436 pidgin_conv_attach(PurpleConversation
*conv
)
6439 g_object_set_data(G_OBJECT(conv
), "unseen-count", NULL
);
6440 g_object_set_data(G_OBJECT(conv
), "unseen-state", NULL
);
6441 purple_conversation_set_ui_ops(conv
, pidgin_conversations_get_conv_ui_ops());
6442 if (!PIDGIN_CONVERSATION(conv
))
6443 private_gtkconv_new(conv
, FALSE
);
6444 timer
= GPOINTER_TO_INT(g_object_get_data(G_OBJECT(conv
), "close-timer"));
6446 g_source_remove(timer
);
6447 g_object_set_data(G_OBJECT(conv
), "close-timer", NULL
);
6451 gboolean
pidgin_conv_attach_to_conversation(PurpleConversation
*conv
)
6454 PidginConversation
*gtkconv
;
6456 if (PIDGIN_IS_PIDGIN_CONVERSATION(conv
)) {
6457 /* This is pretty much always the case now. */
6458 gtkconv
= PIDGIN_CONVERSATION(conv
);
6459 if (gtkconv
->win
!= hidden_convwin
)
6461 pidgin_conv_window_remove_gtkconv(hidden_convwin
, gtkconv
);
6462 pidgin_conv_placement_place(gtkconv
);
6463 purple_signal_emit(pidgin_conversations_get_handle(),
6464 "conversation-displayed", gtkconv
);
6465 list
= gtkconv
->convs
;
6467 pidgin_conv_attach(list
->data
);
6473 pidgin_conv_attach(conv
);
6474 gtkconv
= PIDGIN_CONVERSATION(conv
);
6476 list
= purple_conversation_get_message_history(conv
);
6478 if (PURPLE_IS_IM_CONVERSATION(conv
)) {
6480 list
= g_list_copy(list
);
6481 for (convs
= purple_conversations_get_ims(); convs
; convs
= convs
->next
)
6482 if (convs
->data
!= conv
&&
6483 pidgin_conv_find_gtkconv(convs
->data
) == gtkconv
) {
6484 pidgin_conv_attach(convs
->data
);
6485 list
= g_list_concat(list
, g_list_copy(purple_conversation_get_message_history(convs
->data
)));
6487 list
= g_list_sort(list
, (GCompareFunc
)message_compare
);
6488 gtkconv
->attach_current
= list
;
6489 list
= g_list_last(list
);
6490 } else if (PURPLE_IS_CHAT_CONVERSATION(conv
)) {
6491 gtkconv
->attach_current
= g_list_last(list
);
6494 g_object_set_data(G_OBJECT(gtkconv
->editor
), "attach-start-time",
6495 GINT_TO_POINTER(purple_message_get_time(list
->data
)));
6496 gtkconv
->attach_timer
= g_idle_add(add_message_history_to_gtkconv
, gtkconv
);
6498 purple_signal_emit(pidgin_conversations_get_handle(),
6499 "conversation-displayed", gtkconv
);
6502 if (PURPLE_IS_CHAT_CONVERSATION(conv
)) {
6504 PurpleChatConversation
*chat
= PURPLE_CHAT_CONVERSATION(conv
);
6505 pidgin_conv_update_fields(conv
, PIDGIN_CONV_TOPIC
);
6506 users
= purple_chat_conversation_get_users(chat
);
6507 pidgin_conv_chat_add_users(chat
, users
, TRUE
);
6515 pidgin_conversations_get_handle(void)
6523 pidgin_conversations_pre_uninit(void);
6526 pidgin_conversations_init(void)
6528 void *handle
= pidgin_conversations_get_handle();
6529 void *blist_handle
= purple_blist_get_handle();
6531 e2ee_stock
= g_hash_table_new_full(g_str_hash
, g_str_equal
,
6532 g_free
, g_object_unref
);
6535 purple_prefs_add_none(PIDGIN_PREFS_ROOT
"/conversations");
6536 purple_prefs_add_bool(PIDGIN_PREFS_ROOT
"/conversations/use_smooth_scrolling", TRUE
);
6537 purple_prefs_add_bool(PIDGIN_PREFS_ROOT
"/conversations/close_on_tabs", TRUE
);
6538 purple_prefs_add_bool(PIDGIN_PREFS_ROOT
"/conversations/send_bold", FALSE
);
6539 purple_prefs_add_bool(PIDGIN_PREFS_ROOT
"/conversations/send_italic", FALSE
);
6540 purple_prefs_add_bool(PIDGIN_PREFS_ROOT
"/conversations/send_underline", FALSE
);
6541 purple_prefs_add_bool(PIDGIN_PREFS_ROOT
"/conversations/send_strike", FALSE
);
6542 purple_prefs_add_bool(PIDGIN_PREFS_ROOT
"/conversations/spellcheck", TRUE
);
6543 purple_prefs_add_bool(PIDGIN_PREFS_ROOT
"/conversations/show_incoming_formatting", TRUE
);
6544 /* TODO: it's about *remote* smileys, not local ones */
6545 purple_prefs_add_bool(PIDGIN_PREFS_ROOT
"/conversations/resize_custom_smileys", TRUE
);
6546 purple_prefs_add_int(PIDGIN_PREFS_ROOT
"/conversations/custom_smileys_size", 96);
6547 purple_prefs_add_int(PIDGIN_PREFS_ROOT
"/conversations/minimum_entry_lines", 2);
6549 purple_prefs_add_bool(PIDGIN_PREFS_ROOT
"/conversations/show_formatting_toolbar", TRUE
);
6551 purple_prefs_add_string(PIDGIN_PREFS_ROOT
"/conversations/placement", "last");
6552 purple_prefs_add_int(PIDGIN_PREFS_ROOT
"/conversations/placement_number", 1);
6553 purple_prefs_add_string(PIDGIN_PREFS_ROOT
"/conversations/bgcolor", "");
6554 purple_prefs_add_string(PIDGIN_PREFS_ROOT
"/conversations/fgcolor", "");
6555 purple_prefs_add_string(PIDGIN_PREFS_ROOT
"/conversations/font_face", "");
6556 purple_prefs_add_int(PIDGIN_PREFS_ROOT
"/conversations/font_size", 3);
6557 purple_prefs_add_bool(PIDGIN_PREFS_ROOT
"/conversations/tabs", TRUE
);
6558 purple_prefs_add_int(PIDGIN_PREFS_ROOT
"/conversations/tab_side", GTK_POS_TOP
);
6559 purple_prefs_add_int(PIDGIN_PREFS_ROOT
"/conversations/scrollback_lines", 4000);
6562 purple_prefs_add_bool(PIDGIN_PREFS_ROOT
"/conversations/use_theme_font", TRUE
);
6563 purple_prefs_add_string(PIDGIN_PREFS_ROOT
"/conversations/custom_font", "");
6566 /* Conversations -> Chat */
6567 purple_prefs_add_none(PIDGIN_PREFS_ROOT
"/conversations/chat");
6568 purple_prefs_add_int(PIDGIN_PREFS_ROOT
"/conversations/chat/entry_height", 54);
6569 purple_prefs_add_int(PIDGIN_PREFS_ROOT
"/conversations/chat/userlist_width", 80);
6570 purple_prefs_add_int(PIDGIN_PREFS_ROOT
"/conversations/chat/x", 0);
6571 purple_prefs_add_int(PIDGIN_PREFS_ROOT
"/conversations/chat/y", 0);
6572 purple_prefs_add_int(PIDGIN_PREFS_ROOT
"/conversations/chat/width", 340);
6573 purple_prefs_add_int(PIDGIN_PREFS_ROOT
"/conversations/chat/height", 390);
6575 /* Conversations -> IM */
6576 purple_prefs_add_none(PIDGIN_PREFS_ROOT
"/conversations/im");
6577 purple_prefs_add_int(PIDGIN_PREFS_ROOT
"/conversations/im/x", 0);
6578 purple_prefs_add_int(PIDGIN_PREFS_ROOT
"/conversations/im/y", 0);
6579 purple_prefs_add_int(PIDGIN_PREFS_ROOT
"/conversations/im/width", 340);
6580 purple_prefs_add_int(PIDGIN_PREFS_ROOT
"/conversations/im/height", 390);
6582 purple_prefs_add_bool(PIDGIN_PREFS_ROOT
"/conversations/im/animate_buddy_icons", TRUE
);
6584 purple_prefs_add_int(PIDGIN_PREFS_ROOT
"/conversations/im/entry_height", 54);
6585 purple_prefs_add_bool(PIDGIN_PREFS_ROOT
"/conversations/im/show_buddy_icons", TRUE
);
6587 purple_prefs_add_string(PIDGIN_PREFS_ROOT
"/conversations/im/hide_new", "never");
6588 purple_prefs_add_bool(PIDGIN_PREFS_ROOT
"/conversations/im/close_immediately", TRUE
);
6591 purple_prefs_add_bool(PIDGIN_PREFS_ROOT
"/win32/minimize_new_convs", FALSE
);
6594 /* Connect callbacks. */
6595 purple_prefs_connect_callback(handle
, PIDGIN_PREFS_ROOT
"/conversations/close_on_tabs",
6596 close_on_tabs_pref_cb
, NULL
);
6597 purple_prefs_connect_callback(handle
, PIDGIN_PREFS_ROOT
"/conversations/show_formatting_toolbar",
6598 show_formatting_toolbar_pref_cb
, NULL
);
6599 purple_prefs_connect_callback(handle
, PIDGIN_PREFS_ROOT
"/conversations/spellcheck",
6600 spellcheck_pref_cb
, NULL
);
6601 purple_prefs_connect_callback(handle
, PIDGIN_PREFS_ROOT
"/conversations/tab_side",
6602 tab_side_pref_cb
, NULL
);
6604 purple_prefs_connect_callback(handle
, PIDGIN_PREFS_ROOT
"/conversations/tabs",
6605 conv_placement_usetabs_cb
, NULL
);
6607 purple_prefs_connect_callback(handle
, PIDGIN_PREFS_ROOT
"/conversations/placement",
6608 conv_placement_pref_cb
, NULL
);
6609 purple_prefs_trigger_callback(PIDGIN_PREFS_ROOT
"/conversations/placement");
6611 purple_prefs_connect_callback(handle
, PIDGIN_PREFS_ROOT
"/conversations/minimum_entry_lines",
6612 minimum_entry_lines_pref_cb
, NULL
);
6615 purple_prefs_connect_callback(handle
, PIDGIN_PREFS_ROOT
"/conversations/im/animate_buddy_icons",
6616 animate_buddy_icons_pref_cb
, NULL
);
6617 purple_prefs_connect_callback(handle
, PIDGIN_PREFS_ROOT
"/conversations/im/show_buddy_icons",
6618 show_buddy_icons_pref_cb
, NULL
);
6619 purple_prefs_connect_callback(handle
, PIDGIN_PREFS_ROOT
"/blist/show_protocol_icons",
6620 show_protocol_icons_pref_cb
, NULL
);
6621 purple_prefs_connect_callback(handle
, PIDGIN_PREFS_ROOT
"/conversations/im/hide_new",
6622 hide_new_pref_cb
, NULL
);
6624 /**********************************************************************
6626 **********************************************************************/
6627 purple_signal_register(handle
, "conversation-dragging",
6628 purple_marshal_VOID__POINTER_POINTER
, G_TYPE_NONE
, 2,
6629 G_TYPE_POINTER
, /* pointer to a (PidginConvWindow *) */
6630 G_TYPE_POINTER
); /* pointer to a (PidginConvWindow *) */
6632 purple_signal_register(handle
, "conversation-timestamp",
6633 #if SIZEOF_TIME_T == 4
6634 purple_marshal_POINTER__POINTER_INT_BOOLEAN
,
6635 #elif SIZEOF_TIME_T == 8
6636 purple_marshal_POINTER__POINTER_INT64_BOOLEAN
,
6638 #error Unkown size of time_t
6640 G_TYPE_STRING
, 3, PURPLE_TYPE_CONVERSATION
,
6641 #if SIZEOF_TIME_T == 4
6643 #elif SIZEOF_TIME_T == 8
6646 # error Unknown size of time_t
6650 purple_signal_register(handle
, "displaying-im-msg",
6651 purple_marshal_BOOLEAN__POINTER_POINTER
,
6652 G_TYPE_BOOLEAN
, 2, PURPLE_TYPE_CONVERSATION
, PURPLE_TYPE_MESSAGE
);
6654 purple_signal_register(handle
, "displayed-im-msg",
6655 purple_marshal_VOID__POINTER_POINTER
, G_TYPE_NONE
, 2,
6656 PURPLE_TYPE_CONVERSATION
, PURPLE_TYPE_MESSAGE
);
6658 purple_signal_register(handle
, "displaying-chat-msg",
6659 purple_marshal_BOOLEAN__POINTER_POINTER
,
6660 G_TYPE_BOOLEAN
, 2, PURPLE_TYPE_CONVERSATION
, PURPLE_TYPE_MESSAGE
);
6662 purple_signal_register(handle
, "displayed-chat-msg",
6663 purple_marshal_VOID__POINTER_POINTER
, G_TYPE_NONE
, 2,
6664 PURPLE_TYPE_CONVERSATION
, PURPLE_TYPE_MESSAGE
);
6666 purple_signal_register(handle
, "conversation-switched",
6667 purple_marshal_VOID__POINTER
, G_TYPE_NONE
, 1,
6668 PURPLE_TYPE_CONVERSATION
);
6670 purple_signal_register(handle
, "conversation-hiding",
6671 purple_marshal_VOID__POINTER
, G_TYPE_NONE
, 1,
6672 G_TYPE_POINTER
); /* (PidginConversation *) */
6674 purple_signal_register(handle
, "conversation-displayed",
6675 purple_marshal_VOID__POINTER
, G_TYPE_NONE
, 1,
6676 G_TYPE_POINTER
); /* (PidginConversation *) */
6678 purple_signal_register(handle
, "chat-nick-autocomplete",
6679 purple_marshal_BOOLEAN__POINTER_BOOLEAN
,
6680 G_TYPE_BOOLEAN
, 1, PURPLE_TYPE_CONVERSATION
);
6682 purple_signal_register(handle
, "chat-nick-clicked",
6683 purple_marshal_BOOLEAN__POINTER_POINTER_UINT
,
6684 G_TYPE_BOOLEAN
, 3, PURPLE_TYPE_CONVERSATION
,
6685 G_TYPE_STRING
, G_TYPE_UINT
);
6687 purple_signal_register(handle
, "conversation-window-created",
6688 purple_marshal_VOID__POINTER
, G_TYPE_NONE
, 1,
6689 G_TYPE_POINTER
); /* (PidginConvWindow *) */
6692 /**********************************************************************
6694 **********************************************************************/
6695 purple_cmd_register("say", "S", PURPLE_CMD_P_DEFAULT
,
6696 PURPLE_CMD_FLAG_CHAT
| PURPLE_CMD_FLAG_IM
, NULL
,
6697 say_command_cb
, _("say <message>: Send a message normally as if you weren't using a command."), NULL
);
6698 purple_cmd_register("me", "S", PURPLE_CMD_P_DEFAULT
,
6699 PURPLE_CMD_FLAG_CHAT
| PURPLE_CMD_FLAG_IM
, NULL
,
6700 me_command_cb
, _("me <action>: Send an IRC style action to a buddy or chat."), NULL
);
6701 purple_cmd_register("debug", "w", PURPLE_CMD_P_DEFAULT
,
6702 PURPLE_CMD_FLAG_CHAT
| PURPLE_CMD_FLAG_IM
, NULL
,
6703 debug_command_cb
, _("debug <option>: Send various debug information to the current conversation."), NULL
);
6704 purple_cmd_register("clear", "", PURPLE_CMD_P_DEFAULT
,
6705 PURPLE_CMD_FLAG_CHAT
| PURPLE_CMD_FLAG_IM
, NULL
,
6706 clear_command_cb
, _("clear: Clears the conversation scrollback."), NULL
);
6707 purple_cmd_register("clearall", "", PURPLE_CMD_P_DEFAULT
,
6708 PURPLE_CMD_FLAG_CHAT
| PURPLE_CMD_FLAG_IM
, NULL
,
6709 clearall_command_cb
, _("clear: Clears all conversation scrollbacks."), NULL
);
6710 purple_cmd_register("help", "w", PURPLE_CMD_P_DEFAULT
,
6711 PURPLE_CMD_FLAG_CHAT
| PURPLE_CMD_FLAG_IM
| PURPLE_CMD_FLAG_ALLOW_WRONG_ARGS
, NULL
,
6712 help_command_cb
, _("help <command>: Help on a specific command."), NULL
);
6714 /**********************************************************************
6716 **********************************************************************/
6718 purple_signal_connect(purple_connections_get_handle(), "signed-on", handle
,
6719 G_CALLBACK(account_signed_off_cb
),
6720 GINT_TO_POINTER(PURPLE_CONVERSATION_ACCOUNT_ONLINE
));
6721 purple_signal_connect(purple_connections_get_handle(), "signed-off", handle
,
6722 G_CALLBACK(account_signed_off_cb
),
6723 GINT_TO_POINTER(PURPLE_CONVERSATION_ACCOUNT_OFFLINE
));
6724 purple_signal_connect(purple_connections_get_handle(), "signing-off", handle
,
6725 G_CALLBACK(account_signing_off
), NULL
);
6727 purple_signal_connect(purple_conversations_get_handle(), "writing-im-msg",
6728 handle
, G_CALLBACK(writing_msg
), NULL
);
6729 purple_signal_connect(purple_conversations_get_handle(), "writing-chat-msg",
6730 handle
, G_CALLBACK(writing_msg
), NULL
);
6731 purple_signal_connect(purple_conversations_get_handle(), "received-im-msg",
6732 handle
, G_CALLBACK(received_im_msg_cb
), NULL
);
6733 purple_signal_connect(purple_conversations_get_handle(), "cleared-message-history",
6734 handle
, G_CALLBACK(clear_conversation_scrollback_cb
), NULL
);
6736 purple_signal_connect(purple_conversations_get_handle(), "deleting-chat-user",
6737 handle
, G_CALLBACK(deleting_chat_user_cb
), NULL
);
6739 purple_conversations_set_ui_ops(&conversation_ui_ops
);
6741 hidden_convwin
= pidgin_conv_window_new();
6742 window_list
= g_list_remove(window_list
, hidden_convwin
);
6744 purple_signal_connect(purple_accounts_get_handle(), "account-status-changed",
6745 handle
, PURPLE_CALLBACK(account_status_changed_cb
), NULL
);
6747 purple_signal_connect_priority(purple_get_core(), "quitting", handle
,
6748 PURPLE_CALLBACK(pidgin_conversations_pre_uninit
), NULL
, PURPLE_SIGNAL_PRIORITY_HIGHEST
);
6750 /* Callbacks to update a conversation */
6751 purple_signal_connect(blist_handle
, "blist-node-added", handle
,
6752 G_CALLBACK(buddy_update_cb
), NULL
);
6753 purple_signal_connect(blist_handle
, "blist-node-removed", handle
,
6754 G_CALLBACK(buddy_update_cb
), NULL
);
6755 purple_signal_connect(blist_handle
, "buddy-signed-on",
6756 handle
, PURPLE_CALLBACK(update_buddy_sign
), "on");
6757 purple_signal_connect(blist_handle
, "buddy-signed-off",
6758 handle
, PURPLE_CALLBACK(update_buddy_sign
), "off");
6759 purple_signal_connect(blist_handle
, "buddy-status-changed",
6760 handle
, PURPLE_CALLBACK(update_buddy_status_changed
), NULL
);
6761 purple_signal_connect(blist_handle
, "buddy-privacy-changed",
6762 handle
, PURPLE_CALLBACK(update_buddy_privacy_changed
), NULL
);
6763 purple_signal_connect(blist_handle
, "buddy-idle-changed",
6764 handle
, PURPLE_CALLBACK(update_buddy_idle_changed
), NULL
);
6765 purple_signal_connect(blist_handle
, "buddy-icon-changed",
6766 handle
, PURPLE_CALLBACK(update_buddy_icon
), NULL
);
6767 purple_signal_connect(purple_conversations_get_handle(), "buddy-typing",
6768 handle
, PURPLE_CALLBACK(update_buddy_typing
), NULL
);
6769 purple_signal_connect(purple_conversations_get_handle(), "buddy-typing-stopped",
6770 handle
, PURPLE_CALLBACK(update_buddy_typing
), NULL
);
6771 purple_signal_connect(pidgin_conversations_get_handle(), "conversation-switched",
6772 handle
, PURPLE_CALLBACK(update_conversation_switched
), NULL
);
6773 purple_signal_connect(purple_conversations_get_handle(), "chat-left", handle
,
6774 PURPLE_CALLBACK(update_chat
), NULL
);
6775 purple_signal_connect(purple_conversations_get_handle(), "chat-joined", handle
,
6776 PURPLE_CALLBACK(update_chat
), NULL
);
6777 purple_signal_connect(purple_conversations_get_handle(), "chat-topic-changed", handle
,
6778 PURPLE_CALLBACK(update_chat_topic
), NULL
);
6779 purple_signal_connect_priority(purple_conversations_get_handle(), "conversation-updated", handle
,
6780 PURPLE_CALLBACK(pidgin_conv_updated
), NULL
,
6781 PURPLE_SIGNAL_PRIORITY_LOWEST
);
6782 purple_signal_connect(purple_conversations_get_handle(), "wrote-im-msg", handle
,
6783 PURPLE_CALLBACK(wrote_msg_update_unseen_cb
), NULL
);
6784 purple_signal_connect(purple_conversations_get_handle(), "wrote-chat-msg", handle
,
6785 PURPLE_CALLBACK(wrote_msg_update_unseen_cb
), NULL
);
6789 pidgin_conversations_pre_uninit(void)
6791 g_hash_table_destroy(e2ee_stock
);
6795 /* Invalidate the first tab color set */
6796 static gboolean tab_color_fuse
= TRUE
;
6799 pidgin_conversations_set_tab_colors(void)
6801 /* Set default tab colors */
6802 GString
*str
= g_string_new(NULL
);
6803 GtkSettings
*settings
= gtk_settings_get_default();
6804 GtkStyle
*parent
= gtk_rc_get_style_by_paths(settings
, "tab-container.tab-label*", NULL
, G_TYPE_NONE
), *now
;
6806 const char *stylename
;
6807 const char *labelname
;
6810 {"pidgin_tab_label_typing_default", "tab-label-typing", "#4e9a06"},
6811 {"pidgin_tab_label_typed_default", "tab-label-typed", "#c4a000"},
6812 {"pidgin_tab_label_attention_default", "tab-label-attention", "#006aff"},
6813 {"pidgin_tab_label_unreadchat_default", "tab-label-unreadchat", "#cc0000"},
6814 {"pidgin_tab_label_event_default", "tab-label-event", "#888a85"},
6819 if(tab_color_fuse
) {
6820 tab_color_fuse
= FALSE
;
6824 for (iter
= 0; styles
[iter
].stylename
; iter
++) {
6825 now
= gtk_rc_get_style_by_paths(settings
, styles
[iter
].labelname
, NULL
, G_TYPE_NONE
);
6826 if (parent
== now
||
6827 (parent
&& now
&& parent
->rc_style
== now
->rc_style
)) {
6831 gdk_rgba_parse(&color
, styles
[iter
].color
);
6832 pidgin_style_adjust_contrast(gtk_widget_get_default_style(), &color
);
6834 color_str
= gdk_rgba_to_string(&color
);
6835 g_string_append_printf(str
, "style \"%s\" {\n"
6836 "fg[ACTIVE] = \"%s\"\n"
6838 "widget \"*%s\" style \"%s\"\n",
6839 styles
[iter
].stylename
,
6841 styles
[iter
].labelname
, styles
[iter
].stylename
);
6845 gtk_rc_parse_string(str
->str
);
6846 g_string_free(str
, TRUE
);
6847 gtk_rc_reset_styles(settings
);
6851 pidgin_conversations_uninit(void)
6853 purple_prefs_disconnect_by_handle(pidgin_conversations_get_handle());
6854 purple_signals_disconnect_by_handle(pidgin_conversations_get_handle());
6855 purple_signals_unregister_by_instance(pidgin_conversations_get_handle());
6858 /**************************************************************************
6859 * PidginConversation GBoxed code
6860 **************************************************************************/
6861 static PidginConversation
*
6862 pidgin_conversation_ref(PidginConversation
*gtkconv
)
6864 g_return_val_if_fail(gtkconv
!= NULL
, NULL
);
6866 gtkconv
->box_count
++;
6872 pidgin_conversation_unref(PidginConversation
*gtkconv
)
6874 g_return_if_fail(gtkconv
!= NULL
);
6875 g_return_if_fail(gtkconv
->box_count
>= 0);
6877 if (!gtkconv
->box_count
--)
6878 pidgin_conv_destroy(gtkconv
->active_conv
);
6882 pidgin_conversation_get_type(void)
6884 static GType type
= 0;
6887 type
= g_boxed_type_register_static("PidginConversation",
6888 (GBoxedCopyFunc
)pidgin_conversation_ref
,
6889 (GBoxedFreeFunc
)pidgin_conversation_unref
);
6910 /* down here is where gtkconvwin.c ought to start. except they share like every freaking function,
6911 * and touch each others' private members all day long */
6915 * Pidgin is the legal property of its developers, whose names are too numerous
6916 * to list here. Please refer to the COPYRIGHT file distributed with this
6917 * source distribution.
6919 * This program is free software; you can redistribute it and/or modify
6920 * it under the terms of the GNU General Public License as published by
6921 * the Free Software Foundation; either version 2 of the License, or
6922 * (at your option) any later version.
6924 * This program is distributed in the hope that it will be useful,
6925 * but WITHOUT ANY WARRANTY; without even the implied warranty of
6926 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
6927 * GNU General Public License for more details.
6929 * You should have received a copy of the GNU General Public License
6930 * along with this program; if not, write to the Free Software
6931 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA
6934 #include "internal.h"
6938 #include <gdk/gdkkeysyms.h>
6940 #include "account.h"
6945 #include "protocol.h"
6946 #include "request.h"
6949 #include "gtkdnd-hints.h"
6950 #include "gtkblist.h"
6951 #include "gtkconv.h"
6952 #include "gtkdialogs.h"
6953 #include "gtkmenutray.h"
6954 #include "gtkpounce.h"
6955 #include "gtkprefs.h"
6956 #include "gtkprivacy.h"
6957 #include "gtkutils.h"
6958 #include "pidginstock.h"
6961 do_close(GtkWidget
*w
, int resp
, PidginConvWindow
*win
)
6963 gtk_widget_destroy(warn_close_dialog
);
6964 warn_close_dialog
= NULL
;
6966 if (resp
== GTK_RESPONSE_OK
)
6967 pidgin_conv_window_destroy(win
);
6971 build_warn_close_dialog(PidginConvWindow
*gtkwin
)
6973 GtkWidget
*label
, *vbox
, *hbox
, *img
;
6975 g_return_if_fail(warn_close_dialog
== NULL
);
6977 warn_close_dialog
= gtk_dialog_new_with_buttons(_("Confirm close"),
6978 GTK_WINDOW(gtkwin
->window
), GTK_DIALOG_MODAL
,
6979 GTK_STOCK_CANCEL
, GTK_RESPONSE_CANCEL
,
6980 GTK_STOCK_CLOSE
, GTK_RESPONSE_OK
, NULL
);
6982 gtk_dialog_set_default_response(GTK_DIALOG(warn_close_dialog
),
6985 gtk_container_set_border_width(GTK_CONTAINER(warn_close_dialog
),
6987 gtk_window_set_resizable(GTK_WINDOW(warn_close_dialog
), FALSE
);
6989 /* Setup the outside spacing. */
6990 vbox
= gtk_dialog_get_content_area(GTK_DIALOG(warn_close_dialog
));
6992 gtk_box_set_spacing(GTK_BOX(vbox
), 12);
6993 gtk_container_set_border_width(GTK_CONTAINER(vbox
), 6);
6995 img
= gtk_image_new_from_icon_name("dialog-warning",
6996 GTK_ICON_SIZE_DIALOG
);
6998 /* Setup the inner hbox and put the dialog's icon in it. */
6999 hbox
= gtk_box_new(GTK_ORIENTATION_HORIZONTAL
, 12);
7000 gtk_container_add(GTK_CONTAINER(vbox
), hbox
);
7001 gtk_box_pack_start(GTK_BOX(hbox
), img
, FALSE
, FALSE
, 0);
7002 gtk_widget_set_halign(img
, GTK_ALIGN_START
);
7003 gtk_widget_set_valign(img
, GTK_ALIGN_START
);
7005 /* Setup the right vbox. */
7006 vbox
= gtk_box_new(GTK_ORIENTATION_VERTICAL
, 12);
7007 gtk_container_add(GTK_CONTAINER(hbox
), vbox
);
7009 label
= gtk_label_new(_("You have unread messages. Are you sure you want to close the window?"));
7010 gtk_widget_set_size_request(label
, 350, -1);
7011 gtk_label_set_line_wrap(GTK_LABEL(label
), TRUE
);
7012 gtk_label_set_xalign(GTK_LABEL(label
), 0);
7013 gtk_label_set_yalign(GTK_LABEL(label
), 0);
7014 gtk_box_pack_start(GTK_BOX(vbox
), label
, FALSE
, FALSE
, 0);
7016 /* Connect the signals. */
7017 g_signal_connect(G_OBJECT(warn_close_dialog
), "response",
7018 G_CALLBACK(do_close
), gtkwin
);
7022 /**************************************************************************
7024 **************************************************************************/
7027 close_win_cb(GtkWidget
*w
, GdkEventAny
*e
, gpointer d
)
7029 PidginConvWindow
*win
= d
;
7032 /* If there are unread messages then show a warning dialog */
7033 for (l
= pidgin_conv_window_get_gtkconvs(win
);
7034 l
!= NULL
; l
= l
->next
)
7036 PidginConversation
*gtkconv
= l
->data
;
7037 if (PURPLE_IS_IM_CONVERSATION(gtkconv
->active_conv
) &&
7038 gtkconv
->unseen_state
>= PIDGIN_UNSEEN_TEXT
)
7040 build_warn_close_dialog(win
);
7041 gtk_widget_show_all(warn_close_dialog
);
7047 pidgin_conv_window_destroy(win
);
7053 conv_set_unseen(PurpleConversation
*conv
, PidginUnseenState state
)
7055 int unseen_count
= 0;
7056 PidginUnseenState unseen_state
= PIDGIN_UNSEEN_NONE
;
7058 if(g_object_get_data(G_OBJECT(conv
), "unseen-count"))
7059 unseen_count
= GPOINTER_TO_INT(g_object_get_data(G_OBJECT(conv
), "unseen-count"));
7061 if(g_object_get_data(G_OBJECT(conv
), "unseen-state"))
7062 unseen_state
= GPOINTER_TO_INT(g_object_get_data(G_OBJECT(conv
), "unseen-state"));
7064 if (state
== PIDGIN_UNSEEN_NONE
)
7067 unseen_state
= PIDGIN_UNSEEN_NONE
;
7071 if (state
>= PIDGIN_UNSEEN_TEXT
)
7074 if (state
> unseen_state
)
7075 unseen_state
= state
;
7078 g_object_set_data(G_OBJECT(conv
), "unseen-count", GINT_TO_POINTER(unseen_count
));
7079 g_object_set_data(G_OBJECT(conv
), "unseen-state", GINT_TO_POINTER(unseen_state
));
7081 purple_conversation_update(conv
, PURPLE_CONVERSATION_UPDATE_UNSEEN
);
7085 gtkconv_set_unseen(PidginConversation
*gtkconv
, PidginUnseenState state
)
7087 if (state
== PIDGIN_UNSEEN_NONE
)
7089 gtkconv
->unseen_count
= 0;
7090 gtkconv
->unseen_state
= PIDGIN_UNSEEN_NONE
;
7094 if (state
>= PIDGIN_UNSEEN_TEXT
)
7095 gtkconv
->unseen_count
++;
7097 if (state
> gtkconv
->unseen_state
)
7098 gtkconv
->unseen_state
= state
;
7101 g_object_set_data(G_OBJECT(gtkconv
->active_conv
), "unseen-count", GINT_TO_POINTER(gtkconv
->unseen_count
));
7102 g_object_set_data(G_OBJECT(gtkconv
->active_conv
), "unseen-state", GINT_TO_POINTER(gtkconv
->unseen_state
));
7104 purple_conversation_update(gtkconv
->active_conv
, PURPLE_CONVERSATION_UPDATE_UNSEEN
);
7108 * When a conversation window is focused, we know the user
7109 * has looked at it so we know there are no longer unseen
7113 focus_win_cb(GtkWidget
*w
, GdkEventFocus
*e
, gpointer d
)
7115 PidginConvWindow
*win
= d
;
7116 PidginConversation
*gtkconv
= pidgin_conv_window_get_active_gtkconv(win
);
7119 gtkconv_set_unseen(gtkconv
, PIDGIN_UNSEEN_NONE
);
7125 notebook_init_grab(PidginConvWindow
*gtkwin
, GtkWidget
*widget
, GdkEvent
*event
)
7127 static GdkCursor
*cursor
= NULL
;
7130 gtkwin
->in_drag
= TRUE
;
7132 if (gtkwin
->drag_leave_signal
) {
7133 g_signal_handler_disconnect(G_OBJECT(widget
),
7134 gtkwin
->drag_leave_signal
);
7135 gtkwin
->drag_leave_signal
= 0;
7138 if (cursor
== NULL
) {
7139 GdkDisplay
*display
= gtk_widget_get_display(gtkwin
->notebook
);
7140 cursor
= gdk_cursor_new_for_display(display
, GDK_FLEUR
);
7143 /* Grab the pointer */
7144 gtk_grab_add(gtkwin
->notebook
);
7145 device
= gdk_event_get_device(event
);
7146 if (!gdk_display_device_is_grabbed(gdk_device_get_display(device
), device
))
7147 gdk_device_grab(device
, gtk_widget_get_window(gtkwin
->notebook
),
7148 GDK_OWNERSHIP_WINDOW
, FALSE
,
7149 GDK_BUTTON1_MOTION_MASK
| GDK_BUTTON_RELEASE_MASK
,
7150 cursor
, gdk_event_get_time(event
));
7154 notebook_motion_cb(GtkWidget
*widget
, GdkEventButton
*e
, PidginConvWindow
*win
)
7158 * Make sure the user moved the mouse far enough for the
7159 * drag to be initiated.
7161 if (win
->in_predrag
) {
7162 if (e
->x_root
< win
->drag_min_x
||
7163 e
->x_root
>= win
->drag_max_x
||
7164 e
->y_root
< win
->drag_min_y
||
7165 e
->y_root
>= win
->drag_max_y
) {
7167 win
->in_predrag
= FALSE
;
7168 notebook_init_grab(win
, widget
, (GdkEvent
*)e
);
7171 else { /* Otherwise, draw the arrows. */
7172 PidginConvWindow
*dest_win
;
7173 GtkNotebook
*dest_notebook
;
7176 gboolean horiz_tabs
= FALSE
;
7177 gboolean to_right
= FALSE
;
7179 /* Get the window that the cursor is over. */
7180 dest_win
= pidgin_conv_window_get_at_event((GdkEvent
*)e
);
7182 if (dest_win
== NULL
) {
7183 pidgin_dnd_hints_hide_all();
7188 dest_notebook
= GTK_NOTEBOOK(dest_win
->notebook
);
7190 if (gtk_notebook_get_show_tabs(dest_notebook
)) {
7191 page_num
= pidgin_conv_get_tab_at_xy(dest_win
,
7192 e
->x_root
, e
->y_root
, &to_right
);
7193 to_right
= to_right
&& (win
!= dest_win
);
7194 tab
= pidgin_conv_window_get_gtkconv_at_index(dest_win
, page_num
)->tabby
;
7197 to_right
= pidgin_conv_xy_to_right_infopane(dest_win
, e
->x_root
, e
->y_root
);
7198 tab
= pidgin_conv_window_get_gtkconv_at_index(dest_win
, page_num
)->infopane_hbox
;
7201 if (gtk_notebook_get_tab_pos(dest_notebook
) == GTK_POS_TOP
||
7202 gtk_notebook_get_tab_pos(dest_notebook
) == GTK_POS_BOTTOM
) {
7206 if (gtk_notebook_get_show_tabs(dest_notebook
) == FALSE
&& win
== dest_win
)
7208 /* dragging a tab from a single-tabbed window over its own window */
7209 pidgin_dnd_hints_hide_all();
7211 } else if (horiz_tabs
) {
7212 if (((gpointer
)win
== (gpointer
)dest_win
&& win
->drag_tab
< page_num
) || to_right
) {
7213 pidgin_dnd_hints_show_relative(HINT_ARROW_DOWN
, tab
, HINT_POSITION_RIGHT
, HINT_POSITION_TOP
);
7214 pidgin_dnd_hints_show_relative(HINT_ARROW_UP
, tab
, HINT_POSITION_RIGHT
, HINT_POSITION_BOTTOM
);
7216 pidgin_dnd_hints_show_relative(HINT_ARROW_DOWN
, tab
, HINT_POSITION_LEFT
, HINT_POSITION_TOP
);
7217 pidgin_dnd_hints_show_relative(HINT_ARROW_UP
, tab
, HINT_POSITION_LEFT
, HINT_POSITION_BOTTOM
);
7220 if (((gpointer
)win
== (gpointer
)dest_win
&& win
->drag_tab
< page_num
) || to_right
) {
7221 pidgin_dnd_hints_show_relative(HINT_ARROW_RIGHT
, tab
, HINT_POSITION_LEFT
, HINT_POSITION_BOTTOM
);
7222 pidgin_dnd_hints_show_relative(HINT_ARROW_LEFT
, tab
, HINT_POSITION_RIGHT
, HINT_POSITION_BOTTOM
);
7224 pidgin_dnd_hints_show_relative(HINT_ARROW_RIGHT
, tab
, HINT_POSITION_LEFT
, HINT_POSITION_TOP
);
7225 pidgin_dnd_hints_show_relative(HINT_ARROW_LEFT
, tab
, HINT_POSITION_RIGHT
, HINT_POSITION_TOP
);
7234 notebook_leave_cb(GtkWidget
*widget
, GdkEventCrossing
*e
, PidginConvWindow
*win
)
7239 if (e
->x_root
< win
->drag_min_x
||
7240 e
->x_root
>= win
->drag_max_x
||
7241 e
->y_root
< win
->drag_min_y
||
7242 e
->y_root
>= win
->drag_max_y
) {
7244 win
->in_predrag
= FALSE
;
7245 notebook_init_grab(win
, widget
, (GdkEvent
*)e
);
7256 infopane_press_cb(GtkWidget
*widget
, GdkEventButton
*e
, PidginConversation
*gtkconv
)
7258 if (e
->type
== GDK_2BUTTON_PRESS
&& e
->button
== GDK_BUTTON_PRIMARY
) {
7259 if (infopane_entry_activate(gtkconv
))
7263 if (e
->type
!= GDK_BUTTON_PRESS
)
7266 if (e
->button
== GDK_BUTTON_PRIMARY
) {
7268 GtkAllocation allocation
;
7270 gtk_widget_get_allocation(gtkconv
->infopane_hbox
, &allocation
);
7272 if (gtkconv
->win
->in_drag
)
7275 gtkconv
->win
->in_predrag
= TRUE
;
7276 gtkconv
->win
->drag_tab
= gtk_notebook_page_num(GTK_NOTEBOOK(gtkconv
->win
->notebook
), gtkconv
->tab_cont
);
7278 gdk_window_get_origin(gtk_widget_get_window(gtkconv
->infopane_hbox
), &nb_x
, &nb_y
);
7280 gtkconv
->win
->drag_min_x
= allocation
.x
+ nb_x
;
7281 gtkconv
->win
->drag_min_y
= allocation
.y
+ nb_y
;
7282 gtkconv
->win
->drag_max_x
= allocation
.width
+ gtkconv
->win
->drag_min_x
;
7283 gtkconv
->win
->drag_max_y
= allocation
.height
+ gtkconv
->win
->drag_min_y
;
7285 gtkconv
->win
->drag_motion_signal
= g_signal_connect(G_OBJECT(gtkconv
->win
->notebook
), "motion_notify_event",
7286 G_CALLBACK(notebook_motion_cb
), gtkconv
->win
);
7287 gtkconv
->win
->drag_leave_signal
= g_signal_connect(G_OBJECT(gtkconv
->win
->notebook
), "leave_notify_event",
7288 G_CALLBACK(notebook_leave_cb
), gtkconv
->win
);
7292 if (gdk_event_triggers_context_menu((GdkEvent
*)e
)) {
7293 /* Right click was pressed. Popup the context menu. */
7294 GtkWidget
*menu
= gtk_menu_new(), *sub
;
7295 gboolean populated
= populate_menu_with_options(menu
, gtkconv
, TRUE
);
7297 sub
= gtk_menu_item_get_submenu(GTK_MENU_ITEM(gtkconv
->win
->menu
->send_to
));
7298 if (sub
&& gtk_widget_is_sensitive(gtkconv
->win
->menu
->send_to
)) {
7299 GtkWidget
*item
= gtk_menu_item_new_with_mnemonic(_("S_end To"));
7301 pidgin_separator(menu
);
7302 gtk_menu_shell_append(GTK_MENU_SHELL(menu
), item
);
7303 gtk_menu_item_set_submenu(GTK_MENU_ITEM(item
), sub
);
7304 gtk_widget_show(item
);
7305 gtk_widget_show_all(sub
);
7306 } else if (!populated
) {
7307 gtk_widget_destroy(menu
);
7311 gtk_widget_show_all(menu
);
7312 gtk_menu_popup_at_pointer(GTK_MENU(menu
), (GdkEvent
*)e
);
7319 notebook_press_cb(GtkWidget
*widget
, GdkEventButton
*e
, PidginConvWindow
*win
)
7325 GtkAllocation allocation
;
7327 if (e
->button
== GDK_BUTTON_MIDDLE
&& e
->type
== GDK_BUTTON_PRESS
) {
7328 PidginConversation
*gtkconv
;
7329 tab_clicked
= pidgin_conv_get_tab_at_xy(win
, e
->x_root
, e
->y_root
, NULL
);
7331 if (tab_clicked
== -1)
7334 gtkconv
= pidgin_conv_window_get_gtkconv_at_index(win
, tab_clicked
);
7335 close_conv_cb(NULL
, gtkconv
);
7340 if (e
->button
!= GDK_BUTTON_PRIMARY
|| e
->type
!= GDK_BUTTON_PRESS
)
7345 purple_debug(PURPLE_DEBUG_WARNING
, "gtkconv",
7346 "Already in the middle of a window drag at tab_press_cb\n");
7351 * Make sure a tab was actually clicked. The arrow buttons
7354 tab_clicked
= pidgin_conv_get_tab_at_xy(win
, e
->x_root
, e
->y_root
, NULL
);
7356 if (tab_clicked
== -1)
7360 * Get the relative position of the press event, with regards to
7361 * the position of the notebook.
7363 gdk_window_get_origin(gtk_widget_get_window(win
->notebook
), &nb_x
, &nb_y
);
7365 /* Reset the min/max x/y */
7366 win
->drag_min_x
= 0;
7367 win
->drag_min_y
= 0;
7368 win
->drag_max_x
= 0;
7369 win
->drag_max_y
= 0;
7371 /* Find out which tab was dragged. */
7372 page
= gtk_notebook_get_nth_page(GTK_NOTEBOOK(win
->notebook
), tab_clicked
);
7373 tab
= gtk_notebook_get_tab_label(GTK_NOTEBOOK(win
->notebook
), page
);
7375 gtk_widget_get_allocation(tab
, &allocation
);
7377 win
->drag_min_x
= allocation
.x
+ nb_x
;
7378 win
->drag_min_y
= allocation
.y
+ nb_y
;
7379 win
->drag_max_x
= allocation
.width
+ win
->drag_min_x
;
7380 win
->drag_max_y
= allocation
.height
+ win
->drag_min_y
;
7382 /* Make sure the click occurred in the tab. */
7383 if (e
->x_root
< win
->drag_min_x
||
7384 e
->x_root
>= win
->drag_max_x
||
7385 e
->y_root
< win
->drag_min_y
||
7386 e
->y_root
>= win
->drag_max_y
) {
7391 win
->in_predrag
= TRUE
;
7392 win
->drag_tab
= tab_clicked
;
7394 /* Connect the new motion signals. */
7395 win
->drag_motion_signal
=
7396 g_signal_connect(G_OBJECT(widget
), "motion_notify_event",
7397 G_CALLBACK(notebook_motion_cb
), win
);
7399 win
->drag_leave_signal
=
7400 g_signal_connect(G_OBJECT(widget
), "leave_notify_event",
7401 G_CALLBACK(notebook_leave_cb
), win
);
7407 notebook_release_cb(GtkWidget
*widget
, GdkEventButton
*e
, PidginConvWindow
*win
)
7409 PidginConvWindow
*dest_win
;
7410 GtkNotebook
*dest_notebook
;
7411 PidginConversation
*active_gtkconv
;
7412 PidginConversation
*gtkconv
;
7413 gint dest_page_num
= 0;
7414 gboolean new_window
= FALSE
;
7415 gboolean to_right
= FALSE
;
7419 * Don't check to make sure that the event's window matches the
7420 * widget's, because we may be getting an event passed on from the
7423 if (e
->button
!= GDK_BUTTON_PRIMARY
&& e
->type
!= GDK_BUTTON_RELEASE
)
7426 device
= gdk_event_get_device((GdkEvent
*)e
);
7427 if (gdk_display_device_is_grabbed(gdk_device_get_display(device
), device
)) {
7428 gdk_device_ungrab(device
, gdk_event_get_time((GdkEvent
*)e
));
7429 gtk_grab_remove(widget
);
7432 if (!win
->in_predrag
&& !win
->in_drag
)
7435 /* Disconnect the motion signal. */
7436 if (win
->drag_motion_signal
) {
7437 g_signal_handler_disconnect(G_OBJECT(widget
),
7438 win
->drag_motion_signal
);
7440 win
->drag_motion_signal
= 0;
7444 * If we're in a pre-drag, we'll also need to disconnect the leave
7447 if (win
->in_predrag
) {
7448 win
->in_predrag
= FALSE
;
7450 if (win
->drag_leave_signal
) {
7451 g_signal_handler_disconnect(G_OBJECT(widget
),
7452 win
->drag_leave_signal
);
7454 win
->drag_leave_signal
= 0;
7458 /* If we're not in drag... */
7459 /* We're perfectly normal people! */
7463 win
->in_drag
= FALSE
;
7465 pidgin_dnd_hints_hide_all();
7467 dest_win
= pidgin_conv_window_get_at_event((GdkEvent
*)e
);
7469 active_gtkconv
= pidgin_conv_window_get_active_gtkconv(win
);
7471 if (dest_win
== NULL
) {
7472 /* If the current window doesn't have any other conversations,
7473 * there isn't much point transferring the conv to a new window. */
7474 if (pidgin_conv_window_get_gtkconv_count(win
) > 1) {
7475 /* Make a new window to stick this to. */
7476 dest_win
= pidgin_conv_window_new();
7481 if (dest_win
== NULL
)
7484 purple_signal_emit(pidgin_conversations_get_handle(),
7485 "conversation-dragging", win
, dest_win
);
7487 /* Get the destination page number. */
7489 dest_notebook
= GTK_NOTEBOOK(dest_win
->notebook
);
7490 if (gtk_notebook_get_show_tabs(dest_notebook
)) {
7491 dest_page_num
= pidgin_conv_get_tab_at_xy(dest_win
,
7492 e
->x_root
, e
->y_root
, &to_right
);
7495 to_right
= pidgin_conv_xy_to_right_infopane(dest_win
, e
->x_root
, e
->y_root
);
7499 gtkconv
= pidgin_conv_window_get_gtkconv_at_index(win
, win
->drag_tab
);
7501 if (win
== dest_win
) {
7502 gtk_notebook_reorder_child(GTK_NOTEBOOK(win
->notebook
), gtkconv
->tab_cont
, dest_page_num
);
7504 pidgin_conv_window_remove_gtkconv(win
, gtkconv
);
7505 pidgin_conv_window_add_gtkconv(dest_win
, gtkconv
);
7506 gtk_notebook_reorder_child(GTK_NOTEBOOK(dest_win
->notebook
), gtkconv
->tab_cont
, dest_page_num
+ to_right
);
7507 pidgin_conv_window_switch_gtkconv(dest_win
, gtkconv
);
7509 gint win_width
, win_height
;
7511 gtk_window_get_size(GTK_WINDOW(dest_win
->window
),
7512 &win_width
, &win_height
);
7513 #ifdef _WIN32 /* only override window manager placement on Windows */
7514 gtk_window_move(GTK_WINDOW(dest_win
->window
),
7515 e
->x_root
- (win_width
/ 2),
7516 e
->y_root
- (win_height
/ 2));
7519 pidgin_conv_window_show(dest_win
);
7523 gtk_widget_grab_focus(active_gtkconv
->editor
);
7530 before_switch_conv_cb(GtkNotebook
*notebook
, GtkWidget
*page
, gint page_num
,
7533 PidginConvWindow
*win
;
7534 PurpleConversation
*conv
;
7535 PidginConversation
*gtkconv
;
7538 conv
= pidgin_conv_window_get_active_conversation(win
);
7540 g_return_if_fail(conv
!= NULL
);
7542 if (!PURPLE_IS_IM_CONVERSATION(conv
))
7545 gtkconv
= PIDGIN_CONVERSATION(conv
);
7547 if (gtkconv
->u
.im
->typing_timer
!= 0) {
7548 g_source_remove(gtkconv
->u
.im
->typing_timer
);
7549 gtkconv
->u
.im
->typing_timer
= 0;
7552 stop_anim(NULL
, gtkconv
);
7556 close_window(GtkWidget
*w
, PidginConvWindow
*win
)
7558 close_win_cb(w
, NULL
, win
);
7562 detach_tab_cb(GtkWidget
*w
, PidginConvWindow
*win
)
7564 PidginConvWindow
*new_window
;
7565 PidginConversation
*gtkconv
;
7567 gtkconv
= win
->clicked_tab
;
7572 /* Nothing to do if there's only one tab in the window */
7573 if (pidgin_conv_window_get_gtkconv_count(win
) == 1)
7576 pidgin_conv_window_remove_gtkconv(win
, gtkconv
);
7578 new_window
= pidgin_conv_window_new();
7579 pidgin_conv_window_add_gtkconv(new_window
, gtkconv
);
7580 pidgin_conv_window_show(new_window
);
7584 close_others_cb(GtkWidget
*w
, PidginConvWindow
*win
)
7587 PidginConversation
*gtkconv
;
7589 gtkconv
= win
->clicked_tab
;
7594 for (iter
= pidgin_conv_window_get_gtkconvs(win
); iter
; )
7596 PidginConversation
*gconv
= iter
->data
;
7599 if (gconv
!= gtkconv
)
7601 close_conv_cb(NULL
, gconv
);
7607 close_tab_cb(GtkWidget
*w
, PidginConvWindow
*win
)
7609 PidginConversation
*gtkconv
;
7611 gtkconv
= win
->clicked_tab
;
7614 close_conv_cb(NULL
, gtkconv
);
7618 notebook_menu_switch_cb(GtkWidget
*item
, GtkWidget
*child
)
7620 GtkNotebook
*notebook
;
7623 notebook
= GTK_NOTEBOOK(gtk_widget_get_parent(child
));
7624 index
= gtk_notebook_page_num(notebook
, child
);
7625 gtk_notebook_set_current_page(notebook
, index
);
7629 notebook_menu_update_label_cb(GtkWidget
*child
, GParamSpec
*pspec
,
7630 GtkNotebook
*notebook
)
7635 item
= g_object_get_data(G_OBJECT(child
), "popup-menu-item");
7636 label
= gtk_bin_get_child(GTK_BIN(item
));
7638 gtk_container_remove(GTK_CONTAINER(item
), label
);
7640 label
= gtk_notebook_get_menu_label(notebook
, child
);
7642 gtk_widget_show(label
);
7643 gtk_container_add(GTK_CONTAINER(item
), label
);
7644 gtk_widget_show(item
);
7646 gtk_widget_hide(item
);
7651 notebook_add_tab_to_menu_cb(GtkNotebook
*notebook
, GtkWidget
*child
,
7652 guint page_num
, PidginConvWindow
*win
)
7657 item
= gtk_menu_item_new();
7658 label
= gtk_notebook_get_menu_label(notebook
, child
);
7660 gtk_widget_show(label
);
7661 gtk_container_add(GTK_CONTAINER(item
), label
);
7662 gtk_widget_show(item
);
7665 g_signal_connect(child
, "child-notify::menu-label",
7666 G_CALLBACK(notebook_menu_update_label_cb
), notebook
);
7667 g_signal_connect(item
, "activate",
7668 G_CALLBACK(notebook_menu_switch_cb
), child
);
7669 g_object_set_data(G_OBJECT(child
), "popup-menu-item", item
);
7671 gtk_menu_shell_insert(GTK_MENU_SHELL(win
->notebook_menu
), item
, page_num
);
7675 notebook_remove_tab_from_menu_cb(GtkNotebook
*notebook
, GtkWidget
*child
,
7676 guint page_num
, PidginConvWindow
*win
)
7680 /* Disconnecting the "child-notify::menu-label" signal. */
7681 g_signal_handlers_disconnect_by_data(child
, notebook
);
7683 item
= g_object_get_data(G_OBJECT(child
), "popup-menu-item");
7684 gtk_container_remove(GTK_CONTAINER(win
->notebook_menu
), item
);
7689 notebook_reorder_tab_in_menu_cb(GtkNotebook
*notebook
, GtkWidget
*child
,
7690 guint page_num
, PidginConvWindow
*win
)
7694 item
= g_object_get_data(G_OBJECT(child
), "popup-menu-item");
7695 gtk_menu_reorder_child(GTK_MENU(win
->notebook_menu
), item
, page_num
);
7699 notebook_right_click_menu_cb(GtkNotebook
*notebook
, GdkEventButton
*event
,
7700 PidginConvWindow
*win
)
7703 PidginConversation
*gtkconv
;
7705 if (!gdk_event_triggers_context_menu((GdkEvent
*)event
))
7708 gtkconv
= pidgin_conv_window_get_gtkconv_at_index(win
,
7709 pidgin_conv_get_tab_at_xy(win
, event
->x_root
, event
->y_root
, NULL
));
7711 win
->clicked_tab
= gtkconv
;
7713 menu
= win
->notebook_menu
;
7715 gtk_menu_popup_at_pointer(GTK_MENU(menu
), (GdkEvent
*)event
);
7721 remove_edit_entry(PidginConversation
*gtkconv
, GtkWidget
*entry
)
7723 g_signal_handlers_disconnect_matched(G_OBJECT(entry
), G_SIGNAL_MATCH_DATA
,
7724 0, 0, NULL
, NULL
, gtkconv
);
7725 gtk_widget_show(gtkconv
->infopane
);
7726 gtk_widget_grab_focus(gtkconv
->editor
);
7727 gtk_widget_destroy(entry
);
7731 alias_focus_cb(GtkWidget
*widget
, GdkEventFocus
*event
, gpointer user_data
)
7733 remove_edit_entry(user_data
, widget
);
7738 alias_key_press_cb(GtkWidget
*widget
, GdkEventKey
*event
, gpointer user_data
)
7740 if (event
->keyval
== GDK_KEY_Escape
) {
7741 remove_edit_entry(user_data
, widget
);
7748 alias_cb(GtkEntry
*entry
, gpointer user_data
)
7750 PidginConversation
*gtkconv
;
7751 PurpleConversation
*conv
;
7752 PurpleAccount
*account
;
7755 gtkconv
= (PidginConversation
*)user_data
;
7756 if (gtkconv
== NULL
) {
7759 conv
= gtkconv
->active_conv
;
7760 account
= purple_conversation_get_account(conv
);
7761 name
= purple_conversation_get_name(conv
);
7763 if (PURPLE_IS_IM_CONVERSATION(conv
)) {
7765 buddy
= purple_blist_find_buddy(account
, name
);
7766 if (buddy
!= NULL
) {
7767 purple_buddy_set_local_alias(buddy
, gtk_entry_get_text(entry
));
7769 purple_serv_alias_buddy(buddy
);
7770 } else if (PURPLE_IS_CHAT_CONVERSATION(conv
)) {
7771 gtk_entry_set_text(GTK_ENTRY(gtkconv
->u
.chat
->topic_text
), gtk_entry_get_text(entry
));
7772 topic_callback(NULL
, gtkconv
);
7774 remove_edit_entry(user_data
, GTK_WIDGET(entry
));
7778 infopane_entry_activate(PidginConversation
*gtkconv
)
7780 GtkWidget
*entry
= NULL
;
7781 PurpleConversation
*conv
= gtkconv
->active_conv
;
7782 const char *text
= NULL
;
7784 if (!gtk_widget_get_visible(gtkconv
->infopane
)) {
7785 /* There's already an entry for alias. Let's not create another one. */
7789 if (!purple_account_is_connected(purple_conversation_get_account(gtkconv
->active_conv
))) {
7790 /* Do not allow aliasing someone on a disconnected account. */
7794 if (PURPLE_IS_IM_CONVERSATION(conv
)) {
7795 PurpleBuddy
*buddy
= purple_blist_find_buddy(purple_conversation_get_account(gtkconv
->active_conv
), purple_conversation_get_name(gtkconv
->active_conv
));
7797 /* This buddy isn't in your buddy list, so we can't alias him */
7800 text
= purple_buddy_get_contact_alias(buddy
);
7801 } else if (PURPLE_IS_CHAT_CONVERSATION(conv
)) {
7802 PurpleConnection
*gc
;
7803 PurpleProtocol
*protocol
= NULL
;
7805 gc
= purple_conversation_get_connection(conv
);
7807 protocol
= purple_connection_get_protocol(gc
);
7808 if (protocol
&& !PURPLE_PROTOCOL_IMPLEMENTS(protocol
, CHAT
, set_topic
))
7809 /* This protocol doesn't support setting the chat room topic */
7812 text
= purple_chat_conversation_get_topic(PURPLE_CHAT_CONVERSATION(conv
));
7816 entry
= gtk_entry_new();
7817 gtk_entry_set_has_frame(GTK_ENTRY(entry
), FALSE
);
7818 gtk_entry_set_width_chars(GTK_ENTRY(entry
), 10);
7819 gtk_entry_set_alignment(GTK_ENTRY(entry
), 0.5);
7821 gtk_box_pack_start(GTK_BOX(gtkconv
->infopane_hbox
), entry
, TRUE
, TRUE
, 0);
7822 /* after the tab label */
7823 gtk_box_reorder_child(GTK_BOX(gtkconv
->infopane_hbox
), entry
, 0);
7825 g_signal_connect(G_OBJECT(entry
), "activate", G_CALLBACK(alias_cb
), gtkconv
);
7826 g_signal_connect(G_OBJECT(entry
), "focus-out-event", G_CALLBACK(alias_focus_cb
), gtkconv
);
7827 g_signal_connect(G_OBJECT(entry
), "key-press-event", G_CALLBACK(alias_key_press_cb
), gtkconv
);
7830 gtk_entry_set_text(GTK_ENTRY(entry
), text
);
7831 gtk_widget_show(entry
);
7832 gtk_widget_hide(gtkconv
->infopane
);
7833 gtk_widget_grab_focus(entry
);
7839 window_keypress_cb(GtkWidget
*widget
, GdkEventKey
*event
, PidginConvWindow
*win
)
7841 PidginConversation
*gtkconv
= pidgin_conv_window_get_active_gtkconv(win
);
7843 return conv_keypress_common(gtkconv
, event
);
7847 switch_conv_cb(GtkNotebook
*notebook
, GtkWidget
*page
, gint page_num
,
7850 PidginConvWindow
*win
;
7851 PurpleConversation
*conv
;
7852 PidginConversation
*gtkconv
;
7853 const char *sound_method
;
7856 gtkconv
= pidgin_conv_window_get_gtkconv_at_index(win
, page_num
);
7857 conv
= gtkconv
->active_conv
;
7859 g_return_if_fail(conv
!= NULL
);
7861 /* clear unseen flag if conversation is not hidden */
7862 if(!pidgin_conv_is_hidden(gtkconv
)) {
7863 gtkconv_set_unseen(gtkconv
, PIDGIN_UNSEEN_NONE
);
7866 /* Update the menubar */
7868 gtk_toggle_action_set_active(GTK_TOGGLE_ACTION(gtkconv
->win
->menu
->logging
),
7869 purple_conversation_is_logging(conv
));
7871 generate_send_to_items(win
);
7872 generate_e2ee_controls(win
);
7873 regenerate_options_items(win
);
7874 regenerate_plugins_items(win
);
7876 pidgin_conv_switch_active_conversation(conv
);
7878 sound_method
= purple_prefs_get_string(PIDGIN_PREFS_ROOT
"/sound/method");
7879 if (!purple_strequal(sound_method
, "none"))
7880 gtk_toggle_action_set_active(GTK_TOGGLE_ACTION(win
->menu
->sounds
),
7881 gtkconv
->make_sound
);
7883 gtk_toggle_action_set_active(GTK_TOGGLE_ACTION(win
->menu
->show_formatting_toolbar
),
7884 purple_prefs_get_bool(PIDGIN_PREFS_ROOT
"/conversations/show_formatting_toolbar"));
7887 * We pause icons when they are not visible. If this icon should
7888 * be animated then start it back up again.
7890 if (PURPLE_IS_IM_CONVERSATION(conv
) &&
7891 (gtkconv
->u
.im
->animate
))
7892 start_anim(NULL
, gtkconv
);
7894 purple_signal_emit(pidgin_conversations_get_handle(), "conversation-switched", conv
);
7897 /**************************************************************************
7899 **************************************************************************/
7902 pidgin_conv_windows_get_list()
7908 make_status_icon_list(const char *stock
, GtkWidget
*w
)
7911 l
= g_list_append(l
,
7912 gtk_widget_render_icon(w
, stock
,
7913 gtk_icon_size_from_name(PIDGIN_ICON_SIZE_TANGO_EXTRA_SMALL
), "GtkWindow"));
7914 l
= g_list_append(l
,
7915 gtk_widget_render_icon(w
, stock
,
7916 gtk_icon_size_from_name(PIDGIN_ICON_SIZE_TANGO_SMALL
), "GtkWindow"));
7917 l
= g_list_append(l
,
7918 gtk_widget_render_icon(w
, stock
,
7919 gtk_icon_size_from_name(PIDGIN_ICON_SIZE_TANGO_MEDIUM
), "GtkWindow"));
7920 l
= g_list_append(l
,
7921 gtk_widget_render_icon(w
, stock
,
7922 gtk_icon_size_from_name(PIDGIN_ICON_SIZE_TANGO_LARGE
), "GtkWindow"));
7927 create_icon_lists(GtkWidget
*w
)
7929 available_list
= make_status_icon_list(PIDGIN_STOCK_STATUS_AVAILABLE
, w
);
7930 busy_list
= make_status_icon_list(PIDGIN_STOCK_STATUS_BUSY
, w
);
7931 xa_list
= make_status_icon_list(PIDGIN_STOCK_STATUS_XA
, w
);
7932 offline_list
= make_status_icon_list(PIDGIN_STOCK_STATUS_OFFLINE
, w
);
7933 away_list
= make_status_icon_list(PIDGIN_STOCK_STATUS_AWAY
, w
);
7934 protocol_lists
= g_hash_table_new(g_str_hash
, g_str_equal
);
7938 plugin_changed_cb(PurplePlugin
*p
, gpointer data
)
7940 regenerate_plugins_items(data
);
7943 static gboolean
gtk_conv_configure_cb(GtkWidget
*w
, GdkEventConfigure
*event
, gpointer data
) {
7946 if (gtk_widget_get_visible(w
))
7947 gtk_window_get_position(GTK_WINDOW(w
), &x
, &y
);
7949 return FALSE
; /* carry on normally */
7951 /* Workaround for GTK+ bug # 169811 - "configure_event" is fired
7952 * when the window is being maximized */
7953 if (gdk_window_get_state(gtk_widget_get_window(w
)) & GDK_WINDOW_STATE_MAXIMIZED
)
7956 /* don't save off-screen positioning */
7957 if (x
+ event
->width
< 0 ||
7958 y
+ event
->height
< 0 ||
7959 x
> gdk_screen_width() ||
7960 y
> gdk_screen_height())
7961 return FALSE
; /* carry on normally */
7963 /* store the position */
7964 purple_prefs_set_int(PIDGIN_PREFS_ROOT
"/conversations/im/x", x
);
7965 purple_prefs_set_int(PIDGIN_PREFS_ROOT
"/conversations/im/y", y
);
7966 purple_prefs_set_int(PIDGIN_PREFS_ROOT
"/conversations/im/width", event
->width
);
7967 purple_prefs_set_int(PIDGIN_PREFS_ROOT
"/conversations/im/height", event
->height
);
7969 /* continue to handle event normally */
7975 pidgin_conv_set_position_size(PidginConvWindow
*win
, int conv_x
, int conv_y
,
7976 int conv_width
, int conv_height
)
7978 /* if the window exists, is hidden, we're saving positions, and the
7979 * position is sane... */
7980 if (win
&& win
->window
&&
7981 !gtk_widget_get_visible(win
->window
) && conv_width
!= 0) {
7983 #ifdef _WIN32 /* only override window manager placement on Windows */
7984 /* ...check position is on screen... */
7985 if (conv_x
>= gdk_screen_width())
7986 conv_x
= gdk_screen_width() - 100;
7987 else if (conv_x
+ conv_width
< 0)
7990 if (conv_y
>= gdk_screen_height())
7991 conv_y
= gdk_screen_height() - 100;
7992 else if (conv_y
+ conv_height
< 0)
7995 /* ...and move it back. */
7996 gtk_window_move(GTK_WINDOW(win
->window
), conv_x
, conv_y
);
7998 gtk_window_resize(GTK_WINDOW(win
->window
), conv_width
, conv_height
);
8003 pidgin_conv_restore_position(PidginConvWindow
*win
) {
8004 pidgin_conv_set_position_size(win
,
8005 purple_prefs_get_int(PIDGIN_PREFS_ROOT
"/conversations/im/x"),
8006 purple_prefs_get_int(PIDGIN_PREFS_ROOT
"/conversations/im/y"),
8007 purple_prefs_get_int(PIDGIN_PREFS_ROOT
"/conversations/im/width"),
8008 purple_prefs_get_int(PIDGIN_PREFS_ROOT
"/conversations/im/height"));
8012 pidgin_conv_window_new()
8014 PidginConvWindow
*win
;
8015 GtkPositionType pos
;
8016 GtkWidget
*testidea
;
8020 GdkModifierType state
;
8022 win
= g_malloc0(sizeof(PidginConvWindow
));
8023 win
->menu
= g_malloc0(sizeof(PidginConvWindowMenu
));
8025 window_list
= g_list_append(window_list
, win
);
8027 /* Create the window. */
8028 win
->window
= pidgin_create_window(NULL
, 0, "conversation", TRUE
);
8029 /*_pidgin_widget_set_accessible_name(win->window, "Conversations");*/
8030 if (!gtk_get_current_event_state(&state
))
8031 gtk_window_set_focus_on_map(GTK_WINDOW(win
->window
), FALSE
);
8033 /* Etan: I really think this entire function call should happen only
8034 * when we are on Windows but I was informed that back before we used
8035 * to save the window position we stored the window size, so I'm
8036 * leaving it for now. */
8037 #if TRUE || defined(_WIN32)
8038 pidgin_conv_restore_position(win
);
8041 if (available_list
== NULL
) {
8042 create_icon_lists(win
->window
);
8045 g_signal_connect(G_OBJECT(win
->window
), "delete_event",
8046 G_CALLBACK(close_win_cb
), win
);
8047 g_signal_connect(G_OBJECT(win
->window
), "focus_in_event",
8048 G_CALLBACK(focus_win_cb
), win
);
8050 /* Intercept keystrokes from the menu items */
8051 g_signal_connect(G_OBJECT(win
->window
), "key_press_event",
8052 G_CALLBACK(window_keypress_cb
), win
);
8055 /* Create the notebook. */
8056 win
->notebook
= gtk_notebook_new();
8058 pos
= purple_prefs_get_int(PIDGIN_PREFS_ROOT
"/conversations/tab_side");
8060 gtk_notebook_set_tab_pos(GTK_NOTEBOOK(win
->notebook
), pos
);
8061 gtk_notebook_set_scrollable(GTK_NOTEBOOK(win
->notebook
), TRUE
);
8062 gtk_notebook_set_show_tabs(GTK_NOTEBOOK(win
->notebook
), FALSE
);
8063 gtk_notebook_set_show_border(GTK_NOTEBOOK(win
->notebook
), TRUE
);
8065 menu
= win
->notebook_menu
= gtk_menu_new();
8067 pidgin_separator(GTK_WIDGET(menu
));
8069 item
= gtk_menu_item_new_with_label(_("Close other tabs"));
8070 gtk_widget_show(item
);
8071 gtk_menu_shell_append(GTK_MENU_SHELL(menu
), item
);
8072 g_signal_connect(G_OBJECT(item
), "activate",
8073 G_CALLBACK(close_others_cb
), win
);
8075 item
= gtk_menu_item_new_with_label(_("Close all tabs"));
8076 gtk_widget_show(item
);
8077 gtk_menu_shell_append(GTK_MENU_SHELL(menu
), item
);
8078 g_signal_connect(G_OBJECT(item
), "activate",
8079 G_CALLBACK(close_window
), win
);
8081 pidgin_separator(menu
);
8083 item
= gtk_menu_item_new_with_label(_("Detach this tab"));
8084 gtk_widget_show(item
);
8085 gtk_menu_shell_append(GTK_MENU_SHELL(menu
), item
);
8086 g_signal_connect(G_OBJECT(item
), "activate",
8087 G_CALLBACK(detach_tab_cb
), win
);
8089 item
= gtk_menu_item_new_with_label(_("Close this tab"));
8090 gtk_widget_show(item
);
8091 gtk_menu_shell_append(GTK_MENU_SHELL(menu
), item
);
8092 g_signal_connect(G_OBJECT(item
), "activate",
8093 G_CALLBACK(close_tab_cb
), win
);
8095 g_signal_connect(G_OBJECT(win
->notebook
), "page-added",
8096 G_CALLBACK(notebook_add_tab_to_menu_cb
), win
);
8097 g_signal_connect(G_OBJECT(win
->notebook
), "page-removed",
8098 G_CALLBACK(notebook_remove_tab_from_menu_cb
), win
);
8099 g_signal_connect(G_OBJECT(win
->notebook
), "page-reordered",
8100 G_CALLBACK(notebook_reorder_tab_in_menu_cb
), win
);
8102 g_signal_connect(G_OBJECT(win
->notebook
), "button-press-event",
8103 G_CALLBACK(notebook_right_click_menu_cb
), win
);
8105 gtk_widget_show(win
->notebook
);
8107 g_signal_connect(G_OBJECT(win
->notebook
), "switch_page",
8108 G_CALLBACK(before_switch_conv_cb
), win
);
8109 g_signal_connect_after(G_OBJECT(win
->notebook
), "switch_page",
8110 G_CALLBACK(switch_conv_cb
), win
);
8112 /* Setup the tab drag and drop signals. */
8113 gtk_widget_add_events(win
->notebook
,
8114 GDK_BUTTON1_MOTION_MASK
| GDK_LEAVE_NOTIFY_MASK
);
8115 g_signal_connect(G_OBJECT(win
->notebook
), "button_press_event",
8116 G_CALLBACK(notebook_press_cb
), win
);
8117 g_signal_connect(G_OBJECT(win
->notebook
), "button_release_event",
8118 G_CALLBACK(notebook_release_cb
), win
);
8120 testidea
= gtk_box_new(GTK_ORIENTATION_VERTICAL
, 0);
8122 /* Setup the menubar. */
8123 menubar
= setup_menubar(win
);
8124 gtk_box_pack_start(GTK_BOX(testidea
), menubar
, FALSE
, TRUE
, 0);
8126 gtk_box_pack_start(GTK_BOX(testidea
), win
->notebook
, TRUE
, TRUE
, 0);
8128 gtk_container_add(GTK_CONTAINER(win
->window
), testidea
);
8130 gtk_widget_show(testidea
);
8132 /* Update the plugin actions when plugins are (un)loaded */
8133 purple_signal_connect(purple_plugins_get_handle(), "plugin-load",
8134 win
, PURPLE_CALLBACK(plugin_changed_cb
), win
);
8135 purple_signal_connect(purple_plugins_get_handle(), "plugin-unload",
8136 win
, PURPLE_CALLBACK(plugin_changed_cb
), win
);
8140 g_signal_connect(G_OBJECT(win
->window
), "show",
8141 G_CALLBACK(winpidgin_ensure_onscreen
), win
->window
);
8143 if (purple_prefs_get_bool(PIDGIN_PREFS_ROOT
"/win32/minimize_new_convs")
8144 && !gtk_get_current_event_state(&state
))
8145 gtk_window_iconify(GTK_WINDOW(win
->window
));
8148 purple_signal_emit(pidgin_conversations_get_handle(),
8149 "conversation-window-created", win
);
8152 pidgin_conversations_set_tab_colors();
8158 pidgin_conv_window_destroy(PidginConvWindow
*win
)
8160 if (win
->gtkconvs
) {
8161 GList
*iter
= win
->gtkconvs
;
8164 PidginConversation
*gtkconv
= iter
->data
;
8166 close_conv_cb(NULL
, gtkconv
);
8171 purple_prefs_disconnect_by_handle(win
);
8172 window_list
= g_list_remove(window_list
, win
);
8174 gtk_widget_destroy(win
->notebook_menu
);
8175 gtk_widget_destroy(win
->window
);
8177 g_object_unref(G_OBJECT(win
->menu
->ui
));
8179 purple_notify_close_with_handle(win
);
8180 purple_signals_disconnect_by_handle(win
);
8187 pidgin_conv_window_show(PidginConvWindow
*win
)
8189 gtk_widget_show(win
->window
);
8193 pidgin_conv_window_hide(PidginConvWindow
*win
)
8195 gtk_widget_hide(win
->window
);
8199 pidgin_conv_window_raise(PidginConvWindow
*win
)
8201 gdk_window_raise(GDK_WINDOW(gtk_widget_get_window(win
->window
)));
8205 pidgin_conv_window_switch_gtkconv(PidginConvWindow
*win
, PidginConversation
*gtkconv
)
8207 gtk_notebook_set_current_page(GTK_NOTEBOOK(win
->notebook
),
8208 gtk_notebook_page_num(GTK_NOTEBOOK(win
->notebook
),
8209 gtkconv
->tab_cont
));
8213 gtkconv_tab_set_tip(GtkWidget
*widget
, GdkEventCrossing
*event
, PidginConversation
*gtkconv
)
8215 /* PANGO_VERSION_CHECK macro was introduced in 1.15. So we need this double check. */
8216 #ifndef PANGO_VERSION_CHECK
8217 #define pango_layout_is_ellipsized(l) TRUE
8218 #elif !PANGO_VERSION_CHECK(1,16,0)
8219 #define pango_layout_is_ellipsized(l) TRUE
8221 PangoLayout
*layout
;
8223 layout
= gtk_label_get_layout(GTK_LABEL(gtkconv
->tab_label
));
8224 if (pango_layout_is_ellipsized(layout
))
8225 gtk_widget_set_tooltip_text(widget
, gtk_label_get_text(GTK_LABEL(gtkconv
->tab_label
)));
8227 gtk_widget_set_tooltip_text(widget
, NULL
);
8233 set_default_tab_colors(GtkWidget
*widget
)
8236 GtkCssProvider
*provider
;
8237 GError
*error
= NULL
;
8241 const char *labelname
;
8244 {"tab-label-typing", "#4e9a06"},
8245 {"tab-label-typed", "#c4a000"},
8246 {"tab-label-attention", "#006aff"},
8247 {"tab-label-unreadchat", "#cc0000"},
8248 {"tab-label-event", "#888a85"},
8252 str
= g_string_new(NULL
);
8254 for (iter
= 0; styles
[iter
].labelname
; iter
++) {
8255 g_string_append_printf(str
,
8259 styles
[iter
].labelname
,
8260 styles
[iter
].color
);
8263 provider
= gtk_css_provider_new();
8265 gtk_css_provider_load_from_data(provider
, str
->str
, str
->len
, &error
);
8267 gtk_style_context_add_provider(gtk_widget_get_style_context(widget
),
8268 GTK_STYLE_PROVIDER(provider
),
8269 GTK_STYLE_PROVIDER_PRIORITY_APPLICATION
);
8272 g_error_free(error
);
8273 g_string_free(str
, TRUE
);
8277 pidgin_conv_window_add_gtkconv(PidginConvWindow
*win
, PidginConversation
*gtkconv
)
8279 PurpleConversation
*conv
= gtkconv
->active_conv
;
8280 PidginConversation
*focus_gtkconv
;
8281 GtkWidget
*tab_cont
= gtkconv
->tab_cont
;
8282 const gchar
*tmp_lab
;
8284 win
->gtkconvs
= g_list_append(win
->gtkconvs
, gtkconv
);
8287 if (win
->gtkconvs
&& win
->gtkconvs
->next
&& win
->gtkconvs
->next
->next
== NULL
)
8288 pidgin_conv_tab_pack(win
, ((PidginConversation
*)win
->gtkconvs
->data
));
8292 gtkconv
->close
= pidgin_create_small_button(gtk_label_new("×"));
8293 gtk_widget_set_tooltip_text(gtkconv
->close
, _("Close conversation"));
8295 g_signal_connect(gtkconv
->close
, "clicked", G_CALLBACK (close_conv_cb
), gtkconv
);
8298 gtkconv
->icon
= gtk_image_new();
8299 gtkconv
->menu_icon
= gtk_image_new();
8300 g_object_set(G_OBJECT(gtkconv
->icon
),
8301 "icon-size", gtk_icon_size_from_name(PIDGIN_ICON_SIZE_TANGO_MICROSCOPIC
),
8303 g_object_set(G_OBJECT(gtkconv
->menu_icon
),
8304 "icon-size", gtk_icon_size_from_name(PIDGIN_ICON_SIZE_TANGO_MICROSCOPIC
),
8306 gtk_widget_show(gtkconv
->icon
);
8307 update_tab_icon(conv
);
8310 gtkconv
->tab_label
= gtk_label_new(tmp_lab
= purple_conversation_get_title(conv
));
8311 set_default_tab_colors(gtkconv
->tab_label
);
8312 gtk_widget_set_name(gtkconv
->tab_label
, "tab-label");
8314 gtkconv
->menu_tabby
= gtk_box_new(GTK_ORIENTATION_HORIZONTAL
, PIDGIN_HIG_BOX_SPACE
);
8315 gtkconv
->menu_label
= gtk_label_new(tmp_lab
);
8316 gtk_box_pack_start(GTK_BOX(gtkconv
->menu_tabby
), gtkconv
->menu_icon
, FALSE
, FALSE
, 0);
8318 gtk_widget_show_all(gtkconv
->menu_icon
);
8320 gtk_box_pack_start(GTK_BOX(gtkconv
->menu_tabby
), gtkconv
->menu_label
, TRUE
, TRUE
, 0);
8321 gtk_widget_show(gtkconv
->menu_label
);
8322 gtk_label_set_xalign(GTK_LABEL(gtkconv
->menu_label
), 0);
8323 gtk_label_set_yalign(GTK_LABEL(gtkconv
->menu_label
), 0);
8325 gtk_widget_show(gtkconv
->menu_tabby
);
8327 if (PURPLE_IS_IM_CONVERSATION(conv
))
8328 pidgin_conv_update_buddy_icon(PURPLE_IM_CONVERSATION(conv
));
8330 /* Build and set conversations tab */
8331 pidgin_conv_tab_pack(win
, gtkconv
);
8333 gtk_notebook_set_menu_label(GTK_NOTEBOOK(win
->notebook
), tab_cont
, gtkconv
->menu_tabby
);
8335 gtk_widget_show(tab_cont
);
8337 if (pidgin_conv_window_get_gtkconv_count(win
) == 1) {
8338 /* Er, bug in notebooks? Switch to the page manually. */
8339 gtk_notebook_set_current_page(GTK_NOTEBOOK(win
->notebook
), 0);
8341 gtk_notebook_set_show_tabs(GTK_NOTEBOOK(win
->notebook
), TRUE
);
8344 focus_gtkconv
= g_list_nth_data(pidgin_conv_window_get_gtkconvs(win
),
8345 gtk_notebook_get_current_page(GTK_NOTEBOOK(win
->notebook
)));
8346 gtk_widget_grab_focus(focus_gtkconv
->editor
);
8348 if (pidgin_conv_window_get_gtkconv_count(win
) == 1)
8349 update_send_to_selection(win
);
8353 pidgin_conv_tab_pack(PidginConvWindow
*win
, PidginConversation
*gtkconv
)
8355 gboolean tabs_side
= FALSE
;
8357 GtkWidget
*first
, *third
, *ebox
, *parent
;
8359 if (purple_prefs_get_int(PIDGIN_PREFS_ROOT
"/conversations/tab_side") == GTK_POS_LEFT
||
8360 purple_prefs_get_int(PIDGIN_PREFS_ROOT
"/conversations/tab_side") == GTK_POS_RIGHT
)
8362 else if (purple_prefs_get_int(PIDGIN_PREFS_ROOT
"/conversations/tab_side") == (GTK_POS_LEFT
|8))
8364 else if (purple_prefs_get_int(PIDGIN_PREFS_ROOT
"/conversations/tab_side") == (GTK_POS_RIGHT
|8))
8368 g_object_set(G_OBJECT(gtkconv
->tab_label
), "ellipsize", PANGO_ELLIPSIZE_END
, NULL
);
8369 gtk_label_set_width_chars(GTK_LABEL(gtkconv
->tab_label
), 4);
8371 g_object_set(G_OBJECT(gtkconv
->tab_label
), "ellipsize", PANGO_ELLIPSIZE_NONE
, NULL
);
8372 gtk_label_set_width_chars(GTK_LABEL(gtkconv
->tab_label
), -1);
8376 gtk_label_set_width_chars(
8377 GTK_LABEL(gtkconv
->tab_label
),
8378 MIN(g_utf8_strlen(gtk_label_get_text(GTK_LABEL(gtkconv
->tab_label
)), -1), 12)
8382 gtk_label_set_angle(GTK_LABEL(gtkconv
->tab_label
), angle
);
8385 gtkconv
->tabby
= gtk_box_new(GTK_ORIENTATION_VERTICAL
, PIDGIN_HIG_BOX_SPACE
);
8387 gtkconv
->tabby
= gtk_box_new(GTK_ORIENTATION_HORIZONTAL
, PIDGIN_HIG_BOX_SPACE
);
8388 gtk_widget_set_name(gtkconv
->tabby
, "tab-container");
8390 /* select the correct ordering for verticle tabs */
8392 first
= gtkconv
->close
;
8393 third
= gtkconv
->icon
;
8395 first
= gtkconv
->icon
;
8396 third
= gtkconv
->close
;
8399 ebox
= gtk_event_box_new();
8400 gtk_event_box_set_visible_window(GTK_EVENT_BOX(ebox
), FALSE
);
8401 gtk_container_add(GTK_CONTAINER(ebox
), gtkconv
->tabby
);
8402 g_signal_connect(G_OBJECT(ebox
), "enter-notify-event",
8403 G_CALLBACK(gtkconv_tab_set_tip
), gtkconv
);
8405 parent
= gtk_widget_get_parent(gtkconv
->tab_label
);
8406 if (parent
!= NULL
) {
8407 /* reparent old widgets on preference changes */
8408 g_object_ref(first
);
8409 g_object_ref(gtkconv
->tab_label
);
8410 g_object_ref(third
);
8411 gtk_container_remove(GTK_CONTAINER(parent
), first
);
8412 gtk_container_remove(GTK_CONTAINER(parent
), gtkconv
->tab_label
);
8413 gtk_container_remove(GTK_CONTAINER(parent
), third
);
8416 gtk_box_pack_start(GTK_BOX(gtkconv
->tabby
), first
, FALSE
, FALSE
, 0);
8417 gtk_box_pack_start(GTK_BOX(gtkconv
->tabby
), gtkconv
->tab_label
, TRUE
, TRUE
, 0);
8418 gtk_box_pack_start(GTK_BOX(gtkconv
->tabby
), third
, FALSE
, FALSE
, 0);
8420 if (parent
== NULL
) {
8421 /* Add this pane to the conversation's notebook. */
8422 gtk_notebook_append_page(GTK_NOTEBOOK(win
->notebook
), gtkconv
->tab_cont
, ebox
);
8424 /* reparent old widgets on preference changes */
8425 g_object_unref(first
);
8426 g_object_unref(gtkconv
->tab_label
);
8427 g_object_unref(third
);
8429 /* Reset the tabs label to the new version */
8430 gtk_notebook_set_tab_label(GTK_NOTEBOOK(win
->notebook
), gtkconv
->tab_cont
, ebox
);
8433 gtk_container_child_set(GTK_CONTAINER(win
->notebook
), gtkconv
->tab_cont
,
8434 "tab-expand", !tabs_side
&& !angle
,
8435 "tab-fill", TRUE
, NULL
);
8437 if (pidgin_conv_window_get_gtkconv_count(win
) == 1)
8438 gtk_notebook_set_show_tabs(GTK_NOTEBOOK(win
->notebook
),
8439 purple_prefs_get_bool(PIDGIN_PREFS_ROOT
"/conversations/tabs") &&
8440 (!purple_prefs_get_bool(PIDGIN_PREFS_ROOT
"/conversations/im/show_buddy_icons") ||
8441 purple_prefs_get_int(PIDGIN_PREFS_ROOT
"/conversations/tab_side") != GTK_POS_TOP
));
8443 /* show the widgets */
8444 /* gtk_widget_show(gtkconv->icon); */
8445 gtk_widget_show(gtkconv
->tab_label
);
8446 if (purple_prefs_get_bool(PIDGIN_PREFS_ROOT
"/conversations/close_on_tabs"))
8447 gtk_widget_show(gtkconv
->close
);
8448 gtk_widget_show(gtkconv
->tabby
);
8449 gtk_widget_show(ebox
);
8453 pidgin_conv_window_remove_gtkconv(PidginConvWindow
*win
, PidginConversation
*gtkconv
)
8457 index
= gtk_notebook_page_num(GTK_NOTEBOOK(win
->notebook
), gtkconv
->tab_cont
);
8459 g_object_ref_sink(G_OBJECT(gtkconv
->tab_cont
));
8461 gtk_notebook_remove_page(GTK_NOTEBOOK(win
->notebook
), index
);
8463 win
->gtkconvs
= g_list_remove(win
->gtkconvs
, gtkconv
);
8465 g_signal_handlers_disconnect_matched(win
->window
, G_SIGNAL_MATCH_DATA
,
8466 0, 0, NULL
, NULL
, gtkconv
);
8468 if (win
->gtkconvs
&& win
->gtkconvs
->next
== NULL
)
8469 pidgin_conv_tab_pack(win
, win
->gtkconvs
->data
);
8471 if (!win
->gtkconvs
&& win
!= hidden_convwin
)
8472 pidgin_conv_window_destroy(win
);
8475 PidginConversation
*
8476 pidgin_conv_window_get_gtkconv_at_index(const PidginConvWindow
*win
, int index
)
8478 GtkWidget
*tab_cont
;
8482 tab_cont
= gtk_notebook_get_nth_page(GTK_NOTEBOOK(win
->notebook
), index
);
8483 return tab_cont
? g_object_get_data(G_OBJECT(tab_cont
), "PidginConversation") : NULL
;
8486 PidginConversation
*
8487 pidgin_conv_window_get_active_gtkconv(const PidginConvWindow
*win
)
8490 GtkWidget
*tab_cont
;
8492 index
= gtk_notebook_get_current_page(GTK_NOTEBOOK(win
->notebook
));
8495 tab_cont
= gtk_notebook_get_nth_page(GTK_NOTEBOOK(win
->notebook
), index
);
8498 return g_object_get_data(G_OBJECT(tab_cont
), "PidginConversation");
8502 PurpleConversation
*
8503 pidgin_conv_window_get_active_conversation(const PidginConvWindow
*win
)
8505 PidginConversation
*gtkconv
;
8507 gtkconv
= pidgin_conv_window_get_active_gtkconv(win
);
8508 return gtkconv
? gtkconv
->active_conv
: NULL
;
8512 pidgin_conv_window_is_active_conversation(const PurpleConversation
*conv
)
8514 return conv
== pidgin_conv_window_get_active_conversation(PIDGIN_CONVERSATION(conv
)->win
);
8518 pidgin_conv_window_has_focus(PidginConvWindow
*win
)
8520 gboolean has_focus
= FALSE
;
8522 g_object_get(G_OBJECT(win
->window
), "has-toplevel-focus", &has_focus
, NULL
);
8528 pidgin_conv_window_get_at_event(GdkEvent
*event
)
8530 PidginConvWindow
*win
;
8535 gdkwin
= gdk_device_get_window_at_position(gdk_event_get_device(event
),
8539 gdkwin
= gdk_window_get_toplevel(gdkwin
);
8541 for (l
= pidgin_conv_windows_get_list(); l
!= NULL
; l
= l
->next
) {
8544 if (gdkwin
== gtk_widget_get_window(win
->window
))
8552 pidgin_conv_window_get_gtkconvs(PidginConvWindow
*win
)
8554 return win
->gtkconvs
;
8558 pidgin_conv_window_get_gtkconv_count(PidginConvWindow
*win
)
8560 return g_list_length(win
->gtkconvs
);
8564 pidgin_conv_window_first_im(void)
8566 GList
*wins
, *convs
;
8567 PidginConvWindow
*win
;
8568 PidginConversation
*conv
;
8570 for (wins
= pidgin_conv_windows_get_list(); wins
!= NULL
; wins
= wins
->next
) {
8573 for (convs
= win
->gtkconvs
;
8575 convs
= convs
->next
) {
8579 if (PURPLE_IS_IM_CONVERSATION(conv
->active_conv
))
8588 pidgin_conv_window_last_im(void)
8590 GList
*wins
, *convs
;
8591 PidginConvWindow
*win
;
8592 PidginConversation
*conv
;
8594 for (wins
= g_list_last(pidgin_conv_windows_get_list());
8596 wins
= wins
->prev
) {
8600 for (convs
= win
->gtkconvs
;
8602 convs
= convs
->next
) {
8606 if (PURPLE_IS_IM_CONVERSATION(conv
->active_conv
))
8615 pidgin_conv_window_first_chat(void)
8617 GList
*wins
, *convs
;
8618 PidginConvWindow
*win
;
8619 PidginConversation
*conv
;
8621 for (wins
= pidgin_conv_windows_get_list(); wins
!= NULL
; wins
= wins
->next
) {
8624 for (convs
= win
->gtkconvs
;
8626 convs
= convs
->next
) {
8630 if (PURPLE_IS_CHAT_CONVERSATION(conv
->active_conv
))
8639 pidgin_conv_window_last_chat(void)
8641 GList
*wins
, *convs
;
8642 PidginConvWindow
*win
;
8643 PidginConversation
*conv
;
8645 for (wins
= g_list_last(pidgin_conv_windows_get_list());
8647 wins
= wins
->prev
) {
8651 for (convs
= win
->gtkconvs
;
8653 convs
= convs
->next
) {
8657 if (PURPLE_IS_CHAT_CONVERSATION(conv
->active_conv
))
8666 /**************************************************************************
8667 * Conversation placement functions
8668 **************************************************************************/
8673 PidginConvPlacementFunc fnc
;
8675 } ConvPlacementData
;
8677 static GList
*conv_placement_fncs
= NULL
;
8678 static PidginConvPlacementFunc place_conv
= NULL
;
8680 /* This one places conversations in the last made window. */
8682 conv_placement_last_created_win(PidginConversation
*conv
)
8684 PidginConvWindow
*win
;
8686 GList
*l
= g_list_last(pidgin_conv_windows_get_list());
8687 win
= l
? l
->data
: NULL
;;
8690 win
= pidgin_conv_window_new();
8692 g_signal_connect(G_OBJECT(win
->window
), "configure_event",
8693 G_CALLBACK(gtk_conv_configure_cb
), NULL
);
8695 pidgin_conv_window_add_gtkconv(win
, conv
);
8696 pidgin_conv_window_show(win
);
8698 pidgin_conv_window_add_gtkconv(win
, conv
);
8702 /* This one places conversations in the last made window of the same type. */
8704 conv_placement_last_created_win_type_configured_cb(GtkWidget
*w
,
8705 GdkEventConfigure
*event
, PidginConversation
*conv
)
8710 if (gtk_widget_get_visible(w
))
8711 gtk_window_get_position(GTK_WINDOW(w
), &x
, &y
);
8713 return FALSE
; /* carry on normally */
8715 /* Workaround for GTK+ bug # 169811 - "configure_event" is fired
8716 * when the window is being maximized */
8717 if (gdk_window_get_state(gtk_widget_get_window(w
)) & GDK_WINDOW_STATE_MAXIMIZED
)
8720 /* don't save off-screen positioning */
8721 if (x
+ event
->width
< 0 ||
8722 y
+ event
->height
< 0 ||
8723 x
> gdk_screen_width() ||
8724 y
> gdk_screen_height())
8725 return FALSE
; /* carry on normally */
8727 for (all
= conv
->convs
; all
!= NULL
; all
= all
->next
) {
8728 if (PURPLE_IS_IM_CONVERSATION(conv
->active_conv
) != PURPLE_IS_IM_CONVERSATION(all
->data
)) {
8729 /* this window has different types of conversation, don't save */
8734 if (PURPLE_IS_IM_CONVERSATION(conv
->active_conv
)) {
8735 purple_prefs_set_int(PIDGIN_PREFS_ROOT
"/conversations/im/x", x
);
8736 purple_prefs_set_int(PIDGIN_PREFS_ROOT
"/conversations/im/y", y
);
8737 purple_prefs_set_int(PIDGIN_PREFS_ROOT
"/conversations/im/width", event
->width
);
8738 purple_prefs_set_int(PIDGIN_PREFS_ROOT
"/conversations/im/height", event
->height
);
8739 } else if (PURPLE_IS_CHAT_CONVERSATION(conv
->active_conv
)) {
8740 purple_prefs_set_int(PIDGIN_PREFS_ROOT
"/conversations/chat/x", x
);
8741 purple_prefs_set_int(PIDGIN_PREFS_ROOT
"/conversations/chat/y", y
);
8742 purple_prefs_set_int(PIDGIN_PREFS_ROOT
"/conversations/chat/width", event
->width
);
8743 purple_prefs_set_int(PIDGIN_PREFS_ROOT
"/conversations/chat/height", event
->height
);
8750 conv_placement_last_created_win_type(PidginConversation
*conv
)
8752 PidginConvWindow
*win
;
8754 if (PURPLE_IS_IM_CONVERSATION(conv
->active_conv
))
8755 win
= pidgin_conv_window_last_im();
8757 win
= pidgin_conv_window_last_chat();
8760 win
= pidgin_conv_window_new();
8762 if (PURPLE_IS_IM_CONVERSATION(conv
->active_conv
) ||
8763 purple_prefs_get_int(PIDGIN_PREFS_ROOT
"/conversations/chat/width") == 0) {
8764 pidgin_conv_set_position_size(win
,
8765 purple_prefs_get_int(PIDGIN_PREFS_ROOT
"/conversations/im/x"),
8766 purple_prefs_get_int(PIDGIN_PREFS_ROOT
"/conversations/im/y"),
8767 purple_prefs_get_int(PIDGIN_PREFS_ROOT
"/conversations/im/width"),
8768 purple_prefs_get_int(PIDGIN_PREFS_ROOT
"/conversations/im/height"));
8769 } else if (PURPLE_IS_CHAT_CONVERSATION(conv
->active_conv
)) {
8770 pidgin_conv_set_position_size(win
,
8771 purple_prefs_get_int(PIDGIN_PREFS_ROOT
"/conversations/chat/x"),
8772 purple_prefs_get_int(PIDGIN_PREFS_ROOT
"/conversations/chat/y"),
8773 purple_prefs_get_int(PIDGIN_PREFS_ROOT
"/conversations/chat/width"),
8774 purple_prefs_get_int(PIDGIN_PREFS_ROOT
"/conversations/chat/height"));
8777 pidgin_conv_window_add_gtkconv(win
, conv
);
8778 pidgin_conv_window_show(win
);
8780 g_signal_connect(G_OBJECT(win
->window
), "configure_event",
8781 G_CALLBACK(conv_placement_last_created_win_type_configured_cb
), conv
);
8783 pidgin_conv_window_add_gtkconv(win
, conv
);
8786 /* This one places each conversation in its own window. */
8788 conv_placement_new_window(PidginConversation
*conv
)
8790 PidginConvWindow
*win
;
8792 win
= pidgin_conv_window_new();
8794 g_signal_connect(G_OBJECT(win
->window
), "configure_event",
8795 G_CALLBACK(gtk_conv_configure_cb
), NULL
);
8797 pidgin_conv_window_add_gtkconv(win
, conv
);
8799 pidgin_conv_window_show(win
);
8802 static PurpleGroup
*
8803 conv_get_group(PidginConversation
*conv
)
8805 PurpleGroup
*group
= NULL
;
8807 if (PURPLE_IS_IM_CONVERSATION(conv
->active_conv
)) {
8810 buddy
= purple_blist_find_buddy(purple_conversation_get_account(conv
->active_conv
),
8811 purple_conversation_get_name(conv
->active_conv
));
8814 group
= purple_buddy_get_group(buddy
);
8816 } else if (PURPLE_IS_CHAT_CONVERSATION(conv
->active_conv
)) {
8819 chat
= purple_blist_find_chat(purple_conversation_get_account(conv
->active_conv
),
8820 purple_conversation_get_name(conv
->active_conv
));
8823 group
= purple_chat_get_group(chat
);
8830 * This groups things by, well, group. Buddies from groups will always be
8831 * grouped together, and a buddy from a group not belonging to any currently
8832 * open windows will get a new window.
8835 conv_placement_by_group(PidginConversation
*conv
)
8837 PurpleGroup
*group
= NULL
;
8840 group
= conv_get_group(conv
);
8842 /* Go through the list of IMs and find one with this group. */
8843 for (wl
= pidgin_conv_windows_get_list(); wl
!= NULL
; wl
= wl
->next
) {
8844 PidginConvWindow
*win2
;
8845 PidginConversation
*conv2
;
8846 PurpleGroup
*group2
= NULL
;
8850 for (cl
= win2
->gtkconvs
;
8855 group2
= conv_get_group(conv2
);
8857 if (group
== group2
) {
8858 pidgin_conv_window_add_gtkconv(win2
, conv
);
8865 /* Make a new window. */
8866 conv_placement_new_window(conv
);
8869 /* This groups things by account. Otherwise, the same semantics as above */
8871 conv_placement_by_account(PidginConversation
*conv
)
8873 GList
*wins
, *convs
;
8874 PurpleAccount
*account
;
8876 account
= purple_conversation_get_account(conv
->active_conv
);
8878 /* Go through the list of IMs and find one with this group. */
8879 for (wins
= pidgin_conv_windows_get_list(); wins
!= NULL
; wins
= wins
->next
) {
8880 PidginConvWindow
*win2
;
8881 PidginConversation
*conv2
;
8885 for (convs
= win2
->gtkconvs
;
8887 convs
= convs
->next
) {
8888 conv2
= convs
->data
;
8890 if (account
== purple_conversation_get_account(conv2
->active_conv
)) {
8891 pidgin_conv_window_add_gtkconv(win2
, conv
);
8897 /* Make a new window. */
8898 conv_placement_new_window(conv
);
8901 static ConvPlacementData
*
8902 get_conv_placement_data(const char *id
)
8904 ConvPlacementData
*data
= NULL
;
8907 for (n
= conv_placement_fncs
; n
; n
= n
->next
) {
8909 if (purple_strequal(data
->id
, id
))
8917 add_conv_placement_fnc(const char *id
, const char *name
,
8918 PidginConvPlacementFunc fnc
)
8920 ConvPlacementData
*data
;
8922 data
= g_new(ConvPlacementData
, 1);
8924 data
->id
= g_strdup(id
);
8925 data
->name
= g_strdup(name
);
8928 conv_placement_fncs
= g_list_append(conv_placement_fncs
, data
);
8932 ensure_default_funcs(void)
8934 if (conv_placement_fncs
== NULL
) {
8935 add_conv_placement_fnc("last", _("Last created window"),
8936 conv_placement_last_created_win
);
8937 add_conv_placement_fnc("im_chat", _("Separate IM and Chat windows"),
8938 conv_placement_last_created_win_type
);
8939 add_conv_placement_fnc("new", _("New window"),
8940 conv_placement_new_window
);
8941 add_conv_placement_fnc("group", _("By group"),
8942 conv_placement_by_group
);
8943 add_conv_placement_fnc("account", _("By account"),
8944 conv_placement_by_account
);
8949 pidgin_conv_placement_get_options(void)
8951 GList
*n
, *list
= NULL
;
8952 ConvPlacementData
*data
;
8954 ensure_default_funcs();
8956 for (n
= conv_placement_fncs
; n
; n
= n
->next
) {
8958 list
= g_list_append(list
, data
->name
);
8959 list
= g_list_append(list
, data
->id
);
8967 pidgin_conv_placement_add_fnc(const char *id
, const char *name
,
8968 PidginConvPlacementFunc fnc
)
8970 g_return_if_fail(id
!= NULL
);
8971 g_return_if_fail(name
!= NULL
);
8972 g_return_if_fail(fnc
!= NULL
);
8974 ensure_default_funcs();
8976 add_conv_placement_fnc(id
, name
, fnc
);
8980 pidgin_conv_placement_remove_fnc(const char *id
)
8982 ConvPlacementData
*data
= get_conv_placement_data(id
);
8987 conv_placement_fncs
= g_list_remove(conv_placement_fncs
, data
);
8995 pidgin_conv_placement_get_name(const char *id
)
8997 ConvPlacementData
*data
;
8999 ensure_default_funcs();
9001 data
= get_conv_placement_data(id
);
9009 PidginConvPlacementFunc
9010 pidgin_conv_placement_get_fnc(const char *id
)
9012 ConvPlacementData
*data
;
9014 ensure_default_funcs();
9016 data
= get_conv_placement_data(id
);
9025 pidgin_conv_placement_set_current_func(PidginConvPlacementFunc func
)
9027 g_return_if_fail(func
!= NULL
);
9029 /* If tabs are enabled, set the function, otherwise, NULL it out. */
9030 if (purple_prefs_get_bool(PIDGIN_PREFS_ROOT
"/conversations/tabs"))
9036 PidginConvPlacementFunc
9037 pidgin_conv_placement_get_current_func(void)
9043 pidgin_conv_placement_place(PidginConversation
*gtkconv
)
9046 place_conv(gtkconv
);
9048 conv_placement_new_window(gtkconv
);
9052 pidgin_conv_is_hidden(PidginConversation
*gtkconv
)
9054 g_return_val_if_fail(gtkconv
!= NULL
, FALSE
);
9056 return (gtkconv
->win
== hidden_convwin
);
9060 gdouble
luminance(GdkRGBA color
)
9064 gdouble cutoff
= 0.03928, scale
= 12.92;
9065 gdouble a
= 0.055, d
= 1.055, p
= 2.2;
9071 r
= (rr
> cutoff
) ? pow((rr
+a
)/d
, p
) : rr
/scale
;
9072 g
= (gg
> cutoff
) ? pow((gg
+a
)/d
, p
) : gg
/scale
;
9073 b
= (bb
> cutoff
) ? pow((bb
+a
)/d
, p
) : bb
/scale
;
9075 return (r
*0.2126 + g
*0.7152 + b
*0.0722);
9078 /* Algorithm from https://www.w3.org/TR/2008/REC-WCAG20-20081211/relative-luminance.xml */
9080 color_is_visible(GdkRGBA foreground
, GdkRGBA background
, gdouble min_contrast_ratio
)
9082 gdouble lfg
, lbg
, lmin
, lmax
;
9083 gdouble luminosity_ratio
;
9086 lfg
= luminance(foreground
);
9087 lbg
= luminance(background
);
9090 lmax
= lfg
, lmin
= lbg
;
9092 lmax
= lbg
, lmin
= lfg
;
9094 nr
= lmax
+ 0.05, dr
= lmin
- 0.05;
9095 if (dr
< 0.005 && dr
> -0.005)
9098 luminosity_ratio
= nr
/dr
;
9099 if ( luminosity_ratio
< 0)
9100 luminosity_ratio
*= -1.0;
9101 return (luminosity_ratio
> min_contrast_ratio
);
9106 generate_nick_colors(guint numcolors
, GdkRGBA background
)
9109 GArray
*colors
= g_array_new(FALSE
, FALSE
, sizeof(GdkRGBA
));
9110 GdkRGBA nick_highlight
;
9112 time_t breakout_time
;
9114 gdk_rgba_parse(&nick_highlight
, DEFAULT_HIGHLIGHT_COLOR
);
9115 gdk_rgba_parse(&send_color
, DEFAULT_SEND_COLOR
);
9117 pidgin_style_adjust_contrast(NULL
, &nick_highlight
);
9118 pidgin_style_adjust_contrast(NULL
, &send_color
);
9120 srand(background
.red
* 65535 + background
.green
* 65535 + background
.blue
* 65535 + 1);
9122 breakout_time
= time(NULL
) + 3;
9124 /* first we look through the list of "good" colors: colors that differ from every other color in the
9125 * list. only some of them will differ from the background color though. lets see if we can find
9126 * numcolors of them that do
9128 while (i
< numcolors
&& j
< PIDGIN_NUM_NICK_SEED_COLORS
&& time(NULL
) < breakout_time
)
9130 GdkRGBA color
= nick_seed_colors
[j
];
9132 if (color_is_visible(color
, background
, MIN_LUMINANCE_CONTRAST_RATIO
) &&
9133 color_is_visible(color
, nick_highlight
, MIN_LUMINANCE_CONTRAST_RATIO
) &&
9134 color_is_visible(color
, send_color
, MIN_LUMINANCE_CONTRAST_RATIO
))
9136 g_array_append_val(colors
, color
);
9142 /* we might not have found numcolors in the last loop. if we did, we'll never enter this one.
9143 * if we did not, lets just find some colors that don't conflict with the background. its
9144 * expensive to find colors that not only don't conflict with the background, but also do not
9145 * conflict with each other.
9147 while(i
< numcolors
&& time(NULL
) < breakout_time
)
9149 GdkRGBA color
= {g_random_double_range(0, 1), g_random_double_range(0, 1), g_random_double_range(0, 1), 1};
9151 if (color_is_visible(color
, background
, MIN_LUMINANCE_CONTRAST_RATIO
) &&
9152 color_is_visible(color
, nick_highlight
, MIN_LUMINANCE_CONTRAST_RATIO
) &&
9153 color_is_visible(color
, send_color
, MIN_LUMINANCE_CONTRAST_RATIO
))
9155 g_array_append_val(colors
, color
);
9160 if (i
< numcolors
) {
9161 purple_debug_warning("gtkconv", "Unable to generate enough random colors before timeout. %u colors found.\n", i
);
9165 /* To remove errors caused by an empty array. */
9166 GdkRGBA color
= {0.5, 0.5, 0.5, 1.0};
9167 g_array_append_val(colors
, color
);
9173 /**************************************************************************
9174 * PidginConvWindow GBoxed code
9175 **************************************************************************/
9176 static PidginConvWindow
*
9177 pidgin_conv_window_ref(PidginConvWindow
*win
)
9179 g_return_val_if_fail(win
!= NULL
, NULL
);
9187 pidgin_conv_window_unref(PidginConvWindow
*win
)
9189 g_return_if_fail(win
!= NULL
);
9190 g_return_if_fail(win
->box_count
>= 0);
9192 if (!win
->box_count
--)
9193 pidgin_conv_window_destroy(win
);
9197 pidgin_conv_window_get_type(void)
9199 static GType type
= 0;
9202 type
= g_boxed_type_register_static("PidginConvWindow",
9203 (GBoxedCopyFunc
)pidgin_conv_window_ref
,
9204 (GBoxedFreeFunc
)pidgin_conv_window_unref
);