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
22 #define _PIDGIN_GTKCONV_C_
28 # include <X11/Xlib.h>
31 #include <gdk/gdkkeysyms.h>
37 #include "glibcompat.h"
39 #include "image-store.h"
45 #include "smiley-parser.h"
46 #include "theme-loader.h"
47 #include "theme-manager.h"
51 #include "gtkinternal.h"
52 #include "gtkdnd-hints.h"
55 #include "gtkconvwin.h"
56 #include "gtkconv-theme.h"
57 #include "gtkconv-theme-loader.h"
58 #include "gtkdialogs.h"
60 #include "gtkmenutray.h"
61 #include "gtkpounce.h"
63 #include "gtkprivacy.h"
65 #include "gtkwebview.h"
66 #include "pidginstock.h"
67 #include "pidgintooltip.h"
69 #include "gtknickcolors.h"
71 #define GTK_TOOLTIPS_VAR gtkconv->tooltips
72 #include "gtk3compat.h"
74 #define ADD_MESSAGE_HISTORY_AT_ONCE 100
77 * A GTK+ Instant Message pane.
89 /* Buddy icon stuff */
90 GtkWidget
*icon_container
;
94 GdkPixbufAnimation
*anim
;
95 GdkPixbufAnimationIter
*iter
;
102 struct _PidginChatPane
106 GtkWidget
*topic_text
;
109 #define CLOSE_CONV_TIMEOUT_SECS (10 * 60)
111 #define AUTO_RESPONSE "<AUTO-REPLY> : "
115 PIDGIN_CONV_SET_TITLE
= 1 << 0,
116 PIDGIN_CONV_BUDDY_ICON
= 1 << 1,
117 PIDGIN_CONV_MENU
= 1 << 2,
118 PIDGIN_CONV_TAB_ICON
= 1 << 3,
119 PIDGIN_CONV_TOPIC
= 1 << 4,
120 PIDGIN_CONV_SMILEY_THEME
= 1 << 5,
121 PIDGIN_CONV_COLORIZE_TITLE
= 1 << 6,
122 PIDGIN_CONV_E2EE
= 1 << 7
129 CONV_PROTOCOL_ICON_COLUMN
,
131 } PidginInfopaneColumns
;
133 #define PIDGIN_CONV_ALL ((1 << 7) - 1)
135 /* XXX: These color defines shouldn't really be here. But the nick-color
136 * generation algorithm uses them, so keeping these around until we fix that. */
137 #define DEFAULT_SEND_COLOR "#204a87"
138 #define DEFAULT_HIGHLIGHT_COLOR "#AF7F00"
140 #define BUDDYICON_SIZE_MIN 32
141 #define BUDDYICON_SIZE_MAX 96
143 #define MIN_LUMINANCE_CONTRAST_RATIO 4.5
145 #define NICK_COLOR_GENERATE_COUNT 220
146 static GArray
*generated_nick_colors
= NULL
;
148 /* These probably won't conflict with any WebKit values. */
149 #define PIDGIN_DRAG_BLIST_NODE (1337)
150 #define PIDGIN_DRAG_IM_CONTACT (31337)
152 static const GtkTargetEntry dnd_targets
[] =
154 {"PURPLE_BLIST_NODE", GTK_TARGET_SAME_APP
, PIDGIN_DRAG_BLIST_NODE
},
155 {"application/x-im-contact", 0, PIDGIN_DRAG_IM_CONTACT
}
158 static GtkTargetList
*webkit_dnd_targets
= NULL
;
166 PurpleChatConversation
*chat
;
170 static GtkWidget
*invite_dialog
= NULL
;
171 static GtkWidget
*warn_close_dialog
= NULL
;
173 static PidginConvWindow
*hidden_convwin
= NULL
;
174 static GList
*window_list
= NULL
;
176 /* Lists of status icons at all available sizes for use as window icons */
177 static GList
*available_list
= NULL
;
178 static GList
*away_list
= NULL
;
179 static GList
*busy_list
= NULL
;
180 static GList
*xa_list
= NULL
;
181 static GList
*offline_list
= NULL
;
182 static GHashTable
*protocol_lists
= NULL
;
183 static GHashTable
*e2ee_stock
= NULL
;
185 static PurpleTheme
*default_conv_theme
= NULL
;
187 static GRegex
*image_store_tag_re
= NULL
;
189 static gboolean
update_send_to_selection(PidginConvWindow
*win
);
190 static void generate_send_to_items(PidginConvWindow
*win
);
192 /* Prototypes. <-- because Paco-Paco hates this comment. */
193 static void load_conv_theme(PidginConversation
*gtkconv
);
194 static gboolean
infopane_entry_activate(PidginConversation
*gtkconv
);
195 static void got_typing_keypress(PidginConversation
*gtkconv
, gboolean first
);
196 static void gray_stuff_out(PidginConversation
*gtkconv
);
197 static void add_chat_user_common(PurpleChatConversation
*chat
, PurpleChatUser
*cb
, const char *old_name
);
198 static gboolean
tab_complete(PurpleConversation
*conv
);
199 static void pidgin_conv_updated(PurpleConversation
*conv
, PurpleConversationUpdateType type
);
200 static void conv_set_unseen(PurpleConversation
*gtkconv
, PidginUnseenState state
);
201 static void gtkconv_set_unseen(PidginConversation
*gtkconv
, PidginUnseenState state
);
202 static void update_typing_icon(PidginConversation
*gtkconv
);
203 static void update_typing_message(PidginConversation
*gtkconv
, const char *message
);
204 gboolean
pidgin_conv_has_focus(PurpleConversation
*conv
);
205 static GArray
* generate_nick_colors(guint numcolors
, GdkRGBA background
);
206 gdouble
luminance(GdkRGBA color
);
207 static gboolean
color_is_visible(GdkRGBA foreground
, GdkRGBA background
, gdouble min_contrast_ratio
);
208 static GtkTextTag
*get_buddy_tag(PurpleChatConversation
*chat
, const char *who
, PurpleMessageFlags flag
, gboolean create
);
209 static void pidgin_conv_update_fields(PurpleConversation
*conv
, PidginConvFields fields
);
210 static void focus_out_from_menubar(GtkWidget
*wid
, PidginConvWindow
*win
);
211 static void pidgin_conv_tab_pack(PidginConvWindow
*win
, PidginConversation
*gtkconv
);
212 static gboolean
infopane_press_cb(GtkWidget
*widget
, GdkEventButton
*e
, PidginConversation
*conv
);
213 static void hide_conv(PidginConversation
*gtkconv
, gboolean closetimer
);
215 static void pidgin_conv_set_position_size(PidginConvWindow
*win
, int x
, int y
,
216 int width
, int height
);
217 static gboolean
pidgin_conv_xy_to_right_infopane(PidginConvWindow
*win
, int x
, int y
);
219 static const GdkRGBA
*
220 get_nick_color(PidginConversation
*gtkconv
, const gchar
*name
)
225 col
.red
= col
.green
= col
.blue
= 0;
230 col
= g_array_index(gtkconv
->nick_colors
, GdkRGBA
,
231 g_str_hash(name
) % gtkconv
->nick_colors
->len
);
236 static PurpleBlistNode
*
237 get_conversation_blist_node(PurpleConversation
*conv
)
239 PurpleAccount
*account
= purple_conversation_get_account(conv
);
240 PurpleBlistNode
*node
= NULL
;
242 if (PURPLE_IS_IM_CONVERSATION(conv
)) {
243 node
= PURPLE_BLIST_NODE(purple_blist_find_buddy(account
, purple_conversation_get_name(conv
)));
244 node
= node
? node
->parent
: NULL
;
245 } else if (PURPLE_IS_CHAT_CONVERSATION(conv
)) {
246 node
= PURPLE_BLIST_NODE(purple_blist_find_chat(account
, purple_conversation_get_name(conv
)));
252 /**************************************************************************
254 **************************************************************************/
257 close_this_sucker(gpointer data
)
259 PidginConversation
*gtkconv
= data
;
260 GList
*list
= g_list_copy(gtkconv
->convs
);
261 g_list_foreach(list
, (GFunc
)g_object_unref
, NULL
);
267 close_conv_cb(GtkButton
*button
, PidginConversation
*gtkconv
)
269 /* We are going to destroy the conversations immediately only if the 'close immediately'
270 * preference is selected. Otherwise, close the conversation after a reasonable timeout
271 * (I am going to consider 10 minutes as a 'reasonable timeout' here.
272 * For chats, close immediately if the chat is not in the buddylist, or if the chat is
273 * not marked 'Persistent' */
274 PurpleConversation
*conv
= gtkconv
->active_conv
;
275 PurpleAccount
*account
= purple_conversation_get_account(conv
);
276 const char *name
= purple_conversation_get_name(conv
);
278 if (PURPLE_IS_IM_CONVERSATION(conv
)) {
279 if (purple_prefs_get_bool(PIDGIN_PREFS_ROOT
"/conversations/im/close_immediately"))
280 close_this_sucker(gtkconv
);
282 hide_conv(gtkconv
, TRUE
);
283 } else if (PURPLE_IS_CHAT_CONVERSATION(conv
)) {
284 PurpleChat
*chat
= purple_blist_find_chat(account
, name
);
286 !purple_blist_node_get_bool(&chat
->node
, "gtk-persistent"))
287 close_this_sucker(gtkconv
);
289 hide_conv(gtkconv
, FALSE
);
296 lbox_size_allocate_cb(GtkWidget
*w
, GtkAllocation
*allocation
, gpointer data
)
298 purple_prefs_set_int(PIDGIN_PREFS_ROOT
"/conversations/chat/userlist_width", allocation
->width
== 1 ? 0 : allocation
->width
);
304 default_formatize(PidginConversation
*c
)
306 PurpleConversation
*conv
= c
->active_conv
;
307 pidgin_webview_setup_entry(PIDGIN_WEBVIEW(c
->entry
), purple_conversation_get_features(conv
));
311 conversation_entry_clear(PidginConversation
*gtkconv
)
313 PidginWebView
*webview
= PIDGIN_WEBVIEW(gtkconv
->entry
);
315 //XXX: hotfix for not focused entry after sending a message
316 //pidgin_webview_load_html_string(webview, "");
317 pidgin_webview_load_html_string_with_selection(webview
, "<div id='caret'></div>");
321 gtk_source_undo_manager_begin_not_undoable_action(webview
->undo_manager
);
322 pidgin_webview_clear(webview
);
323 gtk_source_undo_manager_end_not_undoable_action(webview
->undo_manager
);
328 clear_formatting_cb(PidginWebView
*webview
, PidginConversation
*gtkconv
)
330 default_formatize(gtkconv
);
334 pidgin_get_cmd_prefix(void)
340 say_command_cb(PurpleConversation
*conv
,
341 const char *cmd
, char **args
, char **error
, void *data
)
343 purple_conversation_send(conv
, args
[0]);
345 return PURPLE_CMD_RET_OK
;
349 me_command_cb(PurpleConversation
*conv
,
350 const char *cmd
, char **args
, char **error
, void *data
)
354 tmp
= g_strdup_printf("/me %s", args
[0]);
355 purple_conversation_send(conv
, tmp
);
358 return PURPLE_CMD_RET_OK
;
362 debug_command_cb(PurpleConversation
*conv
,
363 const char *cmd
, char **args
, char **error
, void *data
)
367 if (!g_ascii_strcasecmp(args
[0], "version")) {
368 tmp
= g_strdup_printf("Using Pidgin v%s with libpurple v%s.",
369 DISPLAY_VERSION
, purple_core_get_version());
370 } else if (!g_ascii_strcasecmp(args
[0], "plugins")) {
371 /* Show all the loaded plugins, including plugins marked internal.
372 * This is intentional, since third party protocols are often sources of bugs, and some
373 * plugin loaders can also be buggy.
375 GString
*str
= g_string_new("Loaded Plugins: ");
376 const GList
*plugins
= purple_plugins_get_loaded();
378 for (; plugins
; plugins
= plugins
->next
) {
379 PurplePluginInfo
*info
= purple_plugin_get_info(PURPLE_PLUGIN(plugins
->data
));
380 str
= g_string_append(str
, purple_plugin_info_get_name(info
));
383 str
= g_string_append(str
, ", ");
386 str
= g_string_append(str
, "(none)");
389 tmp
= g_string_free(str
, FALSE
);
390 } else if (!g_ascii_strcasecmp(args
[0], "unsafe")) {
391 if (purple_debug_is_unsafe()) {
392 purple_debug_set_unsafe(FALSE
);
393 purple_conversation_write_system_message(conv
,
394 _("Unsafe debugging is now disabled."),
395 PURPLE_MESSAGE_NO_LOG
);
397 purple_debug_set_unsafe(TRUE
);
398 purple_conversation_write_system_message(conv
,
399 _("Unsafe debugging is now enabled."),
400 PURPLE_MESSAGE_NO_LOG
);
403 return PURPLE_CMD_RET_OK
;
404 } else if (!g_ascii_strcasecmp(args
[0], "verbose")) {
405 if (purple_debug_is_verbose()) {
406 purple_debug_set_verbose(FALSE
);
407 purple_conversation_write_system_message(conv
,
408 _("Verbose debugging is now disabled."),
409 PURPLE_MESSAGE_NO_LOG
);
411 purple_debug_set_verbose(TRUE
);
412 purple_conversation_write_system_message(conv
,
413 _("Verbose debugging is now enabled."),
414 PURPLE_MESSAGE_NO_LOG
);
417 return PURPLE_CMD_RET_OK
;
419 purple_conversation_write_system_message(conv
,
420 _("Supported debug options are: plugins, version, unsafe, verbose"),
421 PURPLE_MESSAGE_NO_LOG
);
422 return PURPLE_CMD_RET_OK
;
425 markup
= g_markup_escape_text(tmp
, -1);
426 purple_conversation_send(conv
, markup
);
430 return PURPLE_CMD_RET_OK
;
433 static void clear_conversation_scrollback_cb(PurpleConversation
*conv
,
436 PidginConversation
*gtkconv
= PIDGIN_CONVERSATION(conv
);
438 if (PIDGIN_CONVERSATION(conv
)) {
439 load_conv_theme(gtkconv
);
440 gtkconv
->last_flags
= 0;
445 clear_command_cb(PurpleConversation
*conv
,
446 const char *cmd
, char **args
, char **error
, void *data
)
448 purple_conversation_clear_message_history(conv
);
449 return PURPLE_CMD_RET_OK
;
453 clearall_command_cb(PurpleConversation
*conv
,
454 const char *cmd
, char **args
, char **error
, void *data
)
457 for (l
= purple_conversations_get_all(); l
!= NULL
; l
= l
->next
)
458 purple_conversation_clear_message_history(PURPLE_CONVERSATION(l
->data
));
460 return PURPLE_CMD_RET_OK
;
464 help_command_cb(PurpleConversation
*conv
,
465 const char *cmd
, char **args
, char **error
, void *data
)
470 if (args
[0] != NULL
) {
471 s
= g_string_new("");
472 text
= purple_cmd_help(conv
, args
[0]);
475 for (l
= text
; l
; l
= l
->next
)
477 g_string_append_printf(s
, "%s\n", (char *)l
->data
);
479 g_string_append_printf(s
, "%s", (char *)l
->data
);
481 g_string_append(s
, _("No such command (in this context)."));
484 s
= g_string_new(_("Use \"/help <command>\" for help with a "
485 "specific command.<br/>The following commands are available "
486 "in this context:<br/>"));
488 text
= purple_cmd_list(conv
);
489 for (l
= text
; l
; l
= l
->next
)
491 g_string_append_printf(s
, "%s, ", (char *)l
->data
);
493 g_string_append_printf(s
, "%s.", (char *)l
->data
);
497 purple_conversation_write_system_message(conv
, s
->str
, PURPLE_MESSAGE_NO_LOG
);
498 g_string_free(s
, TRUE
);
500 return PURPLE_CMD_RET_OK
;
504 send_history_add(PidginConversation
*gtkconv
, const char *message
)
508 first
= g_list_first(gtkconv
->send_history
);
510 first
->data
= g_strdup(message
);
511 gtkconv
->send_history
= g_list_prepend(first
, NULL
);
515 check_for_and_do_command(PurpleConversation
*conv
)
517 PidginConversation
*gtkconv
;
520 gboolean retval
= FALSE
;
522 gtkconv
= PIDGIN_CONVERSATION(conv
);
523 prefix
= pidgin_get_cmd_prefix();
525 cmd
= pidgin_webview_get_body_text(PIDGIN_WEBVIEW(gtkconv
->entry
));
527 if (cmd
&& purple_str_has_prefix(cmd
, prefix
)) {
528 PurpleCmdStatus status
;
529 char *error
, *cmdline
, *markup
, *send_history
;
531 send_history
= pidgin_webview_get_body_html(PIDGIN_WEBVIEW(gtkconv
->entry
));
532 send_history_add(gtkconv
, send_history
);
533 g_free(send_history
);
535 cmdline
= cmd
+ strlen(prefix
);
537 if (strcmp(cmdline
, "xyzzy") == 0) {
538 purple_conversation_write_system_message(conv
,
539 "Nothing happens", PURPLE_MESSAGE_NO_LOG
);
544 /* TODO WebKit: Cut out prefix for markup... */
545 markup
= pidgin_webview_get_body_html(PIDGIN_WEBVIEW(gtkconv
->entry
));
546 status
= purple_cmd_do_command(conv
, cmdline
, markup
, &error
);
550 case PURPLE_CMD_STATUS_OK
:
553 case PURPLE_CMD_STATUS_NOT_FOUND
:
555 PurpleProtocol
*protocol
= NULL
;
556 PurpleConnection
*gc
;
558 if ((gc
= purple_conversation_get_connection(conv
)))
559 protocol
= purple_connection_get_protocol(gc
);
561 if ((protocol
!= NULL
) && (purple_protocol_get_options(protocol
) & OPT_PROTO_SLASH_COMMANDS_NATIVE
)) {
564 /* If the first word in the entered text has a '/' in it, then the user
565 * probably didn't mean it as a command. So send the text as message. */
566 spaceslash
= cmdline
;
567 while (*spaceslash
&& *spaceslash
!= ' ' && *spaceslash
!= '/')
570 if (*spaceslash
!= '/') {
571 purple_conversation_write_system_message(conv
,
572 _("Unknown command."), PURPLE_MESSAGE_NO_LOG
);
578 case PURPLE_CMD_STATUS_WRONG_ARGS
:
579 purple_conversation_write_system_message(conv
,
580 _("Syntax Error: You typed the wrong "
581 "number of arguments to that command."),
582 PURPLE_MESSAGE_NO_LOG
);
585 case PURPLE_CMD_STATUS_FAILED
:
586 purple_conversation_write_system_message(conv
,
587 error
? error
: _("Your command failed for an unknown reason."),
588 PURPLE_MESSAGE_NO_LOG
);
592 case PURPLE_CMD_STATUS_WRONG_TYPE
:
593 if(PURPLE_IS_IM_CONVERSATION(conv
))
594 purple_conversation_write_system_message(conv
,
595 _("That command only works in chats, not IMs."),
596 PURPLE_MESSAGE_NO_LOG
);
598 purple_conversation_write_system_message(conv
,
599 _("That command only works in IMs, not chats."),
600 PURPLE_MESSAGE_NO_LOG
);
603 case PURPLE_CMD_STATUS_WRONG_PROTOCOL
:
604 purple_conversation_write_system_message(conv
,
605 _("That command doesn't work on this protocol."),
606 PURPLE_MESSAGE_NO_LOG
);
618 send_cb(GtkWidget
*widget
, PidginConversation
*gtkconv
)
620 PurpleConversation
*conv
= gtkconv
->active_conv
;
621 PurpleAccount
*account
;
623 PurpleConnection
*gc
;
625 PurpleMessageFlags flags
= 0;
628 account
= purple_conversation_get_account(conv
);
630 if (check_for_and_do_command(conv
)) {
631 conversation_entry_clear(gtkconv
);
635 if (PURPLE_IS_CHAT_CONVERSATION(conv
) &&
636 purple_chat_conversation_has_left(PURPLE_CHAT_CONVERSATION(conv
)))
639 if (!purple_account_is_connected(account
))
642 if (pidgin_webview_is_empty(PIDGIN_WEBVIEW(gtkconv
->entry
)))
645 buf
= pidgin_webview_get_body_html(PIDGIN_WEBVIEW(gtkconv
->entry
));
646 g_return_if_fail(buf
!= NULL
);
648 gtk_widget_grab_focus(gtkconv
->entry
);
652 /* XXX: is there a better way to tell if the message has images? */
653 if (strstr(buf
, "<img ") != NULL
)
654 flags
|= PURPLE_MESSAGE_IMAGES
;
657 gc
= purple_account_get_connection(account
);
658 if (gc
&& (purple_conversation_get_features(conv
) & PURPLE_CONNECTION_FLAG_NO_NEWLINES
)) {
663 bufs
= pidgin_webview_get_markup_lines(PIDGIN_WEBVIEW(gtkconv
->entry
));
664 for (i
= 0; bufs
[i
]; i
++) {
665 send_history_add(gtkconv
, bufs
[i
]);
666 purple_conversation_send_with_flags(conv
, bufs
[i
], flags
);
673 send_history_add(gtkconv
, buf
);
674 purple_conversation_send_with_flags(conv
, buf
, flags
);
679 conversation_entry_clear(gtkconv
);
680 gtkconv_set_unseen(gtkconv
, PIDGIN_UNSEEN_NONE
);
681 gtk_widget_grab_focus(gtkconv
->entry
); // XXX: doesn't work
685 add_remove_cb(GtkWidget
*widget
, PidginConversation
*gtkconv
)
687 PurpleAccount
*account
;
689 PurpleConversation
*conv
= gtkconv
->active_conv
;
691 account
= purple_conversation_get_account(conv
);
692 name
= purple_conversation_get_name(conv
);
694 if (PURPLE_IS_IM_CONVERSATION(conv
)) {
697 b
= purple_blist_find_buddy(account
, name
);
699 pidgin_dialogs_remove_buddy(b
);
700 else if (account
!= NULL
&& purple_account_is_connected(account
))
701 purple_blist_request_add_buddy(account
, (char *)name
, NULL
, NULL
);
702 } else if (PURPLE_IS_CHAT_CONVERSATION(conv
)) {
705 c
= purple_blist_find_chat(account
, name
);
707 pidgin_dialogs_remove_chat(c
);
708 else if (account
!= NULL
&& purple_account_is_connected(account
))
709 purple_blist_request_add_chat(account
, NULL
, NULL
, name
);
712 gtk_widget_grab_focus(PIDGIN_CONVERSATION(conv
)->entry
);
715 static void chat_do_info(PidginConversation
*gtkconv
, const char *who
)
717 PurpleChatConversation
*chat
= PURPLE_CHAT_CONVERSATION(gtkconv
->active_conv
);
718 PurpleConnection
*gc
;
720 if ((gc
= purple_conversation_get_connection(gtkconv
->active_conv
))) {
721 pidgin_retrieve_user_info_in_chat(gc
, who
, purple_chat_conversation_get_id(chat
));
727 info_cb(GtkWidget
*widget
, PidginConversation
*gtkconv
)
729 PurpleConversation
*conv
= gtkconv
->active_conv
;
731 if (PURPLE_IS_IM_CONVERSATION(conv
)) {
732 pidgin_retrieve_user_info(purple_conversation_get_connection(conv
),
733 purple_conversation_get_name(conv
));
734 gtk_widget_grab_focus(gtkconv
->entry
);
735 } else if (PURPLE_IS_CHAT_CONVERSATION(conv
)) {
736 /* Get info of the person currently selected in the GtkTreeView */
737 PidginChatPane
*gtkchat
;
740 GtkTreeSelection
*sel
;
743 gtkchat
= gtkconv
->u
.chat
;
745 model
= gtk_tree_view_get_model(GTK_TREE_VIEW(gtkchat
->list
));
746 sel
= gtk_tree_view_get_selection(GTK_TREE_VIEW(gtkchat
->list
));
748 if (gtk_tree_selection_get_selected(sel
, NULL
, &iter
))
749 gtk_tree_model_get(GTK_TREE_MODEL(model
), &iter
, CHAT_USERS_NAME_COLUMN
, &name
, -1);
753 chat_do_info(gtkconv
, name
);
759 block_cb(GtkWidget
*widget
, PidginConversation
*gtkconv
)
761 PurpleConversation
*conv
= gtkconv
->active_conv
;
762 PurpleAccount
*account
;
764 account
= purple_conversation_get_account(conv
);
766 if (account
!= NULL
&& purple_account_is_connected(account
))
767 pidgin_request_add_block(account
, purple_conversation_get_name(conv
));
769 gtk_widget_grab_focus(PIDGIN_CONVERSATION(conv
)->entry
);
773 unblock_cb(GtkWidget
*widget
, PidginConversation
*gtkconv
)
775 PurpleConversation
*conv
= gtkconv
->active_conv
;
776 PurpleAccount
*account
;
778 account
= purple_conversation_get_account(conv
);
780 if (account
!= NULL
&& purple_account_is_connected(account
))
781 pidgin_request_add_permit(account
, purple_conversation_get_name(conv
));
783 gtk_widget_grab_focus(PIDGIN_CONVERSATION(conv
)->entry
);
787 chat_invite_filter(const PidginBuddyCompletionEntry
*entry
, gpointer data
)
789 PurpleAccount
*filter_account
= data
;
790 PurpleAccount
*account
= NULL
;
792 if (entry
->is_buddy
) {
793 if (PURPLE_BUDDY_IS_ONLINE(entry
->entry
.buddy
))
794 account
= purple_buddy_get_account(entry
->entry
.buddy
);
798 account
= entry
->entry
.logged_buddy
->account
;
800 if (account
== filter_account
)
806 do_invite(GtkWidget
*w
, int resp
, InviteBuddyInfo
*info
)
808 const char *buddy
, *message
;
809 PurpleChatConversation
*chat
;
813 if (resp
== GTK_RESPONSE_OK
) {
814 buddy
= gtk_entry_get_text(GTK_ENTRY(info
->entry
));
815 message
= gtk_entry_get_text(GTK_ENTRY(info
->message
));
817 if (!g_ascii_strcasecmp(buddy
, ""))
820 purple_serv_chat_invite(purple_conversation_get_connection(PURPLE_CONVERSATION(chat
)),
821 purple_chat_conversation_get_id(chat
),
825 gtk_widget_destroy(invite_dialog
);
826 invite_dialog
= NULL
;
832 invite_dnd_recv(GtkWidget
*widget
, GdkDragContext
*dc
, gint x
, gint y
,
833 GtkSelectionData
*sd
, guint dnd_info
, guint t
, gpointer data
)
835 InviteBuddyInfo
*info
= (InviteBuddyInfo
*)data
;
836 const char *convprotocol
;
837 gboolean success
= TRUE
;
839 convprotocol
= purple_account_get_protocol_id(
840 purple_conversation_get_account(PURPLE_CONVERSATION(info
->chat
)));
842 if (dnd_info
== PIDGIN_DRAG_BLIST_NODE
)
844 PurpleBlistNode
*node
= NULL
;
846 const guchar
*data
= gtk_selection_data_get_data(sd
);
848 memcpy(&node
, data
, sizeof(node
));
850 if (PURPLE_IS_CONTACT(node
))
851 buddy
= purple_contact_get_priority_buddy((PurpleContact
*)node
);
852 else if (PURPLE_IS_BUDDY(node
))
853 buddy
= (PurpleBuddy
*)node
;
857 if (strcmp(convprotocol
, purple_account_get_protocol_id(purple_buddy_get_account(buddy
))))
859 purple_notify_error(PIDGIN_CONVERSATION(PURPLE_CONVERSATION(info
->chat
)),
860 NULL
, _("That buddy is not on the same protocol"
861 " as this chat."), NULL
,
862 purple_request_cpar_from_conversation(PURPLE_CONVERSATION(info
->chat
)));
866 gtk_entry_set_text(GTK_ENTRY(info
->entry
), purple_buddy_get_name(buddy
));
868 gtk_drag_finish(dc
, success
,
869 gdk_drag_context_get_actions(dc
) == GDK_ACTION_MOVE
, t
);
871 else if (dnd_info
== PIDGIN_DRAG_IM_CONTACT
)
873 char *protocol
= NULL
;
874 char *username
= NULL
;
875 PurpleAccount
*account
;
877 if (pidgin_parse_x_im_contact((const char *) data
, FALSE
, &account
,
878 &protocol
, &username
, NULL
))
882 purple_notify_error(PIDGIN_CONVERSATION(PURPLE_CONVERSATION(info
->chat
)), NULL
,
883 _("You are not currently signed on with an account that "
884 "can invite that buddy."), NULL
, NULL
);
886 else if (strcmp(convprotocol
, purple_account_get_protocol_id(account
)))
889 PIDGIN_CONVERSATION(PURPLE_CONVERSATION(info
->chat
)), NULL
,
890 _("That buddy is not on the same "
891 "protocol as this chat."), NULL
,
892 purple_request_cpar_from_account(
898 gtk_entry_set_text(GTK_ENTRY(info
->entry
), username
);
905 gtk_drag_finish(dc
, success
,
906 gdk_drag_context_get_actions(dc
) == GDK_ACTION_MOVE
, t
);
911 invite_cb(GtkWidget
*widget
, PidginConversation
*gtkconv
)
913 PurpleChatConversation
*chat
= PURPLE_CHAT_CONVERSATION(gtkconv
->active_conv
);
914 InviteBuddyInfo
*info
= NULL
;
916 if (invite_dialog
== NULL
) {
917 PidginConvWindow
*gtkwin
;
919 GtkWidget
*vbox
, *hbox
;
923 img
= gtk_image_new_from_stock(PIDGIN_STOCK_DIALOG_QUESTION
,
924 gtk_icon_size_from_name(PIDGIN_ICON_SIZE_TANGO_HUGE
));
926 info
= g_new0(InviteBuddyInfo
, 1);
929 gtkwin
= pidgin_conv_get_window(gtkconv
);
931 /* Create the new dialog. */
932 invite_dialog
= gtk_dialog_new_with_buttons(
933 _("Invite Buddy Into Chat Room"),
934 GTK_WINDOW(gtkwin
->window
), 0,
935 GTK_STOCK_CANCEL
, GTK_RESPONSE_CANCEL
,
936 PIDGIN_STOCK_INVITE
, GTK_RESPONSE_OK
, NULL
);
938 gtk_dialog_set_default_response(GTK_DIALOG(invite_dialog
),
940 gtk_container_set_border_width(GTK_CONTAINER(invite_dialog
), PIDGIN_HIG_BOX_SPACE
);
941 gtk_window_set_resizable(GTK_WINDOW(invite_dialog
), FALSE
);
943 info
->window
= GTK_WIDGET(invite_dialog
);
945 /* Setup the outside spacing. */
946 vbox
= gtk_dialog_get_content_area(GTK_DIALOG(invite_dialog
));
948 gtk_box_set_spacing(GTK_BOX(vbox
), PIDGIN_HIG_BORDER
);
949 gtk_container_set_border_width(GTK_CONTAINER(vbox
), PIDGIN_HIG_BOX_SPACE
);
951 /* Setup the inner hbox and put the dialog's icon in it. */
952 hbox
= gtk_box_new(GTK_ORIENTATION_HORIZONTAL
, PIDGIN_HIG_BORDER
);
953 gtk_container_add(GTK_CONTAINER(vbox
), hbox
);
954 gtk_box_pack_start(GTK_BOX(hbox
), img
, FALSE
, FALSE
, 0);
955 gtk_widget_set_halign(img
, GTK_ALIGN_START
);
956 gtk_widget_set_valign(img
, GTK_ALIGN_START
);
958 /* Setup the right vbox. */
959 vbox
= gtk_box_new(GTK_ORIENTATION_VERTICAL
, 0);
960 gtk_container_add(GTK_CONTAINER(hbox
), vbox
);
962 /* Put our happy label in it. */
963 label
= gtk_label_new(_("Please enter the name of the user you wish "
964 "to invite, along with an optional invite "
966 gtk_widget_set_size_request(label
, 350, -1);
967 gtk_label_set_line_wrap(GTK_LABEL(label
), TRUE
);
968 gtk_label_set_xalign(GTK_LABEL(label
), 0);
969 gtk_label_set_yalign(GTK_LABEL(label
), 0);
970 gtk_box_pack_start(GTK_BOX(vbox
), label
, FALSE
, FALSE
, 0);
972 /* hbox for the grid, and to give it some spacing on the left. */
973 hbox
= gtk_box_new(GTK_ORIENTATION_HORIZONTAL
, PIDGIN_HIG_BOX_SPACE
);
974 gtk_container_add(GTK_CONTAINER(vbox
), hbox
);
976 /* Setup the grid we're going to use to lay stuff out. */
977 grid
= gtk_grid_new();
978 gtk_grid_set_row_spacing(GTK_GRID(grid
), PIDGIN_HIG_BOX_SPACE
);
979 gtk_grid_set_column_spacing(GTK_GRID(grid
), PIDGIN_HIG_BOX_SPACE
);
980 gtk_container_set_border_width(GTK_CONTAINER(grid
), PIDGIN_HIG_BORDER
);
981 gtk_box_pack_start(GTK_BOX(vbox
), grid
, FALSE
, FALSE
, 0);
983 /* Now the Buddy label */
984 label
= gtk_label_new(NULL
);
985 gtk_label_set_markup_with_mnemonic(GTK_LABEL(label
), _("_Buddy:"));
986 gtk_widget_set_hexpand(label
, TRUE
);
987 gtk_widget_set_vexpand(label
, TRUE
);
988 gtk_label_set_xalign(GTK_LABEL(label
), 0);
989 gtk_label_set_yalign(GTK_LABEL(label
), 0);
990 gtk_grid_attach(GTK_GRID(grid
), label
, 0, 0, 1, 1);
992 /* Now the Buddy drop-down entry field. */
993 info
->entry
= gtk_entry_new();
994 pidgin_setup_screenname_autocomplete(info
->entry
, NULL
, chat_invite_filter
,
995 purple_conversation_get_account(PURPLE_CONVERSATION(chat
)));
996 gtk_widget_set_hexpand(info
->entry
, TRUE
);
997 gtk_widget_set_vexpand(info
->entry
, TRUE
);
998 gtk_grid_attach(GTK_GRID(grid
), info
->entry
, 1, 0, 1, 1);
999 gtk_label_set_mnemonic_widget(GTK_LABEL(label
), info
->entry
);
1001 /* Now the label for "Message" */
1002 label
= gtk_label_new(NULL
);
1003 gtk_label_set_markup_with_mnemonic(GTK_LABEL(label
), _("_Message:"));
1004 gtk_widget_set_hexpand(label
, TRUE
);
1005 gtk_widget_set_vexpand(label
, TRUE
);
1006 gtk_label_set_xalign(GTK_LABEL(label
), 0);
1007 gtk_label_set_yalign(GTK_LABEL(label
), 0);
1008 gtk_grid_attach(GTK_GRID(grid
), label
, 0, 1, 1, 1);
1010 /* And finally, the Message entry field. */
1011 info
->message
= gtk_entry_new();
1012 gtk_entry_set_activates_default(GTK_ENTRY(info
->message
), TRUE
);
1013 gtk_widget_set_hexpand(info
->message
, TRUE
);
1014 gtk_widget_set_vexpand(info
->message
, TRUE
);
1015 gtk_grid_attach(GTK_GRID(grid
), info
->message
, 1, 1, 1, 1);
1016 gtk_label_set_mnemonic_widget(GTK_LABEL(label
), info
->message
);
1018 /* Connect the signals. */
1019 g_signal_connect(G_OBJECT(invite_dialog
), "response",
1020 G_CALLBACK(do_invite
), info
);
1021 /* Setup drag-and-drop */
1022 gtk_drag_dest_set(info
->window
,
1023 GTK_DEST_DEFAULT_MOTION
|
1024 GTK_DEST_DEFAULT_DROP
,
1026 sizeof(dnd_targets
) / sizeof(GtkTargetEntry
),
1028 gtk_drag_dest_set(info
->entry
,
1029 GTK_DEST_DEFAULT_MOTION
|
1030 GTK_DEST_DEFAULT_DROP
,
1032 sizeof(dnd_targets
) / sizeof(GtkTargetEntry
),
1035 g_signal_connect(G_OBJECT(info
->window
), "drag_data_received",
1036 G_CALLBACK(invite_dnd_recv
), info
);
1037 g_signal_connect(G_OBJECT(info
->entry
), "drag_data_received",
1038 G_CALLBACK(invite_dnd_recv
), info
);
1041 gtk_widget_show_all(invite_dialog
);
1044 gtk_widget_grab_focus(info
->entry
);
1048 menu_new_conv_cb(GtkAction
*action
, gpointer data
)
1050 pidgin_dialogs_im();
1054 menu_join_chat_cb(GtkAction
*action
, gpointer data
)
1056 pidgin_blist_joinchat_show();
1060 savelog_writefile_cb(void *user_data
, const char *filename
)
1062 PurpleConversation
*conv
= (PurpleConversation
*)user_data
;
1063 PidginWebView
*webview
;
1068 if ((fp
= g_fopen(filename
, "w+")) == NULL
) {
1069 purple_notify_error(PIDGIN_CONVERSATION(conv
), NULL
,
1070 _("Unable to open file."), NULL
,
1071 purple_request_cpar_from_conversation(conv
));
1075 webview
= PIDGIN_WEBVIEW(PIDGIN_CONVERSATION(conv
)->webview
);
1076 name
= purple_conversation_get_name(conv
);
1077 fprintf(fp
, "<html>\n");
1079 fprintf(fp
, "<head>\n");
1080 fprintf(fp
, "<meta http-equiv=\"content-type\" content=\"text/html; charset=UTF-8\">\n");
1081 fprintf(fp
, "<title>%s</title>\n", name
);
1082 text
= pidgin_webview_get_head_html(webview
);
1083 fprintf(fp
, "%s", text
);
1085 fprintf(fp
, "</head>\n");
1087 fprintf(fp
, "<body>\n");
1088 fprintf(fp
, _("<h1>Conversation with %s</h1>\n"), name
);
1089 text
= pidgin_webview_get_body_html(webview
);
1090 fprintf(fp
, "%s", text
);
1092 fprintf(fp
, "\n</body>\n");
1094 fprintf(fp
, "</html>\n");
1099 * It would be kinda cool if this gave the option of saving a
1100 * plaintext v. HTML file.
1103 menu_save_as_cb(GtkAction
*action
, gpointer data
)
1105 PidginConvWindow
*win
= data
;
1106 PurpleConversation
*conv
= pidgin_conv_window_get_active_conversation(win
);
1107 PurpleAccount
*account
= purple_conversation_get_account(conv
);
1108 PurpleBuddy
*buddy
= purple_blist_find_buddy(account
, purple_conversation_get_name(conv
));
1114 name
= purple_buddy_get_contact_alias(buddy
);
1116 name
= purple_normalize(account
, purple_conversation_get_name(conv
));
1118 buf
= g_strdup_printf("%s.html", name
);
1119 for (c
= buf
; *c
; c
++)
1121 if (*c
== '/' || *c
== '\\')
1124 purple_request_file(PIDGIN_CONVERSATION(conv
), _("Save Conversation"),
1125 buf
, TRUE
, G_CALLBACK(savelog_writefile_cb
), NULL
,
1126 purple_request_cpar_from_conversation(conv
), conv
);
1132 menu_view_log_cb(GtkAction
*action
, gpointer data
)
1134 PidginConvWindow
*win
= data
;
1135 PurpleConversation
*conv
;
1137 PidginBuddyList
*gtkblist
;
1139 PurpleAccount
*account
;
1143 conv
= pidgin_conv_window_get_active_conversation(win
);
1145 if (PURPLE_IS_IM_CONVERSATION(conv
))
1146 type
= PURPLE_LOG_IM
;
1147 else if (PURPLE_IS_CHAT_CONVERSATION(conv
))
1148 type
= PURPLE_LOG_CHAT
;
1152 gtkblist
= pidgin_blist_get_default_gtk_blist();
1154 pidgin_set_cursor(gtkblist
->window
, GDK_WATCH
);
1155 pidgin_set_cursor(win
->window
, GDK_WATCH
);
1157 name
= purple_conversation_get_name(conv
);
1158 account
= purple_conversation_get_account(conv
);
1160 buddies
= purple_blist_find_buddies(account
, name
);
1161 for (cur
= buddies
; cur
!= NULL
; cur
= cur
->next
)
1163 PurpleBlistNode
*node
= cur
->data
;
1164 if ((node
!= NULL
) && ((node
->prev
!= NULL
) || (node
->next
!= NULL
)))
1166 pidgin_log_show_contact((PurpleContact
*)node
->parent
);
1167 g_slist_free(buddies
);
1168 pidgin_clear_cursor(gtkblist
->window
);
1169 pidgin_clear_cursor(win
->window
);
1173 g_slist_free(buddies
);
1175 pidgin_log_show(type
, name
, account
);
1177 pidgin_clear_cursor(gtkblist
->window
);
1178 pidgin_clear_cursor(win
->window
);
1182 menu_clear_cb(GtkAction
*action
, gpointer data
)
1184 PidginConvWindow
*win
= data
;
1185 PurpleConversation
*conv
;
1187 conv
= pidgin_conv_window_get_active_conversation(win
);
1188 purple_conversation_clear_message_history(conv
);
1192 menu_find_cb(GtkAction
*action
, gpointer data
)
1194 PidginConvWindow
*gtkwin
= data
;
1195 PidginConversation
*gtkconv
= pidgin_conv_window_get_active_gtkconv(gtkwin
);
1196 gtk_widget_show_all(gtkconv
->quickfind_container
);
1197 gtk_widget_grab_focus(gtkconv
->quickfind_entry
);
1202 menu_initiate_media_call_cb(GtkAction
*action
, gpointer data
)
1204 PidginConvWindow
*win
= (PidginConvWindow
*)data
;
1205 PurpleConversation
*conv
= pidgin_conv_window_get_active_conversation(win
);
1206 PurpleAccount
*account
= purple_conversation_get_account(conv
);
1208 purple_protocol_initiate_media(account
,
1209 purple_conversation_get_name(conv
),
1210 action
== win
->menu
->audio_call
? PURPLE_MEDIA_AUDIO
:
1211 action
== win
->menu
->video_call
? PURPLE_MEDIA_VIDEO
:
1212 action
== win
->menu
->audio_video_call
? PURPLE_MEDIA_AUDIO
|
1213 PURPLE_MEDIA_VIDEO
: PURPLE_MEDIA_NONE
);
1218 menu_send_file_cb(GtkAction
*action
, gpointer data
)
1220 PidginConvWindow
*win
= data
;
1221 PurpleConversation
*conv
= pidgin_conv_window_get_active_conversation(win
);
1223 if (PURPLE_IS_IM_CONVERSATION(conv
)) {
1224 purple_serv_send_file(purple_conversation_get_connection(conv
), purple_conversation_get_name(conv
), NULL
);
1230 menu_get_attention_cb(GObject
*obj
, gpointer data
)
1232 PidginConvWindow
*win
= data
;
1233 PurpleConversation
*conv
= pidgin_conv_window_get_active_conversation(win
);
1235 if (PURPLE_IS_IM_CONVERSATION(conv
)) {
1237 if ((GtkAction
*)obj
== win
->menu
->get_attention
)
1240 index
= GPOINTER_TO_INT(g_object_get_data(G_OBJECT(obj
), "index"));
1241 purple_protocol_send_attention(purple_conversation_get_connection(conv
),
1242 purple_conversation_get_name(conv
), index
);
1247 menu_add_pounce_cb(GtkAction
*action
, gpointer data
)
1249 PidginConvWindow
*win
= data
;
1250 PurpleConversation
*conv
;
1252 conv
= pidgin_conv_window_get_active_gtkconv(win
)->active_conv
;
1254 pidgin_pounce_editor_show(purple_conversation_get_account(conv
),
1255 purple_conversation_get_name(conv
), NULL
);
1259 menu_insert_link_cb(GtkAction
*action
, gpointer data
)
1261 PidginConvWindow
*win
= data
;
1262 PidginConversation
*gtkconv
;
1263 PidginWebView
*entry
;
1265 gtkconv
= pidgin_conv_window_get_active_gtkconv(win
);
1266 entry
= PIDGIN_WEBVIEW(gtkconv
->entry
);
1268 pidgin_webview_activate_toolbar(entry
, PIDGIN_WEBVIEW_ACTION_LINK
);
1272 menu_insert_image_cb(GtkAction
*action
, gpointer data
)
1274 PidginConvWindow
*win
= data
;
1275 PidginConversation
*gtkconv
;
1276 PidginWebView
*entry
;
1278 gtkconv
= pidgin_conv_window_get_active_gtkconv(win
);
1279 entry
= PIDGIN_WEBVIEW(gtkconv
->entry
);
1281 pidgin_webview_activate_toolbar(entry
, PIDGIN_WEBVIEW_ACTION_IMAGE
);
1285 menu_alias_cb(GtkAction
*action
, gpointer data
)
1287 PidginConvWindow
*win
= data
;
1288 PurpleConversation
*conv
;
1289 PurpleAccount
*account
;
1292 conv
= pidgin_conv_window_get_active_conversation(win
);
1293 account
= purple_conversation_get_account(conv
);
1294 name
= purple_conversation_get_name(conv
);
1296 if (PURPLE_IS_IM_CONVERSATION(conv
)) {
1299 b
= purple_blist_find_buddy(account
, name
);
1301 pidgin_dialogs_alias_buddy(b
);
1302 } else if (PURPLE_IS_CHAT_CONVERSATION(conv
)) {
1305 c
= purple_blist_find_chat(account
, name
);
1307 pidgin_dialogs_alias_chat(c
);
1312 menu_get_info_cb(GtkAction
*action
, gpointer data
)
1314 PidginConvWindow
*win
= data
;
1315 PurpleConversation
*conv
;
1317 conv
= pidgin_conv_window_get_active_conversation(win
);
1319 info_cb(NULL
, PIDGIN_CONVERSATION(conv
));
1323 menu_invite_cb(GtkAction
*action
, gpointer data
)
1325 PidginConvWindow
*win
= data
;
1326 PurpleConversation
*conv
;
1328 conv
= pidgin_conv_window_get_active_conversation(win
);
1330 invite_cb(NULL
, PIDGIN_CONVERSATION(conv
));
1334 menu_block_cb(GtkAction
*action
, gpointer data
)
1336 PidginConvWindow
*win
= data
;
1337 PurpleConversation
*conv
;
1339 conv
= pidgin_conv_window_get_active_conversation(win
);
1341 block_cb(NULL
, PIDGIN_CONVERSATION(conv
));
1345 menu_unblock_cb(GtkAction
*action
, gpointer data
)
1347 PidginConvWindow
*win
= data
;
1348 PurpleConversation
*conv
;
1350 conv
= pidgin_conv_window_get_active_conversation(win
);
1352 unblock_cb(NULL
, PIDGIN_CONVERSATION(conv
));
1356 menu_add_remove_cb(GtkAction
*action
, gpointer data
)
1358 PidginConvWindow
*win
= data
;
1359 PurpleConversation
*conv
;
1361 conv
= pidgin_conv_window_get_active_conversation(win
);
1363 add_remove_cb(NULL
, PIDGIN_CONVERSATION(conv
));
1367 close_already(gpointer data
)
1369 g_object_unref(data
);
1374 hide_conv(PidginConversation
*gtkconv
, gboolean closetimer
)
1378 purple_signal_emit(pidgin_conversations_get_handle(),
1379 "conversation-hiding", gtkconv
);
1381 for (list
= g_list_copy(gtkconv
->convs
); list
; list
= g_list_delete_link(list
, list
)) {
1382 PurpleConversation
*conv
= list
->data
;
1384 guint timer
= GPOINTER_TO_INT(g_object_get_data(G_OBJECT(conv
), "close-timer"));
1386 purple_timeout_remove(timer
);
1387 timer
= purple_timeout_add_seconds(CLOSE_CONV_TIMEOUT_SECS
, close_already
, conv
);
1388 g_object_set_data(G_OBJECT(conv
), "close-timer", GINT_TO_POINTER(timer
));
1391 /* I will miss you */
1392 purple_conversation_set_ui_ops(conv
, NULL
);
1394 pidgin_conv_window_remove_gtkconv(gtkconv
->win
, gtkconv
);
1395 pidgin_conv_window_add_gtkconv(hidden_convwin
, gtkconv
);
1401 menu_close_conv_cb(GtkAction
*action
, gpointer data
)
1403 PidginConvWindow
*win
= data
;
1405 close_conv_cb(NULL
, PIDGIN_CONVERSATION(pidgin_conv_window_get_active_conversation(win
)));
1409 menu_logging_cb(GtkAction
*action
, gpointer data
)
1411 PidginConvWindow
*win
= data
;
1412 PurpleConversation
*conv
;
1414 PurpleBlistNode
*node
;
1416 conv
= pidgin_conv_window_get_active_conversation(win
);
1421 logging
= gtk_toggle_action_get_active(GTK_TOGGLE_ACTION(action
));
1423 if (logging
== purple_conversation_is_logging(conv
))
1426 node
= get_conversation_blist_node(conv
);
1430 /* Enable logging first so the message below can be logged. */
1431 purple_conversation_set_logging(conv
, TRUE
);
1433 purple_conversation_write_system_message(conv
,
1434 _("Logging started. Future messages in this conversation will be logged."), 0);
1438 purple_conversation_write_system_message(conv
,
1439 _("Logging stopped. Future messages in this conversation will not be logged."), 0);
1441 /* Disable the logging second, so that the above message can be logged. */
1442 purple_conversation_set_logging(conv
, FALSE
);
1445 /* Save the setting IFF it's different than the pref. */
1446 if (PURPLE_IS_IM_CONVERSATION(conv
)) {
1447 if (logging
== purple_prefs_get_bool("/purple/logging/log_ims"))
1448 purple_blist_node_remove_setting(node
, "enable-logging");
1450 purple_blist_node_set_bool(node
, "enable-logging", logging
);
1451 } else if (PURPLE_IS_CHAT_CONVERSATION(conv
)) {
1452 if (logging
== purple_prefs_get_bool("/purple/logging/log_chats"))
1453 purple_blist_node_remove_setting(node
, "enable-logging");
1455 purple_blist_node_set_bool(node
, "enable-logging", logging
);
1460 menu_toolbar_cb(GtkAction
*action
, gpointer data
)
1462 purple_prefs_set_bool(PIDGIN_PREFS_ROOT
"/conversations/show_formatting_toolbar",
1463 gtk_toggle_action_get_active(GTK_TOGGLE_ACTION(action
)));
1467 menu_sounds_cb(GtkAction
*action
, gpointer data
)
1469 PidginConvWindow
*win
= data
;
1470 PurpleConversation
*conv
;
1471 PidginConversation
*gtkconv
;
1472 PurpleBlistNode
*node
;
1474 conv
= pidgin_conv_window_get_active_conversation(win
);
1479 gtkconv
= PIDGIN_CONVERSATION(conv
);
1481 gtkconv
->make_sound
=
1482 gtk_toggle_action_get_active(GTK_TOGGLE_ACTION(action
));
1483 node
= get_conversation_blist_node(conv
);
1485 purple_blist_node_set_bool(node
, "gtk-mute-sound", !gtkconv
->make_sound
);
1489 chat_do_im(PidginConversation
*gtkconv
, const char *who
)
1491 PurpleConversation
*conv
= gtkconv
->active_conv
;
1492 PurpleAccount
*account
;
1493 PurpleConnection
*gc
;
1494 PurpleProtocol
*protocol
= NULL
;
1495 gchar
*real_who
= NULL
;
1497 account
= purple_conversation_get_account(conv
);
1498 g_return_if_fail(account
!= NULL
);
1500 gc
= purple_account_get_connection(account
);
1501 g_return_if_fail(gc
!= NULL
);
1503 protocol
= purple_connection_get_protocol(gc
);
1506 real_who
= purple_protocol_chat_iface_get_user_real_name(protocol
, gc
,
1507 purple_chat_conversation_get_id(PURPLE_CHAT_CONVERSATION(conv
)), who
);
1509 if(!who
&& !real_who
)
1512 pidgin_dialogs_im_with_user(account
, real_who
? real_who
: who
);
1517 static void pidgin_conv_chat_update_user(PurpleChatUser
*chatuser
);
1520 ignore_cb(GtkWidget
*w
, PidginConversation
*gtkconv
)
1522 PurpleChatConversation
*chat
= PURPLE_CHAT_CONVERSATION(gtkconv
->active_conv
);
1525 name
= g_object_get_data(G_OBJECT(w
), "user_data");
1530 if (purple_chat_conversation_is_ignored_user(chat
, name
))
1531 purple_chat_conversation_unignore(chat
, name
);
1533 purple_chat_conversation_ignore(chat
, name
);
1535 pidgin_conv_chat_update_user(purple_chat_conversation_find_user(chat
, name
));
1539 menu_chat_im_cb(GtkWidget
*w
, PidginConversation
*gtkconv
)
1541 const char *who
= g_object_get_data(G_OBJECT(w
), "user_data");
1543 chat_do_im(gtkconv
, who
);
1547 menu_chat_send_file_cb(GtkWidget
*w
, PidginConversation
*gtkconv
)
1549 PurpleProtocol
*protocol
;
1550 PurpleConversation
*conv
= gtkconv
->active_conv
;
1551 const char *who
= g_object_get_data(G_OBJECT(w
), "user_data");
1552 PurpleConnection
*gc
= purple_conversation_get_connection(conv
);
1553 gchar
*real_who
= NULL
;
1555 g_return_if_fail(gc
!= NULL
);
1557 protocol
= purple_connection_get_protocol(gc
);
1560 real_who
= purple_protocol_chat_iface_get_user_real_name(protocol
, gc
,
1561 purple_chat_conversation_get_id(PURPLE_CHAT_CONVERSATION(conv
)), who
);
1563 purple_serv_send_file(gc
, real_who
? real_who
: who
, NULL
);
1568 menu_chat_info_cb(GtkWidget
*w
, PidginConversation
*gtkconv
)
1572 who
= g_object_get_data(G_OBJECT(w
), "user_data");
1574 chat_do_info(gtkconv
, who
);
1578 menu_chat_add_remove_cb(GtkWidget
*w
, PidginConversation
*gtkconv
)
1580 PurpleConversation
*conv
= gtkconv
->active_conv
;
1581 PurpleAccount
*account
;
1585 account
= purple_conversation_get_account(conv
);
1586 name
= g_object_get_data(G_OBJECT(w
), "user_data");
1587 b
= purple_blist_find_buddy(account
, name
);
1590 pidgin_dialogs_remove_buddy(b
);
1591 else if (account
!= NULL
&& purple_account_is_connected(account
))
1592 purple_blist_request_add_buddy(account
, name
, NULL
, NULL
);
1594 gtk_widget_grab_focus(PIDGIN_CONVERSATION(conv
)->entry
);
1598 get_class_for_user(const char *who
)
1600 return g_strconcat("-pidgin-user:", who
, NULL
);
1603 static WebKitDOMNode
*
1604 get_mark_for_user(PidginConversation
*gtkconv
, const char *who
)
1606 WebKitDOMDocument
*doc
;
1607 WebKitDOMNodeList
*nodes
;
1608 WebKitDOMNode
*node
= NULL
;
1612 doc
= webkit_web_view_get_dom_document(WEBKIT_WEB_VIEW(gtkconv
->webview
));
1614 tmp
= get_class_for_user(who
);
1615 nodes
= webkit_dom_document_get_elements_by_class_name(doc
, tmp
);
1618 if (nodes
!= NULL
) {
1619 len
= webkit_dom_node_list_get_length(nodes
);
1621 node
= webkit_dom_node_list_item(nodes
, len
- 1);
1624 g_object_unref(nodes
);
1630 menu_last_said_cb(GtkWidget
*w
, PidginConversation
*gtkconv
)
1632 WebKitDOMNode
*node
;
1635 who
= g_object_get_data(G_OBJECT(w
), "user_data");
1636 node
= get_mark_for_user(gtkconv
, who
);
1639 webkit_dom_element_scroll_into_view(WEBKIT_DOM_ELEMENT(node
), TRUE
);
1641 g_return_if_reached();
1645 create_chat_menu(PurpleChatConversation
*chat
, const char *who
, PurpleConnection
*gc
)
1647 static GtkWidget
*menu
= NULL
;
1648 PurpleProtocol
*protocol
= NULL
;
1649 PurpleConversation
*conv
= PURPLE_CONVERSATION(chat
);
1650 PurpleAccount
*account
= purple_conversation_get_account(conv
);
1651 gboolean is_me
= FALSE
;
1653 PurpleBuddy
*buddy
= NULL
;
1656 protocol
= purple_connection_get_protocol(gc
);
1659 * If a menu already exists, destroy it before creating a new one,
1660 * thus freeing-up the memory it occupied.
1663 gtk_widget_destroy(menu
);
1665 if (!strcmp(purple_chat_conversation_get_nick(chat
), purple_normalize(account
, who
)))
1668 menu
= gtk_menu_new();
1671 button
= pidgin_new_menu_item(menu
, _("IM"),
1672 PIDGIN_STOCK_TOOLBAR_MESSAGE_NEW
,
1673 G_CALLBACK(menu_chat_im_cb
),
1674 PIDGIN_CONVERSATION(conv
));
1677 gtk_widget_set_sensitive(button
, FALSE
);
1679 g_object_set_data_full(G_OBJECT(button
), "user_data", g_strdup(who
), g_free
);
1682 if (protocol
&& PURPLE_PROTOCOL_IMPLEMENTS(protocol
, XFER_IFACE
, send
))
1684 gboolean can_receive_file
= TRUE
;
1686 button
= pidgin_new_menu_item(menu
, _("Send File"),
1687 PIDGIN_STOCK_TOOLBAR_SEND_FILE
, G_CALLBACK(menu_chat_send_file_cb
),
1688 PIDGIN_CONVERSATION(conv
));
1690 if (gc
== NULL
|| protocol
== NULL
)
1691 can_receive_file
= FALSE
;
1693 gchar
*real_who
= NULL
;
1694 real_who
= purple_protocol_chat_iface_get_user_real_name(protocol
, gc
,
1695 purple_chat_conversation_get_id(chat
), who
);
1696 if (!(!PURPLE_PROTOCOL_IMPLEMENTS(protocol
, XFER_IFACE
, can_receive
) ||
1697 purple_protocol_xfer_iface_can_receive(protocol
, gc
, real_who
? real_who
: who
)))
1698 can_receive_file
= FALSE
;
1702 if (!can_receive_file
)
1703 gtk_widget_set_sensitive(button
, FALSE
);
1705 g_object_set_data_full(G_OBJECT(button
), "user_data", g_strdup(who
), g_free
);
1709 if (purple_chat_conversation_is_ignored_user(chat
, who
))
1710 button
= pidgin_new_menu_item(menu
, _("Un-Ignore"),
1711 PIDGIN_STOCK_IGNORE
, G_CALLBACK(ignore_cb
),
1712 PIDGIN_CONVERSATION(conv
));
1714 button
= pidgin_new_menu_item(menu
, _("Ignore"),
1715 PIDGIN_STOCK_IGNORE
, G_CALLBACK(ignore_cb
),
1716 PIDGIN_CONVERSATION(conv
));
1719 gtk_widget_set_sensitive(button
, FALSE
);
1721 g_object_set_data_full(G_OBJECT(button
), "user_data", g_strdup(who
), g_free
);
1724 if (protocol
&& PURPLE_PROTOCOL_IMPLEMENTS(protocol
, SERVER_IFACE
, get_info
)) {
1725 button
= pidgin_new_menu_item(menu
, _("Info"),
1726 PIDGIN_STOCK_TOOLBAR_USER_INFO
,
1727 G_CALLBACK(menu_chat_info_cb
),
1728 PIDGIN_CONVERSATION(conv
));
1731 gtk_widget_set_sensitive(button
, FALSE
);
1733 g_object_set_data_full(G_OBJECT(button
), "user_data", g_strdup(who
), g_free
);
1736 if (!is_me
&& protocol
&& !(purple_protocol_get_options(protocol
) & OPT_PROTO_UNIQUE_CHATNAME
)) {
1737 if ((buddy
= purple_blist_find_buddy(account
, who
)) != NULL
)
1738 button
= pidgin_new_menu_item(menu
, _("Remove"),
1740 G_CALLBACK(menu_chat_add_remove_cb
),
1741 PIDGIN_CONVERSATION(conv
));
1743 button
= pidgin_new_menu_item(menu
, _("Add"),
1745 G_CALLBACK(menu_chat_add_remove_cb
),
1746 PIDGIN_CONVERSATION(conv
));
1749 gtk_widget_set_sensitive(button
, FALSE
);
1751 g_object_set_data_full(G_OBJECT(button
), "user_data", g_strdup(who
), g_free
);
1754 button
= pidgin_new_menu_item(menu
, _("Last Said"), GTK_STOCK_INDEX
,
1755 G_CALLBACK(menu_last_said_cb
), PIDGIN_CONVERSATION(conv
));
1756 g_object_set_data_full(G_OBJECT(button
), "user_data", g_strdup(who
), g_free
);
1757 if (!get_mark_for_user(PIDGIN_CONVERSATION(conv
), who
))
1758 gtk_widget_set_sensitive(button
, FALSE
);
1762 if (purple_account_is_connected(account
))
1763 pidgin_append_blist_node_proto_menu(menu
, purple_account_get_connection(account
),
1764 (PurpleBlistNode
*)buddy
);
1765 pidgin_append_blist_node_extended_menu(menu
, (PurpleBlistNode
*)buddy
);
1766 gtk_widget_show_all(menu
);
1774 gtkconv_chat_popup_menu_cb(GtkWidget
*widget
, PidginConversation
*gtkconv
)
1776 PurpleConversation
*conv
= gtkconv
->active_conv
;
1777 PidginChatPane
*gtkchat
;
1778 PurpleConnection
*gc
;
1779 PurpleAccount
*account
;
1780 GtkTreeSelection
*sel
;
1782 GtkTreeModel
*model
;
1786 gtkconv
= PIDGIN_CONVERSATION(conv
);
1787 gtkchat
= gtkconv
->u
.chat
;
1788 account
= purple_conversation_get_account(conv
);
1789 gc
= purple_account_get_connection(account
);
1791 model
= gtk_tree_view_get_model(GTK_TREE_VIEW(gtkchat
->list
));
1793 sel
= gtk_tree_view_get_selection(GTK_TREE_VIEW(gtkchat
->list
));
1794 if(!gtk_tree_selection_get_selected(sel
, NULL
, &iter
))
1797 gtk_tree_model_get(GTK_TREE_MODEL(model
), &iter
, CHAT_USERS_NAME_COLUMN
, &who
, -1);
1798 menu
= create_chat_menu (PURPLE_CHAT_CONVERSATION(conv
), who
, gc
);
1799 gtk_menu_popup(GTK_MENU(menu
), NULL
, NULL
,
1800 pidgin_treeview_popup_menu_position_func
, widget
,
1801 0, GDK_CURRENT_TIME
);
1809 right_click_chat_cb(GtkWidget
*widget
, GdkEventButton
*event
,
1810 PidginConversation
*gtkconv
)
1812 PurpleConversation
*conv
= gtkconv
->active_conv
;
1813 PidginChatPane
*gtkchat
;
1814 PurpleConnection
*gc
;
1815 PurpleAccount
*account
;
1818 GtkTreeModel
*model
;
1819 GtkTreeViewColumn
*column
;
1823 gtkchat
= gtkconv
->u
.chat
;
1824 account
= purple_conversation_get_account(conv
);
1825 gc
= purple_account_get_connection(account
);
1827 model
= gtk_tree_view_get_model(GTK_TREE_VIEW(gtkchat
->list
));
1829 gtk_tree_view_get_path_at_pos(GTK_TREE_VIEW(gtkchat
->list
),
1830 event
->x
, event
->y
, &path
, &column
, &x
, &y
);
1835 gtk_tree_selection_select_path(GTK_TREE_SELECTION(
1836 gtk_tree_view_get_selection(GTK_TREE_VIEW(gtkchat
->list
))), path
);
1837 gtk_tree_view_set_cursor(GTK_TREE_VIEW(gtkchat
->list
),
1839 gtk_widget_grab_focus(GTK_WIDGET(gtkchat
->list
));
1841 gtk_tree_model_get_iter(GTK_TREE_MODEL(model
), &iter
, path
);
1842 gtk_tree_model_get(GTK_TREE_MODEL(model
), &iter
, CHAT_USERS_NAME_COLUMN
, &who
, -1);
1844 /* emit chat-nick-clicked signal */
1845 if (event
->type
== GDK_BUTTON_PRESS
) {
1846 gint plugin_return
= GPOINTER_TO_INT(purple_signal_emit_return_1(
1847 pidgin_conversations_get_handle(), "chat-nick-clicked",
1848 conv
, who
, event
->button
));
1853 if (event
->button
== 1 && event
->type
== GDK_2BUTTON_PRESS
) {
1854 chat_do_im(gtkconv
, who
);
1855 } else if (event
->button
== 2 && event
->type
== GDK_BUTTON_PRESS
) {
1856 /* Move to user's anchor */
1857 WebKitDOMNode
*node
= get_mark_for_user(gtkconv
, who
);
1860 webkit_dom_element_scroll_into_view(WEBKIT_DOM_ELEMENT(node
), TRUE
);
1862 } else if (event
->button
== 3 && event
->type
== GDK_BUTTON_PRESS
) {
1863 GtkWidget
*menu
= create_chat_menu (PURPLE_CHAT_CONVERSATION(conv
), who
, gc
);
1864 gtk_menu_popup(GTK_MENU(menu
), NULL
, NULL
, NULL
, NULL
,
1865 event
->button
, event
->time
);
1870 gtk_tree_path_free(path
);
1876 activate_list_cb(GtkTreeView
*list
, GtkTreePath
*path
, GtkTreeViewColumn
*column
, PidginConversation
*gtkconv
)
1879 GtkTreeModel
*model
;
1882 model
= gtk_tree_view_get_model(GTK_TREE_VIEW(list
));
1884 gtk_tree_model_get_iter(GTK_TREE_MODEL(model
), &iter
, path
);
1885 gtk_tree_model_get(GTK_TREE_MODEL(model
), &iter
, CHAT_USERS_NAME_COLUMN
, &who
, -1);
1886 chat_do_im(gtkconv
, who
);
1892 move_to_next_unread_tab(PidginConversation
*gtkconv
, gboolean forward
)
1894 PidginConversation
*next_gtkconv
= NULL
, *most_active
= NULL
;
1895 PidginUnseenState unseen_state
= PIDGIN_UNSEEN_NONE
;
1896 PidginConvWindow
*win
;
1897 int initial
, i
, total
, diff
;
1900 initial
= gtk_notebook_page_num(GTK_NOTEBOOK(win
->notebook
),
1902 total
= pidgin_conv_window_get_gtkconv_count(win
);
1903 /* By adding total here, the moduli calculated later will always have two
1904 * positive arguments. x % y where x < 0 is not guaranteed to return a
1907 diff
= (forward
? 1 : -1) + total
;
1909 for (i
= (initial
+ diff
) % total
; i
!= initial
; i
= (i
+ diff
) % total
) {
1910 next_gtkconv
= pidgin_conv_window_get_gtkconv_at_index(win
, i
);
1911 if (next_gtkconv
->unseen_state
> unseen_state
) {
1912 most_active
= next_gtkconv
;
1913 unseen_state
= most_active
->unseen_state
;
1914 if(PIDGIN_UNSEEN_NICK
== unseen_state
) /* highest possible state */
1919 if (most_active
== NULL
) { /* no new messages */
1920 i
= (i
+ diff
) % total
;
1921 most_active
= pidgin_conv_window_get_gtkconv_at_index(win
, i
);
1924 if (most_active
!= NULL
&& most_active
!= gtkconv
)
1925 pidgin_conv_window_switch_gtkconv(win
, most_active
);
1929 gtkconv_cycle_focus(PidginConversation
*gtkconv
, GtkDirectionType dir
)
1931 PurpleConversation
*conv
= gtkconv
->active_conv
;
1932 gboolean chat
= PURPLE_IS_CHAT_CONVERSATION(conv
);
1933 GtkWidget
*next
= NULL
;
1938 {gtkconv
->entry
, gtkconv
->webview
},
1939 {gtkconv
->webview
, chat
? gtkconv
->u
.chat
->list
: gtkconv
->entry
},
1940 {chat
? gtkconv
->u
.chat
->list
: NULL
, gtkconv
->entry
},
1944 for (ptr
= transitions
; !next
&& ptr
->from
; ptr
++) {
1945 GtkWidget
*from
, *to
;
1946 if (dir
== GTK_DIR_TAB_FORWARD
) {
1953 if (gtk_widget_is_focus(from
))
1958 gtk_widget_grab_focus(next
);
1963 update_typing_inserting(PidginConversation
*gtkconv
)
1967 g_return_if_fail(gtkconv
!= NULL
);
1969 is_empty
= pidgin_webview_is_empty(PIDGIN_WEBVIEW(gtkconv
->entry
));
1971 got_typing_keypress(gtkconv
, is_empty
);
1975 update_typing_deleting_cb(PidginConversation
*gtkconv
)
1977 PurpleIMConversation
*im
= PURPLE_IM_CONVERSATION(gtkconv
->active_conv
);
1978 gboolean is_empty
= pidgin_webview_is_empty(PIDGIN_WEBVIEW(gtkconv
->entry
));
1981 /* We deleted all the text, so turn off typing. */
1982 purple_im_conversation_stop_send_typed_timeout(im
);
1984 purple_serv_send_typing(purple_conversation_get_connection(gtkconv
->active_conv
),
1985 purple_conversation_get_name(gtkconv
->active_conv
),
1986 PURPLE_IM_NOT_TYPING
);
1989 /* We're deleting, but not all of it, so it counts as typing. */
1990 got_typing_keypress(gtkconv
, FALSE
);
1997 update_typing_deleting(PidginConversation
*gtkconv
)
2001 g_return_if_fail(gtkconv
!= NULL
);
2003 is_empty
= pidgin_webview_is_empty(PIDGIN_WEBVIEW(gtkconv
->entry
));
2006 purple_timeout_add(0, (GSourceFunc
)update_typing_deleting_cb
, gtkconv
);
2010 conv_keypress_common(PidginConversation
*gtkconv
, GdkEventKey
*event
)
2012 PidginConvWindow
*win
;
2016 curconv
= gtk_notebook_get_current_page(GTK_NOTEBOOK(win
->notebook
));
2018 /* clear any tooltips */
2019 pidgin_tooltip_destroy();
2021 /* If CTRL was held down... */
2022 if (event
->state
& GDK_CONTROL_MASK
) {
2023 switch (event
->keyval
) {
2024 case GDK_KEY_Page_Down
:
2025 case GDK_KEY_KP_Page_Down
:
2027 if (!pidgin_conv_window_get_gtkconv_at_index(win
, curconv
+ 1))
2028 gtk_notebook_set_current_page(GTK_NOTEBOOK(win
->notebook
), 0);
2030 gtk_notebook_set_current_page(GTK_NOTEBOOK(win
->notebook
), curconv
+ 1);
2034 case GDK_KEY_Page_Up
:
2035 case GDK_KEY_KP_Page_Up
:
2037 if (!pidgin_conv_window_get_gtkconv_at_index(win
, curconv
- 1))
2038 gtk_notebook_set_current_page(GTK_NOTEBOOK(win
->notebook
), -1);
2040 gtk_notebook_set_current_page(GTK_NOTEBOOK(win
->notebook
), curconv
- 1);
2045 case GDK_KEY_KP_Tab
:
2046 case GDK_KEY_ISO_Left_Tab
:
2047 if (event
->state
& GDK_SHIFT_MASK
) {
2048 move_to_next_unread_tab(gtkconv
, FALSE
);
2050 move_to_next_unread_tab(gtkconv
, TRUE
);
2057 gtk_notebook_reorder_child(GTK_NOTEBOOK(win
->notebook
),
2058 gtk_notebook_get_nth_page(GTK_NOTEBOOK(win
->notebook
), curconv
),
2063 case GDK_KEY_period
:
2064 gtk_notebook_reorder_child(GTK_NOTEBOOK(win
->notebook
),
2065 gtk_notebook_get_nth_page(GTK_NOTEBOOK(win
->notebook
), curconv
),
2066 (curconv
+ 1) % gtk_notebook_get_n_pages(GTK_NOTEBOOK(win
->notebook
)));
2070 if (gtkconv_cycle_focus(gtkconv
, event
->state
& GDK_SHIFT_MASK
? GTK_DIR_TAB_BACKWARD
: GTK_DIR_TAB_FORWARD
))
2073 } /* End of switch */
2076 /* If ALT (or whatever) was held down... */
2077 else if (event
->state
& GDK_MOD1_MASK
)
2079 if (event
->keyval
> '0' && event
->keyval
<= '9')
2081 guint switchto
= event
->keyval
- '1';
2082 if (switchto
< pidgin_conv_window_get_gtkconv_count(win
))
2083 gtk_notebook_set_current_page(GTK_NOTEBOOK(win
->notebook
), switchto
);
2089 /* If neither CTRL nor ALT were held down... */
2092 switch (event
->keyval
) {
2094 if (gtk_widget_is_focus(GTK_WIDGET(win
->notebook
))) {
2095 infopane_entry_activate(gtkconv
);
2100 if (gtkconv_cycle_focus(gtkconv
, event
->state
& GDK_SHIFT_MASK
? GTK_DIR_TAB_BACKWARD
: GTK_DIR_TAB_FORWARD
))
2109 entry_key_press_cb(GtkWidget
*entry
, GdkEventKey
*event
, gpointer data
)
2111 PurpleConversation
*conv
;
2112 PidginConversation
*gtkconv
;
2114 gtkconv
= (PidginConversation
*)data
;
2115 conv
= gtkconv
->active_conv
;
2117 if (conv_keypress_common(gtkconv
, event
))
2120 /* If CTRL was held down... */
2121 if (event
->state
& GDK_CONTROL_MASK
) {
2122 switch (event
->keyval
) {
2124 if (!gtkconv
->send_history
)
2127 if (gtkconv
->entry
!= entry
)
2130 if (!gtkconv
->send_history
->prev
) {
2131 g_free(gtkconv
->send_history
->data
);
2133 gtkconv
->send_history
->data
=
2134 pidgin_webview_get_body_html(PIDGIN_WEBVIEW(gtkconv
->entry
));
2137 if (gtkconv
->send_history
->next
&& gtkconv
->send_history
->next
->data
) {
2140 /* TODO WebKit: maybe not necessary? */
2142 GtkTextBuffer
*buffer
= gtk_text_view_get_buffer(GTK_TEXT_VIEW(gtkconv
->entry
));
2145 gtkconv
->send_history
= gtkconv
->send_history
->next
;
2147 /* Block the signal to prevent application of default formatting. */
2148 object
= g_object_ref(G_OBJECT(gtkconv
->entry
));
2149 g_signal_handlers_block_matched(object
, G_SIGNAL_MATCH_DATA
,
2150 0, 0, NULL
, NULL
, gtkconv
);
2151 /* Clear the formatting. */
2152 pidgin_webview_clear_formatting(PIDGIN_WEBVIEW(gtkconv
->entry
));
2153 /* Unblock the signal. */
2154 g_signal_handlers_unblock_matched(object
, G_SIGNAL_MATCH_DATA
,
2155 0, 0, NULL
, NULL
, gtkconv
);
2156 g_object_unref(object
);
2158 pidgin_webview_load_html_string(PIDGIN_WEBVIEW(gtkconv
->entry
),
2159 gtkconv
->send_history
->data
);
2160 /* this is mainly just a hack so the formatting at the
2161 * cursor gets picked up. */
2163 /* TODO WebKit: maybe not necessary? */
2164 gtk_text_buffer_get_end_iter(buffer
, &iter
);
2165 gtk_text_buffer_move_mark_by_name(buffer
, "insert", &iter
);
2173 if (!gtkconv
->send_history
)
2176 if (gtkconv
->entry
!= entry
)
2179 if (gtkconv
->send_history
->prev
&& gtkconv
->send_history
->prev
->data
) {
2182 /* TODO WebKit: maybe not necessary? */
2184 GtkTextBuffer
*buffer
= gtk_text_view_get_buffer(GTK_TEXT_VIEW(gtkconv
->entry
));
2187 gtkconv
->send_history
= gtkconv
->send_history
->prev
;
2189 /* Block the signal to prevent application of default formatting. */
2190 object
= g_object_ref(G_OBJECT(gtkconv
->entry
));
2191 g_signal_handlers_block_matched(object
, G_SIGNAL_MATCH_DATA
,
2192 0, 0, NULL
, NULL
, gtkconv
);
2193 /* Clear the formatting. */
2194 pidgin_webview_clear_formatting(PIDGIN_WEBVIEW(gtkconv
->entry
));
2195 /* Unblock the signal. */
2196 g_signal_handlers_unblock_matched(object
, G_SIGNAL_MATCH_DATA
,
2197 0, 0, NULL
, NULL
, gtkconv
);
2198 g_object_unref(object
);
2200 pidgin_webview_load_html_string(PIDGIN_WEBVIEW(gtkconv
->entry
),
2201 gtkconv
->send_history
->data
);
2202 /* this is mainly just a hack so the formatting at the
2203 * cursor gets picked up. */
2204 if (*(char *)gtkconv
->send_history
->data
) {
2206 /* TODO WebKit: maybe not necessary? */
2207 gtk_text_buffer_get_end_iter(buffer
, &iter
);
2208 gtk_text_buffer_move_mark_by_name(buffer
, "insert", &iter
);
2211 /* Restore the default formatting */
2212 default_formatize(gtkconv
);
2218 } /* End of switch */
2221 /* If ALT (or whatever) was held down... */
2222 else if (event
->state
& GDK_MOD1_MASK
) {
2226 /* If neither CTRL nor ALT were held down... */
2228 switch (event
->keyval
) {
2230 case GDK_KEY_KP_Tab
:
2231 case GDK_KEY_ISO_Left_Tab
:
2232 if (gtkconv
->entry
!= entry
)
2236 plugin_return
= GPOINTER_TO_INT(purple_signal_emit_return_1(
2237 pidgin_conversations_get_handle(), "chat-nick-autocomplete",
2238 conv
, event
->state
& GDK_SHIFT_MASK
));
2239 return plugin_return
? TRUE
: tab_complete(conv
);
2243 case GDK_KEY_Page_Up
:
2244 case GDK_KEY_KP_Page_Up
:
2245 pidgin_webview_page_up(PIDGIN_WEBVIEW(gtkconv
->webview
));
2249 case GDK_KEY_Page_Down
:
2250 case GDK_KEY_KP_Page_Down
:
2251 pidgin_webview_page_down(PIDGIN_WEBVIEW(gtkconv
->webview
));
2255 case GDK_KEY_KP_Enter
:
2256 case GDK_KEY_Return
:
2257 send_cb(entry
, gtkconv
);
2264 if (PURPLE_IS_IM_CONVERSATION(conv
) &&
2265 purple_prefs_get_bool("/purple/conversations/im/send_typing")) {
2267 switch (event
->keyval
) {
2268 case GDK_KEY_BackSpace
:
2269 case GDK_KEY_Delete
:
2270 case GDK_KEY_KP_Delete
:
2271 update_typing_deleting(gtkconv
);
2274 update_typing_inserting(gtkconv
);
2283 * This guy just kills a single right click from being propagated any
2284 * further. I have no idea *why* we need this, but we do ... It
2285 * prevents right clicks on the GtkTextView in a convo dialog from
2286 * going all the way down to the notebook. I suspect a bug in
2287 * GtkTextView, but I'm not ready to point any fingers yet.
2290 entry_stop_rclick_cb(GtkWidget
*widget
, GdkEventButton
*event
, gpointer data
)
2292 if (event
->button
== 3 && event
->type
== GDK_BUTTON_PRESS
) {
2293 /* Right single click */
2294 g_signal_stop_emission_by_name(G_OBJECT(widget
), "button_press_event");
2303 * If someone tries to type into the conversation backlog of a
2304 * conversation window then we yank focus from the conversation backlog
2305 * and give it to the text entry box so that people can type
2306 * all the live long day and it will get entered into the entry box.
2309 refocus_entry_cb(GtkWidget
*widget
, GdkEventKey
*event
, gpointer data
)
2311 PidginConversation
*gtkconv
= data
;
2313 /* If we have a valid key for the conversation display, then exit */
2314 if ((event
->state
& GDK_CONTROL_MASK
) ||
2315 (event
->keyval
== GDK_KEY_F6
) ||
2316 (event
->keyval
== GDK_KEY_F10
) ||
2317 (event
->keyval
== GDK_KEY_Menu
) ||
2318 (event
->keyval
== GDK_KEY_Shift_L
) ||
2319 (event
->keyval
== GDK_KEY_Shift_R
) ||
2320 (event
->keyval
== GDK_KEY_Control_L
) ||
2321 (event
->keyval
== GDK_KEY_Control_R
) ||
2322 (event
->keyval
== GDK_KEY_Escape
) ||
2323 (event
->keyval
== GDK_KEY_Up
) ||
2324 (event
->keyval
== GDK_KEY_Down
) ||
2325 (event
->keyval
== GDK_KEY_Left
) ||
2326 (event
->keyval
== GDK_KEY_Right
) ||
2327 (event
->keyval
== GDK_KEY_Page_Up
) ||
2328 (event
->keyval
== GDK_KEY_KP_Page_Up
) ||
2329 (event
->keyval
== GDK_KEY_Page_Down
) ||
2330 (event
->keyval
== GDK_KEY_KP_Page_Down
) ||
2331 (event
->keyval
== GDK_KEY_Home
) ||
2332 (event
->keyval
== GDK_KEY_End
) ||
2333 (event
->keyval
== GDK_KEY_Tab
) ||
2334 (event
->keyval
== GDK_KEY_KP_Tab
) ||
2335 (event
->keyval
== GDK_KEY_ISO_Left_Tab
))
2337 if (event
->type
== GDK_KEY_PRESS
)
2338 return conv_keypress_common(gtkconv
, event
);
2342 gtk_widget_grab_focus(gtkconv
->entry
);
2343 gtk_widget_event(gtkconv
->entry
, (GdkEvent
*)event
);
2349 regenerate_options_items(PidginConvWindow
*win
);
2352 pidgin_conv_switch_active_conversation(PurpleConversation
*conv
)
2354 PidginConversation
*gtkconv
;
2355 PurpleConversation
*old_conv
;
2356 PidginWebView
*entry
;
2357 PurpleConnectionFlags features
;
2359 g_return_if_fail(conv
!= NULL
);
2361 gtkconv
= PIDGIN_CONVERSATION(conv
);
2362 old_conv
= gtkconv
->active_conv
;
2364 purple_debug_info("gtkconv", "setting active conversation on toolbar %p\n",
2367 if (old_conv
== conv
)
2370 purple_conversation_close_logs(old_conv
);
2371 gtkconv
->active_conv
= conv
;
2373 pidgin_webview_switch_active_conversation(
2374 PIDGIN_WEBVIEW(gtkconv
->entry
), conv
);
2375 pidgin_webview_switch_active_conversation(
2376 PIDGIN_WEBVIEW(gtkconv
->webview
), conv
);
2377 purple_conversation_set_logging(conv
,
2378 gtk_toggle_action_get_active(GTK_TOGGLE_ACTION(gtkconv
->win
->menu
->logging
)));
2380 entry
= PIDGIN_WEBVIEW(gtkconv
->entry
);
2382 features
= purple_conversation_get_features(conv
);
2383 if (!(features
& PURPLE_CONNECTION_FLAG_HTML
))
2384 pidgin_webview_clear_formatting(PIDGIN_WEBVIEW(gtkconv
->entry
));
2385 else if (features
& PURPLE_CONNECTION_FLAG_FORMATTING_WBFO
&&
2386 !(purple_conversation_get_features(old_conv
) & PURPLE_CONNECTION_FLAG_FORMATTING_WBFO
))
2388 /* The old conversation allowed formatting on parts of the
2389 * buffer, but the new one only allows it on the whole
2390 * buffer. This code saves the formatting from the current
2391 * position of the cursor, clears the formatting, then
2392 * applies the saved formatting to the entire buffer. */
2398 char *fontface
= pidgin_webview_get_current_fontface(entry
);
2399 char *forecolor
= pidgin_webview_get_current_forecolor(entry
);
2400 char *backcolor
= pidgin_webview_get_current_backcolor(entry
);
2402 /* TODO WebKit: Do we need this again? */
2403 char *background
= pidgin_webview_get_current_background(entry
);
2405 gint fontsize
= pidgin_webview_get_current_fontsize(entry
);
2408 gboolean underline2
;
2411 pidgin_webview_get_current_format(entry
, &bold
, &italic
, &underline
, &strike
);
2413 /* Clear existing formatting */
2414 pidgin_webview_clear_formatting(entry
);
2416 /* Apply saved formatting to the whole buffer. */
2418 pidgin_webview_get_current_format(entry
, &bold2
, &italic2
, &underline2
, &strike2
);
2421 pidgin_webview_toggle_bold(entry
);
2423 if (italic
!= italic2
)
2424 pidgin_webview_toggle_italic(entry
);
2426 if (underline
!= underline2
)
2427 pidgin_webview_toggle_underline(entry
);
2429 if (strike
!= strike2
)
2430 pidgin_webview_toggle_strike(entry
);
2432 pidgin_webview_toggle_fontface(entry
, fontface
);
2434 if (!(features
& PURPLE_CONNECTION_FLAG_NO_FONTSIZE
))
2435 pidgin_webview_font_set_size(entry
, fontsize
);
2437 pidgin_webview_toggle_forecolor(entry
, forecolor
);
2439 if (!(features
& PURPLE_CONNECTION_FLAG_NO_BGCOLOR
))
2441 pidgin_webview_toggle_backcolor(entry
, backcolor
);
2443 pidgin_webview_toggle_background(entry
, background
);
2456 /* This is done in default_formatize, which is called from clear_formatting_cb,
2457 * which is (obviously) a clear_formatting signal handler. However, if we're
2458 * here, we didn't call pidgin_webview_clear_formatting() (because we want to
2459 * preserve the formatting exactly as it is), so we have to do this now. */
2460 pidgin_webview_set_whole_buffer_formatting_only(entry
,
2461 (features
& PURPLE_CONNECTION_FLAG_FORMATTING_WBFO
));
2464 purple_signal_emit(pidgin_conversations_get_handle(), "conversation-switched", conv
);
2466 gray_stuff_out(gtkconv
);
2467 update_typing_icon(gtkconv
);
2468 g_object_set_data(G_OBJECT(entry
), "transient_buddy", NULL
);
2469 regenerate_options_items(gtkconv
->win
);
2471 gtk_window_set_title(GTK_WINDOW(gtkconv
->win
->window
),
2472 gtk_label_get_text(GTK_LABEL(gtkconv
->tab_label
)));
2476 menu_conv_sel_send_cb(GObject
*m
, gpointer data
)
2478 PurpleAccount
*account
= g_object_get_data(m
, "purple_account");
2479 gchar
*name
= g_object_get_data(m
, "purple_buddy_name");
2480 PurpleIMConversation
*im
;
2482 if (gtk_check_menu_item_get_active((GtkCheckMenuItem
*) m
) == FALSE
)
2485 im
= purple_im_conversation_new(account
, name
);
2486 pidgin_conv_switch_active_conversation(PURPLE_CONVERSATION(im
));
2489 /**************************************************************************
2490 * A bunch of buddy icon functions
2491 **************************************************************************/
2493 static GList
*get_protocol_icon_list(PurpleAccount
*account
)
2496 PurpleProtocol
*protocol
=
2497 purple_protocols_find(purple_account_get_protocol_id(account
));
2498 const char *protoname
= purple_protocol_class_list_icon(protocol
, account
, NULL
);
2499 l
= g_hash_table_lookup(protocol_lists
, protoname
);
2503 l
= g_list_prepend(l
, pidgin_create_protocol_icon(account
, PIDGIN_PROTOCOL_ICON_LARGE
));
2504 l
= g_list_prepend(l
, pidgin_create_protocol_icon(account
, PIDGIN_PROTOCOL_ICON_MEDIUM
));
2505 l
= g_list_prepend(l
, pidgin_create_protocol_icon(account
, PIDGIN_PROTOCOL_ICON_SMALL
));
2507 g_hash_table_insert(protocol_lists
, g_strdup(protoname
), l
);
2512 pidgin_conv_get_tab_icons(PurpleConversation
*conv
)
2514 PurpleAccount
*account
= NULL
;
2515 const char *name
= NULL
;
2517 g_return_val_if_fail(conv
!= NULL
, NULL
);
2519 account
= purple_conversation_get_account(conv
);
2520 name
= purple_conversation_get_name(conv
);
2522 g_return_val_if_fail(account
!= NULL
, NULL
);
2523 g_return_val_if_fail(name
!= NULL
, NULL
);
2525 /* Use the buddy icon, if possible */
2526 if (PURPLE_IS_IM_CONVERSATION(conv
)) {
2527 PurpleBuddy
*b
= purple_blist_find_buddy(account
, name
);
2530 p
= purple_buddy_get_presence(b
);
2531 if (purple_presence_is_status_primitive_active(p
, PURPLE_STATUS_AWAY
))
2533 if (purple_presence_is_status_primitive_active(p
, PURPLE_STATUS_UNAVAILABLE
))
2535 if (purple_presence_is_status_primitive_active(p
, PURPLE_STATUS_EXTENDED_AWAY
))
2537 if (purple_presence_is_status_primitive_active(p
, PURPLE_STATUS_OFFLINE
))
2538 return offline_list
;
2540 return available_list
;
2544 return get_protocol_icon_list(account
);
2548 pidgin_conv_get_icon_stock(PurpleConversation
*conv
)
2550 PurpleAccount
*account
= NULL
;
2551 const char *stock
= NULL
;
2553 g_return_val_if_fail(conv
!= NULL
, NULL
);
2555 account
= purple_conversation_get_account(conv
);
2556 g_return_val_if_fail(account
!= NULL
, NULL
);
2558 /* Use the buddy icon, if possible */
2559 if (PURPLE_IS_IM_CONVERSATION(conv
)) {
2560 const char *name
= NULL
;
2562 name
= purple_conversation_get_name(conv
);
2563 b
= purple_blist_find_buddy(account
, name
);
2565 PurplePresence
*p
= purple_buddy_get_presence(b
);
2566 PurpleStatus
*active
= purple_presence_get_active_status(p
);
2567 PurpleStatusType
*type
= purple_status_get_status_type(active
);
2568 PurpleStatusPrimitive prim
= purple_status_type_get_primitive(type
);
2569 stock
= pidgin_stock_id_from_status_primitive(prim
);
2571 stock
= PIDGIN_STOCK_STATUS_PERSON
;
2574 stock
= PIDGIN_STOCK_STATUS_CHAT
;
2581 pidgin_conv_get_icon(PurpleConversation
*conv
, GtkWidget
*parent
, const char *icon_size
)
2583 PurpleAccount
*account
= NULL
;
2584 const char *name
= NULL
;
2585 const char *stock
= NULL
;
2586 GdkPixbuf
*status
= NULL
;
2587 PurpleBlistUiOps
*ops
= purple_blist_get_ui_ops();
2590 g_return_val_if_fail(conv
!= NULL
, NULL
);
2592 account
= purple_conversation_get_account(conv
);
2593 name
= purple_conversation_get_name(conv
);
2595 g_return_val_if_fail(account
!= NULL
, NULL
);
2596 g_return_val_if_fail(name
!= NULL
, NULL
);
2598 /* Use the buddy icon, if possible */
2599 if (PURPLE_IS_IM_CONVERSATION(conv
)) {
2600 PurpleBuddy
*b
= purple_blist_find_buddy(account
, name
);
2602 /* I hate this hack. It fixes a bug where the pending message icon
2603 * displays in the conv tab even though it shouldn't.
2604 * A better solution would be great. */
2605 if (ops
&& ops
->update
)
2606 ops
->update(NULL
, (PurpleBlistNode
*)b
);
2610 stock
= pidgin_conv_get_icon_stock(conv
);
2611 size
= gtk_icon_size_from_name(icon_size
);
2612 status
= gtk_widget_render_icon (parent
, stock
, size
, "GtkWidget");
2617 pidgin_conv_get_tab_icon(PurpleConversation
*conv
, gboolean small_icon
)
2619 const char *icon_size
= small_icon
? PIDGIN_ICON_SIZE_TANGO_MICROSCOPIC
: PIDGIN_ICON_SIZE_TANGO_EXTRA_SMALL
;
2620 return pidgin_conv_get_icon(conv
, PIDGIN_CONVERSATION(conv
)->icon
, icon_size
);
2625 update_tab_icon(PurpleConversation
*conv
)
2627 PidginConversation
*gtkconv
;
2628 PidginConvWindow
*win
;
2630 GdkPixbuf
*emblem
= NULL
;
2631 const char *status
= NULL
;
2632 const char *infopane_status
= NULL
;
2634 g_return_if_fail(conv
!= NULL
);
2636 gtkconv
= PIDGIN_CONVERSATION(conv
);
2638 if (conv
!= gtkconv
->active_conv
)
2641 status
= infopane_status
= pidgin_conv_get_icon_stock(conv
);
2643 if (PURPLE_IS_IM_CONVERSATION(conv
)) {
2644 PurpleBuddy
*b
= purple_blist_find_buddy(purple_conversation_get_account(conv
), purple_conversation_get_name(conv
));
2646 emblem
= pidgin_blist_get_emblem((PurpleBlistNode
*)b
);
2649 g_return_if_fail(status
!= NULL
);
2651 g_object_set(G_OBJECT(gtkconv
->icon
), "stock", status
, NULL
);
2652 g_object_set(G_OBJECT(gtkconv
->menu_icon
), "stock", status
, NULL
);
2654 gtk_list_store_set(GTK_LIST_STORE(gtkconv
->infopane_model
),
2655 &(gtkconv
->infopane_iter
),
2656 CONV_ICON_COLUMN
, infopane_status
, -1);
2658 gtk_list_store_set(GTK_LIST_STORE(gtkconv
->infopane_model
),
2659 &(gtkconv
->infopane_iter
),
2660 CONV_EMBLEM_COLUMN
, emblem
, -1);
2662 g_object_unref(emblem
);
2664 if (purple_prefs_get_bool(PIDGIN_PREFS_ROOT
"/blist/show_protocol_icons")) {
2665 emblem
= pidgin_create_protocol_icon(purple_conversation_get_account(gtkconv
->active_conv
), PIDGIN_PROTOCOL_ICON_SMALL
);
2670 gtk_list_store_set(GTK_LIST_STORE(gtkconv
->infopane_model
),
2671 &(gtkconv
->infopane_iter
),
2672 CONV_PROTOCOL_ICON_COLUMN
, emblem
, -1);
2674 g_object_unref(emblem
);
2676 /* XXX seanegan Why do I have to do this? */
2677 gtk_widget_queue_resize(gtkconv
->infopane
);
2678 gtk_widget_queue_draw(gtkconv
->infopane
);
2680 if (pidgin_conv_window_is_active_conversation(conv
) &&
2681 (!PURPLE_IS_IM_CONVERSATION(conv
) || gtkconv
->u
.im
->anim
== NULL
))
2683 l
= pidgin_conv_get_tab_icons(conv
);
2685 gtk_window_set_icon_list(GTK_WINDOW(win
->window
), l
);
2690 /* This gets added as an idle handler when doing something that
2691 * redraws the icon. It sets the auto_resize gboolean to TRUE.
2692 * This way, when the size_allocate callback gets triggered, it notices
2693 * that this is an autoresize, and after the main loop iterates, it
2694 * gets set back to FALSE
2696 static gboolean
reset_auto_resize_cb(gpointer data
)
2698 PidginConversation
*gtkconv
= (PidginConversation
*)data
;
2699 gtkconv
->auto_resize
= FALSE
;
2705 redraw_icon(gpointer data
)
2707 PidginConversation
*gtkconv
= (PidginConversation
*)data
;
2708 PurpleConversation
*conv
= gtkconv
->active_conv
;
2709 PurpleAccount
*account
;
2714 int scale_width
, scale_height
;
2717 gtkconv
= PIDGIN_CONVERSATION(conv
);
2718 account
= purple_conversation_get_account(conv
);
2720 if (!(account
&& purple_account_get_connection(account
))) {
2721 gtkconv
->u
.im
->icon_timer
= 0;
2725 gdk_pixbuf_animation_iter_advance(gtkconv
->u
.im
->iter
, NULL
);
2726 buf
= gdk_pixbuf_animation_iter_get_pixbuf(gtkconv
->u
.im
->iter
);
2728 scale_width
= gdk_pixbuf_get_width(buf
);
2729 scale_height
= gdk_pixbuf_get_height(buf
);
2731 gtk_widget_get_size_request(gtkconv
->u
.im
->icon_container
, NULL
, &size
);
2732 size
= MIN(size
, MIN(scale_width
, scale_height
));
2733 size
= CLAMP(size
, BUDDYICON_SIZE_MIN
, BUDDYICON_SIZE_MAX
);
2735 if (scale_width
== scale_height
) {
2736 scale_width
= scale_height
= size
;
2737 } else if (scale_height
> scale_width
) {
2738 scale_width
= size
* scale_width
/ scale_height
;
2739 scale_height
= size
;
2741 scale_height
= size
* scale_height
/ scale_width
;
2745 scale
= gdk_pixbuf_scale_simple(buf
, scale_width
, scale_height
,
2746 GDK_INTERP_BILINEAR
);
2747 if (pidgin_gdk_pixbuf_is_opaque(scale
))
2748 pidgin_gdk_pixbuf_make_round(scale
);
2750 gtk_image_set_from_pixbuf(GTK_IMAGE(gtkconv
->u
.im
->icon
), scale
);
2751 g_object_unref(G_OBJECT(scale
));
2752 gtk_widget_queue_draw(gtkconv
->u
.im
->icon
);
2754 delay
= gdk_pixbuf_animation_iter_get_delay_time(gtkconv
->u
.im
->iter
);
2759 gtkconv
->u
.im
->icon_timer
= g_timeout_add(delay
, redraw_icon
, gtkconv
);
2765 start_anim(GtkWidget
*widget
, PidginConversation
*gtkconv
)
2769 if (gtkconv
->u
.im
->anim
== NULL
)
2772 if (gtkconv
->u
.im
->icon_timer
!= 0)
2775 if (gdk_pixbuf_animation_is_static_image(gtkconv
->u
.im
->anim
))
2778 delay
= gdk_pixbuf_animation_iter_get_delay_time(gtkconv
->u
.im
->iter
);
2783 gtkconv
->u
.im
->icon_timer
= g_timeout_add(delay
, redraw_icon
, gtkconv
);
2787 remove_icon(GtkWidget
*widget
, PidginConversation
*gtkconv
)
2791 PurpleConversation
*conv
= gtkconv
->active_conv
;
2793 g_return_if_fail(conv
!= NULL
);
2795 gtk_widget_set_size_request(gtkconv
->u
.im
->icon_container
, -1, BUDDYICON_SIZE_MIN
);
2796 children
= gtk_container_get_children(GTK_CONTAINER(gtkconv
->u
.im
->icon_container
));
2798 /* We know there's only one child here. It'd be nice to shortcut to the
2799 event box, but we can't change the PidginConversation until 3.0 */
2800 event
= (GtkWidget
*)children
->data
;
2801 gtk_container_remove(GTK_CONTAINER(gtkconv
->u
.im
->icon_container
), event
);
2802 g_list_free(children
);
2805 if (gtkconv
->u
.im
->anim
!= NULL
)
2806 g_object_unref(G_OBJECT(gtkconv
->u
.im
->anim
));
2808 if (gtkconv
->u
.im
->icon_timer
!= 0)
2809 g_source_remove(gtkconv
->u
.im
->icon_timer
);
2811 if (gtkconv
->u
.im
->iter
!= NULL
)
2812 g_object_unref(G_OBJECT(gtkconv
->u
.im
->iter
));
2814 gtkconv
->u
.im
->icon_timer
= 0;
2815 gtkconv
->u
.im
->icon
= NULL
;
2816 gtkconv
->u
.im
->anim
= NULL
;
2817 gtkconv
->u
.im
->iter
= NULL
;
2818 gtkconv
->u
.im
->show_icon
= FALSE
;
2822 saveicon_writefile_cb(void *user_data
, const char *filename
)
2824 PidginConversation
*gtkconv
= (PidginConversation
*)user_data
;
2825 PurpleIMConversation
*im
= PURPLE_IM_CONVERSATION(gtkconv
->active_conv
);
2826 PurpleBuddyIcon
*icon
;
2830 icon
= purple_im_conversation_get_icon(im
);
2831 data
= purple_buddy_icon_get_data(icon
, &len
);
2833 if ((len
<= 0) || (data
== NULL
) || !purple_util_write_data_to_file_absolute(filename
, data
, len
)) {
2834 purple_notify_error(gtkconv
, NULL
, _("Unable to save icon file to disk."), NULL
, NULL
);
2839 custom_icon_sel_cb(const char *filename
, gpointer data
)
2844 PurpleContact
*contact
;
2845 PidginConversation
*gtkconv
= data
;
2846 PurpleConversation
*conv
= gtkconv
->active_conv
;
2847 PurpleAccount
*account
= purple_conversation_get_account(conv
);
2849 name
= purple_conversation_get_name(conv
);
2850 buddy
= purple_blist_find_buddy(account
, name
);
2852 purple_debug_info("custom-icon", "You can only set custom icons for people on your buddylist.\n");
2855 contact
= purple_buddy_get_contact(buddy
);
2857 purple_buddy_icons_node_set_custom_icon_from_file((PurpleBlistNode
*)contact
, filename
);
2862 set_custom_icon_cb(GtkWidget
*widget
, PidginConversation
*gtkconv
)
2864 GtkWidget
*win
= pidgin_buddy_icon_chooser_new(GTK_WINDOW(gtkconv
->win
->window
),
2865 custom_icon_sel_cb
, gtkconv
);
2866 gtk_widget_show_all(win
);
2870 change_size_cb(GtkWidget
*widget
, PidginConversation
*gtkconv
)
2873 PurpleConversation
*conv
= gtkconv
->active_conv
;
2876 gtk_widget_get_size_request(gtkconv
->u
.im
->icon_container
, NULL
, &size
);
2878 if (size
== BUDDYICON_SIZE_MAX
) {
2879 size
= BUDDYICON_SIZE_MIN
;
2881 size
= BUDDYICON_SIZE_MAX
;
2884 gtk_widget_set_size_request(gtkconv
->u
.im
->icon_container
, -1, size
);
2885 pidgin_conv_update_buddy_icon(PURPLE_IM_CONVERSATION(conv
));
2887 buddies
= purple_blist_find_buddies(purple_conversation_get_account(conv
),
2888 purple_conversation_get_name(conv
));
2889 for (; buddies
; buddies
= g_slist_delete_link(buddies
, buddies
)) {
2890 PurpleBuddy
*buddy
= buddies
->data
;
2891 PurpleContact
*contact
= purple_buddy_get_contact(buddy
);
2892 purple_blist_node_set_int((PurpleBlistNode
*)contact
, "pidgin-infopane-iconsize", size
);
2897 remove_custom_icon_cb(GtkWidget
*widget
, PidginConversation
*gtkconv
)
2901 PurpleAccount
*account
;
2902 PurpleContact
*contact
;
2903 PurpleConversation
*conv
= gtkconv
->active_conv
;
2905 account
= purple_conversation_get_account(conv
);
2906 name
= purple_conversation_get_name(conv
);
2907 buddy
= purple_blist_find_buddy(account
, name
);
2911 contact
= purple_buddy_get_contact(buddy
);
2913 purple_buddy_icons_node_set_custom_icon_from_file((PurpleBlistNode
*)contact
, NULL
);
2917 icon_menu_save_cb(GtkWidget
*widget
, PidginConversation
*gtkconv
)
2919 PurpleConversation
*conv
= gtkconv
->active_conv
;
2923 g_return_if_fail(conv
!= NULL
);
2925 ext
= purple_buddy_icon_get_extension(purple_im_conversation_get_icon(PURPLE_IM_CONVERSATION(conv
)));
2927 buf
= g_strdup_printf("%s.%s", purple_normalize(purple_conversation_get_account(conv
), purple_conversation_get_name(conv
)), ext
);
2929 purple_request_file(gtkconv
, _("Save Icon"), buf
, TRUE
,
2930 G_CALLBACK(saveicon_writefile_cb
), NULL
,
2931 purple_request_cpar_from_conversation(conv
), gtkconv
);
2937 stop_anim(GtkWidget
*widget
, PidginConversation
*gtkconv
)
2939 if (gtkconv
->u
.im
->icon_timer
!= 0)
2940 g_source_remove(gtkconv
->u
.im
->icon_timer
);
2942 gtkconv
->u
.im
->icon_timer
= 0;
2947 toggle_icon_animate_cb(GtkWidget
*w
, PidginConversation
*gtkconv
)
2949 gtkconv
->u
.im
->animate
=
2950 gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(w
));
2952 if (gtkconv
->u
.im
->animate
)
2953 start_anim(NULL
, gtkconv
);
2955 stop_anim(NULL
, gtkconv
);
2959 icon_menu(GtkWidget
*widget
, GdkEventButton
*e
, PidginConversation
*gtkconv
)
2961 static GtkWidget
*menu
= NULL
;
2962 PurpleConversation
*conv
;
2965 if (e
->button
== 1 && e
->type
== GDK_BUTTON_PRESS
) {
2966 change_size_cb(NULL
, gtkconv
);
2970 if (e
->button
!= 3 || e
->type
!= GDK_BUTTON_PRESS
) {
2975 * If a menu already exists, destroy it before creating a new one,
2976 * thus freeing-up the memory it occupied.
2979 gtk_widget_destroy(menu
);
2981 menu
= gtk_menu_new();
2983 if (gtkconv
->u
.im
->anim
&&
2984 !(gdk_pixbuf_animation_is_static_image(gtkconv
->u
.im
->anim
)))
2986 pidgin_new_check_item(menu
, _("Animate"),
2987 G_CALLBACK(toggle_icon_animate_cb
), gtkconv
,
2988 gtkconv
->u
.im
->icon_timer
);
2991 pidgin_new_menu_item(menu
, _("Hide Icon"), NULL
,
2992 G_CALLBACK(remove_icon
), gtkconv
);
2994 pidgin_new_menu_item(menu
, _("Save Icon As..."), GTK_STOCK_SAVE_AS
,
2995 G_CALLBACK(icon_menu_save_cb
), gtkconv
);
2997 pidgin_new_menu_item(menu
, _("Set Custom Icon..."), NULL
,
2998 G_CALLBACK(set_custom_icon_cb
), gtkconv
);
3000 pidgin_new_menu_item(menu
, _("Change Size"), NULL
,
3001 G_CALLBACK(change_size_cb
), gtkconv
);
3003 /* Is there a custom icon for this person? */
3004 conv
= gtkconv
->active_conv
;
3005 buddy
= purple_blist_find_buddy(purple_conversation_get_account(conv
),
3006 purple_conversation_get_name(conv
));
3009 PurpleContact
*contact
= purple_buddy_get_contact(buddy
);
3010 if (contact
&& purple_buddy_icons_node_has_custom_icon((PurpleBlistNode
*)contact
))
3012 pidgin_new_menu_item(menu
, _("Remove Custom Icon"),
3013 NULL
, G_CALLBACK(remove_custom_icon_cb
),
3018 gtk_menu_popup(GTK_MENU(menu
), NULL
, NULL
, NULL
, NULL
, e
->button
, e
->time
);
3023 /**************************************************************************
3024 * End of the bunch of buddy icon functions
3025 **************************************************************************/
3027 pidgin_conv_present_conversation(PurpleConversation
*conv
)
3029 PidginConversation
*gtkconv
;
3030 GdkModifierType state
;
3032 pidgin_conv_attach_to_conversation(conv
);
3033 gtkconv
= PIDGIN_CONVERSATION(conv
);
3035 pidgin_conv_switch_active_conversation(conv
);
3036 /* Switch the tab only if the user initiated the event by pressing
3037 * a button or hitting a key. */
3038 if (gtk_get_current_event_state(&state
))
3039 pidgin_conv_window_switch_gtkconv(gtkconv
->win
, gtkconv
);
3040 gtk_window_present(GTK_WINDOW(gtkconv
->win
->window
));
3044 pidgin_conversations_get_unseen(GList
*l
,
3045 PidginUnseenState min_state
,
3046 gboolean hidden_only
,
3052 for (; l
!= NULL
&& (max_count
== 0 || c
< max_count
); l
= l
->next
) {
3053 PurpleConversation
*conv
= (PurpleConversation
*)l
->data
;
3054 PidginConversation
*gtkconv
= PIDGIN_CONVERSATION(conv
);
3056 if(gtkconv
== NULL
|| gtkconv
->active_conv
!= conv
)
3059 if (gtkconv
->unseen_state
>= min_state
3061 (hidden_only
&& gtkconv
->win
== hidden_convwin
))) {
3063 r
= g_list_prepend(r
, conv
);
3072 pidgin_conversations_get_unseen_all(PidginUnseenState min_state
,
3073 gboolean hidden_only
,
3076 return pidgin_conversations_get_unseen(purple_conversations_get_all(),
3077 min_state
, hidden_only
, max_count
);
3081 pidgin_conversations_get_unseen_ims(PidginUnseenState min_state
,
3082 gboolean hidden_only
,
3085 return pidgin_conversations_get_unseen(purple_conversations_get_ims(),
3086 min_state
, hidden_only
, max_count
);
3090 pidgin_conversations_get_unseen_chats(PidginUnseenState min_state
,
3091 gboolean hidden_only
,
3094 return pidgin_conversations_get_unseen(purple_conversations_get_chats(),
3095 min_state
, hidden_only
, max_count
);
3099 unseen_conv_menu_cb(GtkMenuItem
*item
, PurpleConversation
*conv
)
3101 g_return_if_fail(conv
!= NULL
);
3102 pidgin_conv_present_conversation(conv
);
3106 unseen_all_conv_menu_cb(GtkMenuItem
*item
, GList
*list
)
3108 g_return_if_fail(list
!= NULL
);
3109 /* Do not free the list from here. It will be freed from the
3110 * 'destroy' callback on the menuitem. */
3112 pidgin_conv_present_conversation(list
->data
);
3118 pidgin_conversations_fill_menu(GtkWidget
*menu
, GList
*convs
)
3123 g_return_val_if_fail(menu
!= NULL
, 0);
3124 g_return_val_if_fail(convs
!= NULL
, 0);
3126 for (l
= convs
; l
!= NULL
; l
= l
->next
) {
3127 PurpleConversation
*conv
= (PurpleConversation
*)l
->data
;
3128 PidginConversation
*gtkconv
= PIDGIN_CONVERSATION(conv
);
3130 GtkWidget
*icon
= gtk_image_new_from_stock(pidgin_conv_get_icon_stock(conv
),
3131 gtk_icon_size_from_name(PIDGIN_ICON_SIZE_TANGO_MICROSCOPIC
));
3133 gchar
*text
= g_strdup_printf("%s (%d)",
3134 gtk_label_get_text(GTK_LABEL(gtkconv
->tab_label
)),
3135 gtkconv
->unseen_count
);
3137 item
= gtk_image_menu_item_new_with_label(text
);
3138 gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(item
), icon
);
3139 g_signal_connect(G_OBJECT(item
), "activate", G_CALLBACK(unseen_conv_menu_cb
), conv
);
3140 gtk_menu_shell_append(GTK_MENU_SHELL(menu
), item
);
3146 /* There are more than one conversation. Add an option to show all conversations. */
3148 GList
*list
= g_list_copy(convs
);
3150 pidgin_separator(menu
);
3152 item
= gtk_menu_item_new_with_label(_("Show All"));
3153 g_signal_connect(G_OBJECT(item
), "activate", G_CALLBACK(unseen_all_conv_menu_cb
), list
);
3154 g_signal_connect_swapped(G_OBJECT(item
), "destroy", G_CALLBACK(g_list_free
), list
);
3155 gtk_menu_shell_append(GTK_MENU_SHELL(menu
), item
);
3162 pidgin_conv_get_window(PidginConversation
*gtkconv
)
3164 g_return_val_if_fail(gtkconv
!= NULL
, NULL
);
3165 return gtkconv
->win
;
3168 static GtkActionEntry menu_entries
[] =
3169 /* TODO: fill out tooltips... */
3171 /* Conversation menu */
3172 { "ConversationMenu", NULL
, N_("_Conversation"), NULL
, NULL
, NULL
},
3173 { "NewInstantMessage", PIDGIN_STOCK_TOOLBAR_MESSAGE_NEW
, N_("New Instant _Message..."), "<control>M", NULL
, G_CALLBACK(menu_new_conv_cb
) },
3174 { "JoinAChat", PIDGIN_STOCK_CHAT
, N_("Join a _Chat..."), NULL
, NULL
, G_CALLBACK(menu_join_chat_cb
) },
3175 { "Find", GTK_STOCK_FIND
, N_("_Find..."), NULL
, NULL
, G_CALLBACK(menu_find_cb
) },
3176 { "ViewLog", NULL
, N_("View _Log"), NULL
, NULL
, G_CALLBACK(menu_view_log_cb
) },
3177 { "SaveAs", GTK_STOCK_SAVE_AS
, N_("_Save As..."), NULL
, NULL
, G_CALLBACK(menu_save_as_cb
) },
3178 { "ClearScrollback", GTK_STOCK_CLEAR
, N_("Clea_r Scrollback"), "<control>L", NULL
, G_CALLBACK(menu_clear_cb
) },
3181 { "MediaMenu", NULL
, N_("M_edia"), NULL
, NULL
, NULL
},
3182 { "AudioCall", PIDGIN_STOCK_TOOLBAR_AUDIO_CALL
, N_("_Audio Call"), NULL
, NULL
, G_CALLBACK(menu_initiate_media_call_cb
) },
3183 { "VideoCall", PIDGIN_STOCK_TOOLBAR_VIDEO_CALL
, N_("_Video Call"), NULL
, NULL
, G_CALLBACK(menu_initiate_media_call_cb
) },
3184 { "AudioVideoCall", PIDGIN_STOCK_TOOLBAR_VIDEO_CALL
, N_("Audio/Video _Call"), NULL
, NULL
, G_CALLBACK(menu_initiate_media_call_cb
) },
3187 { "SendFile", PIDGIN_STOCK_TOOLBAR_SEND_FILE
, N_("Se_nd File..."), NULL
, NULL
, G_CALLBACK(menu_send_file_cb
) },
3188 { "GetAttention", PIDGIN_STOCK_TOOLBAR_SEND_ATTENTION
, N_("Get _Attention"), NULL
, NULL
, G_CALLBACK(menu_get_attention_cb
) },
3189 { "AddBuddyPounce", NULL
, N_("Add Buddy _Pounce..."), NULL
, NULL
, G_CALLBACK(menu_add_pounce_cb
) },
3190 { "GetInfo", PIDGIN_STOCK_TOOLBAR_USER_INFO
, N_("_Get Info"), "<control>O", NULL
, G_CALLBACK(menu_get_info_cb
) },
3191 { "Invite", NULL
, N_("In_vite..."), NULL
, NULL
, G_CALLBACK(menu_invite_cb
) },
3192 { "MoreMenu", NULL
, N_("M_ore"), NULL
, NULL
, NULL
},
3193 { "Alias", NULL
, N_("Al_ias..."), NULL
, NULL
, G_CALLBACK(menu_alias_cb
) },
3194 { "Block", PIDGIN_STOCK_TOOLBAR_BLOCK
, N_("_Block..."), NULL
, NULL
, G_CALLBACK(menu_block_cb
) },
3195 { "Unblock", PIDGIN_STOCK_TOOLBAR_UNBLOCK
, N_("_Unblock..."), NULL
, NULL
, G_CALLBACK(menu_unblock_cb
) },
3196 { "Add", GTK_STOCK_ADD
, N_("_Add..."), NULL
, NULL
, G_CALLBACK(menu_add_remove_cb
) },
3197 { "Remove", GTK_STOCK_REMOVE
, N_("_Remove..."), NULL
, NULL
, G_CALLBACK(menu_add_remove_cb
) },
3198 { "InsertLink", PIDGIN_STOCK_TOOLBAR_INSERT_LINK
, N_("Insert Lin_k..."), NULL
, NULL
, G_CALLBACK(menu_insert_link_cb
) },
3199 { "InsertImage", PIDGIN_STOCK_TOOLBAR_INSERT_IMAGE
, N_("Insert Imag_e..."), NULL
, NULL
, G_CALLBACK(menu_insert_image_cb
) },
3200 { "Close", GTK_STOCK_CLOSE
, N_("_Close"), NULL
, NULL
, G_CALLBACK(menu_close_conv_cb
) },
3203 { "OptionsMenu", NULL
, N_("_Options"), NULL
, NULL
, NULL
},
3207 static const GtkToggleActionEntry menu_toggle_entries
[] = {
3208 { "EnableLogging", NULL
, N_("Enable _Logging"), NULL
, NULL
, G_CALLBACK(menu_logging_cb
), FALSE
},
3209 { "EnableSounds", NULL
, N_("Enable _Sounds"), NULL
, NULL
, G_CALLBACK(menu_sounds_cb
), FALSE
},
3210 { "ShowFormattingToolbars", NULL
, N_("Show Formatting _Toolbars"), NULL
, NULL
, G_CALLBACK(menu_toolbar_cb
), FALSE
},
3213 static const char *conversation_menu
=
3215 "<menubar name='Conversation'>"
3216 "<menu action='ConversationMenu'>"
3217 "<menuitem action='NewInstantMessage'/>"
3218 "<menuitem action='JoinAChat'/>"
3220 "<menuitem action='Find'/>"
3221 "<menuitem action='ViewLog'/>"
3222 "<menuitem action='SaveAs'/>"
3223 "<menuitem action='ClearScrollback'/>"
3226 "<menu action='MediaMenu'>"
3227 "<menuitem action='AudioCall'/>"
3228 "<menuitem action='VideoCall'/>"
3229 "<menuitem action='AudioVideoCall'/>"
3232 "<menuitem action='SendFile'/>"
3233 "<menuitem action='GetAttention'/>"
3234 "<menuitem action='AddBuddyPounce'/>"
3235 "<menuitem action='GetInfo'/>"
3236 "<menuitem action='Invite'/>"
3237 "<menu action='MoreMenu'/>"
3239 "<menuitem action='Alias'/>"
3240 "<menuitem action='Block'/>"
3241 "<menuitem action='Unblock'/>"
3242 "<menuitem action='Add'/>"
3243 "<menuitem action='Remove'/>"
3245 "<menuitem action='InsertLink'/>"
3246 "<menuitem action='InsertImage'/>"
3248 "<menuitem action='Close'/>"
3250 "<menu action='OptionsMenu'>"
3251 "<menuitem action='EnableLogging'/>"
3252 "<menuitem action='EnableSounds'/>"
3254 "<menuitem action='ShowFormattingToolbars'/>"
3260 sound_method_pref_changed_cb(const char *name
, PurplePrefType type
,
3261 gconstpointer value
, gpointer data
)
3263 PidginConvWindow
*win
= data
;
3264 const char *method
= value
;
3266 if (!strcmp(method
, "none"))
3268 gtk_toggle_action_set_active(GTK_TOGGLE_ACTION(win
->menu
->sounds
),
3270 gtk_action_set_sensitive(win
->menu
->sounds
, FALSE
);
3274 PidginConversation
*gtkconv
= pidgin_conv_window_get_active_gtkconv(win
);
3276 if (gtkconv
!= NULL
)
3277 gtk_toggle_action_set_active(GTK_TOGGLE_ACTION(win
->menu
->sounds
),
3278 gtkconv
->make_sound
);
3279 gtk_action_set_sensitive(win
->menu
->sounds
, TRUE
);
3283 /* Returns TRUE if some items were added to the menu, FALSE otherwise */
3285 populate_menu_with_options(GtkWidget
*menu
, PidginConversation
*gtkconv
, gboolean all
)
3288 PurpleConversation
*conv
;
3289 PurpleAccount
*account
;
3290 PurpleBlistNode
*node
= NULL
;
3291 PurpleChat
*chat
= NULL
;
3292 PurpleBuddy
*buddy
= NULL
;
3295 conv
= gtkconv
->active_conv
;
3296 account
= purple_conversation_get_account(conv
);
3298 if (PURPLE_IS_CHAT_CONVERSATION(conv
)) {
3299 chat
= purple_blist_find_chat(account
, purple_conversation_get_name(conv
));
3301 if ((chat
== NULL
) && (gtkconv
->webview
!= NULL
)) {
3302 chat
= g_object_get_data(G_OBJECT(gtkconv
->webview
), "transient_chat");
3305 if ((chat
== NULL
) && (gtkconv
->webview
!= NULL
)) {
3306 GHashTable
*components
;
3307 PurpleAccount
*account
= purple_conversation_get_account(conv
);
3308 PurpleProtocol
*protocol
=
3309 purple_protocols_find(purple_account_get_protocol_id(account
));
3310 if (purple_account_get_connection(account
) != NULL
&&
3311 PURPLE_PROTOCOL_IMPLEMENTS(protocol
, CHAT_IFACE
, info_defaults
)) {
3312 components
= purple_protocol_chat_iface_info_defaults(protocol
, purple_account_get_connection(account
),
3313 purple_conversation_get_name(conv
));
3315 components
= g_hash_table_new_full(g_str_hash
, g_str_equal
,
3317 g_hash_table_replace(components
, g_strdup("channel"),
3318 g_strdup(purple_conversation_get_name(conv
)));
3320 chat
= purple_chat_new(account
, NULL
, components
);
3321 purple_blist_node_set_transient((PurpleBlistNode
*)chat
, TRUE
);
3322 g_object_set_data_full(G_OBJECT(gtkconv
->webview
), "transient_chat",
3323 chat
, (GDestroyNotify
)purple_blist_remove_chat
);
3326 if (!purple_account_is_connected(account
))
3329 buddy
= purple_blist_find_buddy(account
, purple_conversation_get_name(conv
));
3330 if (!buddy
&& gtkconv
->webview
) {
3331 buddy
= g_object_get_data(G_OBJECT(gtkconv
->webview
), "transient_buddy");
3334 buddy
= purple_buddy_new(account
, purple_conversation_get_name(conv
), NULL
);
3335 purple_blist_node_set_transient((PurpleBlistNode
*)buddy
, TRUE
);
3336 g_object_set_data_full(G_OBJECT(gtkconv
->webview
), "transient_buddy",
3337 buddy
, (GDestroyNotify
)g_object_unref
);
3343 node
= (PurpleBlistNode
*)chat
;
3345 node
= (PurpleBlistNode
*)buddy
;
3347 /* Now add the stuff */
3350 pidgin_blist_make_buddy_menu(menu
, buddy
, TRUE
);
3355 if (purple_account_is_connected(account
))
3356 pidgin_append_blist_node_proto_menu(menu
, purple_account_get_connection(account
), node
);
3357 pidgin_append_blist_node_extended_menu(menu
, node
);
3360 if ((list
= gtk_container_get_children(GTK_CONTAINER(menu
))) == NULL
) {
3370 regenerate_media_items(PidginConvWindow
*win
)
3373 PurpleAccount
*account
;
3374 PurpleConversation
*conv
;
3376 conv
= pidgin_conv_window_get_active_conversation(win
);
3379 purple_debug_error("gtkconv", "couldn't get active conversation"
3380 " when regenerating media items\n");
3384 account
= purple_conversation_get_account(conv
);
3386 if (account
== NULL
) {
3387 purple_debug_error("gtkconv", "couldn't get account when"
3388 " regenerating media items\n");
3393 * Check if account support voice and/or calls, and
3394 * if the current buddy supports it.
3396 if (account
!= NULL
&& PURPLE_IS_IM_CONVERSATION(conv
)) {
3397 PurpleMediaCaps caps
=
3398 purple_protocol_get_media_caps(account
,
3399 purple_conversation_get_name(conv
));
3401 gtk_action_set_sensitive(win
->menu
->audio_call
,
3402 caps
& PURPLE_MEDIA_CAPS_AUDIO
3404 gtk_action_set_sensitive(win
->menu
->video_call
,
3405 caps
& PURPLE_MEDIA_CAPS_VIDEO
3407 gtk_action_set_sensitive(win
->menu
->audio_video_call
,
3408 caps
& PURPLE_MEDIA_CAPS_AUDIO_VIDEO
3410 } else if (PURPLE_IS_CHAT_CONVERSATION(conv
)) {
3411 /* for now, don't care about chats... */
3412 gtk_action_set_sensitive(win
->menu
->audio_call
, FALSE
);
3413 gtk_action_set_sensitive(win
->menu
->video_call
, FALSE
);
3414 gtk_action_set_sensitive(win
->menu
->audio_video_call
, FALSE
);
3416 gtk_action_set_sensitive(win
->menu
->audio_call
, FALSE
);
3417 gtk_action_set_sensitive(win
->menu
->video_call
, FALSE
);
3418 gtk_action_set_sensitive(win
->menu
->audio_video_call
, FALSE
);
3424 regenerate_attention_items(PidginConvWindow
*win
)
3426 GtkWidget
*attention
;
3428 PurpleConversation
*conv
;
3429 PurpleConnection
*pc
;
3430 PurpleProtocol
*protocol
= NULL
;
3433 conv
= pidgin_conv_window_get_active_conversation(win
);
3437 attention
= gtk_ui_manager_get_widget(win
->menu
->ui
,
3438 "/Conversation/ConversationMenu/GetAttention");
3440 /* Remove the previous entries */
3441 gtk_menu_item_set_submenu(GTK_MENU_ITEM(attention
), NULL
);
3443 pc
= purple_conversation_get_connection(conv
);
3445 protocol
= purple_connection_get_protocol(pc
);
3447 if (protocol
&& PURPLE_PROTOCOL_IMPLEMENTS(protocol
, ATTENTION_IFACE
, get_types
)) {
3448 list
= purple_protocol_attention_iface_get_types(protocol
, purple_connection_get_account(pc
));
3450 /* Multiple attention types */
3451 if (list
&& list
->next
) {
3454 menu
= gtk_menu_new();
3456 PurpleAttentionType
*type
;
3457 GtkWidget
*menuitem
;
3461 menuitem
= gtk_menu_item_new_with_label(purple_attention_type_get_name(type
));
3462 g_object_set_data(G_OBJECT(menuitem
), "index", GINT_TO_POINTER(index
));
3463 g_signal_connect(G_OBJECT(menuitem
), "activate",
3464 G_CALLBACK(menu_get_attention_cb
),
3466 gtk_menu_shell_append(GTK_MENU_SHELL(menu
), menuitem
);
3469 list
= g_list_delete_link(list
, list
);
3472 gtk_menu_item_set_submenu(GTK_MENU_ITEM(attention
), menu
);
3473 gtk_widget_show_all(menu
);
3479 regenerate_options_items(PidginConvWindow
*win
)
3482 PidginConversation
*gtkconv
;
3484 GtkWidget
*more_menu
;
3486 gtkconv
= pidgin_conv_window_get_active_gtkconv(win
);
3487 more_menu
= gtk_ui_manager_get_widget(win
->menu
->ui
,
3488 "/Conversation/ConversationMenu/MoreMenu");
3489 gtk_widget_show(more_menu
);
3490 menu
= gtk_menu_item_get_submenu(GTK_MENU_ITEM(more_menu
));
3492 /* Remove the previous entries */
3493 for (list
= gtk_container_get_children(GTK_CONTAINER(menu
)); list
; )
3495 GtkWidget
*w
= list
->data
;
3496 list
= g_list_delete_link(list
, list
);
3497 gtk_widget_destroy(w
);
3500 if (!populate_menu_with_options(menu
, gtkconv
, FALSE
))
3502 GtkWidget
*item
= gtk_menu_item_new_with_label(_("No actions available"));
3503 gtk_menu_shell_append(GTK_MENU_SHELL(menu
), item
);
3504 gtk_widget_set_sensitive(item
, FALSE
);
3507 gtk_widget_show_all(menu
);
3511 remove_from_list(GtkWidget
*widget
, PidginConvWindow
*win
)
3513 GList
*list
= g_object_get_data(G_OBJECT(win
->window
), "plugin-actions");
3514 list
= g_list_remove(list
, widget
);
3515 g_object_set_data(G_OBJECT(win
->window
), "plugin-actions", list
);
3519 regenerate_plugins_items(PidginConvWindow
*win
)
3521 GList
*action_items
;
3524 PidginConversation
*gtkconv
;
3525 PurpleConversation
*conv
;
3528 if (win
->window
== NULL
|| win
== hidden_convwin
)
3531 gtkconv
= pidgin_conv_window_get_active_gtkconv(win
);
3532 if (gtkconv
== NULL
)
3535 conv
= gtkconv
->active_conv
;
3536 action_items
= g_object_get_data(G_OBJECT(win
->window
), "plugin-actions");
3538 /* Remove the old menuitems */
3539 while (action_items
) {
3540 g_signal_handlers_disconnect_by_func(G_OBJECT(action_items
->data
),
3541 G_CALLBACK(remove_from_list
), win
);
3542 gtk_widget_destroy(action_items
->data
);
3543 action_items
= g_list_delete_link(action_items
, action_items
);
3546 item
= gtk_ui_manager_get_widget(win
->menu
->ui
, "/Conversation/OptionsMenu");
3547 menu
= gtk_menu_item_get_submenu(GTK_MENU_ITEM(item
));
3549 list
= purple_conversation_get_extended_menu(conv
);
3551 action_items
= g_list_prepend(NULL
, (item
= pidgin_separator(menu
)));
3552 g_signal_connect(G_OBJECT(item
), "destroy", G_CALLBACK(remove_from_list
), win
);
3555 for(; list
; list
= g_list_delete_link(list
, list
)) {
3556 PurpleMenuAction
*act
= (PurpleMenuAction
*) list
->data
;
3557 item
= pidgin_append_menu_action(menu
, act
, conv
);
3558 action_items
= g_list_prepend(action_items
, item
);
3559 gtk_widget_show_all(item
);
3560 g_signal_connect(G_OBJECT(item
), "destroy", G_CALLBACK(remove_from_list
), win
);
3562 g_object_set_data(G_OBJECT(win
->window
), "plugin-actions", action_items
);
3565 static void menubar_activated(GtkWidget
*item
, gpointer data
)
3567 PidginConvWindow
*win
= data
;
3568 regenerate_media_items(win
);
3569 regenerate_options_items(win
);
3570 regenerate_plugins_items(win
);
3571 regenerate_attention_items(win
);
3573 /* The following are to make sure the 'More' submenu is not regenerated every time
3574 * the focus shifts from 'Conversations' to some other menu and back. */
3575 g_signal_handlers_block_by_func(G_OBJECT(item
), G_CALLBACK(menubar_activated
), data
);
3576 g_signal_connect(G_OBJECT(win
->menu
->menubar
), "deactivate", G_CALLBACK(focus_out_from_menubar
), data
);
3580 focus_out_from_menubar(GtkWidget
*wid
, PidginConvWindow
*win
)
3582 /* The menubar has been deactivated. Make sure the 'More' submenu is regenerated next time
3583 * the 'Conversation' menu pops up. */
3584 GtkWidget
*menuitem
= gtk_ui_manager_get_widget(win
->menu
->ui
, "/Conversation/ConversationMenu");
3585 g_signal_handlers_unblock_by_func(G_OBJECT(menuitem
), G_CALLBACK(menubar_activated
), win
);
3586 g_signal_handlers_disconnect_by_func(G_OBJECT(win
->menu
->menubar
),
3587 G_CALLBACK(focus_out_from_menubar
), win
);
3591 setup_menubar(PidginConvWindow
*win
)
3593 GtkAccelGroup
*accel_group
;
3595 GtkActionGroup
*action_group
;
3597 GtkWidget
*menuitem
;
3599 action_group
= gtk_action_group_new("ConversationActions");
3601 gtk_action_group_set_translation_domain(action_group
, PACKAGE
);
3603 gtk_action_group_add_actions(action_group
,
3605 G_N_ELEMENTS(menu_entries
),
3607 gtk_action_group_add_toggle_actions(action_group
,
3608 menu_toggle_entries
,
3609 G_N_ELEMENTS(menu_toggle_entries
),
3612 win
->menu
->ui
= gtk_ui_manager_new();
3613 gtk_ui_manager_insert_action_group(win
->menu
->ui
, action_group
, 0);
3615 accel_group
= gtk_ui_manager_get_accel_group(win
->menu
->ui
);
3616 gtk_window_add_accel_group(GTK_WINDOW(win
->window
), accel_group
);
3617 g_signal_connect(G_OBJECT(accel_group
), "accel-changed",
3618 G_CALLBACK(pidgin_save_accels_cb
), NULL
);
3621 if (!gtk_ui_manager_add_ui_from_string(win
->menu
->ui
, conversation_menu
, -1, &error
))
3623 g_message("building menus failed: %s", error
->message
);
3624 g_error_free(error
);
3628 win
->menu
->menubar
=
3629 gtk_ui_manager_get_widget(win
->menu
->ui
, "/Conversation");
3631 /* Make sure the 'Conversation -> More' menuitems are regenerated whenever
3632 * the 'Conversation' menu pops up because the entries can change after the
3633 * conversation is created. */
3634 menuitem
= gtk_ui_manager_get_widget(win
->menu
->ui
, "/Conversation/ConversationMenu");
3635 g_signal_connect(G_OBJECT(menuitem
), "activate", G_CALLBACK(menubar_activated
), win
);
3637 win
->menu
->view_log
=
3638 gtk_ui_manager_get_action(win
->menu
->ui
,
3639 "/Conversation/ConversationMenu/ViewLog");
3642 win
->menu
->audio_call
=
3643 gtk_ui_manager_get_action(win
->menu
->ui
,
3644 "/Conversation/ConversationMenu/MediaMenu/AudioCall");
3645 win
->menu
->video_call
=
3646 gtk_ui_manager_get_action(win
->menu
->ui
,
3647 "/Conversation/ConversationMenu/MediaMenu/VideoCall");
3648 win
->menu
->audio_video_call
=
3649 gtk_ui_manager_get_action(win
->menu
->ui
,
3650 "/Conversation/ConversationMenu/MediaMenu/AudioVideoCall");
3655 win
->menu
->send_file
=
3656 gtk_ui_manager_get_action(win
->menu
->ui
,
3657 "/Conversation/ConversationMenu/SendFile");
3659 win
->menu
->get_attention
=
3660 gtk_ui_manager_get_action(win
->menu
->ui
,
3661 "/Conversation/ConversationMenu/GetAttention");
3663 win
->menu
->add_pounce
=
3664 gtk_ui_manager_get_action(win
->menu
->ui
,
3665 "/Conversation/ConversationMenu/AddBuddyPounce");
3669 win
->menu
->get_info
=
3670 gtk_ui_manager_get_action(win
->menu
->ui
,
3671 "/Conversation/ConversationMenu/GetInfo");
3674 gtk_ui_manager_get_action(win
->menu
->ui
,
3675 "/Conversation/ConversationMenu/Invite");
3680 gtk_ui_manager_get_action(win
->menu
->ui
,
3681 "/Conversation/ConversationMenu/Alias");
3684 gtk_ui_manager_get_action(win
->menu
->ui
,
3685 "/Conversation/ConversationMenu/Block");
3687 win
->menu
->unblock
=
3688 gtk_ui_manager_get_action(win
->menu
->ui
,
3689 "/Conversation/ConversationMenu/Unblock");
3692 gtk_ui_manager_get_action(win
->menu
->ui
,
3693 "/Conversation/ConversationMenu/Add");
3696 gtk_ui_manager_get_action(win
->menu
->ui
,
3697 "/Conversation/ConversationMenu/Remove");
3701 win
->menu
->insert_link
=
3702 gtk_ui_manager_get_action(win
->menu
->ui
,
3703 "/Conversation/ConversationMenu/InsertLink");
3705 win
->menu
->insert_image
=
3706 gtk_ui_manager_get_action(win
->menu
->ui
,
3707 "/Conversation/ConversationMenu/InsertImage");
3711 win
->menu
->logging
=
3712 gtk_ui_manager_get_action(win
->menu
->ui
,
3713 "/Conversation/OptionsMenu/EnableLogging");
3715 gtk_ui_manager_get_action(win
->menu
->ui
,
3716 "/Conversation/OptionsMenu/EnableSounds");
3717 method
= purple_prefs_get_string(PIDGIN_PREFS_ROOT
"/sound/method");
3718 if (method
!= NULL
&& !strcmp(method
, "none"))
3720 gtk_toggle_action_set_active(GTK_TOGGLE_ACTION(win
->menu
->sounds
),
3722 gtk_action_set_sensitive(win
->menu
->sounds
, FALSE
);
3724 purple_prefs_connect_callback(win
, PIDGIN_PREFS_ROOT
"/sound/method",
3725 sound_method_pref_changed_cb
, win
);
3727 win
->menu
->show_formatting_toolbar
=
3728 gtk_ui_manager_get_action(win
->menu
->ui
,
3729 "/Conversation/OptionsMenu/ShowFormattingToolbars");
3731 win
->menu
->tray
= pidgin_menu_tray_new();
3732 gtk_menu_shell_append(GTK_MENU_SHELL(win
->menu
->menubar
),
3734 gtk_widget_show(win
->menu
->tray
);
3736 gtk_widget_show(win
->menu
->menubar
);
3738 return win
->menu
->menubar
;
3742 /**************************************************************************
3744 **************************************************************************/
3747 got_typing_keypress(PidginConversation
*gtkconv
, gboolean first
)
3749 PurpleConversation
*conv
= gtkconv
->active_conv
;
3750 PurpleIMConversation
*im
;
3753 * We know we got something, so we at least have to make sure we don't
3754 * send PURPLE_IM_TYPED any time soon.
3757 im
= PURPLE_IM_CONVERSATION(conv
);
3759 purple_im_conversation_stop_send_typed_timeout(im
);
3760 purple_im_conversation_start_send_typed_timeout(im
);
3762 /* Check if we need to send another PURPLE_IM_TYPING message */
3763 if (first
|| (purple_im_conversation_get_type_again(im
) != 0 &&
3764 time(NULL
) > purple_im_conversation_get_type_again(im
)))
3766 unsigned int timeout
;
3767 timeout
= purple_serv_send_typing(purple_conversation_get_connection(conv
),
3768 purple_conversation_get_name(conv
),
3770 purple_im_conversation_set_type_again(im
, timeout
);
3776 typing_animation(gpointer data
) {
3777 PidginConversation
*gtkconv
= data
;
3778 PidginConvWindow
*gtkwin
= gtkconv
->win
;
3779 const char *stock_id
= NULL
;
3781 if(gtkconv
!= pidgin_conv_window_get_active_gtkconv(gtkwin
)) {
3785 switch (rand() % 5) {
3787 stock_id
= PIDGIN_STOCK_ANIMATION_TYPING0
;
3790 stock_id
= PIDGIN_STOCK_ANIMATION_TYPING1
;
3793 stock_id
= PIDGIN_STOCK_ANIMATION_TYPING2
;
3796 stock_id
= PIDGIN_STOCK_ANIMATION_TYPING3
;
3799 stock_id
= PIDGIN_STOCK_ANIMATION_TYPING4
;
3802 if (gtkwin
->menu
->typing_icon
== NULL
) {
3803 gtkwin
->menu
->typing_icon
= gtk_image_new_from_stock(stock_id
, GTK_ICON_SIZE_MENU
);
3804 pidgin_menu_tray_append(PIDGIN_MENU_TRAY(gtkwin
->menu
->tray
),
3805 gtkwin
->menu
->typing_icon
,
3806 _("User is typing..."));
3808 gtk_image_set_from_stock(GTK_IMAGE(gtkwin
->menu
->typing_icon
), stock_id
, GTK_ICON_SIZE_MENU
);
3810 gtk_widget_show(gtkwin
->menu
->typing_icon
);
3816 update_typing_message(PidginConversation
*gtkconv
, const char *message
)
3818 /* TODO WEBKIT: this is not handled at all */
3820 GtkTextBuffer
*buffer
;
3821 GtkTextMark
*stmark
, *enmark
;
3823 if (g_object_get_data(G_OBJECT(gtkconv
->imhtml
), "disable-typing-notification"))
3826 buffer
= gtk_text_view_get_buffer(GTK_TEXT_VIEW(gtkconv
->imhtml
));
3827 stmark
= gtk_text_buffer_get_mark(buffer
, "typing-notification-start");
3828 enmark
= gtk_text_buffer_get_mark(buffer
, "typing-notification-end");
3829 if (stmark
&& enmark
) {
3830 GtkTextIter start
, end
;
3831 gtk_text_buffer_get_iter_at_mark(buffer
, &start
, stmark
);
3832 gtk_text_buffer_get_iter_at_mark(buffer
, &end
, enmark
);
3833 gtk_text_buffer_delete_mark(buffer
, stmark
);
3834 gtk_text_buffer_delete_mark(buffer
, enmark
);
3835 gtk_text_buffer_delete(buffer
, &start
, &end
);
3836 } else if (message
&& *message
== '\n' && message
[1] == ' ' && message
[2] == '\0')
3841 message
= "\n "; /* The blank space is required to avoid a GTK+/Pango bug */
3846 gtk_text_buffer_get_end_iter(buffer
, &iter
);
3847 gtk_text_buffer_create_mark(buffer
, "typing-notification-start", &iter
, TRUE
);
3848 gtk_text_buffer_insert_with_tags_by_name(buffer
, &iter
, message
, -1, "TYPING-NOTIFICATION", NULL
);
3849 gtk_text_buffer_get_end_iter(buffer
, &iter
);
3850 gtk_text_buffer_create_mark(buffer
, "typing-notification-end", &iter
, TRUE
);
3856 update_typing_icon(PidginConversation
*gtkconv
)
3858 PurpleIMConversation
*im
;
3859 char *message
= NULL
;
3861 if (!PURPLE_IS_IM_CONVERSATION(gtkconv
->active_conv
))
3864 im
= PURPLE_IM_CONVERSATION(gtkconv
->active_conv
);
3866 if (purple_im_conversation_get_typing_state(im
) == PURPLE_IM_NOT_TYPING
) {
3868 update_typing_message(gtkconv
, NULL
);
3870 update_typing_message(gtkconv
, "\n ");
3875 if (purple_im_conversation_get_typing_state(im
) == PURPLE_IM_TYPING
) {
3876 message
= g_strdup_printf(_("\n%s is typing..."), purple_conversation_get_title(PURPLE_CONVERSATION(im
)));
3878 message
= g_strdup_printf(_("\n%s has stopped typing"), purple_conversation_get_title(PURPLE_CONVERSATION(im
)));
3881 update_typing_message(gtkconv
, message
);
3886 update_send_to_selection(PidginConvWindow
*win
)
3888 PurpleAccount
*account
;
3889 PurpleConversation
*conv
;
3894 conv
= pidgin_conv_window_get_active_conversation(win
);
3899 account
= purple_conversation_get_account(conv
);
3901 if (account
== NULL
)
3904 if (win
->menu
->send_to
== NULL
)
3907 if (!(b
= purple_blist_find_buddy(account
, purple_conversation_get_name(conv
))))
3910 gtk_widget_show(win
->menu
->send_to
);
3912 menu
= gtk_menu_item_get_submenu(GTK_MENU_ITEM(win
->menu
->send_to
));
3914 for (child
= gtk_container_get_children(GTK_CONTAINER(menu
));
3916 child
= g_list_delete_link(child
, child
)) {
3918 GtkWidget
*item
= child
->data
;
3919 PurpleBuddy
*item_buddy
;
3920 PurpleAccount
*item_account
= g_object_get_data(G_OBJECT(item
), "purple_account");
3921 gchar
*buddy_name
= g_object_get_data(G_OBJECT(item
),
3922 "purple_buddy_name");
3923 item_buddy
= purple_blist_find_buddy(item_account
, buddy_name
);
3925 if (b
== item_buddy
) {
3926 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(item
), TRUE
);
3936 send_to_item_enter_notify_cb(GtkWidget
*menuitem
, GdkEventCrossing
*event
, GtkWidget
*label
)
3938 gtk_widget_set_sensitive(GTK_WIDGET(label
), TRUE
);
3943 send_to_item_leave_notify_cb(GtkWidget
*menuitem
, GdkEventCrossing
*event
, GtkWidget
*label
)
3945 gtk_widget_set_sensitive(GTK_WIDGET(label
), FALSE
);
3950 e2ee_state_to_gtkimage(PurpleE2eeState
*state
)
3954 img
= _pidgin_e2ee_stock_icon_get(
3955 purple_e2ee_state_get_stock_icon(state
));
3959 return gtk_image_new_from_pixbuf(pidgin_pixbuf_from_image(img
));
3963 create_sendto_item(GtkWidget
*menu
, GtkSizeGroup
*sg
, GSList
**group
,
3964 PurpleBuddy
*buddy
, PurpleAccount
*account
, const char *name
,
3965 gboolean e2ee_enabled
)
3970 GtkWidget
*e2ee_image
= NULL
;
3971 GtkWidget
*menuitem
;
3975 /* Create a pixmap for the protocol icon. */
3976 pixbuf
= pidgin_create_protocol_icon(account
, PIDGIN_PROTOCOL_ICON_SMALL
);
3978 /* Now convert it to GtkImage */
3980 image
= gtk_image_new();
3983 image
= gtk_image_new_from_pixbuf(pixbuf
);
3984 g_object_unref(G_OBJECT(pixbuf
));
3988 PurpleIMConversation
*im
;
3989 PurpleE2eeState
*state
= NULL
;
3991 im
= purple_conversations_find_im_with_account(
3992 purple_buddy_get_name(buddy
), purple_buddy_get_account(buddy
));
3994 state
= purple_conversation_get_e2ee_state(PURPLE_CONVERSATION(im
));
3996 e2ee_image
= e2ee_state_to_gtkimage(state
);
3998 e2ee_image
= gtk_image_new();
4001 gtk_size_group_add_widget(sg
, image
);
4003 /* Make our menu item */
4004 text
= g_strdup_printf("%s (%s)", name
, purple_account_get_name_for_display(account
));
4005 menuitem
= gtk_radio_menu_item_new_with_label(*group
, text
);
4007 *group
= gtk_radio_menu_item_get_group(GTK_RADIO_MENU_ITEM(menuitem
));
4009 /* Do some evil, see some evil, speak some evil. */
4010 box
= gtk_box_new(GTK_ORIENTATION_HORIZONTAL
, 0);
4012 label
= gtk_bin_get_child(GTK_BIN(menuitem
));
4013 g_object_ref(label
);
4014 gtk_container_remove(GTK_CONTAINER(menuitem
), label
);
4016 gtk_box_pack_start(GTK_BOX(box
), image
, FALSE
, FALSE
, 0);
4018 gtk_box_pack_start(GTK_BOX(box
), label
, TRUE
, TRUE
, 4);
4020 gtk_box_pack_start(GTK_BOX(box
), e2ee_image
, FALSE
, FALSE
, 0);
4022 if (buddy
!= NULL
&&
4023 !purple_presence_is_online(purple_buddy_get_presence(buddy
)))
4025 gtk_widget_set_sensitive(label
, FALSE
);
4027 /* Set the label sensitive when the menuitem is highlighted and
4028 * insensitive again when the mouse leaves it. This way, it
4029 * doesn't appear weird from the highlighting of the embossed
4030 * (insensitive style) text.*/
4031 g_signal_connect(menuitem
, "enter-notify-event",
4032 G_CALLBACK(send_to_item_enter_notify_cb
), label
);
4033 g_signal_connect(menuitem
, "leave-notify-event",
4034 G_CALLBACK(send_to_item_leave_notify_cb
), label
);
4037 g_object_unref(label
);
4039 gtk_container_add(GTK_CONTAINER(menuitem
), box
);
4041 gtk_widget_show(label
);
4042 gtk_widget_show(image
);
4044 gtk_widget_show(e2ee_image
);
4045 gtk_widget_show(box
);
4047 /* Set our data and callbacks. */
4048 g_object_set_data(G_OBJECT(menuitem
), "purple_account", account
);
4049 g_object_set_data_full(G_OBJECT(menuitem
), "purple_buddy_name", g_strdup(name
), g_free
);
4051 g_signal_connect(G_OBJECT(menuitem
), "activate",
4052 G_CALLBACK(menu_conv_sel_send_cb
), NULL
);
4054 gtk_widget_show(menuitem
);
4055 gtk_menu_shell_append(GTK_MENU_SHELL(menu
), menuitem
);
4059 compare_buddy_presence(PurplePresence
*p1
, PurplePresence
*p2
)
4061 /* This is necessary because multiple PurpleBuddy's don't share the same
4062 * PurplePresence anymore.
4064 PurpleBuddy
*b1
= purple_buddy_presence_get_buddy(PURPLE_BUDDY_PRESENCE(p1
));
4065 PurpleBuddy
*b2
= purple_buddy_presence_get_buddy(PURPLE_BUDDY_PRESENCE(p2
));
4066 if (purple_buddy_get_account(b1
) == purple_buddy_get_account(b2
) &&
4067 strcmp(purple_buddy_get_name(b1
), purple_buddy_get_name(b2
)) == 0)
4073 generate_send_to_items(PidginConvWindow
*win
)
4076 GSList
*group
= NULL
;
4077 GtkSizeGroup
*sg
= gtk_size_group_new(GTK_SIZE_GROUP_HORIZONTAL
);
4078 PidginConversation
*gtkconv
;
4081 g_return_if_fail(win
!= NULL
);
4083 gtkconv
= pidgin_conv_window_get_active_gtkconv(win
);
4085 g_return_if_fail(gtkconv
!= NULL
);
4087 if (win
->menu
->send_to
!= NULL
)
4088 gtk_widget_destroy(win
->menu
->send_to
);
4090 /* Build the Send To menu */
4091 win
->menu
->send_to
= gtk_menu_item_new_with_mnemonic(_("S_end To"));
4092 gtk_widget_show(win
->menu
->send_to
);
4094 menu
= gtk_menu_new();
4095 gtk_menu_shell_insert(GTK_MENU_SHELL(win
->menu
->menubar
),
4096 win
->menu
->send_to
, 2);
4097 gtk_menu_item_set_submenu(GTK_MENU_ITEM(win
->menu
->send_to
), menu
);
4099 gtk_widget_show(menu
);
4101 if (PURPLE_IS_IM_CONVERSATION(gtkconv
->active_conv
)) {
4102 buds
= purple_blist_find_buddies(purple_conversation_get_account(gtkconv
->active_conv
), purple_conversation_get_name(gtkconv
->active_conv
));
4106 /* The user isn't on the buddy list. So we don't create any sendto menu. */
4110 gboolean e2ee_enabled
= FALSE
;
4111 GList
*list
= NULL
, *iter
;
4112 for (l
= buds
; l
!= NULL
; l
= l
->next
)
4114 PurpleBlistNode
*node
;
4116 node
= PURPLE_BLIST_NODE(purple_buddy_get_contact(PURPLE_BUDDY(l
->data
)));
4118 for (node
= node
->child
; node
!= NULL
; node
= node
->next
)
4120 PurpleBuddy
*buddy
= (PurpleBuddy
*)node
;
4121 PurpleAccount
*account
;
4122 PurpleIMConversation
*im
;
4124 if (!PURPLE_IS_BUDDY(node
))
4127 im
= purple_conversations_find_im_with_account(purple_buddy_get_name(buddy
), purple_buddy_get_account(buddy
));
4128 if (im
&& purple_conversation_get_e2ee_state(PURPLE_CONVERSATION(im
)) != NULL
)
4129 e2ee_enabled
= TRUE
;
4131 account
= purple_buddy_get_account(buddy
);
4132 /* TODO WEBKIT: (I'm not actually sure if this is webkit-related --Mark Doliner) */
4133 if (purple_account_is_connected(account
) /*|| account == purple_conversation_get_account(gtkconv->active_conv)*/)
4135 /* Use the PurplePresence to get unique buddies. */
4136 PurplePresence
*presence
= purple_buddy_get_presence(buddy
);
4137 if (!g_list_find_custom(list
, presence
, (GCompareFunc
)compare_buddy_presence
))
4138 list
= g_list_prepend(list
, presence
);
4143 /* Create the sendto menu only if it has more than one item to show */
4144 if (list
&& list
->next
) {
4145 /* Loop over the list backwards so we get the items in the right order,
4146 * since we did a g_list_prepend() earlier. */
4147 for (iter
= g_list_last(list
); iter
!= NULL
; iter
= iter
->prev
) {
4148 PurplePresence
*pre
= iter
->data
;
4149 PurpleBuddy
*buddy
= purple_buddy_presence_get_buddy(PURPLE_BUDDY_PRESENCE(pre
));
4150 create_sendto_item(menu
, sg
, &group
, buddy
,
4151 purple_buddy_get_account(buddy
), purple_buddy_get_name(buddy
), e2ee_enabled
);
4161 gtk_widget_show(win
->menu
->send_to
);
4162 /* TODO: This should never be insensitive. Possibly hidden or not. */
4164 gtk_widget_set_sensitive(win
->menu
->send_to
, FALSE
);
4165 update_send_to_selection(win
);
4169 _pidgin_e2ee_stock_icon_get(const gchar
*stock_name
)
4171 gchar filename
[100], *path
;
4174 /* core is quitting */
4175 if (e2ee_stock
== NULL
)
4178 if (g_hash_table_lookup_extended(e2ee_stock
, stock_name
, NULL
, (gpointer
*)&image
))
4181 g_snprintf(filename
, sizeof(filename
), "%s.png", stock_name
);
4182 path
= g_build_filename(PURPLE_DATADIR
, "pixmaps", "pidgin",
4183 "e2ee", "16", filename
, NULL
);
4184 image
= purple_image_new_from_file(path
, FALSE
);
4187 g_hash_table_insert(e2ee_stock
, g_strdup(stock_name
), image
);
4192 generate_e2ee_controls(PidginConvWindow
*win
)
4194 PidginConversation
*gtkconv
;
4195 PurpleConversation
*conv
;
4196 PurpleE2eeState
*state
;
4197 PurpleE2eeProvider
*provider
;
4199 GList
*menu_actions
, *it
;
4200 GtkWidget
*e2ee_image
;
4202 gtkconv
= pidgin_conv_window_get_active_gtkconv(win
);
4203 g_return_if_fail(gtkconv
!= NULL
);
4205 conv
= gtkconv
->active_conv
;
4206 g_return_if_fail(conv
!= NULL
);
4208 if (win
->menu
->e2ee
!= NULL
) {
4209 gtk_widget_destroy(win
->menu
->e2ee
);
4210 win
->menu
->e2ee
= NULL
;
4213 provider
= purple_e2ee_provider_get_main();
4214 state
= purple_conversation_get_e2ee_state(conv
);
4215 if (state
== NULL
|| provider
== NULL
)
4217 if (purple_e2ee_state_get_provider(state
) != provider
)
4220 win
->menu
->e2ee
= gtk_image_menu_item_new_with_label(
4221 purple_e2ee_provider_get_name(provider
));
4223 menu
= gtk_menu_new();
4224 gtk_menu_shell_insert(GTK_MENU_SHELL(win
->menu
->menubar
),
4225 win
->menu
->e2ee
, 3);
4226 gtk_menu_item_set_submenu(GTK_MENU_ITEM(win
->menu
->e2ee
), menu
);
4228 e2ee_image
= e2ee_state_to_gtkimage(state
);
4230 gtk_image_menu_item_set_image(
4231 GTK_IMAGE_MENU_ITEM(win
->menu
->e2ee
), e2ee_image
);
4234 gtk_widget_set_tooltip_text(win
->menu
->e2ee
,
4235 purple_e2ee_state_get_name(state
));
4237 menu_actions
= purple_e2ee_provider_get_conv_menu_actions(provider
, conv
);
4238 for (it
= menu_actions
; it
; it
= g_list_next(it
)) {
4239 PurpleMenuAction
*action
= it
->data
;
4241 gtk_widget_show_all(
4242 pidgin_append_menu_action(menu
, action
, conv
));
4244 g_list_free(menu_actions
);
4246 gtk_widget_show(win
->menu
->e2ee
);
4247 gtk_widget_show(menu
);
4251 get_chat_user_status_icon(PurpleChatConversation
*chat
, const char *name
, PurpleChatUserFlags flags
)
4253 const char *image
= NULL
;
4255 if (flags
& PURPLE_CHAT_USER_FOUNDER
) {
4256 image
= PIDGIN_STOCK_STATUS_FOUNDER
;
4257 } else if (flags
& PURPLE_CHAT_USER_OP
) {
4258 image
= PIDGIN_STOCK_STATUS_OPERATOR
;
4259 } else if (flags
& PURPLE_CHAT_USER_HALFOP
) {
4260 image
= PIDGIN_STOCK_STATUS_HALFOP
;
4261 } else if (flags
& PURPLE_CHAT_USER_VOICE
) {
4262 image
= PIDGIN_STOCK_STATUS_VOICE
;
4263 } else if ((!flags
) && purple_chat_conversation_is_ignored_user(chat
, name
)) {
4264 image
= PIDGIN_STOCK_STATUS_IGNORED
;
4272 deleting_chat_user_cb(PurpleChatUser
*cb
)
4274 GtkTreeRowReference
*ref
= purple_chat_user_get_ui_data(cb
);
4277 gtk_tree_row_reference_free(ref
);
4278 purple_chat_user_set_ui_data(cb
, NULL
);
4283 add_chat_user_common(PurpleChatConversation
*chat
, PurpleChatUser
*cb
, const char *old_name
)
4285 PidginConversation
*gtkconv
;
4286 PurpleConversation
*conv
;
4287 PidginChatPane
*gtkchat
;
4288 PurpleConnection
*gc
;
4289 PurpleProtocol
*protocol
;
4292 GtkTreePath
*newpath
;
4295 gboolean is_me
= FALSE
;
4297 const gchar
*name
, *alias
;
4298 gchar
*tmp
, *alias_key
;
4299 PurpleChatUserFlags flags
;
4300 GdkRGBA
*color
= NULL
;
4302 alias
= purple_chat_user_get_alias(cb
);
4303 name
= purple_chat_user_get_name(cb
);
4304 flags
= purple_chat_user_get_flags(cb
);
4306 conv
= PURPLE_CONVERSATION(chat
);
4307 gtkconv
= PIDGIN_CONVERSATION(conv
);
4308 gtkchat
= gtkconv
->u
.chat
;
4309 gc
= purple_conversation_get_connection(conv
);
4311 if (!gc
|| !(protocol
= purple_connection_get_protocol(gc
)))
4314 tm
= gtk_tree_view_get_model(GTK_TREE_VIEW(gtkchat
->list
));
4315 ls
= GTK_LIST_STORE(tm
);
4317 stock
= get_chat_user_status_icon(chat
, name
, flags
);
4319 if (!strcmp(purple_chat_conversation_get_nick(chat
), purple_normalize(purple_conversation_get_account(conv
), old_name
!= NULL
? old_name
: name
)))
4322 is_buddy
= purple_chat_user_is_buddy(cb
);
4324 tmp
= g_utf8_casefold(alias
, -1);
4325 alias_key
= g_utf8_collate_key(tmp
, -1);
4330 /* TODO WEBKIT: No tags in webkit stuff, yet. */
4331 GtkTextTag
*tag
= gtk_text_tag_table_lookup(
4332 gtk_text_buffer_get_tag_table(GTK_IMHTML(gtkconv
->webview
)->text_buffer
),
4334 g_object_get(tag
, "foreground-rgba", &color
, NULL
);
4338 if ((tag
= get_buddy_tag(chat
, name
, 0, FALSE
)))
4339 g_object_set(G_OBJECT(tag
), "style", PANGO_STYLE_NORMAL
, NULL
);
4340 if ((tag
= get_buddy_tag(chat
, name
, PURPLE_MESSAGE_NICK
, FALSE
)))
4341 g_object_set(G_OBJECT(tag
), "style", PANGO_STYLE_NORMAL
, NULL
);
4342 color
= (GdkRGBA
*)get_nick_color(gtkconv
, name
);
4345 gtk_list_store_insert_with_values(ls
, &iter
,
4347 * The GTK docs are mute about the effects of the "row" value for performance.
4348 * X-Chat hardcodes their value to 0 (prepend) and -1 (append), so we will too.
4349 * It *might* be faster to search the gtk_list_store and set row accurately,
4350 * but no one in #gtk+ seems to know anything about it either.
4351 * Inserting in the "wrong" location has no visible ill effects. - F.P.
4354 CHAT_USERS_ICON_STOCK_COLUMN
, stock
,
4355 CHAT_USERS_ALIAS_COLUMN
, alias
,
4356 CHAT_USERS_ALIAS_KEY_COLUMN
, alias_key
,
4357 CHAT_USERS_NAME_COLUMN
, name
,
4358 CHAT_USERS_FLAGS_COLUMN
, flags
,
4359 CHAT_USERS_COLOR_COLUMN
, color
,
4360 CHAT_USERS_WEIGHT_COLUMN
, is_buddy
? PANGO_WEIGHT_BOLD
: PANGO_WEIGHT_NORMAL
,
4363 if (purple_chat_user_get_ui_data(cb
)) {
4364 GtkTreeRowReference
*ref
= purple_chat_user_get_ui_data(cb
);
4365 gtk_tree_row_reference_free(ref
);
4368 newpath
= gtk_tree_model_get_path(tm
, &iter
);
4369 purple_chat_user_set_ui_data(cb
, gtk_tree_row_reference_new(tm
, newpath
));
4370 gtk_tree_path_free(newpath
);
4374 gdk_rgba_free(color
);
4380 * tab_complete_process_item:
4381 * @most_matched: Used internally by this function.
4382 * @entered: The partial string that the user types before hitting the
4384 * @entered_chars: The length of entered.
4385 * @partial: This is a return variable. This will be set to a string
4386 * containing the largest common string between all matches. This will
4387 * be inserted into the input box at the start of the word that the
4388 * user is tab completing. For example, if a chat room contains
4389 * "AlfFan" and "AlfHater" and the user types "a<TAB>" then this will
4391 * @matches: This is a return variable. If the given name is a potential
4392 * match for the entered string, then add a copy of the name to this
4393 * list. The caller is responsible for g_free'ing the data in this
4395 * @name: The buddy name or alias or slash command name that we're
4396 * checking for a match.
4399 tab_complete_process_item(int *most_matched
, const char *entered
, gsize entered_chars
, char **partial
,
4400 GList
**matches
, const char *name
)
4403 gsize name_len
= g_utf8_strlen(name
, -1);
4405 if (entered_chars
> name_len
)
4408 nick_partial
= g_utf8_substring(name
, 0, entered_chars
);
4409 if (purple_utf8_strcasecmp(nick_partial
, entered
)) {
4410 g_free(nick_partial
);
4413 g_free(nick_partial
);
4415 /* if we're here, it's a possible completion */
4417 if (*most_matched
== -1) {
4419 * this will only get called once, since from now
4420 * on *most_matched is >= 0
4422 *most_matched
= name_len
;
4423 *partial
= g_strdup(name
);
4425 else if (*most_matched
) {
4426 char *tmp
= g_strdup(name
);
4428 while (purple_utf8_strcasecmp(tmp
, *partial
)) {
4429 *(g_utf8_offset_to_pointer(*partial
, *most_matched
)) = '\0';
4430 if (*most_matched
< (goffset
)g_utf8_strlen(tmp
, -1))
4431 *(g_utf8_offset_to_pointer(tmp
, *most_matched
)) = '\0';
4439 *matches
= g_list_insert_sorted(*matches
, g_strdup(name
),
4440 (GCompareFunc
)purple_utf8_strcasecmp
);
4444 is_first_container(WebKitDOMNode
*container
)
4447 WebKitDOMNode
*parent
;
4450 parent
= webkit_dom_node_get_parent_node(container
);
4452 name
= webkit_dom_node_get_node_name(parent
);
4454 if (!strcmp(name
, "BODY")) {
4457 if (webkit_dom_node_get_previous_sibling(container
) == NULL
)
4474 tab_complete(PurpleConversation
*conv
)
4476 PidginConversation
*gtkconv
;
4477 WebKitDOMNode
*container
;
4478 glong caret
, word_start
, content_len
;
4479 int most_matched
= -1, colon
= 0;
4480 char *ch
, *ch2
= NULL
;
4481 char *entered
, *partial
= NULL
;
4482 char *content
, *sub1
, *sub2
, *modified
;
4484 GList
*matches
= NULL
;
4485 gboolean command
= FALSE
;
4486 gsize entered_chars
= 0;
4488 gtkconv
= PIDGIN_CONVERSATION(conv
);
4489 pidgin_webview_get_caret(PIDGIN_WEBVIEW(gtkconv
->entry
), &container
, &caret
);
4491 /* if there's nothing there just return */
4493 return PURPLE_IS_CHAT_CONVERSATION(conv
);
4495 content
= webkit_dom_node_get_node_value(container
);
4496 content_len
= g_utf8_strlen(content
, -1);
4498 /* if we're at the end of ":" or ": " we need to move back 1 or 2 spaces */
4500 ch
= g_utf8_offset_to_pointer(content
, caret
- 2);
4501 ch2
= g_utf8_find_next_char(ch
, NULL
);
4504 if (caret
>= 2 && *ch
== ':' && g_unichar_isspace(g_utf8_get_char(ch2
)))
4506 else if (caret
>= 1 && content
[caret
- 1] == ':')
4512 /* find the start of the word that we're tabbing. */
4513 ch
= g_utf8_offset_to_pointer(content
, caret
);
4514 while ((ch
= g_utf8_find_prev_char(content
, ch
))) {
4515 if (!g_unichar_isspace(g_utf8_get_char(ch
)))
4521 prefix
= pidgin_get_cmd_prefix();
4522 if (word_start
== 0 &&
4523 ((gsize
)caret
>= strlen(prefix
)) && !strncmp(content
, prefix
, strlen(prefix
))) {
4525 word_start
+= strlen(prefix
);
4528 entered
= g_utf8_substring(content
, word_start
, caret
);
4529 entered_chars
= g_utf8_strlen(entered
, -1);
4531 if (!entered_chars
) {
4534 return PURPLE_IS_CHAT_CONVERSATION(conv
);
4538 GList
*list
= purple_cmd_list(conv
);
4542 for (l
= list
; l
!= NULL
; l
= l
->next
) {
4543 tab_complete_process_item(&most_matched
, entered
, entered_chars
, &partial
,
4547 } else if (PURPLE_IS_CHAT_CONVERSATION(conv
)) {
4549 GtkTreeModel
*model
= gtk_tree_view_get_model(GTK_TREE_VIEW(PIDGIN_CONVERSATION(conv
)->u
.chat
->list
));
4554 users
= purple_chat_conversation_get_users(PURPLE_CHAT_CONVERSATION(conv
));
4555 for (l
= users
; l
!= NULL
; l
= l
->next
) {
4556 tab_complete_process_item(&most_matched
, entered
, entered_chars
, &partial
,
4557 &matches
, purple_chat_user_get_name((PurpleChatUser
*)l
->data
));
4562 if (gtk_tree_model_get_iter_first(GTK_TREE_MODEL(model
), &iter
))
4568 gtk_tree_model_get(model
, &iter
,
4569 CHAT_USERS_NAME_COLUMN
, &name
,
4570 CHAT_USERS_ALIAS_COLUMN
, &alias
,
4573 if (name
&& alias
&& strcmp(name
, alias
))
4574 tab_complete_process_item(&most_matched
, entered
, entered_chars
, &partial
,
4579 f
= gtk_tree_model_iter_next(model
, &iter
);
4588 /* if there weren't any matches, return */
4590 /* if matches isn't set partials won't be either */
4593 return PURPLE_IS_CHAT_CONVERSATION(conv
);
4596 sub1
= g_utf8_substring(content
, 0, word_start
);
4597 sub2
= g_utf8_substring(content
, caret
, content_len
);
4599 if (!matches
->next
) {
4600 /* there was only one match. fill it in. */
4602 if (!colon
&& !word_start
&& is_first_container(container
)) {
4604 if (caret
< content_len
) {
4605 tmp
= g_strdup_printf("%s: ", (char *)matches
->data
);
4608 g_unichar_to_utf8(0xA0, nbsp
);
4609 tmp
= g_strdup_printf("%s:%s", (char *)matches
->data
, nbsp
);
4612 modified
= g_strdup_printf("%s%s", tmp
, sub2
);
4613 webkit_dom_node_set_node_value(container
, modified
, NULL
);
4614 pidgin_webview_set_caret(PIDGIN_WEBVIEW(gtkconv
->entry
), container
,
4615 g_utf8_strlen(tmp
, -1));
4620 modified
= g_strdup_printf("%s%s%s", sub1
, (char *)matches
->data
, sub2
);
4621 webkit_dom_node_set_node_value(container
, modified
, NULL
);
4622 pidgin_webview_set_caret(PIDGIN_WEBVIEW(gtkconv
->entry
), container
,
4623 word_start
+ g_utf8_strlen(matches
->data
, -1) + colon
);
4627 g_free(matches
->data
);
4628 g_list_free(matches
);
4632 * there were lots of matches, fill in as much as possible
4633 * and display all of them
4635 char *addthis
= g_malloc0(1);
4638 char *tmp
= addthis
;
4639 addthis
= g_strconcat(tmp
, matches
->data
, " ", NULL
);
4641 g_free(matches
->data
);
4642 matches
= g_list_remove(matches
, matches
->data
);
4645 purple_conversation_write_system_message(conv
, addthis
, PURPLE_MESSAGE_NO_LOG
);
4647 modified
= g_strdup_printf("%s%s%s", sub1
, partial
, sub2
);
4648 webkit_dom_node_set_node_value(container
, modified
, NULL
);
4649 pidgin_webview_set_caret(PIDGIN_WEBVIEW(gtkconv
->entry
), container
,
4650 word_start
+ g_utf8_strlen(partial
, -1) + colon
);
4664 static void topic_callback(GtkWidget
*w
, PidginConversation
*gtkconv
)
4666 PurpleProtocol
*protocol
= NULL
;
4667 PurpleConnection
*gc
;
4668 PurpleConversation
*conv
= gtkconv
->active_conv
;
4669 PidginChatPane
*gtkchat
;
4671 const char *current_topic
;
4673 gc
= purple_conversation_get_connection(conv
);
4675 if(!gc
|| !(protocol
= purple_connection_get_protocol(gc
)))
4678 if(!PURPLE_PROTOCOL_IMPLEMENTS(protocol
, CHAT_IFACE
, set_topic
))
4681 gtkconv
= PIDGIN_CONVERSATION(conv
);
4682 gtkchat
= gtkconv
->u
.chat
;
4683 new_topic
= g_strdup(gtk_entry_get_text(GTK_ENTRY(gtkchat
->topic_text
)));
4684 current_topic
= purple_chat_conversation_get_topic(PURPLE_CHAT_CONVERSATION(conv
));
4686 if(current_topic
&& !g_utf8_collate(new_topic
, current_topic
)){
4692 gtk_entry_set_text(GTK_ENTRY(gtkchat
->topic_text
), current_topic
);
4694 gtk_entry_set_text(GTK_ENTRY(gtkchat
->topic_text
), "");
4696 purple_protocol_chat_iface_set_topic(protocol
, gc
, purple_chat_conversation_get_id(PURPLE_CHAT_CONVERSATION(conv
)),
4703 sort_chat_users(GtkTreeModel
*model
, GtkTreeIter
*a
, GtkTreeIter
*b
, gpointer userdata
)
4705 PurpleChatUserFlags f1
= 0, f2
= 0;
4706 char *user1
= NULL
, *user2
= NULL
;
4707 gboolean buddy1
= FALSE
, buddy2
= FALSE
;
4710 gtk_tree_model_get(model
, a
,
4711 CHAT_USERS_ALIAS_KEY_COLUMN
, &user1
,
4712 CHAT_USERS_FLAGS_COLUMN
, &f1
,
4713 CHAT_USERS_WEIGHT_COLUMN
, &buddy1
,
4715 gtk_tree_model_get(model
, b
,
4716 CHAT_USERS_ALIAS_KEY_COLUMN
, &user2
,
4717 CHAT_USERS_FLAGS_COLUMN
, &f2
,
4718 CHAT_USERS_WEIGHT_COLUMN
, &buddy2
,
4721 /* Only sort by membership levels */
4722 f1
&= PURPLE_CHAT_USER_VOICE
| PURPLE_CHAT_USER_HALFOP
| PURPLE_CHAT_USER_OP
|
4723 PURPLE_CHAT_USER_FOUNDER
;
4724 f2
&= PURPLE_CHAT_USER_VOICE
| PURPLE_CHAT_USER_HALFOP
| PURPLE_CHAT_USER_OP
|
4725 PURPLE_CHAT_USER_FOUNDER
;
4727 if (user1
== NULL
|| user2
== NULL
) {
4728 if (!(user1
== NULL
&& user2
== NULL
))
4729 ret
= (user1
== NULL
) ? -1: 1;
4730 } else if (f1
!= f2
) {
4731 /* sort more important users first */
4732 ret
= (f1
> f2
) ? -1 : 1;
4733 } else if (buddy1
!= buddy2
) {
4734 ret
= (buddy1
> buddy2
) ? -1 : 1;
4736 ret
= strcmp(user1
, user2
);
4746 update_chat_alias(PurpleBuddy
*buddy
, PurpleChatConversation
*chat
, PurpleConnection
*gc
, PurpleProtocol
*protocol
)
4748 PidginConversation
*gtkconv
= PIDGIN_CONVERSATION(PURPLE_CONVERSATION(chat
));
4749 PurpleAccount
*account
= purple_conversation_get_account(PURPLE_CONVERSATION(chat
));
4750 GtkTreeModel
*model
;
4751 char *normalized_name
;
4755 g_return_if_fail(buddy
!= NULL
);
4756 g_return_if_fail(chat
!= NULL
);
4758 /* This is safe because this callback is only used in chats, not IMs. */
4759 model
= gtk_tree_view_get_model(GTK_TREE_VIEW(gtkconv
->u
.chat
->list
));
4761 if (!gtk_tree_model_get_iter_first(GTK_TREE_MODEL(model
), &iter
))
4764 normalized_name
= g_strdup(purple_normalize(account
, purple_buddy_get_name(buddy
)));
4769 gtk_tree_model_get(model
, &iter
, CHAT_USERS_NAME_COLUMN
, &name
, -1);
4771 if (!strcmp(normalized_name
, purple_normalize(account
, name
))) {
4772 const char *alias
= name
;
4774 char *alias_key
= NULL
;
4775 PurpleBuddy
*buddy2
;
4777 if (strcmp(purple_chat_conversation_get_nick(chat
), purple_normalize(account
, name
))) {
4778 /* This user is not me, so look into updating the alias. */
4780 if ((buddy2
= purple_blist_find_buddy(account
, name
)) != NULL
) {
4781 alias
= purple_buddy_get_contact_alias(buddy2
);
4784 tmp
= g_utf8_casefold(alias
, -1);
4785 alias_key
= g_utf8_collate_key(tmp
, -1);
4788 gtk_list_store_set(GTK_LIST_STORE(model
), &iter
,
4789 CHAT_USERS_ALIAS_COLUMN
, alias
,
4790 CHAT_USERS_ALIAS_KEY_COLUMN
, alias_key
,
4798 f
= gtk_tree_model_iter_next(model
, &iter
);
4803 g_free(normalized_name
);
4807 blist_node_aliased_cb(PurpleBlistNode
*node
, const char *old_alias
, PurpleChatConversation
*chat
)
4809 PurpleConnection
*gc
;
4810 PurpleProtocol
*protocol
;
4811 PurpleConversation
*conv
= PURPLE_CONVERSATION(chat
);
4813 g_return_if_fail(node
!= NULL
);
4814 g_return_if_fail(conv
!= NULL
);
4816 gc
= purple_conversation_get_connection(conv
);
4817 g_return_if_fail(gc
!= NULL
);
4818 g_return_if_fail(purple_connection_get_protocol(gc
) != NULL
);
4819 protocol
= purple_connection_get_protocol(gc
);
4821 if (purple_protocol_get_options(protocol
) & OPT_PROTO_UNIQUE_CHATNAME
)
4824 if (PURPLE_IS_CONTACT(node
))
4826 PurpleBlistNode
*bnode
;
4828 for(bnode
= node
->child
; bnode
; bnode
= bnode
->next
) {
4830 if(!PURPLE_IS_BUDDY(bnode
))
4833 update_chat_alias((PurpleBuddy
*)bnode
, chat
, gc
, protocol
);
4836 else if (PURPLE_IS_BUDDY(node
))
4837 update_chat_alias((PurpleBuddy
*)node
, chat
, gc
, protocol
);
4838 else if (PURPLE_IS_CHAT(node
) &&
4839 purple_conversation_get_account(conv
) == purple_chat_get_account((PurpleChat
*)node
))
4841 if (old_alias
== NULL
|| g_utf8_collate(old_alias
, purple_conversation_get_title(conv
)) == 0)
4842 pidgin_conv_update_fields(conv
, PIDGIN_CONV_SET_TITLE
);
4847 buddy_cb_common(PurpleBuddy
*buddy
, PurpleChatConversation
*chat
, gboolean is_buddy
)
4849 GtkTreeModel
*model
;
4850 char *normalized_name
;
4852 GtkTextTag
*texttag
;
4853 PurpleConversation
*conv
= PURPLE_CONVERSATION(chat
);
4856 g_return_if_fail(buddy
!= NULL
);
4857 g_return_if_fail(conv
!= NULL
);
4859 /* Do nothing if the buddy does not belong to the conv's account */
4860 if (purple_buddy_get_account(buddy
) != purple_conversation_get_account(conv
))
4863 /* This is safe because this callback is only used in chats, not IMs. */
4864 model
= gtk_tree_view_get_model(GTK_TREE_VIEW(PIDGIN_CONVERSATION(conv
)->u
.chat
->list
));
4866 if (!gtk_tree_model_get_iter_first(GTK_TREE_MODEL(model
), &iter
))
4869 normalized_name
= g_strdup(purple_normalize(purple_conversation_get_account(conv
), purple_buddy_get_name(buddy
)));
4874 gtk_tree_model_get(model
, &iter
, CHAT_USERS_NAME_COLUMN
, &name
, -1);
4876 if (!strcmp(normalized_name
, purple_normalize(purple_conversation_get_account(conv
), name
))) {
4877 gtk_list_store_set(GTK_LIST_STORE(model
), &iter
,
4878 CHAT_USERS_WEIGHT_COLUMN
, is_buddy
? PANGO_WEIGHT_BOLD
: PANGO_WEIGHT_NORMAL
, -1);
4883 f
= gtk_tree_model_iter_next(model
, &iter
);
4888 g_free(normalized_name
);
4890 blist_node_aliased_cb((PurpleBlistNode
*)buddy
, NULL
, chat
);
4892 texttag
= get_buddy_tag(chat
, purple_buddy_get_name(buddy
), 0, FALSE
); /* XXX: do we want the normalized name? */
4894 g_object_set(texttag
, "weight", is_buddy
? PANGO_WEIGHT_BOLD
: PANGO_WEIGHT_NORMAL
, NULL
);
4899 buddy_added_cb(PurpleBlistNode
*node
, PurpleChatConversation
*chat
)
4901 if (!PURPLE_IS_BUDDY(node
))
4904 buddy_cb_common(PURPLE_BUDDY(node
), chat
, TRUE
);
4908 buddy_removed_cb(PurpleBlistNode
*node
, PurpleChatConversation
*chat
)
4910 if (!PURPLE_IS_BUDDY(node
))
4913 /* If there's another buddy for the same "dude" on the list, do nothing. */
4914 if (purple_blist_find_buddy(purple_buddy_get_account(PURPLE_BUDDY(node
)),
4915 purple_buddy_get_name(PURPLE_BUDDY(node
))) != NULL
)
4918 buddy_cb_common(PURPLE_BUDDY(node
), chat
, FALSE
);
4922 entry_popup_menu_cb(PidginWebView
*webview
, GtkMenu
*menu
, gpointer data
)
4924 GtkWidget
*menuitem
;
4925 PidginConversation
*gtkconv
= data
;
4928 g_return_if_fail(menu
!= NULL
);
4929 g_return_if_fail(gtkconv
!= NULL
);
4931 menuitem
= pidgin_new_menu_item(NULL
, _("_Send"), NULL
,
4932 G_CALLBACK(send_cb
), gtkconv
);
4933 is_empty
= pidgin_webview_is_empty(webview
);
4935 gtk_widget_set_sensitive(menuitem
, FALSE
);
4936 gtk_menu_shell_insert(GTK_MENU_SHELL(menu
), menuitem
, 0);
4938 menuitem
= gtk_separator_menu_item_new();
4939 gtk_widget_show(menuitem
);
4940 gtk_menu_shell_insert(GTK_MENU_SHELL(menu
), menuitem
, 1);
4944 resize_webview_cb(PidginConversation
*gtkconv
)
4946 PidginWebView
*webview
;
4956 GtkAllocation webview_allocation
;
4957 GtkAllocation entry_allocation
;
4959 webview
= PIDGIN_WEBVIEW(gtkconv
->entry
);
4961 /* Get text height from the DOM */
4962 height
= pidgin_webview_get_DOM_height(webview
);
4964 /* Find the height of the conversation window to calculate the maximum possible entry
4965 * size (1/2 of the window)
4967 gtk_widget_get_allocation(gtkconv
->webview
, &webview_allocation
);
4968 gtk_widget_get_allocation(gtkconv
->entry
, &entry_allocation
);
4969 total_height
= webview_allocation
.height
+ entry_allocation
.height
;
4970 max_height
= total_height
/ 2;
4972 /* Get size of the characters to calculate initial minimum space for the entry */
4973 font_size
= pidgin_webview_get_font_size(webview
);
4975 /* Allow to have a minimum of "min_lines" height as defined in the preference */
4976 min_lines
= purple_prefs_get_int(PIDGIN_PREFS_ROOT
"/conversations/minimum_entry_lines");
4977 min_height
= (font_size
+ WEBVIEW_DOM_FONT_PADDING
) * min_lines
+ WEBVIEW_DOM_TEXT_PADDING
;
4980 /* Take into account the size of the formatting toolbar */
4981 if (purple_prefs_get_bool(PIDGIN_PREFS_ROOT
"/conversations/show_formatting_toolbar")) {
4982 toolbar_size
= gtk_widget_get_allocated_height(pidgin_webview_get_toolbar(webview
));
4987 /* Calculate conv entry height */
4988 height
= CLAMP(height
, MIN(min_height
, max_height
), max_height
);
4989 /* Add the size used by the toolbar so we always take it into consideration. */
4990 height
+= toolbar_size
;
4992 /* Actually set the size of the gtkconv entry widget. */
4993 gtk_widget_get_size_request(gtkconv
->lower_hbox
, &old_w
, &old_h
);
4994 if (old_w
!= -1 || old_h
!= height
) {
4995 gtk_widget_set_size_request(gtkconv
->lower_hbox
, -1, height
);
4996 purple_debug_info("pidgin", "resizing to %d, %d lines\n", height
, min_lines
);
5003 minimum_entry_lines_pref_cb(const char *name
,
5004 PurplePrefType type
,
5005 gconstpointer value
,
5008 GList
*l
= purple_conversations_get_all();
5009 PurpleConversation
*conv
;
5012 conv
= (PurpleConversation
*)l
->data
;
5014 if (PIDGIN_IS_PIDGIN_CONVERSATION(conv
))
5015 resize_webview_cb(PIDGIN_CONVERSATION(conv
));
5022 setup_chat_topic(PidginConversation
*gtkconv
, GtkWidget
*vbox
)
5024 PurpleConversation
*conv
= gtkconv
->active_conv
;
5025 PurpleConnection
*gc
= purple_conversation_get_connection(conv
);
5026 PurpleProtocol
*protocol
= purple_connection_get_protocol(gc
);
5027 if (purple_protocol_get_options(protocol
) & OPT_PROTO_CHAT_TOPIC
)
5029 GtkWidget
*hbox
, *label
;
5030 PidginChatPane
*gtkchat
= gtkconv
->u
.chat
;
5032 hbox
= gtk_box_new(GTK_ORIENTATION_HORIZONTAL
, PIDGIN_HIG_BOX_SPACE
);
5033 gtk_box_pack_start(GTK_BOX(vbox
), hbox
, FALSE
, FALSE
, 0);
5035 label
= gtk_label_new(_("Topic:"));
5036 gtk_box_pack_start(GTK_BOX(hbox
), label
, FALSE
, FALSE
, 0);
5038 gtkchat
->topic_text
= gtk_entry_new();
5039 gtk_widget_set_size_request(gtkchat
->topic_text
, -1, BUDDYICON_SIZE_MIN
);
5041 if(!PURPLE_PROTOCOL_IMPLEMENTS(protocol
, CHAT_IFACE
, set_topic
)) {
5042 gtk_editable_set_editable(GTK_EDITABLE(gtkchat
->topic_text
), FALSE
);
5044 g_signal_connect(G_OBJECT(gtkchat
->topic_text
), "activate",
5045 G_CALLBACK(topic_callback
), gtkconv
);
5048 gtk_box_pack_start(GTK_BOX(hbox
), gtkchat
->topic_text
, TRUE
, TRUE
, 0);
5049 g_signal_connect(G_OBJECT(gtkchat
->topic_text
), "key_press_event",
5050 G_CALLBACK(entry_key_press_cb
), gtkconv
);
5055 pidgin_conv_userlist_create_tooltip(GtkWidget
*tipwindow
, GtkTreePath
*path
,
5056 gpointer userdata
, int *w
, int *h
)
5058 PidginConversation
*gtkconv
= userdata
;
5060 GtkTreeModel
*model
= gtk_tree_view_get_model(GTK_TREE_VIEW(gtkconv
->u
.chat
->list
));
5061 PurpleConversation
*conv
= gtkconv
->active_conv
;
5062 PurpleBlistNode
*node
;
5063 PurpleProtocol
*protocol
;
5064 PurpleAccount
*account
= purple_conversation_get_account(conv
);
5067 if (purple_account_get_connection(account
) == NULL
)
5070 if (!gtk_tree_model_get_iter(GTK_TREE_MODEL(model
), &iter
, path
))
5073 gtk_tree_model_get(GTK_TREE_MODEL(model
), &iter
, CHAT_USERS_NAME_COLUMN
, &who
, -1);
5075 protocol
= purple_connection_get_protocol(purple_account_get_connection(account
));
5076 node
= (PurpleBlistNode
*)(purple_blist_find_buddy(purple_conversation_get_account(conv
), who
));
5077 if (node
&& protocol
&& (purple_protocol_get_options(protocol
) & OPT_PROTO_UNIQUE_CHATNAME
))
5078 pidgin_blist_draw_tooltip(node
, gtkconv
->infopane
);
5085 setup_chat_userlist(PidginConversation
*gtkconv
, GtkWidget
*hpaned
)
5087 PidginChatPane
*gtkchat
= gtkconv
->u
.chat
;
5088 GtkWidget
*lbox
, *list
;
5090 GtkCellRenderer
*rend
;
5091 GtkTreeViewColumn
*col
;
5093 void *blist_handle
= purple_blist_get_handle();
5094 PurpleConversation
*conv
= gtkconv
->active_conv
;
5096 /* Build the right pane. */
5097 lbox
= gtk_box_new(GTK_ORIENTATION_VERTICAL
, PIDGIN_HIG_BOX_SPACE
);
5098 gtk_paned_pack2(GTK_PANED(hpaned
), lbox
, FALSE
, TRUE
);
5099 gtk_widget_show(lbox
);
5101 /* Setup the label telling how many people are in the room. */
5102 gtkchat
->count
= gtk_label_new(_("0 people in room"));
5103 gtk_label_set_ellipsize(GTK_LABEL(gtkchat
->count
), PANGO_ELLIPSIZE_END
);
5104 gtk_box_pack_start(GTK_BOX(lbox
), gtkchat
->count
, FALSE
, FALSE
, 0);
5105 gtk_widget_show(gtkchat
->count
);
5107 /* Setup the list of users. */
5109 ls
= gtk_list_store_new(CHAT_USERS_COLUMNS
, GDK_TYPE_PIXBUF
, G_TYPE_STRING
,
5110 G_TYPE_STRING
, G_TYPE_STRING
, G_TYPE_INT
,
5111 GDK_TYPE_RGBA
, G_TYPE_INT
, G_TYPE_STRING
);
5112 gtk_tree_sortable_set_sort_func(GTK_TREE_SORTABLE(ls
), CHAT_USERS_ALIAS_KEY_COLUMN
,
5113 sort_chat_users
, NULL
, NULL
);
5115 list
= gtk_tree_view_new_with_model(GTK_TREE_MODEL(ls
));
5117 /* Allow a user to specify gtkrc settings for the chat userlist only */
5118 gtk_widget_set_name(list
, "pidgin_conv_userlist");
5120 rend
= gtk_cell_renderer_pixbuf_new();
5121 g_object_set(G_OBJECT(rend
),
5122 "stock-size", gtk_icon_size_from_name(PIDGIN_ICON_SIZE_TANGO_EXTRA_SMALL
),
5124 col
= gtk_tree_view_column_new_with_attributes(NULL
, rend
,
5125 "stock-id", CHAT_USERS_ICON_STOCK_COLUMN
, NULL
);
5126 gtk_tree_view_column_set_sizing(col
, GTK_TREE_VIEW_COLUMN_AUTOSIZE
);
5127 gtk_tree_view_append_column(GTK_TREE_VIEW(list
), col
);
5128 ul_width
= purple_prefs_get_int(PIDGIN_PREFS_ROOT
"/conversations/chat/userlist_width");
5129 gtk_widget_set_size_request(lbox
, ul_width
, -1);
5131 /* Hack to prevent completely collapsed userlist coming back with a 1 pixel width.
5132 * I would have liked to use the GtkPaned "max-position", but for some reason that didn't work */
5134 gtk_paned_set_position(GTK_PANED(hpaned
), 999999);
5136 g_signal_connect(G_OBJECT(list
), "button_press_event",
5137 G_CALLBACK(right_click_chat_cb
), gtkconv
);
5138 g_signal_connect(G_OBJECT(list
), "row-activated",
5139 G_CALLBACK(activate_list_cb
), gtkconv
);
5140 g_signal_connect(G_OBJECT(list
), "popup-menu",
5141 G_CALLBACK(gtkconv_chat_popup_menu_cb
), gtkconv
);
5142 g_signal_connect(G_OBJECT(lbox
), "size-allocate", G_CALLBACK(lbox_size_allocate_cb
), gtkconv
);
5144 pidgin_tooltip_setup_for_treeview(list
, gtkconv
,
5145 pidgin_conv_userlist_create_tooltip
, NULL
);
5147 rend
= gtk_cell_renderer_text_new();
5149 "foreground-set", TRUE
,
5152 g_object_set(G_OBJECT(rend
), "editable", TRUE
, NULL
);
5154 col
= gtk_tree_view_column_new_with_attributes(NULL
, rend
,
5155 "text", CHAT_USERS_ALIAS_COLUMN
,
5156 "foreground-rgba", CHAT_USERS_COLOR_COLUMN
,
5157 "weight", CHAT_USERS_WEIGHT_COLUMN
,
5160 purple_signal_connect(blist_handle
, "blist-node-added",
5161 gtkchat
, PURPLE_CALLBACK(buddy_added_cb
), conv
);
5162 purple_signal_connect(blist_handle
, "blist-node-removed",
5163 gtkchat
, PURPLE_CALLBACK(buddy_removed_cb
), conv
);
5164 purple_signal_connect(blist_handle
, "blist-node-aliased",
5165 gtkchat
, PURPLE_CALLBACK(blist_node_aliased_cb
), conv
);
5167 gtk_tree_view_column_set_expand(col
, TRUE
);
5168 g_object_set(rend
, "ellipsize", PANGO_ELLIPSIZE_END
, NULL
);
5170 gtk_tree_view_append_column(GTK_TREE_VIEW(list
), col
);
5172 gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(list
), FALSE
);
5173 gtk_widget_show(list
);
5175 gtkchat
->list
= list
;
5177 gtk_box_pack_start(GTK_BOX(lbox
),
5178 pidgin_make_scrollable(list
, GTK_POLICY_AUTOMATIC
, GTK_POLICY_AUTOMATIC
, GTK_SHADOW_IN
, -1, -1),
5183 pidgin_conv_create_tooltip(GtkWidget
*tipwindow
, gpointer userdata
, int *w
, int *h
)
5185 PurpleBlistNode
*node
= NULL
;
5186 PurpleConversation
*conv
;
5187 PidginConversation
*gtkconv
= userdata
;
5189 conv
= gtkconv
->active_conv
;
5190 if (PURPLE_IS_CHAT_CONVERSATION(conv
)) {
5191 node
= (PurpleBlistNode
*)(purple_blist_find_chat(purple_conversation_get_account(conv
), purple_conversation_get_name(conv
)));
5193 node
= g_object_get_data(G_OBJECT(gtkconv
->webview
), "transient_chat");
5195 node
= (PurpleBlistNode
*)(purple_blist_find_buddy(purple_conversation_get_account(conv
), purple_conversation_get_name(conv
)));
5197 /* Using the transient blist nodes to show the tooltip doesn't quite work yet. */
5199 node
= g_object_get_data(G_OBJECT(gtkconv
->webview
), "transient_buddy");
5204 pidgin_blist_draw_tooltip(node
, gtkconv
->infopane
);
5208 /* Quick Find {{{ */
5210 pidgin_conv_end_quickfind(PidginConversation
*gtkconv
)
5212 GtkStyleContext
*context
= gtk_widget_get_style_context(gtkconv
->quickfind_entry
);
5213 gtk_style_context_remove_class(context
, "not-found");
5215 webkit_web_view_unmark_text_matches(WEBKIT_WEB_VIEW(gtkconv
->webview
));
5216 gtk_widget_hide(gtkconv
->quickfind_container
);
5218 gtk_widget_grab_focus(gtkconv
->entry
);
5223 quickfind_process_input(GtkWidget
*entry
, GdkEventKey
*event
, PidginConversation
*gtkconv
)
5225 switch (event
->keyval
) {
5226 case GDK_KEY_Return
:
5227 case GDK_KEY_KP_Enter
:
5228 if (webkit_web_view_search_text(WEBKIT_WEB_VIEW(gtkconv
->webview
), gtk_entry_get_text(GTK_ENTRY(entry
)), FALSE
, TRUE
, TRUE
)) {
5229 GtkStyleContext
*context
= gtk_widget_get_style_context(gtkconv
->quickfind_entry
);
5230 gtk_style_context_remove_class(context
, "not-found");
5232 GtkStyleContext
*context
= gtk_widget_get_style_context(gtkconv
->quickfind_entry
);
5233 gtk_style_context_add_class(context
, "not-found");
5236 case GDK_KEY_Escape
:
5237 pidgin_conv_end_quickfind(gtkconv
);
5246 pidgin_conv_setup_quickfind(PidginConversation
*gtkconv
, GtkWidget
*container
)
5248 GtkWidget
*widget
= gtk_box_new(GTK_ORIENTATION_HORIZONTAL
, 0);
5249 GtkWidget
*label
, *entry
, *close
;
5250 GtkStyleContext
*context
;
5251 GtkCssProvider
*filter_css
;
5252 const gchar filter_style
[] =
5254 "color: @error_fg_color;"
5255 "text-shadow: 0 1px @error_text_shadow;"
5256 "background-image: none;"
5257 "background-color: @error_bg_color;"
5260 gtk_box_pack_start(GTK_BOX(container
), widget
, FALSE
, FALSE
, 0);
5262 close
= pidgin_create_small_button(gtk_label_new("×"));
5263 gtk_box_pack_start(GTK_BOX(widget
), close
, FALSE
, FALSE
, 0);
5264 gtk_widget_set_tooltip_text(close
, _("Close Find bar"));
5266 label
= gtk_label_new(_("Find:"));
5267 gtk_box_pack_start(GTK_BOX(widget
), label
, FALSE
, FALSE
, 10);
5269 entry
= gtk_entry_new();
5270 gtk_box_pack_start(GTK_BOX(widget
), entry
, TRUE
, TRUE
, 0);
5271 filter_css
= gtk_css_provider_new();
5272 gtk_css_provider_load_from_data(filter_css
, filter_style
, -1, NULL
);
5273 context
= gtk_widget_get_style_context(entry
);
5274 gtk_style_context_add_provider(context
,
5275 GTK_STYLE_PROVIDER(filter_css
),
5276 GTK_STYLE_PROVIDER_PRIORITY_APPLICATION
);
5278 gtkconv
->quickfind_entry
= entry
;
5279 gtkconv
->quickfind_container
= widget
;
5281 /* Hook to signals and stuff */
5282 g_signal_connect(G_OBJECT(entry
), "key-press-event",
5283 G_CALLBACK(quickfind_process_input
), gtkconv
);
5284 g_signal_connect_swapped(G_OBJECT(close
), "button-press-event",
5285 G_CALLBACK(pidgin_conv_end_quickfind
), gtkconv
);
5291 replace_header_tokens(PurpleConversation
*conv
, const char *text
)
5293 PurpleAccount
*account
= purple_conversation_get_account(conv
);
5295 const char *cur
= text
;
5296 const char *prev
= cur
;
5298 struct tm
*tm
= NULL
;
5300 if (text
== NULL
|| *text
== '\0')
5303 str
= g_string_new(NULL
);
5304 while ((cur
= strchr(cur
, '%'))) {
5305 char *freeval
= NULL
;
5306 const char *replace
= NULL
;
5307 const char *fin
= NULL
;
5309 if (g_str_has_prefix(cur
, "%chatName%")) {
5310 replace
= purple_conversation_get_name(conv
);
5312 } else if (g_str_has_prefix(cur
, "%sourceName%")) {
5313 replace
= purple_account_get_private_alias(account
);
5314 if (replace
== NULL
)
5315 replace
= purple_account_get_username(account
);
5317 } else if (g_str_has_prefix(cur
, "%destinationName%")) {
5318 PurpleBuddy
*buddy
= purple_blist_find_buddy(account
, purple_conversation_get_name(conv
));
5320 replace
= purple_buddy_get_alias(buddy
);
5322 replace
= purple_conversation_get_name(conv
);
5325 } else if (g_str_has_prefix(cur
, "%incomingIconPath%")) {
5326 PurpleBuddyIcon
*icon
= purple_im_conversation_get_icon(PURPLE_IM_CONVERSATION(conv
));
5328 replace
= purple_buddy_icon_get_full_path(icon
);
5330 } else if (g_str_has_prefix(cur
, "%outgoingIconPath%")) {
5331 replace
= purple_account_get_buddy_icon_path(account
);
5333 } else if (g_str_has_prefix(cur
, "%timeOpened")) {
5334 const char *tmp
= cur
+ strlen("%timeOpened");
5339 end
= strstr(tmp
, "}%");
5340 if (!end
) /* Invalid string */
5344 tm
= localtime(&mtime
);
5346 replace
= freeval
= purple_uts35_to_str(tmp
, end
- tmp
, tm
);
5351 tm
= localtime(&mtime
);
5354 replace
= purple_utf8_strftime("%X", tm
);
5357 } else if (g_str_has_prefix(cur
, "%dateOpened%")) {
5360 tm
= localtime(&mtime
);
5363 replace
= purple_date_format_short(tm
);
5370 /* Here we have a replacement to make */
5371 g_string_append_len(str
, prev
, cur
- prev
);
5373 g_string_append(str
, replace
);
5375 /* And update the pointers */
5377 prev
= cur
= fin
+ 1;
5379 prev
= cur
= strchr(cur
+ 1, '%') + 1;
5385 /* And wrap it up */
5386 g_string_append(str
, prev
);
5387 return g_string_free(str
, FALSE
);
5391 replace_template_tokens(PidginConvTheme
*theme
, const char *header
, const char *footer
)
5398 text
= pidgin_conversation_theme_get_template(theme
, PIDGIN_CONVERSATION_THEME_TEMPLATE_MAIN
);
5402 ms
= g_strsplit(text
, "%@", 6);
5403 if (ms
[0] == NULL
|| ms
[1] == NULL
|| ms
[2] == NULL
|| ms
[3] == NULL
|| ms
[4] == NULL
|| ms
[5] == NULL
) {
5408 str
= g_string_new(NULL
);
5410 g_string_append(str
, ms
[0]);
5411 g_string_append(str
, "file://");
5412 path
= pidgin_conversation_theme_get_template_path(theme
);
5413 g_string_append(str
, path
);
5416 g_string_append(str
, ms
[1]);
5418 text
= pidgin_conversation_theme_get_template(theme
, PIDGIN_CONVERSATION_THEME_TEMPLATE_BASESTYLE_CSS
);
5419 g_string_append(str
, text
);
5421 g_string_append(str
, ms
[2]);
5423 g_string_append(str
, "file://");
5424 path
= pidgin_conversation_theme_get_css_path(theme
);
5425 g_string_append(str
, path
);
5428 g_string_append(str
, ms
[3]);
5430 g_string_append(str
, header
);
5431 g_string_append(str
, ms
[4]);
5433 g_string_append(str
, footer
);
5434 g_string_append(str
, ms
[5]);
5438 return g_string_free(str
, FALSE
);
5442 set_theme_webkit_settings(WebKitWebView
*webview
, PidginConvTheme
*theme
)
5444 WebKitWebSettings
*settings
;
5447 g_object_get(G_OBJECT(webview
), "settings", &settings
, NULL
);
5449 val
= pidgin_conversation_theme_lookup(theme
, "DefaultFontFamily", TRUE
);
5450 if (val
&& G_VALUE_HOLDS_STRING(val
))
5452 const gchar
*font_family
= g_value_get_string(val
);
5454 /* XXX: a hack for not converting backslash to yen sign.
5455 * See gtkwebview.c: pidgin_webview_new.
5457 if (g_ascii_strcasecmp(font_family
, "sans-serif") == 0)
5461 g_object_set(G_OBJECT(settings
), "default-font-family", font_family
, NULL
);
5464 val
= pidgin_conversation_theme_lookup(theme
, "DefaultFontSize", TRUE
);
5465 if (val
&& G_VALUE_HOLDS_INT(val
))
5466 g_object_set(G_OBJECT(settings
), "default-font-size", GINT_TO_POINTER(g_value_get_int(val
)), NULL
);
5468 val
= pidgin_conversation_theme_lookup(theme
, "DefaultBackgroundIsTransparent", TRUE
);
5469 if (val
&& G_VALUE_HOLDS_BOOLEAN(val
))
5470 /* this does not work :( */
5471 webkit_web_view_set_transparent(webview
, g_value_get_boolean(val
));
5475 conv_variant_changed_cb(GObject
*gobject
, GParamSpec
*pspec
, gpointer user_data
)
5477 PidginConversation
*gtkconv
= user_data
;
5480 path
= pidgin_conversation_theme_get_css_path(PIDGIN_CONV_THEME(gobject
));
5481 js
= g_strdup_printf("setStylesheet(\"mainStyle\", \"file://%s\");", path
);
5483 pidgin_webview_safe_execute_script(PIDGIN_WEBVIEW(gtkconv
->webview
), js
);
5488 load_conv_theme(PidginConversation
*gtkconv
)
5490 char *header
, *footer
;
5492 char *basedir
, *baseuri
;
5494 header
= replace_header_tokens(gtkconv
->active_conv
,
5495 pidgin_conversation_theme_get_template(gtkconv
->theme
, PIDGIN_CONVERSATION_THEME_TEMPLATE_HEADER
));
5496 footer
= replace_header_tokens(gtkconv
->active_conv
,
5497 pidgin_conversation_theme_get_template(gtkconv
->theme
, PIDGIN_CONVERSATION_THEME_TEMPLATE_FOOTER
));
5498 template = replace_template_tokens(gtkconv
->theme
, header
, footer
);
5502 if (template == NULL
)
5505 set_theme_webkit_settings(WEBKIT_WEB_VIEW(gtkconv
->webview
), gtkconv
->theme
);
5507 basedir
= pidgin_conversation_theme_get_template_path(gtkconv
->theme
);
5508 baseuri
= g_strdup_printf("file://%s", basedir
);
5509 webkit_web_view_load_string(WEBKIT_WEB_VIEW(gtkconv
->webview
), template,
5510 "text/html", "UTF-8", baseuri
);
5512 if (PURPLE_IS_CHAT_CONVERSATION(gtkconv
->active_conv
))
5513 pidgin_webview_safe_execute_script(PIDGIN_WEBVIEW(gtkconv
->webview
),
5514 "document.getElementById('Chat').className = 'groupchat'");
5516 g_signal_connect(G_OBJECT(gtkconv
->theme
), "notify::variant",
5517 G_CALLBACK(conv_variant_changed_cb
), gtkconv
);
5525 setup_common_pane(PidginConversation
*gtkconv
)
5527 GtkWidget
*vbox
, *frame
, *webview_sw
, *event_box
;
5528 GtkCellRenderer
*rend
;
5530 PurpleConversation
*conv
= gtkconv
->active_conv
;
5532 gboolean chat
= PURPLE_IS_CHAT_CONVERSATION(conv
);
5533 int buddyicon_size
= 0;
5535 /* Setup the top part of the pane */
5536 vbox
= gtk_box_new(GTK_ORIENTATION_VERTICAL
, PIDGIN_HIG_BOX_SPACE
);
5537 gtk_widget_show(vbox
);
5539 /* Setup the info pane */
5540 event_box
= gtk_event_box_new();
5541 gtk_event_box_set_visible_window(GTK_EVENT_BOX(event_box
), FALSE
);
5542 gtk_widget_show(event_box
);
5543 gtkconv
->infopane_hbox
= gtk_box_new(GTK_ORIENTATION_HORIZONTAL
, 0);
5544 gtk_box_pack_start(GTK_BOX(vbox
), event_box
, FALSE
, FALSE
, 0);
5545 gtk_container_add(GTK_CONTAINER(event_box
), gtkconv
->infopane_hbox
);
5546 gtk_widget_show(gtkconv
->infopane_hbox
);
5547 gtk_widget_add_events(event_box
,
5548 GDK_POINTER_MOTION_MASK
| GDK_LEAVE_NOTIFY_MASK
);
5549 g_signal_connect(G_OBJECT(event_box
), "button-press-event",
5550 G_CALLBACK(infopane_press_cb
), gtkconv
);
5552 pidgin_tooltip_setup_for_widget(event_box
, gtkconv
,
5553 pidgin_conv_create_tooltip
, NULL
);
5555 gtkconv
->infopane
= gtk_cell_view_new();
5556 gtkconv
->infopane_model
= gtk_list_store_new(CONV_NUM_COLUMNS
, G_TYPE_STRING
, G_TYPE_STRING
, GDK_TYPE_PIXBUF
, GDK_TYPE_PIXBUF
);
5557 gtk_cell_view_set_model(GTK_CELL_VIEW(gtkconv
->infopane
),
5558 GTK_TREE_MODEL(gtkconv
->infopane_model
));
5559 g_object_unref(gtkconv
->infopane_model
);
5560 gtk_list_store_append(gtkconv
->infopane_model
, &(gtkconv
->infopane_iter
));
5561 gtk_box_pack_start(GTK_BOX(gtkconv
->infopane_hbox
), gtkconv
->infopane
, TRUE
, TRUE
, 0);
5562 path
= gtk_tree_path_new_from_string("0");
5563 gtk_cell_view_set_displayed_row(GTK_CELL_VIEW(gtkconv
->infopane
), path
);
5564 gtk_tree_path_free(path
);
5567 /* This empty widget is used to ensure that the infopane is consistently
5568 sized for chat windows. The correct fix is to put an icon in the chat
5569 window as well, because that would make "Set Custom Icon" consistent
5570 for both the buddy list and the chat window, but PidginConversation
5571 is pretty much stuck until 3.0. */
5572 GtkWidget
*sizing_vbox
;
5573 sizing_vbox
= gtk_box_new(GTK_ORIENTATION_VERTICAL
, 0);
5574 gtk_widget_set_size_request(sizing_vbox
, -1, BUDDYICON_SIZE_MIN
);
5575 gtk_box_pack_start(GTK_BOX(gtkconv
->infopane_hbox
), sizing_vbox
, FALSE
, FALSE
, 0);
5576 gtk_widget_show(sizing_vbox
);
5579 gtkconv
->u
.im
->icon_container
= gtk_box_new(GTK_ORIENTATION_VERTICAL
, 0);
5581 if ((buddy
= purple_blist_find_buddy(purple_conversation_get_account(conv
),
5582 purple_conversation_get_name(conv
))) != NULL
) {
5583 PurpleContact
*contact
= purple_buddy_get_contact(buddy
);
5585 buddyicon_size
= purple_blist_node_get_int((PurpleBlistNode
*)contact
, "pidgin-infopane-iconsize");
5588 buddyicon_size
= CLAMP(buddyicon_size
, BUDDYICON_SIZE_MIN
, BUDDYICON_SIZE_MAX
);
5589 gtk_widget_set_size_request(gtkconv
->u
.im
->icon_container
, -1, buddyicon_size
);
5591 gtk_box_pack_start(GTK_BOX(gtkconv
->infopane_hbox
),
5592 gtkconv
->u
.im
->icon_container
, FALSE
, FALSE
, 0);
5594 gtk_widget_show(gtkconv
->u
.im
->icon_container
);
5597 gtk_widget_show(gtkconv
->infopane
);
5599 rend
= gtk_cell_renderer_pixbuf_new();
5600 gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(gtkconv
->infopane
), rend
, FALSE
);
5601 gtk_cell_layout_set_attributes(GTK_CELL_LAYOUT(gtkconv
->infopane
), rend
, "stock-id", CONV_ICON_COLUMN
, NULL
);
5602 g_object_set(rend
, "xalign", 0.0, "xpad", 6, "ypad", 0,
5603 "stock-size", gtk_icon_size_from_name(PIDGIN_ICON_SIZE_TANGO_EXTRA_SMALL
),
5606 rend
= gtk_cell_renderer_text_new();
5607 gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(gtkconv
->infopane
), rend
, TRUE
);
5608 gtk_cell_layout_set_attributes(GTK_CELL_LAYOUT(gtkconv
->infopane
), rend
, "markup", CONV_TEXT_COLUMN
, NULL
);
5609 g_object_set(rend
, "ypad", 0, "yalign", 0.5, NULL
);
5611 g_object_set(rend
, "ellipsize", PANGO_ELLIPSIZE_END
, NULL
);
5613 rend
= gtk_cell_renderer_pixbuf_new();
5614 gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(gtkconv
->infopane
), rend
, FALSE
);
5615 gtk_cell_layout_set_attributes(GTK_CELL_LAYOUT(gtkconv
->infopane
), rend
, "pixbuf", CONV_PROTOCOL_ICON_COLUMN
, NULL
);
5616 g_object_set(rend
, "xalign", 0.0, "xpad", 3, "ypad", 0, NULL
);
5618 rend
= gtk_cell_renderer_pixbuf_new();
5619 gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(gtkconv
->infopane
), rend
, FALSE
);
5620 gtk_cell_layout_set_attributes(GTK_CELL_LAYOUT(gtkconv
->infopane
), rend
, "pixbuf", CONV_EMBLEM_COLUMN
, NULL
);
5621 g_object_set(rend
, "xalign", 0.0, "xpad", 6, "ypad", 0, NULL
);
5623 /* Setup the webkit widget */
5624 frame
= pidgin_create_webview(FALSE
, >kconv
->webview
, &webview_sw
);
5625 g_object_set(G_OBJECT(gtkconv
->webview
), "expand", TRUE
, NULL
);
5626 _pidgin_widget_set_accessible_name(frame
, "Conversation Pane");
5628 load_conv_theme(gtkconv
);
5634 setup_chat_topic(gtkconv
, vbox
);
5636 /* Add the gtkwebview frame */
5637 hpaned
= gtk_paned_new(GTK_ORIENTATION_HORIZONTAL
);
5638 gtk_box_pack_start(GTK_BOX(vbox
), hpaned
, TRUE
, TRUE
, 0);
5639 gtk_widget_show(hpaned
);
5640 gtk_paned_pack1(GTK_PANED(hpaned
), frame
, TRUE
, TRUE
);
5642 /* Now add the userlist */
5643 setup_chat_userlist(gtkconv
, hpaned
);
5645 gtk_box_pack_start(GTK_BOX(vbox
), frame
, TRUE
, TRUE
, 0);
5647 gtk_widget_show_all(frame
);
5649 gtk_widget_set_name(gtkconv
->webview
, "pidgin_conv_webview");
5650 g_object_set_data(G_OBJECT(gtkconv
->webview
), "gtkconv", gtkconv
);
5652 g_signal_connect_after(G_OBJECT(gtkconv
->webview
), "button_press_event",
5653 G_CALLBACK(entry_stop_rclick_cb
), NULL
);
5654 g_signal_connect(G_OBJECT(gtkconv
->webview
), "key_press_event",
5655 G_CALLBACK(refocus_entry_cb
), gtkconv
);
5656 g_signal_connect(G_OBJECT(gtkconv
->webview
), "key_release_event",
5657 G_CALLBACK(refocus_entry_cb
), gtkconv
);
5659 pidgin_conv_setup_quickfind(gtkconv
, vbox
);
5661 gtkconv
->lower_hbox
= gtk_box_new(GTK_ORIENTATION_HORIZONTAL
, PIDGIN_HIG_BOX_SPACE
);
5662 gtk_box_pack_start(GTK_BOX(vbox
), gtkconv
->lower_hbox
, FALSE
, FALSE
, 0);
5663 gtk_widget_show(gtkconv
->lower_hbox
);
5665 /* Setup the entry widget and all signals */
5666 frame
= pidgin_create_webview(TRUE
, >kconv
->entry
, NULL
);
5667 gtk_box_pack_start(GTK_BOX(gtkconv
->lower_hbox
), frame
, TRUE
, TRUE
, 0);
5668 gtk_widget_show(frame
);
5670 _pidgin_widget_set_accessible_name(frame
, "Message Input");
5671 gtk_widget_set_name(gtkconv
->entry
, "pidgin_conv_entry");
5673 g_signal_connect(G_OBJECT(gtkconv
->entry
), "populate-popup",
5674 G_CALLBACK(entry_popup_menu_cb
), gtkconv
);
5675 g_signal_connect(G_OBJECT(gtkconv
->entry
), "key-press-event",
5676 G_CALLBACK(entry_key_press_cb
), gtkconv
);
5678 /* TODO WebKit: Why? */
5679 g_signal_connect_after(G_OBJECT(gtkconv
->entry
), "button-press-event",
5680 G_CALLBACK(entry_stop_rclick_cb
), NULL
);
5684 /* For sending typing notifications for IMs */
5685 gtkconv
->u
.im
->typing_timer
= 0;
5686 gtkconv
->u
.im
->animate
= purple_prefs_get_bool(PIDGIN_PREFS_ROOT
"/conversations/im/animate_buddy_icons");
5687 gtkconv
->u
.im
->show_icon
= TRUE
;
5691 /* TODO WebKit: sizing stuff? */
5692 g_signal_connect_swapped(G_OBJECT(gtkconv
->entry_buffer
), "changed",
5693 G_CALLBACK(resize_webview_cb
), gtkconv
);
5694 g_signal_connect_swapped(G_OBJECT(gtkconv
->entry
), "size-allocate",
5695 G_CALLBACK(resize_webview_cb
), gtkconv
);
5697 g_signal_connect_swapped(G_OBJECT(gtkconv
->entry
), "changed",
5698 G_CALLBACK(resize_webview_cb
), gtkconv
);
5699 g_signal_connect_swapped(G_OBJECT(gtkconv
->entry
), "size-allocate",
5700 G_CALLBACK(resize_webview_cb
), gtkconv
);
5702 default_formatize(gtkconv
);
5703 g_signal_connect_after(G_OBJECT(gtkconv
->entry
), "format-cleared",
5704 G_CALLBACK(clear_formatting_cb
), gtkconv
);
5709 conv_dnd_recv(GtkWidget
*widget
, GdkDragContext
*dc
, guint x
, guint y
,
5710 GtkSelectionData
*sd
, guint info
, guint t
,
5711 PidginConversation
*gtkconv
)
5713 PurpleConversation
*conv
= gtkconv
->active_conv
;
5714 PidginConvWindow
*win
= gtkconv
->win
;
5715 PurpleIMConversation
*im
;
5716 PurpleAccount
*convaccount
= purple_conversation_get_account(conv
);
5717 PurpleConnection
*gc
= purple_account_get_connection(convaccount
);
5718 PurpleProtocol
*protocol
= gc
? purple_connection_get_protocol(gc
) : NULL
;
5719 const guchar
*data
= gtk_selection_data_get_data(sd
);
5721 if (info
== PIDGIN_DRAG_BLIST_NODE
)
5723 PurpleBlistNode
*n
= NULL
;
5725 PidginConversation
*gtkconv
= NULL
;
5726 PurpleAccount
*buddyaccount
;
5727 const char *buddyname
;
5728 PurpleBlistNode
**data_val
= (gpointer
)data
;
5732 if (PURPLE_IS_CONTACT(n
))
5733 b
= purple_contact_get_priority_buddy((PurpleContact
*)n
);
5734 else if (PURPLE_IS_BUDDY(n
))
5735 b
= (PurpleBuddy
*)n
;
5739 buddyaccount
= purple_buddy_get_account(b
);
5740 buddyname
= purple_buddy_get_name(b
);
5742 * If a buddy is dragged to a chat window of the same protocol,
5743 * invite him to the chat.
5745 if (PURPLE_IS_CHAT_CONVERSATION(conv
) &&
5746 protocol
&& PURPLE_PROTOCOL_IMPLEMENTS(protocol
, CHAT_IFACE
, invite
) &&
5747 strcmp(purple_account_get_protocol_id(convaccount
),
5748 purple_account_get_protocol_id(buddyaccount
)) == 0) {
5749 purple_chat_conversation_invite_user(PURPLE_CHAT_CONVERSATION(conv
), buddyname
, NULL
, TRUE
);
5752 * If we already have an open conversation with this buddy, then
5753 * just move the conv to this window. Otherwise, create a new
5754 * conv and add it to this window.
5756 im
= purple_conversations_find_im_with_account(buddyname
, buddyaccount
);
5758 PidginConvWindow
*oldwin
;
5759 gtkconv
= PIDGIN_CONVERSATION(PURPLE_CONVERSATION(im
));
5760 oldwin
= gtkconv
->win
;
5761 if (oldwin
!= win
) {
5762 pidgin_conv_window_remove_gtkconv(oldwin
, gtkconv
);
5763 pidgin_conv_window_add_gtkconv(win
, gtkconv
);
5766 im
= purple_im_conversation_new(buddyaccount
, buddyname
);
5767 gtkconv
= PIDGIN_CONVERSATION(PURPLE_CONVERSATION(im
));
5768 if (gtkconv
->win
!= win
) {
5769 pidgin_conv_window_remove_gtkconv(gtkconv
->win
, gtkconv
);
5770 pidgin_conv_window_add_gtkconv(win
, gtkconv
);
5774 /* Make this conversation the active conversation */
5775 pidgin_conv_window_switch_gtkconv(win
, gtkconv
);
5778 gtk_drag_finish(dc
, TRUE
,
5779 gdk_drag_context_get_actions(dc
) == GDK_ACTION_MOVE
, t
);
5781 else if (info
== PIDGIN_DRAG_IM_CONTACT
)
5783 char *protocol_id
= NULL
;
5784 char *username
= NULL
;
5785 PurpleAccount
*account
;
5786 PidginConversation
*gtkconv
;
5788 if (pidgin_parse_x_im_contact((const char *) data
, FALSE
, &account
,
5789 &protocol_id
, &username
, NULL
))
5791 if (account
== NULL
)
5793 purple_notify_error(win
, NULL
,
5794 _("You are not currently signed on with an account that "
5795 "can add that buddy."), NULL
, NULL
);
5798 * If a buddy is dragged to a chat window of the same protocol,
5799 * invite him to the chat.
5801 if (PURPLE_IS_CHAT_CONVERSATION(conv
) &&
5802 protocol
&& PURPLE_PROTOCOL_IMPLEMENTS(protocol
, CHAT_IFACE
, invite
) &&
5803 strcmp(purple_account_get_protocol_id(convaccount
), protocol_id
) == 0) {
5804 purple_chat_conversation_invite_user(PURPLE_CHAT_CONVERSATION(conv
), username
, NULL
, TRUE
);
5806 im
= purple_im_conversation_new(account
, username
);
5807 gtkconv
= PIDGIN_CONVERSATION(PURPLE_CONVERSATION(im
));
5808 if (gtkconv
->win
!= win
) {
5809 pidgin_conv_window_remove_gtkconv(gtkconv
->win
, gtkconv
);
5810 pidgin_conv_window_add_gtkconv(win
, gtkconv
);
5817 g_free(protocol_id
);
5819 gtk_drag_finish(dc
, TRUE
,
5820 gdk_drag_context_get_actions(dc
) == GDK_ACTION_MOVE
, t
);
5822 else if (info
== WEBKIT_WEB_VIEW_TARGET_INFO_URI_LIST
) {
5823 if (PURPLE_IS_IM_CONVERSATION(conv
))
5824 pidgin_dnd_file_manage(sd
, convaccount
, purple_conversation_get_name(conv
));
5825 gtk_drag_finish(dc
, TRUE
,
5826 gdk_drag_context_get_actions(dc
) == GDK_ACTION_MOVE
, t
);
5829 gtk_drag_finish(dc
, FALSE
, FALSE
, t
);
5833 static PidginConversation
*
5834 pidgin_conv_find_gtkconv(PurpleConversation
* conv
)
5836 PurpleBuddy
*bud
= purple_blist_find_buddy(purple_conversation_get_account(conv
), purple_conversation_get_name(conv
));
5838 PurpleBlistNode
*cn
, *bn
;
5843 if (!(c
= purple_buddy_get_contact(bud
)))
5846 cn
= PURPLE_BLIST_NODE(c
);
5847 for (bn
= purple_blist_node_get_first_child(cn
); bn
; bn
= purple_blist_node_get_sibling_next(bn
)) {
5848 PurpleBuddy
*b
= PURPLE_BUDDY(bn
);
5849 PurpleIMConversation
*im
;
5850 if ((im
= purple_conversations_find_im_with_account(purple_buddy_get_name(b
), purple_buddy_get_account(b
)))) {
5851 if (PIDGIN_CONVERSATION(PURPLE_CONVERSATION(im
)))
5852 return PIDGIN_CONVERSATION(PURPLE_CONVERSATION(im
));
5860 buddy_update_cb(PurpleBlistNode
*bnode
, gpointer null
)
5864 g_return_if_fail(bnode
);
5865 if (!PURPLE_IS_BUDDY(bnode
))
5868 for (list
= pidgin_conv_windows_get_list(); list
; list
= list
->next
)
5870 PidginConvWindow
*win
= list
->data
;
5871 PurpleConversation
*conv
= pidgin_conv_window_get_active_conversation(win
);
5873 if (!PURPLE_IS_IM_CONVERSATION(conv
))
5876 pidgin_conv_update_fields(conv
, PIDGIN_CONV_MENU
);
5881 ignore_middle_click(GtkWidget
*widget
, GdkEventButton
*e
, gpointer null
)
5883 /* A click on the pane is propagated to the notebook containing the pane.
5884 * So if Stu accidentally aims high and middle clicks on the pane-handle,
5885 * it causes a conversation tab to close. Let's stop that from happening.
5887 if (e
->button
== 2 && e
->type
== GDK_BUTTON_PRESS
)
5892 static void set_typing_font(GtkWidget
*widget
, PidginConversation
*gtkconv
)
5896 static PangoFontDescription
*font_desc
= NULL
;
5897 static GdkRGBA
*color
= NULL
;
5898 static gboolean enable
= TRUE
;
5900 if (font_desc
== NULL
) {
5901 char *string
= NULL
;
5902 gtk_widget_style_get(widget
,
5903 "typing-notification-font", &string
,
5904 "typing-notification-color", &color
,
5905 "typing-notification-enable", &enable
,
5907 font_desc
= pango_font_description_from_string(string
);
5909 if (color
== NULL
) {
5910 GdkRGBA def
= {0x8888/65535.0, 0x8888/65535.0, 0x8888/65535.0, 1.0};
5911 color
= gdk_rgba_copy(&def
);
5915 gtk_text_buffer_create_tag(GTK_IMHTML(widget
)->text_buffer
, "TYPING-NOTIFICATION",
5916 "foreground-rgba", color
,
5917 "font-desc", font_desc
,
5921 g_object_set_data(G_OBJECT(widget
), "disable-typing-notification", GINT_TO_POINTER(TRUE
));
5922 /* or may be 'gtkconv->disable_typing = TRUE;' instead? */
5925 g_signal_handlers_disconnect_by_func(G_OBJECT(widget
), set_typing_font
, gtkconv
);
5929 /**************************************************************************
5930 * Conversation UI operations
5931 **************************************************************************/
5933 private_gtkconv_new(PurpleConversation
*conv
, gboolean hidden
)
5935 PidginConversation
*gtkconv
;
5936 const char *theme_name
;
5937 PurpleTheme
*theme
= NULL
;
5938 GtkWidget
*pane
= NULL
;
5939 GtkWidget
*tab_cont
;
5940 PurpleBlistNode
*convnode
;
5941 GtkTargetList
*targets
;
5943 if (PURPLE_IS_IM_CONVERSATION(conv
) && (gtkconv
= pidgin_conv_find_gtkconv(conv
))) {
5944 purple_conversation_set_ui_data(conv
, gtkconv
);
5945 if (!g_list_find(gtkconv
->convs
, conv
))
5946 gtkconv
->convs
= g_list_prepend(gtkconv
->convs
, conv
);
5947 pidgin_conv_switch_active_conversation(conv
);
5951 gtkconv
= g_new0(PidginConversation
, 1);
5952 purple_conversation_set_ui_data(conv
, gtkconv
);
5953 gtkconv
->active_conv
= conv
;
5954 gtkconv
->convs
= g_list_prepend(gtkconv
->convs
, conv
);
5955 gtkconv
->send_history
= g_list_append(NULL
, NULL
);
5957 /* Setup some initial variables. */
5958 gtkconv
->unseen_state
= PIDGIN_UNSEEN_NONE
;
5959 gtkconv
->unseen_count
= 0;
5960 theme_name
= purple_prefs_get_string(PIDGIN_PREFS_ROOT
"/conversations/theme");
5961 if (theme_name
&& *theme_name
)
5962 theme
= purple_theme_manager_find_theme(theme_name
, "conversation");
5964 theme
= default_conv_theme
;
5965 gtkconv
->theme
= PIDGIN_CONV_THEME(g_object_ref(theme
));
5966 gtkconv
->last_flags
= 0;
5968 if (PURPLE_IS_IM_CONVERSATION(conv
)) {
5969 gtkconv
->u
.im
= g_malloc0(sizeof(PidginImPane
));
5970 } else if (PURPLE_IS_CHAT_CONVERSATION(conv
)) {
5971 gtkconv
->u
.chat
= g_malloc0(sizeof(PidginChatPane
));
5973 pane
= setup_common_pane(gtkconv
);
5976 if (PURPLE_IS_CHAT_CONVERSATION(conv
))
5977 g_free(gtkconv
->u
.chat
);
5978 else if (PURPLE_IS_IM_CONVERSATION(conv
))
5979 g_free(gtkconv
->u
.im
);
5982 purple_conversation_set_ui_data(conv
, NULL
);
5986 /* Setup drag-and-drop */
5987 gtk_drag_dest_set(pane
, GTK_DEST_DEFAULT_MOTION
| GTK_DEST_DEFAULT_DROP
,
5988 NULL
, 0, GDK_ACTION_COPY
);
5989 targets
= gtk_target_list_new(dnd_targets
, G_N_ELEMENTS(dnd_targets
));
5990 gtk_target_list_add(targets
, gdk_atom_intern("text/uri-list", FALSE
), 0,
5991 WEBKIT_WEB_VIEW_TARGET_INFO_URI_LIST
);
5992 gtk_drag_dest_set_target_list(pane
, targets
);
5994 if (webkit_dnd_targets
) {
5995 targets
= webkit_dnd_targets
;
5997 GtkTargetEntry
*entries
;
6000 targets
= webkit_web_view_get_paste_target_list(WEBKIT_WEB_VIEW(gtkconv
->webview
));
6001 entries
= gtk_target_table_new_from_list(targets
, &count
);
6002 targets
= gtk_target_list_new(entries
, count
);
6003 gtk_target_table_free(entries
, count
);
6005 gtk_target_list_add_table(targets
, dnd_targets
, G_N_ELEMENTS(dnd_targets
));
6006 webkit_dnd_targets
= targets
;
6009 gtk_drag_dest_set(gtkconv
->webview
, 0, NULL
, 0, GDK_ACTION_COPY
);
6010 gtk_drag_dest_set_target_list(gtkconv
->webview
, targets
);
6012 gtk_drag_dest_set(gtkconv
->entry
, 0, NULL
, 0, GDK_ACTION_COPY
);
6013 gtk_drag_dest_set_target_list(gtkconv
->entry
, targets
);
6015 g_signal_connect(G_OBJECT(pane
), "button_press_event",
6016 G_CALLBACK(ignore_middle_click
), NULL
);
6017 g_signal_connect(G_OBJECT(pane
), "drag-data-received",
6018 G_CALLBACK(conv_dnd_recv
), gtkconv
);
6020 /* FIXME: WebKit confuses the dnd source when this is enabled */
6021 g_signal_connect(G_OBJECT(gtkconv
->webview
), "drag-data-received",
6022 G_CALLBACK(conv_dnd_recv
), gtkconv
);
6023 g_signal_connect(G_OBJECT(gtkconv
->entry
), "drag-data-received",
6024 G_CALLBACK(conv_dnd_recv
), gtkconv
);
6027 g_signal_connect(gtkconv
->webview
, "style-updated", G_CALLBACK(set_typing_font
), gtkconv
);
6029 /* Setup the container for the tab. */
6030 gtkconv
->tab_cont
= tab_cont
= gtk_box_new(GTK_ORIENTATION_VERTICAL
, PIDGIN_HIG_BOX_SPACE
);
6031 g_object_set_data(G_OBJECT(tab_cont
), "PidginConversation", gtkconv
);
6032 gtk_container_set_border_width(GTK_CONTAINER(tab_cont
), PIDGIN_HIG_BOX_SPACE
);
6033 gtk_container_add(GTK_CONTAINER(tab_cont
), pane
);
6034 gtk_widget_show(pane
);
6036 convnode
= get_conversation_blist_node(conv
);
6037 if (convnode
== NULL
|| !purple_blist_node_get_bool(convnode
, "gtk-mute-sound"))
6038 gtkconv
->make_sound
= TRUE
;
6040 if (convnode
!= NULL
&& purple_blist_node_has_setting(convnode
, "enable-logging")) {
6041 gboolean logging
= purple_blist_node_get_bool(convnode
, "enable-logging");
6042 purple_conversation_set_logging(conv
, logging
);
6045 if (purple_prefs_get_bool(PIDGIN_PREFS_ROOT
"/conversations/show_formatting_toolbar"))
6046 pidgin_webview_show_toolbar(PIDGIN_WEBVIEW(gtkconv
->entry
));
6048 pidgin_webview_hide_toolbar(PIDGIN_WEBVIEW(gtkconv
->entry
));
6049 pidgin_webview_switch_active_conversation(
6050 PIDGIN_WEBVIEW(gtkconv
->entry
), conv
);
6051 pidgin_webview_switch_active_conversation(
6052 PIDGIN_WEBVIEW(gtkconv
->webview
), conv
);
6054 if (purple_prefs_get_bool(PIDGIN_PREFS_ROOT
"/conversations/im/show_buddy_icons"))
6055 gtk_widget_show(gtkconv
->infopane_hbox
);
6057 gtk_widget_hide(gtkconv
->infopane_hbox
);
6060 g_signal_connect_swapped(G_OBJECT(pane
), "focus",
6061 G_CALLBACK(gtk_widget_grab_focus
),
6065 pidgin_conv_window_add_gtkconv(hidden_convwin
, gtkconv
);
6067 pidgin_conv_placement_place(gtkconv
);
6069 if (generated_nick_colors
== NULL
) {
6072 /* FIXME: No matter how I ask the GtkStyleContext, it always gives me
6073 * back black instead of the _actual_ background colour. */
6074 color
= gtk_widget_get_style(gtkconv
->webview
)->base
[GTK_STATE_NORMAL
];
6075 rgba
.red
= color
.red
/ 65535.0;
6076 rgba
.green
= color
.green
/ 65535.0;
6077 rgba
.blue
= color
.blue
/ 65535.0;
6078 generated_nick_colors
= generate_nick_colors(NICK_COLOR_GENERATE_COUNT
, rgba
);
6081 if(NULL
== (gtkconv
->nick_colors
= pidgin_conversation_theme_get_nick_colors(gtkconv
->theme
)))
6083 gtkconv
->nick_colors
= g_array_ref(generated_nick_colors
);
6088 pidgin_conv_new_hidden(PurpleConversation
*conv
)
6090 private_gtkconv_new(conv
, TRUE
);
6094 pidgin_conv_new(PurpleConversation
*conv
)
6096 private_gtkconv_new(conv
, FALSE
);
6097 if (PIDGIN_IS_PIDGIN_CONVERSATION(conv
))
6098 purple_signal_emit(pidgin_conversations_get_handle(),
6099 "conversation-displayed", PIDGIN_CONVERSATION(conv
));
6103 received_im_msg_cb(PurpleAccount
*account
, char *sender
, char *message
,
6104 PurpleConversation
*conv
, PurpleMessageFlags flags
)
6106 PurpleConversationUiOps
*ui_ops
= pidgin_conversations_get_conv_ui_ops();
6107 gboolean hide
= FALSE
;
6110 /* create hidden conv if hide_new pref is always */
6111 if (strcmp(purple_prefs_get_string(PIDGIN_PREFS_ROOT
"/conversations/im/hide_new"), "always") == 0)
6114 /* create hidden conv if hide_new pref is away and account is away */
6115 if (strcmp(purple_prefs_get_string(PIDGIN_PREFS_ROOT
"/conversations/im/hide_new"), "away") == 0 &&
6116 !purple_status_is_available(purple_account_get_active_status(account
)))
6119 if (conv
&& PIDGIN_IS_PIDGIN_CONVERSATION(conv
) && !hide
) {
6120 PidginConversation
*gtkconv
= PIDGIN_CONVERSATION(conv
);
6121 if (gtkconv
->win
== hidden_convwin
) {
6122 pidgin_conv_attach_to_conversation(gtkconv
->active_conv
);
6128 ui_ops
->create_conversation
= pidgin_conv_new_hidden
;
6129 purple_im_conversation_new(account
, sender
);
6130 ui_ops
->create_conversation
= pidgin_conv_new
;
6133 /* Somebody wants to keep this conversation around, so don't time it out */
6135 timer
= GPOINTER_TO_INT(g_object_get_data(G_OBJECT(conv
), "close-timer"));
6137 purple_timeout_remove(timer
);
6138 g_object_set_data(G_OBJECT(conv
), "close-timer", GINT_TO_POINTER(0));
6144 pidgin_conv_destroy(PurpleConversation
*conv
)
6146 PidginConversation
*gtkconv
= PIDGIN_CONVERSATION(conv
);
6148 gtkconv
->convs
= g_list_remove(gtkconv
->convs
, conv
);
6149 /* Don't destroy ourselves until all our convos are gone */
6150 if (gtkconv
->convs
) {
6151 /* Make sure the destroyed conversation is not the active one */
6152 if (gtkconv
->active_conv
== conv
) {
6153 gtkconv
->active_conv
= gtkconv
->convs
->data
;
6154 purple_conversation_update(gtkconv
->active_conv
, PURPLE_CONVERSATION_UPDATE_FEATURES
);
6159 pidgin_conv_window_remove_gtkconv(gtkconv
->win
, gtkconv
);
6161 /* If the "Save Conversation" or "Save Icon" dialogs are open then close them */
6162 purple_request_close_with_handle(gtkconv
);
6163 purple_notify_close_with_handle(gtkconv
);
6165 gtk_widget_destroy(gtkconv
->tab_cont
);
6166 g_object_unref(gtkconv
->tab_cont
);
6168 if (PURPLE_IS_IM_CONVERSATION(conv
)) {
6169 if (gtkconv
->u
.im
->icon_timer
!= 0)
6170 g_source_remove(gtkconv
->u
.im
->icon_timer
);
6172 if (gtkconv
->u
.im
->anim
!= NULL
)
6173 g_object_unref(G_OBJECT(gtkconv
->u
.im
->anim
));
6175 if (gtkconv
->u
.im
->typing_timer
!= 0)
6176 g_source_remove(gtkconv
->u
.im
->typing_timer
);
6178 g_free(gtkconv
->u
.im
);
6179 } else if (PURPLE_IS_CHAT_CONVERSATION(conv
)) {
6180 purple_signals_disconnect_by_handle(gtkconv
->u
.chat
);
6181 g_free(gtkconv
->u
.chat
);
6184 gtkconv
->send_history
= g_list_first(gtkconv
->send_history
);
6185 g_list_foreach(gtkconv
->send_history
, (GFunc
)g_free
, NULL
);
6186 g_list_free(gtkconv
->send_history
);
6188 if (gtkconv
->attach_timer
) {
6189 g_source_remove(gtkconv
->attach_timer
);
6192 g_array_unref(gtkconv
->nick_colors
);
6194 g_object_disconnect(G_OBJECT(gtkconv
->theme
), "any_signal::notify",
6195 conv_variant_changed_cb
, gtkconv
, NULL
);
6196 g_object_unref(gtkconv
->theme
);
6203 get_text_tag_color(GtkTextTag
*tag
)
6205 GdkRGBA
*color
= NULL
;
6206 gboolean set
= FALSE
;
6207 static char colcode
[] = "#XXXXXX";
6209 g_object_get(G_OBJECT(tag
), "foreground-set", &set
, "foreground-rgba", &color
, NULL
);
6211 g_snprintf(colcode
, sizeof(colcode
), "#%02x%02x%02x",
6212 (unsigned int)(color
->red
* 255),
6213 (unsigned int)(color
->green
* 255),
6214 (unsigned int)(color
->blue
* 255));
6218 gdk_rgba_free(color
);
6222 /* The callback for an event on a link tag. */
6223 static gboolean
buddytag_event(GtkTextTag
*tag
, GObject
*imhtml
,
6224 GdkEvent
*event
, GtkTextIter
*arg2
, gpointer data
)
6226 if (event
->type
== GDK_BUTTON_PRESS
6227 || event
->type
== GDK_2BUTTON_PRESS
) {
6228 GdkEventButton
*btn_event
= (GdkEventButton
*) event
;
6229 PurpleConversation
*conv
= data
;
6233 g_object_get(G_OBJECT(tag
), "name", &name
, NULL
);
6235 /* strlen("BUDDY " or "HILIT ") == 6 */
6236 g_return_val_if_fail((name
!= NULL
) && (strlen(name
) > 6), FALSE
);
6238 buddyname
= name
+ 6;
6240 /* emit chat-nick-clicked signal */
6241 if (event
->type
== GDK_BUTTON_PRESS
) {
6242 gint plugin_return
= GPOINTER_TO_INT(purple_signal_emit_return_1(
6243 pidgin_conversations_get_handle(), "chat-nick-clicked",
6244 data
, buddyname
, btn_event
->button
));
6245 if (plugin_return
) {
6251 if (btn_event
->button
== 1 && event
->type
== GDK_2BUTTON_PRESS
) {
6252 chat_do_im(PIDGIN_CONVERSATION(conv
), buddyname
);
6256 } else if (btn_event
->button
== 2 && event
->type
== GDK_2BUTTON_PRESS
) {
6257 chat_do_info(PIDGIN_CONVERSATION(conv
), buddyname
);
6261 } else if (btn_event
->button
== 3 && event
->type
== GDK_BUTTON_PRESS
) {
6262 GtkTextIter start
, end
;
6264 /* we shouldn't display the popup
6265 * if the user has selected something: */
6266 if (!gtk_text_buffer_get_selection_bounds(
6267 gtk_text_iter_get_buffer(arg2
),
6269 GtkWidget
*menu
= NULL
;
6270 PurpleConnection
*gc
=
6271 purple_conversation_get_connection(conv
);
6274 menu
= create_chat_menu(conv
, buddyname
, gc
);
6275 gtk_menu_popup(GTK_MENU(menu
), NULL
, NULL
,
6276 NULL
, GTK_WIDGET(imhtml
),
6282 /* Don't propagate the event any further */
6294 static GtkTextTag
*get_buddy_tag(PurpleChatConversation
*chat
, const char *who
, PurpleMessageFlags flag
,
6299 PidginConversation
*gtkconv
= PIDGIN_CONVERSATION(conv
);
6300 GtkTextTag
*buddytag
;
6302 gboolean highlight
= (flag
& PURPLE_MESSAGE_NICK
);
6303 GtkTextBuffer
*buffer
= GTK_IMHTML(gtkconv
->imhtml
)->text_buffer
;
6305 str
= g_strdup_printf(highlight
? "HILIT %s" : "BUDDY %s", who
);
6307 buddytag
= gtk_text_tag_table_lookup(
6308 gtk_text_buffer_get_tag_table(buffer
), str
);
6310 if (buddytag
== NULL
&& create
) {
6312 buddytag
= gtk_text_buffer_create_tag(buffer
, str
,
6313 "foreground", get_text_tag_color(gtk_text_tag_table_lookup(
6314 gtk_text_buffer_get_tag_table(buffer
), "highlight-name")),
6315 "weight", PANGO_WEIGHT_BOLD
,
6318 buddytag
= gtk_text_buffer_create_tag(
6320 "foreground-rgba", get_nick_color(gtkconv
, who
),
6321 "weight", purple_blist_find_buddy(purple_conversation_get_account(conv
), who
) ? PANGO_WEIGHT_BOLD
: PANGO_WEIGHT_NORMAL
,
6324 g_object_set_data(G_OBJECT(buddytag
), "cursor", "");
6325 g_signal_connect(G_OBJECT(buddytag
), "event",
6326 G_CALLBACK(buddytag_event
), conv
);
6337 static void pidgin_conv_calculate_newday(PidginConversation
*gtkconv
, time_t mtime
)
6339 struct tm
*tm
= localtime(&mtime
);
6341 tm
->tm_hour
= tm
->tm_min
= tm
->tm_sec
= 0;
6344 gtkconv
->newday
= mktime(tm
);
6347 /* Detect string direction and encapsulate the string in RLE/LRE/PDF unicode characters
6348 str - pointer to string (string is re-allocated and the pointer updated) */
6350 str_embed_direction_chars(char **str
)
6357 if (PANGO_DIRECTION_RTL
== pango_find_base_dir(*str
, -1))
6359 sprintf(pre_str
, "%c%c%c",
6360 0xE2, 0x80, 0xAB); /* RLE */
6361 sprintf(post_str
, "%c%c%c%c%c%c%c%c%c",
6362 0xE2, 0x80, 0xAC, /* PDF */
6363 0xE2, 0x80, 0x8E, /* LRM */
6364 0xE2, 0x80, 0xAC); /* PDF */
6368 sprintf(pre_str
, "%c%c%c",
6369 0xE2, 0x80, 0xAA); /* LRE */
6370 sprintf(post_str
, "%c%c%c%c%c%c%c%c%c",
6371 0xE2, 0x80, 0xAC, /* PDF */
6372 0xE2, 0x80, 0x8F, /* RLM */
6373 0xE2, 0x80, 0xAC); /* PDF */
6376 ret
= g_strconcat(pre_str
, *str
, post_str
, NULL
);
6385 replace_message_tokens(
6387 PurpleConversation
*conv
,
6388 const char *name
, /* author */
6389 const char *alias
, /* author's alias */
6390 const char *message
,
6391 PurpleMessageFlags flags
,
6395 const char *cur
= text
;
6396 const char *prev
= cur
;
6397 struct tm
*tm
= NULL
;
6399 if (text
== NULL
|| *text
== '\0')
6402 str
= g_string_new(NULL
);
6403 while ((cur
= strchr(cur
, '%'))) {
6404 const char *replace
= NULL
;
6405 const char *fin
= NULL
;
6406 gpointer freeval
= NULL
;
6408 if (g_str_has_prefix(cur
, "%message%")) {
6411 } else if (g_str_has_prefix(cur
, "%messageClasses%")) {
6413 GString
*classes
= g_string_new(NULL
);
6414 #define ADD_CLASS(f, class) \
6416 g_string_append(classes, class);
6417 ADD_CLASS(PURPLE_MESSAGE_SEND
, "outgoing ");
6418 ADD_CLASS(PURPLE_MESSAGE_RECV
, "incoming ");
6419 ADD_CLASS(PURPLE_MESSAGE_SYSTEM
, "event ");
6420 ADD_CLASS(PURPLE_MESSAGE_AUTO_RESP
, "autoreply ");
6421 ADD_CLASS(PURPLE_MESSAGE_DELAYED
, "history ");
6422 ADD_CLASS(PURPLE_MESSAGE_NICK
, "mention ");
6424 user
= get_class_for_user(name
);
6425 g_string_append(classes
, user
);
6428 replace
= freeval
= g_string_free(classes
, FALSE
);
6430 } else if (g_str_has_prefix(cur
, "%time")) {
6431 const char *tmp
= cur
+ strlen("%time");
6436 end
= strstr(tmp
, "}%");
6437 if (!end
) /* Invalid string */
6440 tm
= localtime(&mtime
);
6441 replace
= freeval
= purple_uts35_to_str(tmp
, end
- tmp
, tm
);
6445 tm
= localtime(&mtime
);
6447 replace
= purple_utf8_strftime("%X", tm
);
6450 } else if (g_str_has_prefix(cur
, "%shortTime%")) {
6452 tm
= localtime(&mtime
);
6454 replace
= purple_utf8_strftime("%H:%M", tm
);
6456 } else if (g_str_has_prefix(cur
, "%userIconPath%")) {
6457 if (flags
& PURPLE_MESSAGE_SEND
) {
6458 if (purple_account_get_bool(purple_conversation_get_account(conv
), "use-global-buddyicon", TRUE
)) {
6459 replace
= purple_prefs_get_path(PIDGIN_PREFS_ROOT
"/accounts/buddyicon");
6461 PurpleImage
*img
= purple_buddy_icons_find_account_icon(purple_conversation_get_account(conv
));
6462 /* XXX: this may be NULL */
6463 replace
= purple_image_get_path(img
);
6465 if (replace
== NULL
|| !g_file_test(replace
, G_FILE_TEST_EXISTS
)) {
6466 replace
= freeval
= g_build_filename("Outgoing", "buddy_icon.png", NULL
);
6468 } else if (flags
& PURPLE_MESSAGE_RECV
) {
6469 PurpleBuddyIcon
*icon
= purple_im_conversation_get_icon(PURPLE_IM_CONVERSATION(conv
));
6471 replace
= purple_buddy_icon_get_full_path(icon
);
6472 if (replace
== NULL
|| !g_file_test(replace
, G_FILE_TEST_EXISTS
)) {
6473 replace
= freeval
= g_build_filename("Incoming", "buddy_icon.png", NULL
);
6477 } else if (g_str_has_prefix(cur
, "%senderScreenName%")) {
6480 } else if (g_str_has_prefix(cur
, "%sender%")) {
6483 } else if (g_str_has_prefix(cur
, "%senderColor%")) {
6484 const GdkRGBA
*color
= get_nick_color(PIDGIN_CONVERSATION(conv
), name
);
6485 replace
= freeval
= g_strdup_printf("#%02x%02x%02x",
6486 (unsigned int)(color
->red
* 255),
6487 (unsigned int)(color
->green
* 255),
6488 (unsigned int)(color
->blue
* 255));
6490 } else if (g_str_has_prefix(cur
, "%service%")) {
6491 replace
= purple_account_get_protocol_name(purple_conversation_get_account(conv
));
6493 } else if (g_str_has_prefix(cur
, "%messageDirection%")) {
6494 replace
= purple_markup_is_rtl(message
) ? "rtl" : "ltr";
6496 } else if (g_str_has_prefix(cur
, "%status%")) {
6497 GString
*classes
= g_string_new(NULL
);
6499 if (flags
& PURPLE_MESSAGE_ERROR
)
6500 g_string_append(classes
, "error ");
6502 replace
= freeval
= g_string_free(classes
, FALSE
);
6504 } else if (g_str_has_prefix(cur
, "%variant%")) {
6505 replace
= pidgin_conversation_theme_get_variant(PIDGIN_CONVERSATION(conv
)->theme
);
6506 replace
= freeval
= g_strdup(replace
);
6507 purple_util_chrreplace(freeval
, ' ', '_');
6514 /* Here we have a replacement to make */
6515 g_string_append_len(str
, prev
, cur
- prev
);
6517 g_string_append(str
, replace
);
6519 replace
= freeval
= NULL
;
6521 /* And update the pointers */
6523 prev
= cur
= fin
+ 1;
6525 prev
= cur
= strchr(cur
+ 1, '%') + 1;
6530 /* And wrap it up */
6531 g_string_append(str
, prev
);
6533 return g_string_free(str
, FALSE
);
6537 pidgin_conv_write_smiley(GString
*out
, PurpleSmiley
*smiley
,
6538 PurpleConversation
*conv
, gpointer _proto_name
)
6541 gchar
*escaped_shortcut
;
6544 escaped_shortcut
= g_markup_escape_text(
6545 purple_smiley_get_shortcut(smiley
), -1);
6546 image
= purple_smiley_get_image(smiley
);
6547 uri
= purple_image_store_get_uri(image
);
6549 g_string_append_printf(out
,
6550 "<img class=\"emoticon\" alt=\"%s\" title=\"%s\" "
6551 "src=\"%s\" />", escaped_shortcut
,
6552 escaped_shortcut
, uri
);
6555 g_free(escaped_shortcut
);
6561 remote_image_got(PurpleImage
*image
, gpointer _conv
)
6563 PurpleConversation
*conv
= _conv
;
6564 PidginConversation
*gtkconv
= PIDGIN_CONVERSATION(conv
);
6571 image_id
= purple_image_store_add_temporary(image
);
6573 purple_debug_info("gtkconv", "Remote image %u is ready for display",
6576 js
= g_strdup_printf("remoteImageIsReady(%u)", image_id
);
6577 pidgin_webview_safe_execute_script(
6578 PIDGIN_WEBVIEW(gtkconv
->webview
), js
);
6583 box_remote_image_cb(const GMatchInfo
*info
, GString
*result
, gpointer _conv
)
6585 PurpleConversation
*conv
= _conv
;
6586 gchar
*uri
, *before
, *after
, *full
, *alt
;
6590 uri
= g_match_info_fetch(info
, 2);
6591 image
= purple_image_store_get_from_uri(uri
);
6594 full
= g_match_info_fetch(info
, 0);
6596 if (purple_image_is_ready(image
)) {
6597 g_string_append(result
, full
);
6602 /* search for alt */
6603 alt
= strstr(full
, "alt=\"");
6606 alt
+= strlen("alt=\"");
6607 end
= strstr(alt
, "\"");
6612 if (alt
&& alt
[0] == '\0')
6616 /* add for ever - we don't know, when transfer finishes */
6617 img_id
= purple_image_store_add(image
);
6619 before
= g_match_info_fetch(info
, 1);
6620 after
= g_match_info_fetch(info
, 3);
6622 g_string_append_printf(result
, "<span class=\"pending-image "
6623 "pending-image-id-%u\">", img_id
);
6626 g_string_append(result
, alt
);
6628 g_string_append(result
, "<img>");
6630 g_string_append(result
, before
);
6631 g_string_append(result
, "about:blank");
6632 g_string_append(result
, after
);
6634 g_string_append(result
, "</span>");
6640 g_signal_connect_object(image
, "ready",
6641 G_CALLBACK(remote_image_got
), conv
, 0);
6647 box_remote_images(PurpleConversation
*conv
, const gchar
*msg
)
6649 return g_regex_replace_eval(image_store_tag_re
, msg
, -1, 0, 0,
6650 box_remote_image_cb
, conv
, NULL
);
6654 writing_msg(PurpleConversation
*conv
, PurpleMessage
*msg
, gpointer _unused
)
6656 PidginConversation
*gtkconv
;
6658 g_return_val_if_fail(msg
!= NULL
, FALSE
);
6660 if (!(purple_message_get_flags(msg
) & PURPLE_MESSAGE_ACTIVE_ONLY
))
6663 g_return_val_if_fail(conv
!= NULL
, FALSE
);
6664 gtkconv
= PIDGIN_CONVERSATION(conv
);
6665 g_return_val_if_fail(gtkconv
!= NULL
, FALSE
);
6667 if (conv
== gtkconv
->active_conv
)
6670 purple_debug_info("gtkconv",
6671 "Suppressing message for an inactive conversation");
6677 pidgin_conv_write_conv(PurpleConversation
*conv
, PurpleMessage
*pmsg
)
6679 PidginConversation
*gtkconv
;
6680 PurpleConnection
*gc
;
6681 PurpleAccount
*account
;
6683 int gtk_font_options
= 0;
6684 int gtk_font_options_all
= 0;
6685 char buf2
[BUF_LONG
];
6689 char *with_font_tag
;
6690 char *sml_attrib
= NULL
;
6694 gboolean plugin_return
;
6696 gboolean is_rtl_message
= FALSE
;
6699 const char *message_html
;
6700 char *msg_tokenized
;
6705 PurpleMessageFlags flags
, old_flags
;
6706 const char *func
= "appendMessage";
6708 g_return_if_fail(conv
!= NULL
);
6709 gtkconv
= PIDGIN_CONVERSATION(conv
);
6710 g_return_if_fail(gtkconv
!= NULL
);
6711 flags
= purple_message_get_flags(pmsg
);
6713 if (gtkconv
->attach_timer
) {
6714 /* We are currently in the process of filling up the buffer with the message
6715 * history of the conversation. So we do not need to add the message here.
6716 * Instead, this message will be added to the message-list, which in turn will
6717 * be processed and displayed by the attach-callback.
6722 if (conv
!= gtkconv
->active_conv
)
6724 /* Set the active conversation to the one that just messaged us. */
6725 /* TODO: consider not doing this if the account is offline or something */
6726 if (flags
& (PURPLE_MESSAGE_SEND
| PURPLE_MESSAGE_RECV
))
6727 pidgin_conv_switch_active_conversation(conv
);
6730 account
= purple_conversation_get_account(conv
);
6731 g_return_if_fail(account
!= NULL
);
6732 gc
= purple_account_get_connection(account
);
6733 g_return_if_fail(gc
!= NULL
|| !(flags
& (PURPLE_MESSAGE_SEND
| PURPLE_MESSAGE_RECV
)));
6735 /* Make sure URLs are clickable */
6736 if(flags
& PURPLE_MESSAGE_NO_LINKIFY
)
6737 displaying
= g_strdup(purple_message_get_contents(pmsg
));
6739 displaying
= purple_markup_linkify(purple_message_get_contents(pmsg
));
6741 plugin_return
= GPOINTER_TO_INT(purple_signal_emit_return_1(
6742 pidgin_conversations_get_handle(),
6743 (PURPLE_IS_IM_CONVERSATION(conv
) ? "displaying-im-msg" : "displaying-chat-msg"),
6751 length
= strlen(displaying
) + 1;
6754 old_flags
= gtkconv
->last_flags
;
6755 if ((flags
& PURPLE_MESSAGE_SEND
) && (old_flags
& PURPLE_MESSAGE_SEND
)) {
6756 message_html
= pidgin_conversation_theme_get_template(gtkconv
->theme
,
6757 PIDGIN_CONVERSATION_THEME_TEMPLATE_OUTGOING_NEXT_CONTENT
);
6758 func
= "appendNextMessage";
6760 } else if (flags
& PURPLE_MESSAGE_SEND
) {
6761 message_html
= pidgin_conversation_theme_get_template(gtkconv
->theme
,
6762 PIDGIN_CONVERSATION_THEME_TEMPLATE_OUTGOING_CONTENT
);
6764 } else if ((flags
& PURPLE_MESSAGE_RECV
) && (old_flags
& PURPLE_MESSAGE_RECV
)) {
6765 GList
*history
= purple_conversation_get_message_history(gtkconv
->last_conversed
);
6766 PurpleMessage
*last_msg
= history
? history
->data
: NULL
;
6768 g_assert(history
!= NULL
);
6769 g_assert(last_msg
!= NULL
);
6771 /* If the senders are the same, use appendNextMessage */
6772 if (purple_strequal(purple_message_get_author(last_msg
), purple_message_get_author(pmsg
))) {
6773 message_html
= pidgin_conversation_theme_get_template(gtkconv
->theme
,
6774 PIDGIN_CONVERSATION_THEME_TEMPLATE_INCOMING_NEXT_CONTENT
);
6775 func
= "appendNextMessage";
6777 message_html
= pidgin_conversation_theme_get_template(gtkconv
->theme
,
6778 PIDGIN_CONVERSATION_THEME_TEMPLATE_INCOMING_CONTENT
);
6780 } else if (flags
& PURPLE_MESSAGE_RECV
) {
6781 message_html
= pidgin_conversation_theme_get_template(gtkconv
->theme
,
6782 PIDGIN_CONVERSATION_THEME_TEMPLATE_INCOMING_CONTENT
);
6785 message_html
= pidgin_conversation_theme_get_template(gtkconv
->theme
,
6786 PIDGIN_CONVERSATION_THEME_TEMPLATE_STATUS
);
6788 gtkconv
->last_flags
= flags
;
6789 gtkconv
->last_conversed
= conv
;
6791 if(purple_message_get_flags(pmsg
) & PURPLE_MESSAGE_SYSTEM
) {
6792 smileyed
= g_strdup(displaying
);
6794 smileyed
= purple_smiley_parser_smileify(conv
, displaying
,
6795 (flags
& PURPLE_MESSAGE_RECV
), pidgin_conv_write_smiley
,
6796 (gpointer
)purple_account_get_protocol_name(account
));
6798 imgized
= box_remote_images(conv
, smileyed
);
6799 msg_tokenized
= replace_message_tokens(message_html
, conv
,
6800 purple_message_get_author(pmsg
),
6801 purple_message_get_author_alias(pmsg
),
6803 purple_message_get_flags(pmsg
),
6804 purple_message_get_time(pmsg
));
6805 escape
= pidgin_webview_quote_js_string(msg_tokenized
? msg_tokenized
: "");
6806 script
= g_strdup_printf("%s(%s)", func
, escape
);
6808 purple_debug_info("webkit", "JS: %s\n", script
);
6809 pidgin_webview_safe_execute_script(PIDGIN_WEBVIEW(gtkconv
->webview
), script
);
6814 g_free(msg_tokenized
);
6818 /* if the buffer is not empty add a <br> */
6819 if (!pidgin_webview_is_empty(PIDGIN_WEBVIEW(gtkconv
->webview
)))
6820 pidgin_webview_append_html(PIDGIN_WEBVIEW(gtkconv
->webview
), "<br />");
6822 /* First message in a conversation. */
6823 if (gtkconv
->newday
== 0)
6824 pidgin_conv_calculate_newday(gtkconv
, mtime
);
6826 /* Show the date on the first message in a new day, or if the message is
6827 * older than 20 minutes. */
6828 show_date
= (mtime
>= gtkconv
->newday
) || (time(NULL
) > mtime
+ 20*60);
6830 mdate
= purple_signal_emit_return_1(pidgin_conversations_get_handle(),
6831 "conversation-timestamp",
6832 conv
, mtime
, show_date
);
6836 struct tm
*tm
= localtime(&mtime
);
6839 tmp
= purple_date_format_long(tm
);
6841 tmp
= purple_time_format(tm
);
6842 mdate
= g_strdup_printf("(%s)", tmp
);
6845 /* Bi-Directional support - set timestamp direction using unicode characters */
6846 is_rtl_message
= purple_markup_is_rtl(message
);
6848 /* Handle plaintext messages with RTL text but no direction in the markup */
6849 if (!is_rtl_message
&& pango_find_base_dir(message
, -1) == PANGO_DIRECTION_RTL
)
6851 char *wrapped
= g_strdup_printf("<SPAN style=\"direction:rtl;text-align:right;\">%s</SPAN>", displaying
);
6854 displaying
= wrapped
;
6856 length
= strlen(displaying
) + 1;
6857 is_rtl_message
= TRUE
;
6860 /* Enforce direction only if message is RTL - doesn't effect LTR users */
6862 str_embed_direction_chars(&mdate
);
6864 if (mtime
>= gtkconv
->newday
)
6865 pidgin_conv_calculate_newday(gtkconv
, mtime
);
6867 sml_attrib
= g_strdup_printf("sml=\"%s\"", purple_account_get_protocol_name(account
));
6869 gtk_font_options
|= GTK_IMHTML_NO_COMMENTS
;
6871 if ((flags
& PURPLE_MESSAGE_RECV
) &&
6872 !purple_prefs_get_bool(PIDGIN_PREFS_ROOT
"/conversations/show_incoming_formatting"))
6873 gtk_font_options
|= GTK_IMHTML_NO_COLOURS
| GTK_IMHTML_NO_FONTS
| GTK_IMHTML_NO_SIZES
| GTK_IMHTML_NO_FORMATTING
;
6875 /* this is gonna crash one day, I can feel it. */
6876 if (purple_protocols_find(purple_account_get_protocol_id(purple_conversation_get_account(conv
)))->options
&
6877 OPT_PROTO_USE_POINTSIZE
) {
6878 gtk_font_options
|= GTK_IMHTML_USE_POINTSIZE
;
6881 /* TODO: These colors should not be hardcoded so log.c can use them */
6882 if (flags
& PURPLE_MESSAGE_RAW
) {
6883 pidgin_webview_append_html(PIDGIN_WEBVIEW(gtkconv
->webview
), message
);
6884 } else if (flags
& PURPLE_MESSAGE_SYSTEM
) {
6885 g_snprintf(buf2
, sizeof(buf2
),
6886 "<font %s><font size=\"2\"><span class='timestamp'>%s</span></font><b>%s</b></font>",
6887 sml_attrib
? sml_attrib
: "", mdate
, displaying
);
6889 pidgin_webview_append_html(PIDGIN_WEBVIEW(gtkconv
->webview
), buf2
);
6891 } else if (flags
& PURPLE_MESSAGE_ERROR
) {
6892 g_snprintf(buf2
, sizeof(buf2
),
6893 "<font color=\"#ff0000\"><font %s><font size=\"2\"><span class='timestamp'>%s</span> </font><b>%s</b></font></font>",
6894 sml_attrib
? sml_attrib
: "", mdate
, displaying
);
6896 pidgin_webview_append_html(PIDGIN_WEBVIEW(gtkconv
->webview
), buf2
);
6898 } else if (flags
& PURPLE_MESSAGE_NO_LOG
) {
6899 g_snprintf(buf2
, BUF_LONG
,
6900 "<b><font %s color=\"#777777\">%s</font></b>",
6901 sml_attrib
? sml_attrib
: "", displaying
);
6903 pidgin_webview_append_html(PIDGIN_WEBVIEW(gtkconv
->webview
), buf2
);
6905 char *new_message
= g_memdup(displaying
, length
);
6906 char *alias_escaped
= (alias
? g_markup_escape_text(alias
, strlen(alias
)) : g_strdup(""));
6907 /* The initial offset is to deal with
6908 * escaped entities making the string longer */
6909 int tag_start_offset
= 0;
6910 const char *tagname
= NULL
;
6912 /* Enforce direction on alias */
6914 str_embed_direction_chars(&alias_escaped
);
6916 str
= g_malloc(1024);
6917 if (TRUE
) { /* XXX: reduce numer of indentations */
6918 if (purple_message_meify(new_message
, -1)) {
6919 if (flags
& PURPLE_MESSAGE_AUTO_RESP
) {
6920 g_snprintf(str
, 1024, "%s ***%s", AUTO_RESPONSE
, alias_escaped
);
6921 tag_start_offset
+= strlen(AUTO_RESPONSE
) - 6 + 4;
6923 g_snprintf(str
, 1024, "***%s", alias_escaped
);
6924 tag_start_offset
+= 3;
6927 if (flags
& PURPLE_MESSAGE_NICK
)
6928 tagname
= "highlight-name";
6930 tagname
= "action-name";
6932 if (flags
& PURPLE_MESSAGE_AUTO_RESP
) {
6933 g_snprintf(str
, 1024, "%s %s", alias_escaped
, AUTO_RESPONSE
);
6934 tag_start_offset
+= strlen(AUTO_RESPONSE
) - 6 + 1;
6936 g_snprintf(str
, 1024, "%s:", alias_escaped
);
6942 if (flags
& PURPLE_MESSAGE_NICK
) {
6943 if (type
== PURPLE_CONV_TYPE_IM
) {
6944 tagname
= "highlight-name";
6946 } else if (flags
& PURPLE_MESSAGE_RECV
) {
6947 /* The tagname for chats is handled by get_buddy_tag */
6948 if (type
== PURPLE_CONV_TYPE_IM
) {
6949 tagname
= "receive-name";
6951 } else if (flags
& PURPLE_MESSAGE_SEND
) {
6952 tagname
= "send-name";
6954 purple_debug_error("gtkconv", "message missing flags\n");
6959 g_free(alias_escaped
);
6964 tag
= gtk_text_tag_table_lookup(gtk_text_buffer_get_tag_table(buffer
), tagname
);
6966 tag
= get_buddy_tag(conv
, name
, flags
, TRUE
);
6968 if (GTK_IMHTML(gtkconv
->imhtml
)->show_comments
) {
6970 /* The color for the timestamp has to be set in the font-tags, unfortunately.
6971 * Applying the nick-tag to timestamps would work, but that can make it
6972 * bold. I thought applying the "comment" tag again, which has "weight" set
6973 * to PANGO_WEIGHT_NORMAL, would remove the boldness. But it doesn't. So
6974 * this will have to do. I don't terribly like it. -- sadrul */
6975 /* const char *color = get_text_tag_color(tag); */
6976 g_snprintf(buf2
, BUF_LONG
, "<FONT %s%s%s SIZE=\"2\"><!--%s --></FONT>",
6977 color
? "COLOR=\"" : "", color
? color
: "", color
? "\"" : "", mdate
);
6978 pidgin_webview_append_html (PIDGIN_WEBVIEW(gtkconv
->webview
), buf2
);
6981 g_snprintf(buf2
, BUF_LONG
, "<font %s>%s</font> ", sml_attrib
? sml_attrib
: "", str
);
6982 pidgin_webview_append_html(PIDGIN_WEBVIEW(gtkconv
->webview
), buf2
);
6987 char *pre
= g_strdup_printf("<font %s>", sml_attrib
? sml_attrib
: "");
6988 char *post
= "</font>";
6989 with_font_tag
= g_strdup_printf("%s%s%s", pre
, new_message
, post
);
6992 with_font_tag
= g_memdup(new_message
, length
);
6994 pidgin_webview_append_html(PIDGIN_WEBVIEW(gtkconv
->webview
),
6997 g_free(with_font_tag
);
6998 g_free(new_message
);
7006 /* Tab highlighting stuff */
7007 if (!(flags
& PURPLE_MESSAGE_SEND
) && !pidgin_conv_has_focus(conv
))
7009 PidginUnseenState unseen
= PIDGIN_UNSEEN_NONE
;
7011 if ((flags
& PURPLE_MESSAGE_NICK
) == PURPLE_MESSAGE_NICK
)
7012 unseen
= PIDGIN_UNSEEN_NICK
;
7013 else if (((flags
& PURPLE_MESSAGE_SYSTEM
) == PURPLE_MESSAGE_SYSTEM
) ||
7014 ((flags
& PURPLE_MESSAGE_ERROR
) == PURPLE_MESSAGE_ERROR
))
7015 unseen
= PIDGIN_UNSEEN_EVENT
;
7016 else if ((flags
& PURPLE_MESSAGE_NO_LOG
) == PURPLE_MESSAGE_NO_LOG
)
7017 unseen
= PIDGIN_UNSEEN_NO_LOG
;
7019 unseen
= PIDGIN_UNSEEN_TEXT
;
7021 gtkconv_set_unseen(gtkconv
, unseen
);
7024 /* on rejoin only request message history from after this message */
7025 if (flags
& (PURPLE_MESSAGE_SEND
| PURPLE_MESSAGE_RECV
) &&
7026 PURPLE_IS_CHAT_CONVERSATION(conv
)) {
7027 PurpleChat
*chat
= purple_blist_find_chat(
7028 purple_conversation_get_account(conv
),
7029 purple_conversation_get_name(conv
));
7031 GHashTable
*comps
= purple_chat_get_components(chat
);
7032 time_t now
, history_since
, prev_history_since
= 0;
7033 struct tm
*history_since_tm
;
7034 const char *history_since_s
, *prev_history_since_s
;
7036 history_since
= purple_message_get_time(pmsg
) + 1;
7038 prev_history_since_s
= g_hash_table_lookup(comps
,
7040 if (prev_history_since_s
!= NULL
)
7041 prev_history_since
= purple_str_to_time(
7042 prev_history_since_s
, TRUE
, NULL
, NULL
,
7046 /* in case of incorrectly stored timestamps */
7047 if (prev_history_since
> now
)
7048 prev_history_since
= now
;
7049 /* in case of delayed messages */
7050 if (history_since
< prev_history_since
)
7051 history_since
= prev_history_since
;
7053 history_since_tm
= gmtime(&history_since
);
7054 history_since_s
= purple_utf8_strftime(
7055 "%Y-%m-%dT%H:%M:%SZ", history_since_tm
);
7056 if (g_strcmp0(prev_history_since_s
,
7057 history_since_s
) != 0)
7058 g_hash_table_replace(comps
,
7059 g_strdup("history_since"),
7060 g_strdup(history_since_s
));
7064 purple_signal_emit(pidgin_conversations_get_handle(),
7065 (PURPLE_IS_IM_CONVERSATION(conv
) ? "displayed-im-msg" : "displayed-chat-msg"),
7068 update_typing_message(gtkconv
, NULL
);
7071 static gboolean
get_iter_from_chatuser(PurpleChatUser
*cb
, GtkTreeIter
*iter
)
7073 GtkTreeRowReference
*ref
;
7075 GtkTreeModel
*model
;
7077 g_return_val_if_fail(cb
!= NULL
, FALSE
);
7079 ref
= purple_chat_user_get_ui_data(cb
);
7083 if ((path
= gtk_tree_row_reference_get_path(ref
)) == NULL
)
7086 model
= gtk_tree_row_reference_get_model(ref
);
7087 if (!gtk_tree_model_get_iter(GTK_TREE_MODEL(model
), iter
, path
)) {
7088 gtk_tree_path_free(path
);
7092 gtk_tree_path_free(path
);
7097 pidgin_conv_chat_add_users(PurpleChatConversation
*chat
, GList
*cbuddies
, gboolean new_arrivals
)
7099 PidginConversation
*gtkconv
;
7100 PidginChatPane
*gtkchat
;
7107 gtkconv
= PIDGIN_CONVERSATION(PURPLE_CONVERSATION(chat
));
7108 gtkchat
= gtkconv
->u
.chat
;
7110 num_users
= purple_chat_conversation_get_users_count(chat
);
7112 g_snprintf(tmp
, sizeof(tmp
),
7113 ngettext("%d person in room", "%d people in room",
7117 gtk_label_set_text(GTK_LABEL(gtkchat
->count
), tmp
);
7119 ls
= GTK_LIST_STORE(gtk_tree_view_get_model(GTK_TREE_VIEW(gtkchat
->list
)));
7121 gtk_tree_sortable_set_sort_column_id(GTK_TREE_SORTABLE(ls
), GTK_TREE_SORTABLE_UNSORTED_SORT_COLUMN_ID
,
7122 GTK_TREE_SORTABLE_UNSORTED_SORT_COLUMN_ID
);
7126 add_chat_user_common(chat
, (PurpleChatUser
*)l
->data
, NULL
);
7130 /* Currently GTK+ maintains our sorted list after it's in the tree.
7131 * This may change if it turns out we can manage it faster ourselves.
7133 gtk_tree_sortable_set_sort_column_id(GTK_TREE_SORTABLE(ls
), CHAT_USERS_ALIAS_KEY_COLUMN
,
7134 GTK_SORT_ASCENDING
);
7138 pidgin_conv_chat_rename_user(PurpleChatConversation
*chat
, const char *old_name
,
7139 const char *new_name
, const char *new_alias
)
7141 PidginConversation
*gtkconv
;
7142 PidginChatPane
*gtkchat
;
7143 PurpleChatUser
*old_chatuser
, *new_chatuser
;
7145 GtkTreeModel
*model
;
7148 gtkconv
= PIDGIN_CONVERSATION(PURPLE_CONVERSATION(chat
));
7149 gtkchat
= gtkconv
->u
.chat
;
7151 model
= gtk_tree_view_get_model(GTK_TREE_VIEW(gtkchat
->list
));
7153 if (!gtk_tree_model_get_iter_first(GTK_TREE_MODEL(model
), &iter
))
7156 if ((tag
= get_buddy_tag(chat
, old_name
, 0, FALSE
)))
7157 g_object_set(G_OBJECT(tag
), "style", PANGO_STYLE_ITALIC
, NULL
);
7158 if ((tag
= get_buddy_tag(chat
, old_name
, PURPLE_MESSAGE_NICK
, FALSE
)))
7159 g_object_set(G_OBJECT(tag
), "style", PANGO_STYLE_ITALIC
, NULL
);
7161 old_chatuser
= purple_chat_conversation_find_user(chat
, old_name
);
7165 if (get_iter_from_chatuser(old_chatuser
, &iter
)) {
7166 GtkTreeRowReference
*ref
= purple_chat_user_get_ui_data(old_chatuser
);
7168 gtk_list_store_remove(GTK_LIST_STORE(model
), &iter
);
7169 gtk_tree_row_reference_free(ref
);
7170 purple_chat_user_set_ui_data(old_chatuser
, NULL
);
7173 g_return_if_fail(new_alias
!= NULL
);
7175 new_chatuser
= purple_chat_conversation_find_user(chat
, new_name
);
7177 add_chat_user_common(chat
, new_chatuser
, old_name
);
7181 pidgin_conv_chat_remove_users(PurpleChatConversation
*chat
, GList
*users
)
7183 PidginConversation
*gtkconv
;
7184 PidginChatPane
*gtkchat
;
7186 GtkTreeModel
*model
;
7193 gtkconv
= PIDGIN_CONVERSATION(PURPLE_CONVERSATION(chat
));
7194 gtkchat
= gtkconv
->u
.chat
;
7196 num_users
= purple_chat_conversation_get_users_count(chat
);
7198 for (l
= users
; l
!= NULL
; l
= l
->next
) {
7199 model
= gtk_tree_view_get_model(GTK_TREE_VIEW(gtkchat
->list
));
7201 if (!gtk_tree_model_get_iter_first(GTK_TREE_MODEL(model
), &iter
))
7208 gtk_tree_model_get(GTK_TREE_MODEL(model
), &iter
,
7209 CHAT_USERS_NAME_COLUMN
, &val
, -1);
7211 if (!purple_utf8_strcasecmp((char *)l
->data
, val
)) {
7212 f
= gtk_list_store_remove(GTK_LIST_STORE(model
), &iter
);
7215 f
= gtk_tree_model_iter_next(GTK_TREE_MODEL(model
), &iter
);
7220 if ((tag
= get_buddy_tag(chat
, l
->data
, 0, FALSE
)))
7221 g_object_set(G_OBJECT(tag
), "style", PANGO_STYLE_ITALIC
, NULL
);
7222 if ((tag
= get_buddy_tag(chat
, l
->data
, PURPLE_MESSAGE_NICK
, FALSE
)))
7223 g_object_set(G_OBJECT(tag
), "style", PANGO_STYLE_ITALIC
, NULL
);
7226 g_snprintf(tmp
, sizeof(tmp
),
7227 ngettext("%d person in room", "%d people in room",
7228 num_users
), num_users
);
7230 gtk_label_set_text(GTK_LABEL(gtkchat
->count
), tmp
);
7234 pidgin_conv_chat_update_user(PurpleChatUser
*chatuser
)
7236 PurpleChatConversation
*chat
;
7237 PidginConversation
*gtkconv
;
7238 PidginChatPane
*gtkchat
;
7240 GtkTreeModel
*model
;
7245 chat
= purple_chat_user_get_chat(chatuser
);
7246 gtkconv
= PIDGIN_CONVERSATION(PURPLE_CONVERSATION(chat
));
7247 gtkchat
= gtkconv
->u
.chat
;
7249 model
= gtk_tree_view_get_model(GTK_TREE_VIEW(gtkchat
->list
));
7251 if (!gtk_tree_model_get_iter_first(GTK_TREE_MODEL(model
), &iter
))
7254 if (get_iter_from_chatuser(chatuser
, &iter
)) {
7255 GtkTreeRowReference
*ref
= purple_chat_user_get_ui_data(chatuser
);
7256 gtk_list_store_remove(GTK_LIST_STORE(model
), &iter
);
7257 gtk_tree_row_reference_free(ref
);
7258 purple_chat_user_set_ui_data(chatuser
, NULL
);
7262 add_chat_user_common(chat
, chatuser
, NULL
);
7266 pidgin_conv_has_focus(PurpleConversation
*conv
)
7268 PidginConversation
*gtkconv
= PIDGIN_CONVERSATION(conv
);
7269 PidginConvWindow
*win
;
7274 g_object_get(G_OBJECT(win
->window
), "has-toplevel-focus", &has_focus
, NULL
);
7276 if (has_focus
&& pidgin_conv_window_is_active_conversation(conv
))
7283 pidgin_conv_send_confirm(PurpleConversation
*conv
, const char *message
)
7285 PidginConversation
*gtkconv
= PIDGIN_CONVERSATION(conv
);
7287 pidgin_webview_append_html(PIDGIN_WEBVIEW(gtkconv
->webview
), message
);
7291 * Makes sure all the menu items and all the buttons are hidden/shown and
7292 * sensitive/insensitive. This is called after changing tabs and when an
7293 * account signs on or off.
7296 gray_stuff_out(PidginConversation
*gtkconv
)
7298 PidginConvWindow
*win
;
7299 PurpleConversation
*conv
= gtkconv
->active_conv
;
7300 PurpleConnection
*gc
;
7301 PurpleProtocol
*protocol
= NULL
;
7302 GdkPixbuf
*window_icon
= NULL
;
7303 PidginWebViewButtons buttons
;
7304 PurpleAccount
*account
;
7306 win
= pidgin_conv_get_window(gtkconv
);
7307 gc
= purple_conversation_get_connection(conv
);
7308 account
= purple_conversation_get_account(conv
);
7311 protocol
= purple_connection_get_protocol(gc
);
7313 if (win
->menu
->send_to
!= NULL
)
7314 update_send_to_selection(win
);
7317 * Handle hiding and showing stuff based on what type of conv this is.
7318 * Stuff that Purple IMs support in general should be shown for IM
7319 * conversations. Stuff that Purple chats support in general should be
7320 * shown for chat conversations. It doesn't matter whether the protocol
7321 * supports it or not--that only affects if the button or menu item
7322 * is sensitive or not.
7324 if (PURPLE_IS_IM_CONVERSATION(conv
)) {
7325 /* Show stuff that applies to IMs, hide stuff that applies to chats */
7327 /* Deal with menu items */
7328 gtk_action_set_visible(win
->menu
->view_log
, TRUE
);
7329 gtk_action_set_visible(win
->menu
->send_file
, TRUE
);
7330 gtk_action_set_visible(win
->menu
->get_attention
, TRUE
);
7331 gtk_action_set_visible(win
->menu
->add_pounce
, TRUE
);
7332 gtk_action_set_visible(win
->menu
->get_info
, TRUE
);
7333 gtk_action_set_visible(win
->menu
->invite
, FALSE
);
7334 gtk_action_set_visible(win
->menu
->alias
, TRUE
);
7335 if (purple_account_privacy_check(account
, purple_conversation_get_name(conv
))) {
7336 gtk_action_set_visible(win
->menu
->unblock
, FALSE
);
7337 gtk_action_set_visible(win
->menu
->block
, TRUE
);
7339 gtk_action_set_visible(win
->menu
->block
, FALSE
);
7340 gtk_action_set_visible(win
->menu
->unblock
, TRUE
);
7343 if (purple_blist_find_buddy(account
, purple_conversation_get_name(conv
)) == NULL
) {
7344 gtk_action_set_visible(win
->menu
->add
, TRUE
);
7345 gtk_action_set_visible(win
->menu
->remove
, FALSE
);
7347 gtk_action_set_visible(win
->menu
->remove
, TRUE
);
7348 gtk_action_set_visible(win
->menu
->add
, FALSE
);
7351 gtk_action_set_visible(win
->menu
->insert_link
, TRUE
);
7352 gtk_action_set_visible(win
->menu
->insert_image
, TRUE
);
7353 } else if (PURPLE_IS_CHAT_CONVERSATION(conv
)) {
7354 /* Show stuff that applies to Chats, hide stuff that applies to IMs */
7356 /* Deal with menu items */
7357 gtk_action_set_visible(win
->menu
->view_log
, TRUE
);
7358 gtk_action_set_visible(win
->menu
->send_file
, FALSE
);
7359 gtk_action_set_visible(win
->menu
->get_attention
, FALSE
);
7360 gtk_action_set_visible(win
->menu
->add_pounce
, FALSE
);
7361 gtk_action_set_visible(win
->menu
->get_info
, FALSE
);
7362 gtk_action_set_visible(win
->menu
->invite
, TRUE
);
7363 gtk_action_set_visible(win
->menu
->alias
, TRUE
);
7364 gtk_action_set_visible(win
->menu
->block
, FALSE
);
7365 gtk_action_set_visible(win
->menu
->unblock
, FALSE
);
7367 if ((account
== NULL
) || purple_blist_find_chat(account
, purple_conversation_get_name(conv
)) == NULL
) {
7368 /* If the chat is NOT in the buddy list */
7369 gtk_action_set_visible(win
->menu
->add
, TRUE
);
7370 gtk_action_set_visible(win
->menu
->remove
, FALSE
);
7372 /* If the chat IS in the buddy list */
7373 gtk_action_set_visible(win
->menu
->add
, FALSE
);
7374 gtk_action_set_visible(win
->menu
->remove
, TRUE
);
7377 gtk_action_set_visible(win
->menu
->insert_link
, TRUE
);
7378 gtk_action_set_visible(win
->menu
->insert_image
, TRUE
);
7382 * Handle graying stuff out based on whether an account is connected
7383 * and what features that account supports.
7386 (!PURPLE_IS_CHAT_CONVERSATION(conv
) ||
7387 !purple_chat_conversation_has_left(PURPLE_CHAT_CONVERSATION(conv
)) ))
7389 PurpleConnectionFlags features
= purple_conversation_get_features(conv
);
7390 /* Account is online */
7391 /* Deal with the toolbar */
7392 if (features
& PURPLE_CONNECTION_FLAG_HTML
)
7394 buttons
= PIDGIN_WEBVIEW_ALL
; /* Everything on */
7395 if (features
& PURPLE_CONNECTION_FLAG_NO_BGCOLOR
)
7396 buttons
&= ~PIDGIN_WEBVIEW_BACKCOLOR
;
7397 if (features
& PURPLE_CONNECTION_FLAG_NO_FONTSIZE
)
7399 buttons
&= ~PIDGIN_WEBVIEW_GROW
;
7400 buttons
&= ~PIDGIN_WEBVIEW_SHRINK
;
7402 if (features
& PURPLE_CONNECTION_FLAG_NO_URLDESC
)
7403 buttons
&= ~PIDGIN_WEBVIEW_LINKDESC
;
7405 buttons
= PIDGIN_WEBVIEW_SMILEY
| PIDGIN_WEBVIEW_IMAGE
;
7408 if (features
& PURPLE_CONNECTION_FLAG_NO_IMAGES
)
7409 buttons
&= ~PIDGIN_WEBVIEW_IMAGE
;
7411 if (features
& PURPLE_CONNECTION_FLAG_ALLOW_CUSTOM_SMILEY
)
7412 buttons
|= PIDGIN_WEBVIEW_CUSTOM_SMILEY
;
7414 buttons
&= ~PIDGIN_WEBVIEW_CUSTOM_SMILEY
;
7416 pidgin_webview_set_format_functions(PIDGIN_WEBVIEW(gtkconv
->entry
), buttons
);
7418 /* Deal with menu items */
7419 gtk_action_set_sensitive(win
->menu
->view_log
, TRUE
);
7420 gtk_action_set_sensitive(win
->menu
->add_pounce
, TRUE
);
7421 gtk_action_set_sensitive(win
->menu
->get_info
, (PURPLE_PROTOCOL_IMPLEMENTS(protocol
, SERVER_IFACE
, get_info
)));
7422 gtk_action_set_sensitive(win
->menu
->invite
, (PURPLE_PROTOCOL_IMPLEMENTS(protocol
, CHAT_IFACE
, invite
)));
7423 gtk_action_set_sensitive(win
->menu
->insert_link
, (features
& PURPLE_CONNECTION_FLAG_HTML
));
7424 gtk_action_set_sensitive(win
->menu
->insert_image
, !(features
& PURPLE_CONNECTION_FLAG_NO_IMAGES
));
7426 if (PURPLE_IS_IM_CONVERSATION(conv
))
7428 gtk_action_set_sensitive(win
->menu
->add
, (PURPLE_PROTOCOL_IMPLEMENTS(protocol
, SERVER_IFACE
, add_buddy
)));
7429 gtk_action_set_sensitive(win
->menu
->remove
, (PURPLE_PROTOCOL_IMPLEMENTS(protocol
, SERVER_IFACE
, remove_buddy
)));
7430 gtk_action_set_sensitive(win
->menu
->send_file
,
7431 (PURPLE_PROTOCOL_IMPLEMENTS(protocol
, XFER_IFACE
, send
) &&
7432 (!PURPLE_PROTOCOL_IMPLEMENTS(protocol
, XFER_IFACE
, can_receive
) ||
7433 purple_protocol_xfer_iface_can_receive(protocol
, gc
, purple_conversation_get_name(conv
)))));
7434 gtk_action_set_sensitive(win
->menu
->get_attention
, (PURPLE_PROTOCOL_IMPLEMENTS(protocol
, ATTENTION_IFACE
, send
)));
7435 gtk_action_set_sensitive(win
->menu
->alias
,
7436 (account
!= NULL
) &&
7437 (purple_blist_find_buddy(account
, purple_conversation_get_name(conv
)) != NULL
));
7439 else if (PURPLE_IS_CHAT_CONVERSATION(conv
))
7441 gtk_action_set_sensitive(win
->menu
->add
, (PURPLE_PROTOCOL_IMPLEMENTS(protocol
, CHAT_IFACE
, join
)));
7442 gtk_action_set_sensitive(win
->menu
->remove
, (PURPLE_PROTOCOL_IMPLEMENTS(protocol
, CHAT_IFACE
, join
)));
7443 gtk_action_set_sensitive(win
->menu
->alias
,
7444 (account
!= NULL
) &&
7445 (purple_blist_find_chat(account
, purple_conversation_get_name(conv
)) != NULL
));
7449 /* Account is offline */
7450 /* Or it's a chat that we've left. */
7452 /* Then deal with menu items */
7453 gtk_action_set_sensitive(win
->menu
->view_log
, TRUE
);
7454 gtk_action_set_sensitive(win
->menu
->send_file
, FALSE
);
7455 gtk_action_set_sensitive(win
->menu
->get_attention
, FALSE
);
7456 gtk_action_set_sensitive(win
->menu
->add_pounce
, TRUE
);
7457 gtk_action_set_sensitive(win
->menu
->get_info
, FALSE
);
7458 gtk_action_set_sensitive(win
->menu
->invite
, FALSE
);
7459 gtk_action_set_sensitive(win
->menu
->alias
, FALSE
);
7460 gtk_action_set_sensitive(win
->menu
->add
, FALSE
);
7461 gtk_action_set_sensitive(win
->menu
->remove
, FALSE
);
7462 gtk_action_set_sensitive(win
->menu
->insert_link
, TRUE
);
7463 gtk_action_set_sensitive(win
->menu
->insert_image
, FALSE
);
7467 * Update the window's icon
7469 if (pidgin_conv_window_is_active_conversation(conv
))
7472 if (PURPLE_IS_IM_CONVERSATION(conv
) &&
7473 (gtkconv
->u
.im
->anim
))
7475 PurpleBuddy
*buddy
= purple_blist_find_buddy(purple_conversation_get_account(conv
), purple_conversation_get_name(conv
));
7477 gdk_pixbuf_animation_get_static_image(gtkconv
->u
.im
->anim
);
7479 if (buddy
&& !PURPLE_BUDDY_IS_ONLINE(buddy
))
7480 gdk_pixbuf_saturate_and_pixelate(window_icon
, window_icon
, 0.0, FALSE
);
7482 g_object_ref(window_icon
);
7483 l
= g_list_append(l
, window_icon
);
7485 l
= pidgin_conv_get_tab_icons(conv
);
7487 gtk_window_set_icon_list(GTK_WINDOW(win
->window
), l
);
7488 if (window_icon
!= NULL
) {
7489 g_object_unref(G_OBJECT(window_icon
));
7496 pidgin_conv_update_fields(PurpleConversation
*conv
, PidginConvFields fields
)
7498 PidginConversation
*gtkconv
;
7499 PidginConvWindow
*win
;
7501 gtkconv
= PIDGIN_CONVERSATION(conv
);
7504 win
= pidgin_conv_get_window(gtkconv
);
7508 if (fields
& PIDGIN_CONV_SET_TITLE
)
7510 purple_conversation_autoset_title(conv
);
7513 if (fields
& PIDGIN_CONV_BUDDY_ICON
)
7515 if (PURPLE_IS_IM_CONVERSATION(conv
))
7516 pidgin_conv_update_buddy_icon(PURPLE_IM_CONVERSATION(conv
));
7519 if (fields
& PIDGIN_CONV_MENU
)
7521 gray_stuff_out(PIDGIN_CONVERSATION(conv
));
7522 generate_send_to_items(win
);
7523 regenerate_plugins_items(win
);
7526 if (fields
& PIDGIN_CONV_E2EE
)
7527 generate_e2ee_controls(win
);
7529 if (fields
& PIDGIN_CONV_TAB_ICON
)
7531 update_tab_icon(conv
);
7532 generate_send_to_items(win
); /* To update the icons in SendTo menu */
7535 if ((fields
& PIDGIN_CONV_TOPIC
) &&
7536 PURPLE_IS_CHAT_CONVERSATION(conv
))
7539 PidginChatPane
*gtkchat
= gtkconv
->u
.chat
;
7541 if (gtkchat
->topic_text
!= NULL
)
7543 topic
= purple_chat_conversation_get_topic(PURPLE_CHAT_CONVERSATION(conv
));
7545 gtk_entry_set_text(GTK_ENTRY(gtkchat
->topic_text
), topic
? topic
: "");
7546 gtk_widget_set_tooltip_text(gtkchat
->topic_text
,
7547 topic
? topic
: "");
7551 if ((fields
& PIDGIN_CONV_COLORIZE_TITLE
) ||
7552 (fields
& PIDGIN_CONV_SET_TITLE
) ||
7553 (fields
& PIDGIN_CONV_TOPIC
))
7556 PurpleIMConversation
*im
= NULL
;
7557 PurpleAccount
*account
= purple_conversation_get_account(conv
);
7558 PurpleBuddy
*buddy
= NULL
;
7559 char *markup
= NULL
;
7560 AtkObject
*accessibility_obj
;
7561 /* I think this is a little longer than it needs to be but I'm lazy. */
7564 if (PURPLE_IS_IM_CONVERSATION(conv
))
7565 im
= PURPLE_IM_CONVERSATION(conv
);
7567 if ((account
== NULL
) ||
7568 !purple_account_is_connected(account
) ||
7569 (PURPLE_IS_CHAT_CONVERSATION(conv
)
7570 && purple_chat_conversation_has_left(PURPLE_CHAT_CONVERSATION(conv
))))
7571 title
= g_strdup_printf("(%s)", purple_conversation_get_title(conv
));
7573 title
= g_strdup(purple_conversation_get_title(conv
));
7575 if (PURPLE_IS_IM_CONVERSATION(conv
)) {
7576 buddy
= purple_blist_find_buddy(account
, purple_conversation_get_name(conv
));
7578 markup
= pidgin_blist_get_name_markup(buddy
, FALSE
, FALSE
);
7582 } else if (PURPLE_IS_CHAT_CONVERSATION(conv
)) {
7583 const char *topic
= gtkconv
->u
.chat
->topic_text
7584 ? gtk_entry_get_text(GTK_ENTRY(gtkconv
->u
.chat
->topic_text
))
7586 const char *title
= purple_conversation_get_title(conv
);
7587 const char *name
= purple_conversation_get_name(conv
);
7589 char *topic_esc
, *unaliased
, *unaliased_esc
, *title_esc
;
7591 topic_esc
= topic
? g_markup_escape_text(topic
, -1) : NULL
;
7592 unaliased
= g_utf8_collate(title
, name
) ? g_strdup_printf("(%s)", name
) : NULL
;
7593 unaliased_esc
= unaliased
? g_markup_escape_text(unaliased
, -1) : NULL
;
7594 title_esc
= g_markup_escape_text(title
, -1);
7596 markup
= g_strdup_printf("%s%s<span size='smaller'>%s</span>%s<span color='%s' size='smaller'>%s</span>",
7598 unaliased_esc
? " " : "",
7599 unaliased_esc
? unaliased_esc
: "",
7600 topic_esc
&& *topic_esc
? "\n" : "",
7601 pidgin_get_dim_grey_string(gtkconv
->infopane
),
7602 topic_esc
? topic_esc
: "");
7607 g_free(unaliased_esc
);
7609 gtk_list_store_set(gtkconv
->infopane_model
, &(gtkconv
->infopane_iter
),
7610 CONV_TEXT_COLUMN
, markup
, -1);
7611 /* XXX seanegan Why do I have to do this? */
7612 gtk_widget_queue_draw(gtkconv
->infopane
);
7614 if (title
!= markup
)
7617 if (!gtk_widget_get_realized(gtkconv
->tab_label
))
7618 gtk_widget_realize(gtkconv
->tab_label
);
7620 accessibility_obj
= gtk_widget_get_accessible(gtkconv
->tab_cont
);
7622 purple_im_conversation_get_typing_state(im
) == PURPLE_IM_TYPING
) {
7623 atk_object_set_description(accessibility_obj
, _("Typing"));
7624 style
= "tab-label-typing";
7625 } else if (im
!= NULL
&&
7626 purple_im_conversation_get_typing_state(im
) == PURPLE_IM_TYPED
) {
7627 atk_object_set_description(accessibility_obj
, _("Stopped Typing"));
7628 style
= "tab-label-typed";
7629 } else if (gtkconv
->unseen_state
== PIDGIN_UNSEEN_NICK
) {
7630 atk_object_set_description(accessibility_obj
, _("Nick Said"));
7631 style
= "tab-label-attention";
7632 } else if (gtkconv
->unseen_state
== PIDGIN_UNSEEN_TEXT
) {
7633 atk_object_set_description(accessibility_obj
, _("Unread Messages"));
7634 if (PURPLE_IS_CHAT_CONVERSATION(gtkconv
->active_conv
))
7635 style
= "tab-label-unreadchat";
7637 style
= "tab-label-attention";
7638 } else if (gtkconv
->unseen_state
== PIDGIN_UNSEEN_EVENT
) {
7639 atk_object_set_description(accessibility_obj
, _("New Event"));
7640 style
= "tab-label-event";
7642 style
= "tab-label";
7645 gtk_widget_set_name(gtkconv
->tab_label
, style
);
7646 gtk_label_set_text(GTK_LABEL(gtkconv
->tab_label
), title
);
7647 gtk_widget_set_state_flags(gtkconv
->tab_label
, GTK_STATE_FLAG_ACTIVE
, TRUE
);
7649 if (gtkconv
->unseen_state
== PIDGIN_UNSEEN_TEXT
||
7650 gtkconv
->unseen_state
== PIDGIN_UNSEEN_NICK
||
7651 gtkconv
->unseen_state
== PIDGIN_UNSEEN_EVENT
) {
7652 PangoAttrList
*list
= pango_attr_list_new();
7653 PangoAttribute
*attr
= pango_attr_weight_new(PANGO_WEIGHT_BOLD
);
7654 attr
->start_index
= 0;
7655 attr
->end_index
= -1;
7656 pango_attr_list_insert(list
, attr
);
7657 gtk_label_set_attributes(GTK_LABEL(gtkconv
->tab_label
), list
);
7658 pango_attr_list_unref(list
);
7660 gtk_label_set_attributes(GTK_LABEL(gtkconv
->tab_label
), NULL
);
7662 if (pidgin_conv_window_is_active_conversation(conv
))
7663 update_typing_icon(gtkconv
);
7665 gtk_label_set_text(GTK_LABEL(gtkconv
->menu_label
), title
);
7666 if (pidgin_conv_window_is_active_conversation(conv
)) {
7667 const char* current_title
= gtk_window_get_title(GTK_WINDOW(win
->window
));
7668 if (current_title
== NULL
|| g_strcmp0(current_title
, title
) != 0)
7669 gtk_window_set_title(GTK_WINDOW(win
->window
), title
);
7677 pidgin_conv_updated(PurpleConversation
*conv
, PurpleConversationUpdateType type
)
7679 PidginConvFields flags
= 0;
7681 g_return_if_fail(conv
!= NULL
);
7683 if (type
== PURPLE_CONVERSATION_UPDATE_ACCOUNT
)
7685 flags
= PIDGIN_CONV_ALL
;
7687 else if (type
== PURPLE_CONVERSATION_UPDATE_TYPING
||
7688 type
== PURPLE_CONVERSATION_UPDATE_UNSEEN
||
7689 type
== PURPLE_CONVERSATION_UPDATE_TITLE
)
7691 flags
= PIDGIN_CONV_COLORIZE_TITLE
;
7693 else if (type
== PURPLE_CONVERSATION_UPDATE_TOPIC
)
7695 flags
= PIDGIN_CONV_TOPIC
;
7697 else if (type
== PURPLE_CONVERSATION_ACCOUNT_ONLINE
||
7698 type
== PURPLE_CONVERSATION_ACCOUNT_OFFLINE
)
7700 flags
= PIDGIN_CONV_MENU
| PIDGIN_CONV_TAB_ICON
| PIDGIN_CONV_SET_TITLE
;
7702 else if (type
== PURPLE_CONVERSATION_UPDATE_AWAY
)
7704 flags
= PIDGIN_CONV_TAB_ICON
;
7706 else if (type
== PURPLE_CONVERSATION_UPDATE_ADD
||
7707 type
== PURPLE_CONVERSATION_UPDATE_REMOVE
||
7708 type
== PURPLE_CONVERSATION_UPDATE_CHATLEFT
)
7710 flags
= PIDGIN_CONV_SET_TITLE
| PIDGIN_CONV_MENU
;
7712 else if (type
== PURPLE_CONVERSATION_UPDATE_ICON
)
7714 flags
= PIDGIN_CONV_BUDDY_ICON
;
7716 else if (type
== PURPLE_CONVERSATION_UPDATE_FEATURES
)
7718 flags
= PIDGIN_CONV_MENU
;
7720 else if (type
== PURPLE_CONVERSATION_UPDATE_E2EE
)
7722 flags
= PIDGIN_CONV_E2EE
| PIDGIN_CONV_MENU
;
7725 pidgin_conv_update_fields(conv
, flags
);
7729 wrote_msg_update_unseen_cb(PurpleConversation
*conv
, PurpleMessage
*msg
,
7732 PidginConversation
*gtkconv
= conv
? PIDGIN_CONVERSATION(conv
) : NULL
;
7733 PurpleMessageFlags flags
;
7734 if (conv
== NULL
|| (gtkconv
&& gtkconv
->win
!= hidden_convwin
))
7736 flags
= purple_message_get_flags(msg
);
7737 if (flags
& (PURPLE_MESSAGE_SEND
| PURPLE_MESSAGE_RECV
)) {
7738 PidginUnseenState unseen
= PIDGIN_UNSEEN_NONE
;
7740 if ((flags
& PURPLE_MESSAGE_NICK
) == PURPLE_MESSAGE_NICK
)
7741 unseen
= PIDGIN_UNSEEN_NICK
;
7742 else if (((flags
& PURPLE_MESSAGE_SYSTEM
) == PURPLE_MESSAGE_SYSTEM
) ||
7743 ((flags
& PURPLE_MESSAGE_ERROR
) == PURPLE_MESSAGE_ERROR
))
7744 unseen
= PIDGIN_UNSEEN_EVENT
;
7745 else if ((flags
& PURPLE_MESSAGE_NO_LOG
) == PURPLE_MESSAGE_NO_LOG
)
7746 unseen
= PIDGIN_UNSEEN_NO_LOG
;
7748 unseen
= PIDGIN_UNSEEN_TEXT
;
7750 conv_set_unseen(conv
, unseen
);
7754 static PurpleConversationUiOps conversation_ui_ops
=
7757 pidgin_conv_destroy
, /* destroy_conversation */
7758 NULL
, /* write_chat */
7759 NULL
, /* write_im */
7760 pidgin_conv_write_conv
, /* write_conv */
7761 pidgin_conv_chat_add_users
, /* chat_add_users */
7762 pidgin_conv_chat_rename_user
, /* chat_rename_user */
7763 pidgin_conv_chat_remove_users
, /* chat_remove_users */
7764 pidgin_conv_chat_update_user
, /* chat_update_user */
7765 pidgin_conv_present_conversation
, /* present */
7766 pidgin_conv_has_focus
, /* has_focus */
7767 pidgin_conv_send_confirm
, /* send_confirm */
7774 PurpleConversationUiOps
*
7775 pidgin_conversations_get_conv_ui_ops(void)
7777 return &conversation_ui_ops
;
7780 /**************************************************************************
7781 * Public conversation utility functions
7782 **************************************************************************/
7784 pidgin_conv_update_buddy_icon(PurpleIMConversation
*im
)
7786 PidginConversation
*gtkconv
;
7787 PurpleConversation
*conv
;
7788 PidginConvWindow
*win
;
7792 PurpleImage
*custom_img
= NULL
;
7793 gconstpointer data
= NULL
;
7801 int scale_width
, scale_height
;
7804 PurpleAccount
*account
;
7806 PurpleBuddyIcon
*icon
;
7808 conv
= PURPLE_CONVERSATION(im
);
7810 g_return_if_fail(conv
!= NULL
);
7811 g_return_if_fail(PIDGIN_IS_PIDGIN_CONVERSATION(conv
));
7813 gtkconv
= PIDGIN_CONVERSATION(conv
);
7815 if (conv
!= gtkconv
->active_conv
)
7818 if (!gtkconv
->u
.im
->show_icon
)
7821 account
= purple_conversation_get_account(conv
);
7823 /* Remove the current icon stuff */
7824 children
= gtk_container_get_children(GTK_CONTAINER(gtkconv
->u
.im
->icon_container
));
7826 /* We know there's only one child here. It'd be nice to shortcut to the
7827 event box, but we can't change the PidginConversation until 3.0 */
7828 event
= (GtkWidget
*)children
->data
;
7829 gtk_container_remove(GTK_CONTAINER(gtkconv
->u
.im
->icon_container
), event
);
7830 g_list_free(children
);
7833 if (gtkconv
->u
.im
->anim
!= NULL
)
7834 g_object_unref(G_OBJECT(gtkconv
->u
.im
->anim
));
7836 gtkconv
->u
.im
->anim
= NULL
;
7838 if (gtkconv
->u
.im
->icon_timer
!= 0)
7839 g_source_remove(gtkconv
->u
.im
->icon_timer
);
7841 gtkconv
->u
.im
->icon_timer
= 0;
7843 if (gtkconv
->u
.im
->iter
!= NULL
)
7844 g_object_unref(G_OBJECT(gtkconv
->u
.im
->iter
));
7846 gtkconv
->u
.im
->iter
= NULL
;
7848 if (!purple_prefs_get_bool(PIDGIN_PREFS_ROOT
"/conversations/im/show_buddy_icons"))
7851 if (purple_conversation_get_connection(conv
) == NULL
)
7854 buddy
= purple_blist_find_buddy(account
, purple_conversation_get_name(conv
));
7857 PurpleContact
*contact
= purple_buddy_get_contact(buddy
);
7859 custom_img
= purple_buddy_icons_node_find_custom_icon((PurpleBlistNode
*)contact
);
7861 /* There is a custom icon for this user */
7862 data
= purple_image_get_data(custom_img
);
7863 len
= purple_image_get_size(custom_img
);
7869 icon
= purple_im_conversation_get_icon(im
);
7872 gtk_widget_set_size_request(gtkconv
->u
.im
->icon_container
,
7873 -1, BUDDYICON_SIZE_MIN
);
7877 data
= purple_buddy_icon_get_data(icon
, &len
);
7880 gtk_widget_set_size_request(gtkconv
->u
.im
->icon_container
,
7881 -1, BUDDYICON_SIZE_MIN
);
7886 gtkconv
->u
.im
->anim
= pidgin_pixbuf_anim_from_data(data
, len
);
7888 g_object_unref(custom_img
);
7890 if (!gtkconv
->u
.im
->anim
) {
7891 purple_debug_error("gtkconv", "Couldn't load icon for conv %s\n",
7892 purple_conversation_get_name(conv
));
7896 if (gdk_pixbuf_animation_is_static_image(gtkconv
->u
.im
->anim
)) {
7898 gtkconv
->u
.im
->iter
= NULL
;
7899 stat
= gdk_pixbuf_animation_get_static_image(gtkconv
->u
.im
->anim
);
7900 buf
= gdk_pixbuf_add_alpha(stat
, FALSE
, 0, 0, 0);
7903 gtkconv
->u
.im
->iter
=
7904 gdk_pixbuf_animation_get_iter(gtkconv
->u
.im
->anim
, NULL
); /* LEAK */
7905 stat
= gdk_pixbuf_animation_iter_get_pixbuf(gtkconv
->u
.im
->iter
);
7906 buf
= gdk_pixbuf_add_alpha(stat
, FALSE
, 0, 0, 0);
7907 if (gtkconv
->u
.im
->animate
)
7908 start_anim(NULL
, gtkconv
);
7911 scale_width
= gdk_pixbuf_get_width(buf
);
7912 scale_height
= gdk_pixbuf_get_height(buf
);
7914 gtk_widget_get_size_request(gtkconv
->u
.im
->icon_container
, NULL
, &size
);
7915 size
= MIN(size
, MIN(scale_width
, scale_height
));
7917 /* Some sanity checks */
7918 size
= CLAMP(size
, BUDDYICON_SIZE_MIN
, BUDDYICON_SIZE_MAX
);
7919 if (scale_width
== scale_height
) {
7920 scale_width
= scale_height
= size
;
7921 } else if (scale_height
> scale_width
) {
7922 scale_width
= size
* scale_width
/ scale_height
;
7923 scale_height
= size
;
7925 scale_height
= size
* scale_height
/ scale_width
;
7928 scale
= gdk_pixbuf_scale_simple(buf
, scale_width
, scale_height
,
7929 GDK_INTERP_BILINEAR
);
7930 g_object_unref(buf
);
7931 if (pidgin_gdk_pixbuf_is_opaque(scale
))
7932 pidgin_gdk_pixbuf_make_round(scale
);
7934 event
= gtk_event_box_new();
7935 gtk_container_add(GTK_CONTAINER(gtkconv
->u
.im
->icon_container
), event
);
7936 gtk_event_box_set_visible_window(GTK_EVENT_BOX(event
), FALSE
);
7937 gtk_widget_add_events(event
,
7938 GDK_POINTER_MOTION_MASK
| GDK_LEAVE_NOTIFY_MASK
);
7939 g_signal_connect(G_OBJECT(event
), "button-press-event",
7940 G_CALLBACK(icon_menu
), gtkconv
);
7942 pidgin_tooltip_setup_for_widget(event
, gtkconv
, pidgin_conv_create_tooltip
, NULL
);
7943 gtk_widget_show(event
);
7945 gtkconv
->u
.im
->icon
= gtk_image_new_from_pixbuf(scale
);
7946 gtk_container_add(GTK_CONTAINER(event
), gtkconv
->u
.im
->icon
);
7947 gtk_widget_show(gtkconv
->u
.im
->icon
);
7949 g_object_unref(G_OBJECT(scale
));
7951 /* The buddy icon code needs badly to be fixed. */
7952 if(pidgin_conv_window_is_active_conversation(conv
))
7954 buf
= gdk_pixbuf_animation_get_static_image(gtkconv
->u
.im
->anim
);
7955 if (buddy
&& !PURPLE_BUDDY_IS_ONLINE(buddy
))
7956 gdk_pixbuf_saturate_and_pixelate(buf
, buf
, 0.0, FALSE
);
7957 gtk_window_set_icon(GTK_WINDOW(win
->window
), buf
);
7962 pidgin_conv_update_buttons_by_protocol(PurpleConversation
*conv
)
7964 PidginConvWindow
*win
;
7966 if (!PIDGIN_IS_PIDGIN_CONVERSATION(conv
))
7969 win
= PIDGIN_CONVERSATION(conv
)->win
;
7971 if (win
!= NULL
&& pidgin_conv_window_is_active_conversation(conv
))
7972 gray_stuff_out(PIDGIN_CONVERSATION(conv
));
7976 pidgin_conv_xy_to_right_infopane(PidginConvWindow
*win
, int x
, int y
)
7978 gint pane_x
, pane_y
, x_rel
;
7979 PidginConversation
*gtkconv
;
7980 GtkAllocation allocation
;
7982 gdk_window_get_origin(gtk_widget_get_window(win
->notebook
),
7985 gtkconv
= pidgin_conv_window_get_active_gtkconv(win
);
7986 gtk_widget_get_allocation(gtkconv
->infopane
, &allocation
);
7987 return (x_rel
> allocation
.x
+ allocation
.width
/ 2);
7991 pidgin_conv_get_tab_at_xy(PidginConvWindow
*win
, int x
, int y
, gboolean
*to_right
)
7993 gint nb_x
, nb_y
, x_rel
, y_rel
;
7994 GtkNotebook
*notebook
;
7995 GtkWidget
*page
, *tab
;
7996 gint i
, page_num
= -1;
8003 notebook
= GTK_NOTEBOOK(win
->notebook
);
8005 gdk_window_get_origin(gtk_widget_get_window(win
->notebook
), &nb_x
, &nb_y
);
8009 horiz
= (gtk_notebook_get_tab_pos(notebook
) == GTK_POS_TOP
||
8010 gtk_notebook_get_tab_pos(notebook
) == GTK_POS_BOTTOM
);
8012 count
= gtk_notebook_get_n_pages(GTK_NOTEBOOK(notebook
));
8014 for (i
= 0; i
< count
; i
++) {
8015 GtkAllocation allocation
;
8017 page
= gtk_notebook_get_nth_page(GTK_NOTEBOOK(notebook
), i
);
8018 tab
= gtk_notebook_get_tab_label(GTK_NOTEBOOK(notebook
), page
);
8019 gtk_widget_get_allocation(tab
, &allocation
);
8021 /* Make sure the tab is not hidden beyond an arrow */
8022 if (!gtk_widget_is_drawable(tab
) && gtk_notebook_get_show_tabs(notebook
))
8026 if (x_rel
>= allocation
.x
- PIDGIN_HIG_BOX_SPACE
&&
8027 x_rel
<= allocation
.x
+ allocation
.width
+ PIDGIN_HIG_BOX_SPACE
) {
8030 if (to_right
&& x_rel
>= allocation
.x
+ allocation
.width
/2)
8036 if (y_rel
>= allocation
.y
- PIDGIN_HIG_BOX_SPACE
&&
8037 y_rel
<= allocation
.y
+ allocation
.height
+ PIDGIN_HIG_BOX_SPACE
) {
8040 if (to_right
&& y_rel
>= allocation
.y
+ allocation
.height
/2)
8048 if (page_num
== -1) {
8049 /* Add after the last tab */
8050 page_num
= count
- 1;
8057 close_on_tabs_pref_cb(const char *name
, PurplePrefType type
,
8058 gconstpointer value
, gpointer data
)
8061 PurpleConversation
*conv
;
8062 PidginConversation
*gtkconv
;
8064 for (l
= purple_conversations_get_all(); l
!= NULL
; l
= l
->next
) {
8065 conv
= (PurpleConversation
*)l
->data
;
8067 if (!PIDGIN_IS_PIDGIN_CONVERSATION(conv
))
8070 gtkconv
= PIDGIN_CONVERSATION(conv
);
8073 gtk_widget_show(gtkconv
->close
);
8075 gtk_widget_hide(gtkconv
->close
);
8080 spellcheck_pref_cb(const char *name
, PurplePrefType type
,
8081 gconstpointer value
, gpointer data
)
8084 PurpleConversation
*conv
;
8085 PidginConversation
*gtkconv
;
8087 for (cl
= purple_conversations_get_all(); cl
!= NULL
; cl
= cl
->next
) {
8089 conv
= (PurpleConversation
*)cl
->data
;
8091 if (!PIDGIN_IS_PIDGIN_CONVERSATION(conv
))
8094 gtkconv
= PIDGIN_CONVERSATION(conv
);
8096 pidgin_webview_set_spellcheck(PIDGIN_WEBVIEW(gtkconv
->entry
),
8097 (gboolean
)GPOINTER_TO_INT(value
));
8102 tab_side_pref_cb(const char *name
, PurplePrefType type
,
8103 gconstpointer value
, gpointer data
)
8105 GList
*gtkwins
, *gtkconvs
;
8106 GtkPositionType pos
;
8107 PidginConvWindow
*gtkwin
;
8109 pos
= GPOINTER_TO_INT(value
);
8111 for (gtkwins
= pidgin_conv_windows_get_list(); gtkwins
!= NULL
; gtkwins
= gtkwins
->next
) {
8112 gtkwin
= gtkwins
->data
;
8113 gtk_notebook_set_tab_pos(GTK_NOTEBOOK(gtkwin
->notebook
), pos
&~8);
8114 for (gtkconvs
= gtkwin
->gtkconvs
; gtkconvs
!= NULL
; gtkconvs
= gtkconvs
->next
) {
8115 pidgin_conv_tab_pack(gtkwin
, gtkconvs
->data
);
8121 show_formatting_toolbar_pref_cb(const char *name
, PurplePrefType type
,
8122 gconstpointer value
, gpointer data
)
8125 PurpleConversation
*conv
;
8126 PidginConversation
*gtkconv
;
8127 PidginConvWindow
*win
;
8129 for (l
= purple_conversations_get_all(); l
!= NULL
; l
= l
->next
)
8131 conv
= (PurpleConversation
*)l
->data
;
8133 if (!PIDGIN_IS_PIDGIN_CONVERSATION(conv
))
8136 gtkconv
= PIDGIN_CONVERSATION(conv
);
8139 gtk_toggle_action_set_active(
8140 GTK_TOGGLE_ACTION(win
->menu
->show_formatting_toolbar
),
8141 (gboolean
)GPOINTER_TO_INT(value
));
8143 if ((gboolean
)GPOINTER_TO_INT(value
))
8144 pidgin_webview_show_toolbar(PIDGIN_WEBVIEW(gtkconv
->entry
));
8146 pidgin_webview_hide_toolbar(PIDGIN_WEBVIEW(gtkconv
->entry
));
8148 resize_webview_cb(gtkconv
);
8153 animate_buddy_icons_pref_cb(const char *name
, PurplePrefType type
,
8154 gconstpointer value
, gpointer data
)
8157 PurpleConversation
*conv
;
8158 PidginConversation
*gtkconv
;
8159 PidginConvWindow
*win
;
8161 if (!purple_prefs_get_bool(PIDGIN_PREFS_ROOT
"/conversations/im/show_buddy_icons"))
8164 /* Set the "animate" flag for each icon based on the new preference */
8165 for (l
= purple_conversations_get_ims(); l
!= NULL
; l
= l
->next
) {
8166 conv
= (PurpleConversation
*)l
->data
;
8167 gtkconv
= PIDGIN_CONVERSATION(conv
);
8169 gtkconv
->u
.im
->animate
= GPOINTER_TO_INT(value
);
8172 /* Now either stop or start animation for the active conversation in each window */
8173 for (l
= pidgin_conv_windows_get_list(); l
!= NULL
; l
= l
->next
) {
8175 conv
= pidgin_conv_window_get_active_conversation(win
);
8176 pidgin_conv_update_buddy_icon(PURPLE_IM_CONVERSATION(conv
));
8181 show_buddy_icons_pref_cb(const char *name
, PurplePrefType type
,
8182 gconstpointer value
, gpointer data
)
8186 for (l
= purple_conversations_get_all(); l
!= NULL
; l
= l
->next
) {
8187 PurpleConversation
*conv
= l
->data
;
8188 if (!PIDGIN_CONVERSATION(conv
))
8190 if (GPOINTER_TO_INT(value
))
8191 gtk_widget_show(PIDGIN_CONVERSATION(conv
)->infopane_hbox
);
8193 gtk_widget_hide(PIDGIN_CONVERSATION(conv
)->infopane_hbox
);
8195 if (PURPLE_IS_IM_CONVERSATION(conv
)) {
8196 pidgin_conv_update_buddy_icon(PURPLE_IM_CONVERSATION(conv
));
8200 /* Make the tabs show/hide correctly */
8201 for (l
= pidgin_conv_windows_get_list(); l
!= NULL
; l
= l
->next
) {
8202 PidginConvWindow
*win
= l
->data
;
8203 if (pidgin_conv_window_get_gtkconv_count(win
) == 1)
8204 gtk_notebook_set_show_tabs(GTK_NOTEBOOK(win
->notebook
),
8205 GPOINTER_TO_INT(value
) == 0);
8210 show_protocol_icons_pref_cb(const char *name
, PurplePrefType type
,
8211 gconstpointer value
, gpointer data
)
8214 for (l
= purple_conversations_get_all(); l
!= NULL
; l
= l
->next
) {
8215 PurpleConversation
*conv
= l
->data
;
8216 if (PIDGIN_CONVERSATION(conv
))
8217 update_tab_icon(conv
);
8222 conv_placement_usetabs_cb(const char *name
, PurplePrefType type
,
8223 gconstpointer value
, gpointer data
)
8225 purple_prefs_trigger_callback(PIDGIN_PREFS_ROOT
"/conversations/placement");
8229 account_status_changed_cb(PurpleAccount
*account
, PurpleStatus
*oldstatus
,
8230 PurpleStatus
*newstatus
)
8233 PurpleConversation
*conv
= NULL
;
8234 PidginConversation
*gtkconv
;
8236 if(strcmp(purple_prefs_get_string(PIDGIN_PREFS_ROOT
"/conversations/im/hide_new"), "away")!=0)
8239 if(purple_status_is_available(oldstatus
) || !purple_status_is_available(newstatus
))
8242 for (l
= hidden_convwin
->gtkconvs
; l
; ) {
8246 conv
= gtkconv
->active_conv
;
8247 if (PURPLE_IS_CHAT_CONVERSATION(conv
) ||
8248 account
!= purple_conversation_get_account(conv
))
8251 pidgin_conv_attach_to_conversation(conv
);
8253 /* TODO: do we need to do anything for any other conversations that are in the same gtkconv here?
8254 * I'm a little concerned that not doing so will cause the "pending" indicator in the gtkblist not to be cleared. -DAA*/
8255 purple_conversation_update(conv
, PURPLE_CONVERSATION_UPDATE_UNSEEN
);
8260 hide_new_pref_cb(const char *name
, PurplePrefType type
,
8261 gconstpointer value
, gpointer data
)
8264 PurpleConversation
*conv
= NULL
;
8265 PidginConversation
*gtkconv
;
8266 gboolean when_away
= FALSE
;
8271 if(strcmp(purple_prefs_get_string(PIDGIN_PREFS_ROOT
"/conversations/im/hide_new"), "always")==0)
8274 if(strcmp(purple_prefs_get_string(PIDGIN_PREFS_ROOT
"/conversations/im/hide_new"), "away")==0)
8277 for (l
= hidden_convwin
->gtkconvs
; l
; )
8282 conv
= gtkconv
->active_conv
;
8284 if (PURPLE_IS_CHAT_CONVERSATION(conv
) ||
8285 gtkconv
->unseen_count
== 0 ||
8286 (when_away
&& !purple_status_is_available(
8287 purple_account_get_active_status(
8288 purple_conversation_get_account(conv
)))))
8291 pidgin_conv_attach_to_conversation(conv
);
8297 conv_placement_pref_cb(const char *name
, PurplePrefType type
,
8298 gconstpointer value
, gpointer data
)
8300 PidginConvPlacementFunc func
;
8302 if (strcmp(name
, PIDGIN_PREFS_ROOT
"/conversations/placement"))
8305 func
= pidgin_conv_placement_get_fnc(value
);
8310 pidgin_conv_placement_set_current_func(func
);
8313 static PidginConversation
*
8314 get_gtkconv_with_contact(PurpleContact
*contact
)
8316 PurpleBlistNode
*node
;
8318 node
= ((PurpleBlistNode
*)contact
)->child
;
8320 for (; node
; node
= node
->next
)
8322 PurpleBuddy
*buddy
= (PurpleBuddy
*)node
;
8323 PurpleIMConversation
*im
;
8324 im
= purple_conversations_find_im_with_account(purple_buddy_get_name(buddy
), purple_buddy_get_account(buddy
));
8326 return PIDGIN_CONVERSATION(PURPLE_CONVERSATION(im
));
8332 account_signed_off_cb(PurpleConnection
*gc
, gpointer event
)
8336 for (iter
= purple_conversations_get_all(); iter
; iter
= iter
->next
)
8338 PurpleConversation
*conv
= iter
->data
;
8340 /* This seems fine in theory, but we also need to cover the
8341 * case of this account matching one of the other buddies in
8342 * one of the contacts containing the buddy corresponding to
8343 * a conversation. It's easier to just update them all. */
8344 /* if (purple_conversation_get_account(conv) == account) */
8345 pidgin_conv_update_fields(conv
, PIDGIN_CONV_TAB_ICON
|
8346 PIDGIN_CONV_MENU
| PIDGIN_CONV_COLORIZE_TITLE
);
8348 if (PURPLE_CONNECTION_IS_CONNECTED(gc
) &&
8349 PURPLE_IS_CHAT_CONVERSATION(conv
) &&
8350 purple_conversation_get_account(conv
) == purple_connection_get_account(gc
) &&
8351 g_object_get_data(G_OBJECT(conv
), "want-to-rejoin")) {
8352 GHashTable
*comps
= NULL
;
8353 PurpleChat
*chat
= purple_blist_find_chat(purple_conversation_get_account(conv
), purple_conversation_get_name(conv
));
8355 PurpleProtocol
*protocol
= purple_connection_get_protocol(gc
);
8356 comps
= purple_protocol_chat_iface_info_defaults(protocol
, gc
, purple_conversation_get_name(conv
));
8358 comps
= purple_chat_get_components(chat
);
8360 purple_serv_join_chat(gc
, comps
);
8361 if (chat
== NULL
&& comps
!= NULL
)
8362 g_hash_table_destroy(comps
);
8368 account_signing_off(PurpleConnection
*gc
)
8370 GList
*list
= purple_conversations_get_chats();
8371 PurpleAccount
*account
= purple_connection_get_account(gc
);
8373 /* We are about to sign off. See which chats we are currently in, and mark
8374 * them for rejoin on reconnect. */
8376 PurpleConversation
*conv
= list
->data
;
8377 if (!purple_chat_conversation_has_left(PURPLE_CHAT_CONVERSATION(conv
)) &&
8378 purple_conversation_get_account(conv
) == account
) {
8379 g_object_set_data(G_OBJECT(conv
), "want-to-rejoin", GINT_TO_POINTER(TRUE
));
8380 purple_conversation_write_system_message(conv
,
8381 _("The account has disconnected and you are no "
8382 "longer in this chat. You will automatically "
8383 "rejoin the chat when the account reconnects."),
8384 PURPLE_MESSAGE_NO_LOG
);
8391 update_buddy_status_changed(PurpleBuddy
*buddy
, PurpleStatus
*old
, PurpleStatus
*newstatus
)
8393 PidginConversation
*gtkconv
;
8394 PurpleConversation
*conv
;
8396 gtkconv
= get_gtkconv_with_contact(purple_buddy_get_contact(buddy
));
8399 conv
= gtkconv
->active_conv
;
8400 pidgin_conv_update_fields(conv
, PIDGIN_CONV_TAB_ICON
8401 | PIDGIN_CONV_COLORIZE_TITLE
8402 | PIDGIN_CONV_BUDDY_ICON
);
8403 if ((purple_status_is_online(old
) ^ purple_status_is_online(newstatus
)) != 0)
8404 pidgin_conv_update_fields(conv
, PIDGIN_CONV_MENU
);
8409 update_buddy_privacy_changed(PurpleBuddy
*buddy
)
8411 PidginConversation
*gtkconv
;
8412 PurpleConversation
*conv
;
8414 gtkconv
= get_gtkconv_with_contact(purple_buddy_get_contact(buddy
));
8416 conv
= gtkconv
->active_conv
;
8417 pidgin_conv_update_fields(conv
, PIDGIN_CONV_TAB_ICON
| PIDGIN_CONV_MENU
);
8422 update_buddy_idle_changed(PurpleBuddy
*buddy
, gboolean old
, gboolean newidle
)
8424 PurpleIMConversation
*im
;
8426 im
= purple_conversations_find_im_with_account(purple_buddy_get_name(buddy
), purple_buddy_get_account(buddy
));
8428 pidgin_conv_update_fields(PURPLE_CONVERSATION(im
), PIDGIN_CONV_TAB_ICON
);
8432 update_buddy_icon(PurpleBuddy
*buddy
)
8434 PurpleIMConversation
*im
;
8436 im
= purple_conversations_find_im_with_account(purple_buddy_get_name(buddy
), purple_buddy_get_account(buddy
));
8438 pidgin_conv_update_fields(PURPLE_CONVERSATION(im
), PIDGIN_CONV_BUDDY_ICON
);
8442 update_buddy_sign(PurpleBuddy
*buddy
, const char *which
)
8444 PurplePresence
*presence
;
8445 PurpleStatus
*on
, *off
;
8447 presence
= purple_buddy_get_presence(buddy
);
8450 off
= purple_presence_get_status(presence
, "offline");
8451 on
= purple_presence_get_status(presence
, "available");
8453 if (*(which
+1) == 'f')
8454 update_buddy_status_changed(buddy
, on
, off
);
8456 update_buddy_status_changed(buddy
, off
, on
);
8460 update_conversation_switched(PurpleConversation
*conv
)
8462 pidgin_conv_update_fields(conv
, PIDGIN_CONV_TAB_ICON
|
8463 PIDGIN_CONV_SET_TITLE
| PIDGIN_CONV_MENU
|
8464 PIDGIN_CONV_BUDDY_ICON
| PIDGIN_CONV_E2EE
);
8468 update_buddy_typing(PurpleAccount
*account
, const char *who
)
8470 PurpleConversation
*conv
;
8471 PidginConversation
*gtkconv
;
8473 conv
= PURPLE_CONVERSATION(purple_conversations_find_im_with_account(who
, account
));
8477 gtkconv
= PIDGIN_CONVERSATION(conv
);
8478 if (gtkconv
&& gtkconv
->active_conv
== conv
)
8479 pidgin_conv_update_fields(conv
, PIDGIN_CONV_COLORIZE_TITLE
);
8483 update_chat(PurpleChatConversation
*chat
)
8485 pidgin_conv_update_fields(PURPLE_CONVERSATION(chat
), PIDGIN_CONV_TOPIC
|
8486 PIDGIN_CONV_MENU
| PIDGIN_CONV_SET_TITLE
);
8490 update_chat_topic(PurpleChatConversation
*chat
, const char *old
, const char *new)
8492 pidgin_conv_update_fields(PURPLE_CONVERSATION(chat
), PIDGIN_CONV_TOPIC
);
8495 /* Message history stuff */
8497 /* Compare two PurpleMessages, according to time in ascending order. */
8499 message_compare(gconstpointer p1
, gconstpointer p2
)
8501 const PurpleMessage
*m1
= p1
, *m2
= p2
;
8502 return (purple_message_get_time(m1
) > purple_message_get_time(m2
));
8505 /* Adds some message history to the gtkconv. This happens in a idle-callback. */
8507 add_message_history_to_gtkconv(gpointer data
)
8509 PidginConversation
*gtkconv
= data
;
8510 PidginWebView
*webview
= PIDGIN_WEBVIEW(gtkconv
->webview
);
8512 int timer
= gtkconv
->attach_timer
;
8513 time_t when
= GPOINTER_TO_INT(g_object_get_data(G_OBJECT(gtkconv
->entry
), "attach-start-time"));
8514 gboolean im
= (PURPLE_IS_IM_CONVERSATION(gtkconv
->active_conv
));
8516 gtkconv
->attach_timer
= 0;
8517 while (gtkconv
->attach_current
&& count
< ADD_MESSAGE_HISTORY_AT_ONCE
) {
8518 PurpleMessage
*msg
= gtkconv
->attach_current
->data
;
8519 if (!im
&& when
&& (guint64
)when
< purple_message_get_time(msg
)) {
8520 pidgin_webview_append_html(webview
, "<BR><HR>");
8521 pidgin_webview_scroll_to_end(webview
, TRUE
);
8522 g_object_set_data(G_OBJECT(gtkconv
->entry
), "attach-start-time", NULL
);
8524 /* XXX: should it be gtkconv->active_conv? */
8525 pidgin_conv_write_conv(gtkconv
->active_conv
, msg
);
8527 gtkconv
->attach_current
= g_list_delete_link(gtkconv
->attach_current
, gtkconv
->attach_current
);
8529 gtkconv
->attach_current
= gtkconv
->attach_current
->prev
;
8533 gtkconv
->attach_timer
= timer
;
8534 if (gtkconv
->attach_current
)
8537 g_source_remove(gtkconv
->attach_timer
);
8538 gtkconv
->attach_timer
= 0;
8540 /* Print any message that was sent while the old history was being added back. */
8542 GList
*iter
= gtkconv
->convs
;
8543 for (; iter
; iter
= iter
->next
) {
8544 PurpleConversation
*conv
= iter
->data
;
8545 GList
*history
= purple_conversation_get_message_history(conv
);
8546 for (; history
; history
= history
->next
) {
8547 PurpleMessage
*msg
= history
->data
;
8548 if (purple_message_get_time(msg
) > (guint64
)when
)
8549 msgs
= g_list_prepend(msgs
, msg
);
8552 msgs
= g_list_sort(msgs
, message_compare
);
8553 for (; msgs
; msgs
= g_list_delete_link(msgs
, msgs
)) {
8554 PurpleMessage
*msg
= msgs
->data
;
8555 /* XXX: see above - should it be active_conv? */
8556 pidgin_conv_write_conv(gtkconv
->active_conv
, msg
);
8558 pidgin_webview_append_html(webview
, "<BR><HR>");
8559 pidgin_webview_scroll_to_end(webview
, TRUE
);
8560 g_object_set_data(G_OBJECT(gtkconv
->entry
), "attach-start-time", NULL
);
8563 g_object_set_data(G_OBJECT(gtkconv
->entry
), "attach-start-time", NULL
);
8564 purple_signal_emit(pidgin_conversations_get_handle(),
8565 "conversation-displayed", gtkconv
);
8570 pidgin_conv_attach(PurpleConversation
*conv
)
8573 g_object_set_data(G_OBJECT(conv
), "unseen-count", NULL
);
8574 g_object_set_data(G_OBJECT(conv
), "unseen-state", NULL
);
8575 purple_conversation_set_ui_ops(conv
, pidgin_conversations_get_conv_ui_ops());
8576 if (!PIDGIN_CONVERSATION(conv
))
8577 private_gtkconv_new(conv
, FALSE
);
8578 timer
= GPOINTER_TO_INT(g_object_get_data(G_OBJECT(conv
), "close-timer"));
8580 purple_timeout_remove(timer
);
8581 g_object_set_data(G_OBJECT(conv
), "close-timer", NULL
);
8585 gboolean
pidgin_conv_attach_to_conversation(PurpleConversation
*conv
)
8588 PidginConversation
*gtkconv
;
8590 if (PIDGIN_IS_PIDGIN_CONVERSATION(conv
)) {
8591 /* This is pretty much always the case now. */
8592 gtkconv
= PIDGIN_CONVERSATION(conv
);
8593 if (gtkconv
->win
!= hidden_convwin
)
8595 pidgin_conv_window_remove_gtkconv(hidden_convwin
, gtkconv
);
8596 pidgin_conv_placement_place(gtkconv
);
8597 purple_signal_emit(pidgin_conversations_get_handle(),
8598 "conversation-displayed", gtkconv
);
8599 list
= gtkconv
->convs
;
8601 pidgin_conv_attach(list
->data
);
8607 pidgin_conv_attach(conv
);
8608 gtkconv
= PIDGIN_CONVERSATION(conv
);
8610 list
= purple_conversation_get_message_history(conv
);
8612 if (PURPLE_IS_IM_CONVERSATION(conv
)) {
8614 list
= g_list_copy(list
);
8615 for (convs
= purple_conversations_get_ims(); convs
; convs
= convs
->next
)
8616 if (convs
->data
!= conv
&&
8617 pidgin_conv_find_gtkconv(convs
->data
) == gtkconv
) {
8618 pidgin_conv_attach(convs
->data
);
8619 list
= g_list_concat(list
, g_list_copy(purple_conversation_get_message_history(convs
->data
)));
8621 list
= g_list_sort(list
, message_compare
);
8622 gtkconv
->attach_current
= list
;
8623 list
= g_list_last(list
);
8624 } else if (PURPLE_IS_CHAT_CONVERSATION(conv
)) {
8625 gtkconv
->attach_current
= g_list_last(list
);
8628 g_object_set_data(G_OBJECT(gtkconv
->entry
), "attach-start-time",
8629 GINT_TO_POINTER(purple_message_get_time(list
->data
)));
8630 gtkconv
->attach_timer
= g_idle_add(add_message_history_to_gtkconv
, gtkconv
);
8632 purple_signal_emit(pidgin_conversations_get_handle(),
8633 "conversation-displayed", gtkconv
);
8636 if (PURPLE_IS_CHAT_CONVERSATION(conv
)) {
8638 PurpleChatConversation
*chat
= PURPLE_CHAT_CONVERSATION(conv
);
8639 pidgin_conv_update_fields(conv
, PIDGIN_CONV_TOPIC
);
8640 users
= purple_chat_conversation_get_users(chat
);
8641 pidgin_conv_chat_add_users(chat
, users
, TRUE
);
8649 pidgin_conversations_get_default_theme(void)
8651 return default_conv_theme
;
8655 pidgin_conversations_get_handle(void)
8663 pidgin_conversations_pre_uninit(void);
8666 pidgin_conversations_init(void)
8668 void *handle
= pidgin_conversations_get_handle();
8669 void *blist_handle
= purple_blist_get_handle();
8672 e2ee_stock
= g_hash_table_new_full(g_str_hash
, g_str_equal
,
8673 g_free
, g_object_unref
);
8675 image_store_tag_re
= g_regex_new("(<img [^>]*src=\")("
8676 PURPLE_IMAGE_STORE_PROTOCOL
"[0-9]+)(\"[^>]*>)",
8677 G_REGEX_OPTIMIZE
| G_REGEX_DOTALL
, 0, NULL
);
8680 purple_prefs_add_none(PIDGIN_PREFS_ROOT
"/conversations");
8681 purple_prefs_add_none(PIDGIN_PREFS_ROOT
"/conversations/themes");
8682 purple_prefs_add_bool(PIDGIN_PREFS_ROOT
"/conversations/use_smooth_scrolling", TRUE
);
8683 purple_prefs_add_bool(PIDGIN_PREFS_ROOT
"/conversations/close_on_tabs", TRUE
);
8684 purple_prefs_add_bool(PIDGIN_PREFS_ROOT
"/conversations/send_bold", FALSE
);
8685 purple_prefs_add_bool(PIDGIN_PREFS_ROOT
"/conversations/send_italic", FALSE
);
8686 purple_prefs_add_bool(PIDGIN_PREFS_ROOT
"/conversations/send_underline", FALSE
);
8687 purple_prefs_add_bool(PIDGIN_PREFS_ROOT
"/conversations/send_strike", FALSE
);
8688 purple_prefs_add_bool(PIDGIN_PREFS_ROOT
"/conversations/spellcheck", TRUE
);
8689 purple_prefs_add_bool(PIDGIN_PREFS_ROOT
"/conversations/show_incoming_formatting", TRUE
);
8690 /* TODO: it's about *remote* smileys, not local ones */
8691 purple_prefs_add_bool(PIDGIN_PREFS_ROOT
"/conversations/resize_custom_smileys", TRUE
);
8692 purple_prefs_add_int(PIDGIN_PREFS_ROOT
"/conversations/custom_smileys_size", 96);
8693 purple_prefs_add_int(PIDGIN_PREFS_ROOT
"/conversations/minimum_entry_lines", 2);
8695 purple_prefs_add_bool(PIDGIN_PREFS_ROOT
"/conversations/show_formatting_toolbar", TRUE
);
8697 purple_prefs_add_string(PIDGIN_PREFS_ROOT
"/conversations/placement", "last");
8698 purple_prefs_add_int(PIDGIN_PREFS_ROOT
"/conversations/placement_number", 1);
8699 purple_prefs_add_string(PIDGIN_PREFS_ROOT
"/conversations/bgcolor", "");
8700 purple_prefs_add_string(PIDGIN_PREFS_ROOT
"/conversations/fgcolor", "");
8701 purple_prefs_add_string(PIDGIN_PREFS_ROOT
"/conversations/font_face", "");
8702 purple_prefs_add_int(PIDGIN_PREFS_ROOT
"/conversations/font_size", 3);
8703 purple_prefs_add_bool(PIDGIN_PREFS_ROOT
"/conversations/tabs", TRUE
);
8704 purple_prefs_add_int(PIDGIN_PREFS_ROOT
"/conversations/tab_side", GTK_POS_TOP
);
8705 purple_prefs_add_int(PIDGIN_PREFS_ROOT
"/conversations/scrollback_lines", 4000);
8708 purple_prefs_add_bool(PIDGIN_PREFS_ROOT
"/conversations/use_theme_font", TRUE
);
8709 purple_prefs_add_string(PIDGIN_PREFS_ROOT
"/conversations/custom_font", "");
8712 /* Conversations -> Chat */
8713 purple_prefs_add_none(PIDGIN_PREFS_ROOT
"/conversations/chat");
8714 purple_prefs_add_int(PIDGIN_PREFS_ROOT
"/conversations/chat/entry_height", 54);
8715 purple_prefs_add_int(PIDGIN_PREFS_ROOT
"/conversations/chat/userlist_width", 80);
8716 purple_prefs_add_int(PIDGIN_PREFS_ROOT
"/conversations/chat/x", 0);
8717 purple_prefs_add_int(PIDGIN_PREFS_ROOT
"/conversations/chat/y", 0);
8718 purple_prefs_add_int(PIDGIN_PREFS_ROOT
"/conversations/chat/width", 340);
8719 purple_prefs_add_int(PIDGIN_PREFS_ROOT
"/conversations/chat/height", 390);
8721 /* Conversations -> IM */
8722 purple_prefs_add_none(PIDGIN_PREFS_ROOT
"/conversations/im");
8723 purple_prefs_add_int(PIDGIN_PREFS_ROOT
"/conversations/im/x", 0);
8724 purple_prefs_add_int(PIDGIN_PREFS_ROOT
"/conversations/im/y", 0);
8725 purple_prefs_add_int(PIDGIN_PREFS_ROOT
"/conversations/im/width", 340);
8726 purple_prefs_add_int(PIDGIN_PREFS_ROOT
"/conversations/im/height", 390);
8728 purple_prefs_add_bool(PIDGIN_PREFS_ROOT
"/conversations/im/animate_buddy_icons", TRUE
);
8730 purple_prefs_add_int(PIDGIN_PREFS_ROOT
"/conversations/im/entry_height", 54);
8731 purple_prefs_add_bool(PIDGIN_PREFS_ROOT
"/conversations/im/show_buddy_icons", TRUE
);
8733 purple_prefs_add_string(PIDGIN_PREFS_ROOT
"/conversations/im/hide_new", "never");
8734 purple_prefs_add_bool(PIDGIN_PREFS_ROOT
"/conversations/im/close_immediately", TRUE
);
8737 purple_prefs_add_bool(PIDGIN_PREFS_ROOT
"/win32/minimize_new_convs", FALSE
);
8740 /* Connect callbacks. */
8741 purple_prefs_connect_callback(handle
, PIDGIN_PREFS_ROOT
"/conversations/close_on_tabs",
8742 close_on_tabs_pref_cb
, NULL
);
8743 purple_prefs_connect_callback(handle
, PIDGIN_PREFS_ROOT
"/conversations/show_formatting_toolbar",
8744 show_formatting_toolbar_pref_cb
, NULL
);
8745 purple_prefs_connect_callback(handle
, PIDGIN_PREFS_ROOT
"/conversations/spellcheck",
8746 spellcheck_pref_cb
, NULL
);
8747 purple_prefs_connect_callback(handle
, PIDGIN_PREFS_ROOT
"/conversations/tab_side",
8748 tab_side_pref_cb
, NULL
);
8750 purple_prefs_connect_callback(handle
, PIDGIN_PREFS_ROOT
"/conversations/tabs",
8751 conv_placement_usetabs_cb
, NULL
);
8753 purple_prefs_connect_callback(handle
, PIDGIN_PREFS_ROOT
"/conversations/placement",
8754 conv_placement_pref_cb
, NULL
);
8755 purple_prefs_trigger_callback(PIDGIN_PREFS_ROOT
"/conversations/placement");
8757 purple_prefs_connect_callback(handle
, PIDGIN_PREFS_ROOT
"/conversations/minimum_entry_lines",
8758 minimum_entry_lines_pref_cb
, NULL
);
8761 purple_prefs_connect_callback(handle
, PIDGIN_PREFS_ROOT
"/conversations/im/animate_buddy_icons",
8762 animate_buddy_icons_pref_cb
, NULL
);
8763 purple_prefs_connect_callback(handle
, PIDGIN_PREFS_ROOT
"/conversations/im/show_buddy_icons",
8764 show_buddy_icons_pref_cb
, NULL
);
8765 purple_prefs_connect_callback(handle
, PIDGIN_PREFS_ROOT
"/blist/show_protocol_icons",
8766 show_protocol_icons_pref_cb
, NULL
);
8767 purple_prefs_connect_callback(handle
, PIDGIN_PREFS_ROOT
"/conversations/im/hide_new",
8768 hide_new_pref_cb
, NULL
);
8770 /**********************************************************************
8772 **********************************************************************/
8773 purple_signal_register(handle
, "conversation-dragging",
8774 purple_marshal_VOID__POINTER_POINTER
, G_TYPE_NONE
, 2,
8775 G_TYPE_POINTER
, /* pointer to a (PidginConvWindow *) */
8776 G_TYPE_POINTER
); /* pointer to a (PidginConvWindow *) */
8778 purple_signal_register(handle
, "conversation-timestamp",
8779 #if SIZEOF_TIME_T == 4
8780 purple_marshal_POINTER__POINTER_INT_BOOLEAN
,
8781 #elif SIZEOF_TIME_T == 8
8782 purple_marshal_POINTER__POINTER_INT64_BOOLEAN
,
8784 #error Unkown size of time_t
8786 G_TYPE_STRING
, 3, PURPLE_TYPE_CONVERSATION
,
8787 #if SIZEOF_TIME_T == 4
8789 #elif SIZEOF_TIME_T == 8
8792 # error Unknown size of time_t
8796 purple_signal_register(handle
, "displaying-im-msg",
8797 purple_marshal_BOOLEAN__POINTER_POINTER
,
8798 G_TYPE_BOOLEAN
, 2, PURPLE_TYPE_CONVERSATION
, PURPLE_TYPE_MESSAGE
);
8800 purple_signal_register(handle
, "displayed-im-msg",
8801 purple_marshal_VOID__POINTER_POINTER
, G_TYPE_NONE
, 2,
8802 PURPLE_TYPE_CONVERSATION
, PURPLE_TYPE_MESSAGE
);
8804 purple_signal_register(handle
, "displaying-chat-msg",
8805 purple_marshal_BOOLEAN__POINTER_POINTER
,
8806 G_TYPE_BOOLEAN
, 2, PURPLE_TYPE_CONVERSATION
, PURPLE_TYPE_MESSAGE
);
8808 purple_signal_register(handle
, "displayed-chat-msg",
8809 purple_marshal_VOID__POINTER_POINTER
, G_TYPE_NONE
, 2,
8810 PURPLE_TYPE_CONVERSATION
, PURPLE_TYPE_MESSAGE
);
8812 purple_signal_register(handle
, "conversation-switched",
8813 purple_marshal_VOID__POINTER
, G_TYPE_NONE
, 1,
8814 PURPLE_TYPE_CONVERSATION
);
8816 purple_signal_register(handle
, "conversation-hiding",
8817 purple_marshal_VOID__POINTER
, G_TYPE_NONE
, 1,
8818 G_TYPE_POINTER
); /* (PidginConversation *) */
8820 purple_signal_register(handle
, "conversation-displayed",
8821 purple_marshal_VOID__POINTER
, G_TYPE_NONE
, 1,
8822 G_TYPE_POINTER
); /* (PidginConversation *) */
8824 purple_signal_register(handle
, "chat-nick-autocomplete",
8825 purple_marshal_BOOLEAN__POINTER_BOOLEAN
,
8826 G_TYPE_BOOLEAN
, 1, PURPLE_TYPE_CONVERSATION
);
8828 purple_signal_register(handle
, "chat-nick-clicked",
8829 purple_marshal_BOOLEAN__POINTER_POINTER_UINT
,
8830 G_TYPE_BOOLEAN
, 3, PURPLE_TYPE_CONVERSATION
,
8831 G_TYPE_STRING
, G_TYPE_UINT
);
8833 purple_signal_register(handle
, "conversation-window-created",
8834 purple_marshal_VOID__POINTER
, G_TYPE_NONE
, 1,
8835 G_TYPE_POINTER
); /* (PidginConvWindow *) */
8838 /**********************************************************************
8840 **********************************************************************/
8841 purple_cmd_register("say", "S", PURPLE_CMD_P_DEFAULT
,
8842 PURPLE_CMD_FLAG_CHAT
| PURPLE_CMD_FLAG_IM
, NULL
,
8843 say_command_cb
, _("say <message>: Send a message normally as if you weren't using a command."), NULL
);
8844 purple_cmd_register("me", "S", PURPLE_CMD_P_DEFAULT
,
8845 PURPLE_CMD_FLAG_CHAT
| PURPLE_CMD_FLAG_IM
, NULL
,
8846 me_command_cb
, _("me <action>: Send an IRC style action to a buddy or chat."), NULL
);
8847 purple_cmd_register("debug", "w", PURPLE_CMD_P_DEFAULT
,
8848 PURPLE_CMD_FLAG_CHAT
| PURPLE_CMD_FLAG_IM
, NULL
,
8849 debug_command_cb
, _("debug <option>: Send various debug information to the current conversation."), NULL
);
8850 purple_cmd_register("clear", "", PURPLE_CMD_P_DEFAULT
,
8851 PURPLE_CMD_FLAG_CHAT
| PURPLE_CMD_FLAG_IM
, NULL
,
8852 clear_command_cb
, _("clear: Clears the conversation scrollback."), NULL
);
8853 purple_cmd_register("clearall", "", PURPLE_CMD_P_DEFAULT
,
8854 PURPLE_CMD_FLAG_CHAT
| PURPLE_CMD_FLAG_IM
, NULL
,
8855 clearall_command_cb
, _("clear: Clears all conversation scrollbacks."), NULL
);
8856 purple_cmd_register("help", "w", PURPLE_CMD_P_DEFAULT
,
8857 PURPLE_CMD_FLAG_CHAT
| PURPLE_CMD_FLAG_IM
| PURPLE_CMD_FLAG_ALLOW_WRONG_ARGS
, NULL
,
8858 help_command_cb
, _("help <command>: Help on a specific command."), NULL
);
8860 /**********************************************************************
8862 **********************************************************************/
8864 purple_signal_connect(purple_connections_get_handle(), "signed-on", handle
,
8865 G_CALLBACK(account_signed_off_cb
),
8866 GINT_TO_POINTER(PURPLE_CONVERSATION_ACCOUNT_ONLINE
));
8867 purple_signal_connect(purple_connections_get_handle(), "signed-off", handle
,
8868 G_CALLBACK(account_signed_off_cb
),
8869 GINT_TO_POINTER(PURPLE_CONVERSATION_ACCOUNT_OFFLINE
));
8870 purple_signal_connect(purple_connections_get_handle(), "signing-off", handle
,
8871 G_CALLBACK(account_signing_off
), NULL
);
8873 purple_signal_connect(purple_conversations_get_handle(), "writing-im-msg",
8874 handle
, G_CALLBACK(writing_msg
), NULL
);
8875 purple_signal_connect(purple_conversations_get_handle(), "writing-chat-msg",
8876 handle
, G_CALLBACK(writing_msg
), NULL
);
8877 purple_signal_connect(purple_conversations_get_handle(), "received-im-msg",
8878 handle
, G_CALLBACK(received_im_msg_cb
), NULL
);
8879 purple_signal_connect(purple_conversations_get_handle(), "cleared-message-history",
8880 handle
, G_CALLBACK(clear_conversation_scrollback_cb
), NULL
);
8882 purple_signal_connect(purple_conversations_get_handle(), "deleting-chat-user",
8883 handle
, G_CALLBACK(deleting_chat_user_cb
), NULL
);
8885 purple_conversations_set_ui_ops(&conversation_ui_ops
);
8887 hidden_convwin
= pidgin_conv_window_new();
8888 window_list
= g_list_remove(window_list
, hidden_convwin
);
8890 purple_signal_connect(purple_accounts_get_handle(), "account-status-changed",
8891 handle
, PURPLE_CALLBACK(account_status_changed_cb
), NULL
);
8893 purple_signal_connect_priority(purple_get_core(), "quitting", handle
,
8894 PURPLE_CALLBACK(pidgin_conversations_pre_uninit
), NULL
, PURPLE_SIGNAL_PRIORITY_HIGHEST
);
8896 /* Callbacks to update a conversation */
8897 purple_signal_connect(blist_handle
, "blist-node-added", handle
,
8898 G_CALLBACK(buddy_update_cb
), NULL
);
8899 purple_signal_connect(blist_handle
, "blist-node-removed", handle
,
8900 G_CALLBACK(buddy_update_cb
), NULL
);
8901 purple_signal_connect(blist_handle
, "buddy-signed-on",
8902 handle
, PURPLE_CALLBACK(update_buddy_sign
), "on");
8903 purple_signal_connect(blist_handle
, "buddy-signed-off",
8904 handle
, PURPLE_CALLBACK(update_buddy_sign
), "off");
8905 purple_signal_connect(blist_handle
, "buddy-status-changed",
8906 handle
, PURPLE_CALLBACK(update_buddy_status_changed
), NULL
);
8907 purple_signal_connect(blist_handle
, "buddy-privacy-changed",
8908 handle
, PURPLE_CALLBACK(update_buddy_privacy_changed
), NULL
);
8909 purple_signal_connect(blist_handle
, "buddy-idle-changed",
8910 handle
, PURPLE_CALLBACK(update_buddy_idle_changed
), NULL
);
8911 purple_signal_connect(blist_handle
, "buddy-icon-changed",
8912 handle
, PURPLE_CALLBACK(update_buddy_icon
), NULL
);
8913 purple_signal_connect(purple_conversations_get_handle(), "buddy-typing",
8914 handle
, PURPLE_CALLBACK(update_buddy_typing
), NULL
);
8915 purple_signal_connect(purple_conversations_get_handle(), "buddy-typing-stopped",
8916 handle
, PURPLE_CALLBACK(update_buddy_typing
), NULL
);
8917 purple_signal_connect(pidgin_conversations_get_handle(), "conversation-switched",
8918 handle
, PURPLE_CALLBACK(update_conversation_switched
), NULL
);
8919 purple_signal_connect(purple_conversations_get_handle(), "chat-left", handle
,
8920 PURPLE_CALLBACK(update_chat
), NULL
);
8921 purple_signal_connect(purple_conversations_get_handle(), "chat-joined", handle
,
8922 PURPLE_CALLBACK(update_chat
), NULL
);
8923 purple_signal_connect(purple_conversations_get_handle(), "chat-topic-changed", handle
,
8924 PURPLE_CALLBACK(update_chat_topic
), NULL
);
8925 purple_signal_connect_priority(purple_conversations_get_handle(), "conversation-updated", handle
,
8926 PURPLE_CALLBACK(pidgin_conv_updated
), NULL
,
8927 PURPLE_SIGNAL_PRIORITY_LOWEST
);
8928 purple_signal_connect(purple_conversations_get_handle(), "wrote-im-msg", handle
,
8929 PURPLE_CALLBACK(wrote_msg_update_unseen_cb
), NULL
);
8930 purple_signal_connect(purple_conversations_get_handle(), "wrote-chat-msg", handle
,
8931 PURPLE_CALLBACK(wrote_msg_update_unseen_cb
), NULL
);
8933 purple_theme_manager_register_type(g_object_new(PIDGIN_TYPE_CONV_THEME_LOADER
, "type", "conversation", NULL
));
8934 #if defined(_WIN32) && !defined(USE_WIN32_FHS)
8935 theme_dir
= g_build_filename(PURPLE_DATADIR
, "theme", NULL
);
8937 theme_dir
= g_build_filename(PURPLE_DATADIR
, "pidgin", "theme", NULL
);
8939 default_conv_theme
= purple_theme_manager_load_theme(theme_dir
, "conversation");
8945 pidgin_conversations_pre_uninit(void)
8947 g_hash_table_destroy(e2ee_stock
);
8952 pidgin_conversations_uninit(void)
8954 purple_prefs_disconnect_by_handle(pidgin_conversations_get_handle());
8955 purple_signals_disconnect_by_handle(pidgin_conversations_get_handle());
8956 purple_signals_unregister_by_instance(pidgin_conversations_get_handle());
8958 g_regex_unref(image_store_tag_re
);
8959 image_store_tag_re
= NULL
;
8962 /**************************************************************************
8963 * PidginConversation GBoxed code
8964 **************************************************************************/
8965 static PidginConversation
*
8966 pidgin_conversation_ref(PidginConversation
*gtkconv
)
8968 g_return_val_if_fail(gtkconv
!= NULL
, NULL
);
8970 gtkconv
->box_count
++;
8976 pidgin_conversation_unref(PidginConversation
*gtkconv
)
8978 g_return_if_fail(gtkconv
!= NULL
);
8979 g_return_if_fail(gtkconv
->box_count
>= 0);
8981 if (!gtkconv
->box_count
--)
8982 pidgin_conv_destroy(gtkconv
->active_conv
);
8986 pidgin_conversation_get_type(void)
8988 static GType type
= 0;
8991 type
= g_boxed_type_register_static("PidginConversation",
8992 (GBoxedCopyFunc
)pidgin_conversation_ref
,
8993 (GBoxedFreeFunc
)pidgin_conversation_unref
);
9014 /* down here is where gtkconvwin.c ought to start. except they share like every freaking function,
9015 * and touch each others' private members all day long */
9019 * Pidgin is the legal property of its developers, whose names are too numerous
9020 * to list here. Please refer to the COPYRIGHT file distributed with this
9021 * source distribution.
9023 * This program is free software; you can redistribute it and/or modify
9024 * it under the terms of the GNU General Public License as published by
9025 * the Free Software Foundation; either version 2 of the License, or
9026 * (at your option) any later version.
9028 * This program is distributed in the hope that it will be useful,
9029 * but WITHOUT ANY WARRANTY; without even the implied warranty of
9030 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
9031 * GNU General Public License for more details.
9033 * You should have received a copy of the GNU General Public License
9034 * along with this program; if not, write to the Free Software
9035 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA
9038 #include "internal.h"
9042 #include <gdk/gdkkeysyms.h>
9044 #include "account.h"
9049 #include "protocol.h"
9050 #include "request.h"
9053 #include "gtkdnd-hints.h"
9054 #include "gtkblist.h"
9055 #include "gtkconv.h"
9056 #include "gtkdialogs.h"
9057 #include "gtkmenutray.h"
9058 #include "gtkpounce.h"
9059 #include "gtkprefs.h"
9060 #include "gtkprivacy.h"
9061 #include "gtkutils.h"
9062 #include "pidginstock.h"
9065 do_close(GtkWidget
*w
, int resp
, PidginConvWindow
*win
)
9067 gtk_widget_destroy(warn_close_dialog
);
9068 warn_close_dialog
= NULL
;
9070 if (resp
== GTK_RESPONSE_OK
)
9071 pidgin_conv_window_destroy(win
);
9075 build_warn_close_dialog(PidginConvWindow
*gtkwin
)
9077 GtkWidget
*label
, *vbox
, *hbox
, *img
;
9079 g_return_if_fail(warn_close_dialog
== NULL
);
9081 warn_close_dialog
= gtk_dialog_new_with_buttons(_("Confirm close"),
9082 GTK_WINDOW(gtkwin
->window
), GTK_DIALOG_MODAL
,
9083 GTK_STOCK_CANCEL
, GTK_RESPONSE_CANCEL
,
9084 GTK_STOCK_CLOSE
, GTK_RESPONSE_OK
, NULL
);
9086 gtk_dialog_set_default_response(GTK_DIALOG(warn_close_dialog
),
9089 gtk_container_set_border_width(GTK_CONTAINER(warn_close_dialog
),
9091 gtk_window_set_resizable(GTK_WINDOW(warn_close_dialog
), FALSE
);
9093 /* Setup the outside spacing. */
9094 vbox
= gtk_dialog_get_content_area(GTK_DIALOG(warn_close_dialog
));
9096 gtk_box_set_spacing(GTK_BOX(vbox
), 12);
9097 gtk_container_set_border_width(GTK_CONTAINER(vbox
), 6);
9099 img
= gtk_image_new_from_stock(PIDGIN_STOCK_DIALOG_WARNING
,
9100 gtk_icon_size_from_name(PIDGIN_ICON_SIZE_TANGO_HUGE
));
9101 /* Setup the inner hbox and put the dialog's icon in it. */
9102 hbox
= gtk_box_new(GTK_ORIENTATION_HORIZONTAL
, 12);
9103 gtk_container_add(GTK_CONTAINER(vbox
), hbox
);
9104 gtk_box_pack_start(GTK_BOX(hbox
), img
, FALSE
, FALSE
, 0);
9105 gtk_widget_set_halign(img
, GTK_ALIGN_START
);
9106 gtk_widget_set_valign(img
, GTK_ALIGN_START
);
9108 /* Setup the right vbox. */
9109 vbox
= gtk_box_new(GTK_ORIENTATION_VERTICAL
, 12);
9110 gtk_container_add(GTK_CONTAINER(hbox
), vbox
);
9112 label
= gtk_label_new(_("You have unread messages. Are you sure you want to close the window?"));
9113 gtk_widget_set_size_request(label
, 350, -1);
9114 gtk_label_set_line_wrap(GTK_LABEL(label
), TRUE
);
9115 gtk_label_set_xalign(GTK_LABEL(label
), 0);
9116 gtk_label_set_yalign(GTK_LABEL(label
), 0);
9117 gtk_box_pack_start(GTK_BOX(vbox
), label
, FALSE
, FALSE
, 0);
9119 /* Connect the signals. */
9120 g_signal_connect(G_OBJECT(warn_close_dialog
), "response",
9121 G_CALLBACK(do_close
), gtkwin
);
9125 /**************************************************************************
9127 **************************************************************************/
9130 close_win_cb(GtkWidget
*w
, GdkEventAny
*e
, gpointer d
)
9132 PidginConvWindow
*win
= d
;
9135 /* If there are unread messages then show a warning dialog */
9136 for (l
= pidgin_conv_window_get_gtkconvs(win
);
9137 l
!= NULL
; l
= l
->next
)
9139 PidginConversation
*gtkconv
= l
->data
;
9140 if (PURPLE_IS_IM_CONVERSATION(gtkconv
->active_conv
) &&
9141 gtkconv
->unseen_state
>= PIDGIN_UNSEEN_TEXT
)
9143 build_warn_close_dialog(win
);
9144 gtk_widget_show_all(warn_close_dialog
);
9150 pidgin_conv_window_destroy(win
);
9156 conv_set_unseen(PurpleConversation
*conv
, PidginUnseenState state
)
9158 int unseen_count
= 0;
9159 PidginUnseenState unseen_state
= PIDGIN_UNSEEN_NONE
;
9161 if(g_object_get_data(G_OBJECT(conv
), "unseen-count"))
9162 unseen_count
= GPOINTER_TO_INT(g_object_get_data(G_OBJECT(conv
), "unseen-count"));
9164 if(g_object_get_data(G_OBJECT(conv
), "unseen-state"))
9165 unseen_state
= GPOINTER_TO_INT(g_object_get_data(G_OBJECT(conv
), "unseen-state"));
9167 if (state
== PIDGIN_UNSEEN_NONE
)
9170 unseen_state
= PIDGIN_UNSEEN_NONE
;
9174 if (state
>= PIDGIN_UNSEEN_TEXT
)
9177 if (state
> unseen_state
)
9178 unseen_state
= state
;
9181 g_object_set_data(G_OBJECT(conv
), "unseen-count", GINT_TO_POINTER(unseen_count
));
9182 g_object_set_data(G_OBJECT(conv
), "unseen-state", GINT_TO_POINTER(unseen_state
));
9184 purple_conversation_update(conv
, PURPLE_CONVERSATION_UPDATE_UNSEEN
);
9188 gtkconv_set_unseen(PidginConversation
*gtkconv
, PidginUnseenState state
)
9190 if (state
== PIDGIN_UNSEEN_NONE
)
9192 gtkconv
->unseen_count
= 0;
9193 gtkconv
->unseen_state
= PIDGIN_UNSEEN_NONE
;
9197 if (state
>= PIDGIN_UNSEEN_TEXT
)
9198 gtkconv
->unseen_count
++;
9200 if (state
> gtkconv
->unseen_state
)
9201 gtkconv
->unseen_state
= state
;
9204 g_object_set_data(G_OBJECT(gtkconv
->active_conv
), "unseen-count", GINT_TO_POINTER(gtkconv
->unseen_count
));
9205 g_object_set_data(G_OBJECT(gtkconv
->active_conv
), "unseen-state", GINT_TO_POINTER(gtkconv
->unseen_state
));
9207 purple_conversation_update(gtkconv
->active_conv
, PURPLE_CONVERSATION_UPDATE_UNSEEN
);
9211 * When a conversation window is focused, we know the user
9212 * has looked at it so we know there are no longer unseen
9216 focus_win_cb(GtkWidget
*w
, GdkEventFocus
*e
, gpointer d
)
9218 PidginConvWindow
*win
= d
;
9219 PidginConversation
*gtkconv
= pidgin_conv_window_get_active_gtkconv(win
);
9222 gtkconv_set_unseen(gtkconv
, PIDGIN_UNSEEN_NONE
);
9228 notebook_init_grab(PidginConvWindow
*gtkwin
, GtkWidget
*widget
, GdkEvent
*event
)
9230 static GdkCursor
*cursor
= NULL
;
9233 gtkwin
->in_drag
= TRUE
;
9235 if (gtkwin
->drag_leave_signal
) {
9236 g_signal_handler_disconnect(G_OBJECT(widget
),
9237 gtkwin
->drag_leave_signal
);
9238 gtkwin
->drag_leave_signal
= 0;
9241 if (cursor
== NULL
) {
9242 GdkDisplay
*display
= gtk_widget_get_display(gtkwin
->notebook
);
9243 cursor
= gdk_cursor_new_for_display(display
, GDK_FLEUR
);
9246 /* Grab the pointer */
9247 gtk_grab_add(gtkwin
->notebook
);
9248 device
= gdk_event_get_device(event
);
9249 if (!gdk_display_device_is_grabbed(gdk_device_get_display(device
), device
))
9250 gdk_device_grab(device
, gtk_widget_get_window(gtkwin
->notebook
),
9251 GDK_OWNERSHIP_WINDOW
, FALSE
,
9252 GDK_BUTTON1_MOTION_MASK
| GDK_BUTTON_RELEASE_MASK
,
9253 cursor
, gdk_event_get_time(event
));
9257 notebook_motion_cb(GtkWidget
*widget
, GdkEventButton
*e
, PidginConvWindow
*win
)
9261 * Make sure the user moved the mouse far enough for the
9262 * drag to be initiated.
9264 if (win
->in_predrag
) {
9265 if (e
->x_root
< win
->drag_min_x
||
9266 e
->x_root
>= win
->drag_max_x
||
9267 e
->y_root
< win
->drag_min_y
||
9268 e
->y_root
>= win
->drag_max_y
) {
9270 win
->in_predrag
= FALSE
;
9271 notebook_init_grab(win
, widget
, (GdkEvent
*)e
);
9274 else { /* Otherwise, draw the arrows. */
9275 PidginConvWindow
*dest_win
;
9276 GtkNotebook
*dest_notebook
;
9279 gboolean horiz_tabs
= FALSE
;
9280 gboolean to_right
= FALSE
;
9282 /* Get the window that the cursor is over. */
9283 dest_win
= pidgin_conv_window_get_at_event((GdkEvent
*)e
);
9285 if (dest_win
== NULL
) {
9286 pidgin_dnd_hints_hide_all();
9291 dest_notebook
= GTK_NOTEBOOK(dest_win
->notebook
);
9293 if (gtk_notebook_get_show_tabs(dest_notebook
)) {
9294 page_num
= pidgin_conv_get_tab_at_xy(dest_win
,
9295 e
->x_root
, e
->y_root
, &to_right
);
9296 to_right
= to_right
&& (win
!= dest_win
);
9297 tab
= pidgin_conv_window_get_gtkconv_at_index(dest_win
, page_num
)->tabby
;
9300 to_right
= pidgin_conv_xy_to_right_infopane(dest_win
, e
->x_root
, e
->y_root
);
9301 tab
= pidgin_conv_window_get_gtkconv_at_index(dest_win
, page_num
)->infopane_hbox
;
9304 if (gtk_notebook_get_tab_pos(dest_notebook
) == GTK_POS_TOP
||
9305 gtk_notebook_get_tab_pos(dest_notebook
) == GTK_POS_BOTTOM
) {
9309 if (gtk_notebook_get_show_tabs(dest_notebook
) == FALSE
&& win
== dest_win
)
9311 /* dragging a tab from a single-tabbed window over its own window */
9312 pidgin_dnd_hints_hide_all();
9314 } else if (horiz_tabs
) {
9315 if (((gpointer
)win
== (gpointer
)dest_win
&& win
->drag_tab
< page_num
) || to_right
) {
9316 pidgin_dnd_hints_show_relative(HINT_ARROW_DOWN
, tab
, HINT_POSITION_RIGHT
, HINT_POSITION_TOP
);
9317 pidgin_dnd_hints_show_relative(HINT_ARROW_UP
, tab
, HINT_POSITION_RIGHT
, HINT_POSITION_BOTTOM
);
9319 pidgin_dnd_hints_show_relative(HINT_ARROW_DOWN
, tab
, HINT_POSITION_LEFT
, HINT_POSITION_TOP
);
9320 pidgin_dnd_hints_show_relative(HINT_ARROW_UP
, tab
, HINT_POSITION_LEFT
, HINT_POSITION_BOTTOM
);
9323 if (((gpointer
)win
== (gpointer
)dest_win
&& win
->drag_tab
< page_num
) || to_right
) {
9324 pidgin_dnd_hints_show_relative(HINT_ARROW_RIGHT
, tab
, HINT_POSITION_LEFT
, HINT_POSITION_BOTTOM
);
9325 pidgin_dnd_hints_show_relative(HINT_ARROW_LEFT
, tab
, HINT_POSITION_RIGHT
, HINT_POSITION_BOTTOM
);
9327 pidgin_dnd_hints_show_relative(HINT_ARROW_RIGHT
, tab
, HINT_POSITION_LEFT
, HINT_POSITION_TOP
);
9328 pidgin_dnd_hints_show_relative(HINT_ARROW_LEFT
, tab
, HINT_POSITION_RIGHT
, HINT_POSITION_TOP
);
9337 notebook_leave_cb(GtkWidget
*widget
, GdkEventCrossing
*e
, PidginConvWindow
*win
)
9342 if (e
->x_root
< win
->drag_min_x
||
9343 e
->x_root
>= win
->drag_max_x
||
9344 e
->y_root
< win
->drag_min_y
||
9345 e
->y_root
>= win
->drag_max_y
) {
9347 win
->in_predrag
= FALSE
;
9348 notebook_init_grab(win
, widget
, (GdkEvent
*)e
);
9359 infopane_press_cb(GtkWidget
*widget
, GdkEventButton
*e
, PidginConversation
*gtkconv
)
9361 if (e
->type
== GDK_2BUTTON_PRESS
&& e
->button
== 1) {
9362 if (infopane_entry_activate(gtkconv
))
9366 if (e
->type
!= GDK_BUTTON_PRESS
)
9369 if (e
->button
== 1) {
9371 GtkAllocation allocation
;
9373 gtk_widget_get_allocation(gtkconv
->infopane_hbox
, &allocation
);
9375 if (gtkconv
->win
->in_drag
)
9378 gtkconv
->win
->in_predrag
= TRUE
;
9379 gtkconv
->win
->drag_tab
= gtk_notebook_page_num(GTK_NOTEBOOK(gtkconv
->win
->notebook
), gtkconv
->tab_cont
);
9381 gdk_window_get_origin(gtk_widget_get_window(gtkconv
->infopane_hbox
), &nb_x
, &nb_y
);
9383 gtkconv
->win
->drag_min_x
= allocation
.x
+ nb_x
;
9384 gtkconv
->win
->drag_min_y
= allocation
.y
+ nb_y
;
9385 gtkconv
->win
->drag_max_x
= allocation
.width
+ gtkconv
->win
->drag_min_x
;
9386 gtkconv
->win
->drag_max_y
= allocation
.height
+ gtkconv
->win
->drag_min_y
;
9388 gtkconv
->win
->drag_motion_signal
= g_signal_connect(G_OBJECT(gtkconv
->win
->notebook
), "motion_notify_event",
9389 G_CALLBACK(notebook_motion_cb
), gtkconv
->win
);
9390 gtkconv
->win
->drag_leave_signal
= g_signal_connect(G_OBJECT(gtkconv
->win
->notebook
), "leave_notify_event",
9391 G_CALLBACK(notebook_leave_cb
), gtkconv
->win
);
9395 if (e
->button
== 3) {
9396 /* Right click was pressed. Popup the context menu. */
9397 GtkWidget
*menu
= gtk_menu_new(), *sub
;
9398 gboolean populated
= populate_menu_with_options(menu
, gtkconv
, TRUE
);
9400 sub
= gtk_menu_item_get_submenu(GTK_MENU_ITEM(gtkconv
->win
->menu
->send_to
));
9401 if (sub
&& gtk_widget_is_sensitive(gtkconv
->win
->menu
->send_to
)) {
9402 GtkWidget
*item
= gtk_menu_item_new_with_mnemonic(_("S_end To"));
9404 pidgin_separator(menu
);
9405 gtk_menu_shell_append(GTK_MENU_SHELL(menu
), item
);
9406 gtk_menu_item_set_submenu(GTK_MENU_ITEM(item
), sub
);
9407 gtk_widget_show(item
);
9408 gtk_widget_show_all(sub
);
9409 } else if (!populated
) {
9410 gtk_widget_destroy(menu
);
9414 gtk_widget_show_all(menu
);
9415 gtk_menu_popup(GTK_MENU(menu
), NULL
, NULL
, NULL
, NULL
, e
->button
, e
->time
);
9422 notebook_press_cb(GtkWidget
*widget
, GdkEventButton
*e
, PidginConvWindow
*win
)
9428 GtkAllocation allocation
;
9430 if (e
->button
== 2 && e
->type
== GDK_BUTTON_PRESS
) {
9431 PidginConversation
*gtkconv
;
9432 tab_clicked
= pidgin_conv_get_tab_at_xy(win
, e
->x_root
, e
->y_root
, NULL
);
9434 if (tab_clicked
== -1)
9437 gtkconv
= pidgin_conv_window_get_gtkconv_at_index(win
, tab_clicked
);
9438 close_conv_cb(NULL
, gtkconv
);
9443 if (e
->button
!= 1 || e
->type
!= GDK_BUTTON_PRESS
)
9448 purple_debug(PURPLE_DEBUG_WARNING
, "gtkconv",
9449 "Already in the middle of a window drag at tab_press_cb\n");
9454 * Make sure a tab was actually clicked. The arrow buttons
9457 tab_clicked
= pidgin_conv_get_tab_at_xy(win
, e
->x_root
, e
->y_root
, NULL
);
9459 if (tab_clicked
== -1)
9463 * Get the relative position of the press event, with regards to
9464 * the position of the notebook.
9466 gdk_window_get_origin(gtk_widget_get_window(win
->notebook
), &nb_x
, &nb_y
);
9468 /* Reset the min/max x/y */
9469 win
->drag_min_x
= 0;
9470 win
->drag_min_y
= 0;
9471 win
->drag_max_x
= 0;
9472 win
->drag_max_y
= 0;
9474 /* Find out which tab was dragged. */
9475 page
= gtk_notebook_get_nth_page(GTK_NOTEBOOK(win
->notebook
), tab_clicked
);
9476 tab
= gtk_notebook_get_tab_label(GTK_NOTEBOOK(win
->notebook
), page
);
9478 gtk_widget_get_allocation(tab
, &allocation
);
9480 win
->drag_min_x
= allocation
.x
+ nb_x
;
9481 win
->drag_min_y
= allocation
.y
+ nb_y
;
9482 win
->drag_max_x
= allocation
.width
+ win
->drag_min_x
;
9483 win
->drag_max_y
= allocation
.height
+ win
->drag_min_y
;
9485 /* Make sure the click occurred in the tab. */
9486 if (e
->x_root
< win
->drag_min_x
||
9487 e
->x_root
>= win
->drag_max_x
||
9488 e
->y_root
< win
->drag_min_y
||
9489 e
->y_root
>= win
->drag_max_y
) {
9494 win
->in_predrag
= TRUE
;
9495 win
->drag_tab
= tab_clicked
;
9497 /* Connect the new motion signals. */
9498 win
->drag_motion_signal
=
9499 g_signal_connect(G_OBJECT(widget
), "motion_notify_event",
9500 G_CALLBACK(notebook_motion_cb
), win
);
9502 win
->drag_leave_signal
=
9503 g_signal_connect(G_OBJECT(widget
), "leave_notify_event",
9504 G_CALLBACK(notebook_leave_cb
), win
);
9510 notebook_release_cb(GtkWidget
*widget
, GdkEventButton
*e
, PidginConvWindow
*win
)
9512 PidginConvWindow
*dest_win
;
9513 GtkNotebook
*dest_notebook
;
9514 PidginConversation
*active_gtkconv
;
9515 PidginConversation
*gtkconv
;
9516 gint dest_page_num
= 0;
9517 gboolean new_window
= FALSE
;
9518 gboolean to_right
= FALSE
;
9522 * Don't check to make sure that the event's window matches the
9523 * widget's, because we may be getting an event passed on from the
9526 if (e
->button
!= 1 && e
->type
!= GDK_BUTTON_RELEASE
)
9529 device
= gdk_event_get_device((GdkEvent
*)e
);
9530 if (gdk_display_device_is_grabbed(gdk_device_get_display(device
), device
)) {
9531 gdk_device_ungrab(device
, gdk_event_get_time((GdkEvent
*)e
));
9532 gtk_grab_remove(widget
);
9535 if (!win
->in_predrag
&& !win
->in_drag
)
9538 /* Disconnect the motion signal. */
9539 if (win
->drag_motion_signal
) {
9540 g_signal_handler_disconnect(G_OBJECT(widget
),
9541 win
->drag_motion_signal
);
9543 win
->drag_motion_signal
= 0;
9547 * If we're in a pre-drag, we'll also need to disconnect the leave
9550 if (win
->in_predrag
) {
9551 win
->in_predrag
= FALSE
;
9553 if (win
->drag_leave_signal
) {
9554 g_signal_handler_disconnect(G_OBJECT(widget
),
9555 win
->drag_leave_signal
);
9557 win
->drag_leave_signal
= 0;
9561 /* If we're not in drag... */
9562 /* We're perfectly normal people! */
9566 win
->in_drag
= FALSE
;
9568 pidgin_dnd_hints_hide_all();
9570 dest_win
= pidgin_conv_window_get_at_event((GdkEvent
*)e
);
9572 active_gtkconv
= pidgin_conv_window_get_active_gtkconv(win
);
9574 if (dest_win
== NULL
) {
9575 /* If the current window doesn't have any other conversations,
9576 * there isn't much point transferring the conv to a new window. */
9577 if (pidgin_conv_window_get_gtkconv_count(win
) > 1) {
9578 /* Make a new window to stick this to. */
9579 dest_win
= pidgin_conv_window_new();
9584 if (dest_win
== NULL
)
9587 purple_signal_emit(pidgin_conversations_get_handle(),
9588 "conversation-dragging", win
, dest_win
);
9590 /* Get the destination page number. */
9592 dest_notebook
= GTK_NOTEBOOK(dest_win
->notebook
);
9593 if (gtk_notebook_get_show_tabs(dest_notebook
)) {
9594 dest_page_num
= pidgin_conv_get_tab_at_xy(dest_win
,
9595 e
->x_root
, e
->y_root
, &to_right
);
9598 to_right
= pidgin_conv_xy_to_right_infopane(dest_win
, e
->x_root
, e
->y_root
);
9602 gtkconv
= pidgin_conv_window_get_gtkconv_at_index(win
, win
->drag_tab
);
9604 if (win
== dest_win
) {
9605 gtk_notebook_reorder_child(GTK_NOTEBOOK(win
->notebook
), gtkconv
->tab_cont
, dest_page_num
);
9607 pidgin_conv_window_remove_gtkconv(win
, gtkconv
);
9608 pidgin_conv_window_add_gtkconv(dest_win
, gtkconv
);
9609 gtk_notebook_reorder_child(GTK_NOTEBOOK(dest_win
->notebook
), gtkconv
->tab_cont
, dest_page_num
+ to_right
);
9610 pidgin_conv_window_switch_gtkconv(dest_win
, gtkconv
);
9612 gint win_width
, win_height
;
9614 gtk_window_get_size(GTK_WINDOW(dest_win
->window
),
9615 &win_width
, &win_height
);
9616 #ifdef _WIN32 /* only override window manager placement on Windows */
9617 gtk_window_move(GTK_WINDOW(dest_win
->window
),
9618 e
->x_root
- (win_width
/ 2),
9619 e
->y_root
- (win_height
/ 2));
9622 pidgin_conv_window_show(dest_win
);
9626 gtk_widget_grab_focus(active_gtkconv
->entry
);
9633 before_switch_conv_cb(GtkNotebook
*notebook
, GtkWidget
*page
, gint page_num
,
9636 PidginConvWindow
*win
;
9637 PurpleConversation
*conv
;
9638 PidginConversation
*gtkconv
;
9641 conv
= pidgin_conv_window_get_active_conversation(win
);
9643 g_return_if_fail(conv
!= NULL
);
9645 if (!PURPLE_IS_IM_CONVERSATION(conv
))
9648 gtkconv
= PIDGIN_CONVERSATION(conv
);
9650 if (gtkconv
->u
.im
->typing_timer
!= 0) {
9651 g_source_remove(gtkconv
->u
.im
->typing_timer
);
9652 gtkconv
->u
.im
->typing_timer
= 0;
9655 stop_anim(NULL
, gtkconv
);
9659 close_window(GtkWidget
*w
, PidginConvWindow
*win
)
9661 close_win_cb(w
, NULL
, win
);
9665 detach_tab_cb(GtkWidget
*w
, PidginConvWindow
*win
)
9667 PidginConvWindow
*new_window
;
9668 PidginConversation
*gtkconv
;
9670 gtkconv
= win
->clicked_tab
;
9675 /* Nothing to do if there's only one tab in the window */
9676 if (pidgin_conv_window_get_gtkconv_count(win
) == 1)
9679 pidgin_conv_window_remove_gtkconv(win
, gtkconv
);
9681 new_window
= pidgin_conv_window_new();
9682 pidgin_conv_window_add_gtkconv(new_window
, gtkconv
);
9683 pidgin_conv_window_show(new_window
);
9687 close_others_cb(GtkWidget
*w
, PidginConvWindow
*win
)
9690 PidginConversation
*gtkconv
;
9692 gtkconv
= win
->clicked_tab
;
9697 for (iter
= pidgin_conv_window_get_gtkconvs(win
); iter
; )
9699 PidginConversation
*gconv
= iter
->data
;
9702 if (gconv
!= gtkconv
)
9704 close_conv_cb(NULL
, gconv
);
9710 close_tab_cb(GtkWidget
*w
, PidginConvWindow
*win
)
9712 PidginConversation
*gtkconv
;
9714 gtkconv
= win
->clicked_tab
;
9717 close_conv_cb(NULL
, gtkconv
);
9721 notebook_menu_switch_cb(GtkWidget
*item
, GtkWidget
*child
)
9723 GtkNotebook
*notebook
;
9726 notebook
= GTK_NOTEBOOK(gtk_widget_get_parent(child
));
9727 index
= gtk_notebook_page_num(notebook
, child
);
9728 gtk_notebook_set_current_page(notebook
, index
);
9732 notebook_menu_update_label_cb(GtkWidget
*child
, GParamSpec
*pspec
,
9733 GtkNotebook
*notebook
)
9738 item
= g_object_get_data(G_OBJECT(child
), "popup-menu-item");
9739 label
= gtk_bin_get_child(GTK_BIN(item
));
9741 gtk_container_remove(GTK_CONTAINER(item
), label
);
9743 label
= gtk_notebook_get_menu_label(notebook
, child
);
9745 gtk_widget_show(label
);
9746 gtk_container_add(GTK_CONTAINER(item
), label
);
9747 gtk_widget_show(item
);
9749 gtk_widget_hide(item
);
9754 notebook_add_tab_to_menu_cb(GtkNotebook
*notebook
, GtkWidget
*child
,
9755 guint page_num
, PidginConvWindow
*win
)
9760 item
= gtk_menu_item_new();
9761 label
= gtk_notebook_get_menu_label(notebook
, child
);
9763 gtk_widget_show(label
);
9764 gtk_container_add(GTK_CONTAINER(item
), label
);
9765 gtk_widget_show(item
);
9768 g_signal_connect(child
, "child-notify::menu-label",
9769 G_CALLBACK(notebook_menu_update_label_cb
), notebook
);
9770 g_signal_connect(item
, "activate",
9771 G_CALLBACK(notebook_menu_switch_cb
), child
);
9772 g_object_set_data(G_OBJECT(child
), "popup-menu-item", item
);
9774 gtk_menu_shell_insert(GTK_MENU_SHELL(win
->notebook_menu
), item
, page_num
);
9778 notebook_remove_tab_from_menu_cb(GtkNotebook
*notebook
, GtkWidget
*child
,
9779 guint page_num
, PidginConvWindow
*win
)
9783 /* Disconnecting the "child-notify::menu-label" signal. */
9784 g_signal_handlers_disconnect_by_data(child
, notebook
);
9786 item
= g_object_get_data(G_OBJECT(child
), "popup-menu-item");
9787 gtk_container_remove(GTK_CONTAINER(win
->notebook_menu
), item
);
9792 notebook_reorder_tab_in_menu_cb(GtkNotebook
*notebook
, GtkWidget
*child
,
9793 guint page_num
, PidginConvWindow
*win
)
9797 item
= g_object_get_data(G_OBJECT(child
), "popup-menu-item");
9798 gtk_menu_reorder_child(GTK_MENU(win
->notebook_menu
), item
, page_num
);
9802 notebook_right_click_menu_cb(GtkNotebook
*notebook
, GdkEventButton
*event
,
9803 PidginConvWindow
*win
)
9806 PidginConversation
*gtkconv
;
9808 if (event
->type
!= GDK_BUTTON_PRESS
|| event
->button
!= 3)
9811 gtkconv
= pidgin_conv_window_get_gtkconv_at_index(win
,
9812 pidgin_conv_get_tab_at_xy(win
, event
->x_root
, event
->y_root
, NULL
));
9814 win
->clicked_tab
= gtkconv
;
9816 menu
= win
->notebook_menu
;
9818 gtk_menu_popup(GTK_MENU(menu
), NULL
, NULL
, NULL
, NULL
, 3, event
->time
);
9824 remove_edit_entry(PidginConversation
*gtkconv
, GtkWidget
*entry
)
9826 g_signal_handlers_disconnect_matched(G_OBJECT(entry
), G_SIGNAL_MATCH_DATA
,
9827 0, 0, NULL
, NULL
, gtkconv
);
9828 gtk_widget_show(gtkconv
->infopane
);
9829 gtk_widget_grab_focus(gtkconv
->entry
);
9830 gtk_widget_destroy(entry
);
9834 alias_focus_cb(GtkWidget
*widget
, GdkEventFocus
*event
, gpointer user_data
)
9836 remove_edit_entry(user_data
, widget
);
9841 alias_key_press_cb(GtkWidget
*widget
, GdkEventKey
*event
, gpointer user_data
)
9843 if (event
->keyval
== GDK_KEY_Escape
) {
9844 remove_edit_entry(user_data
, widget
);
9851 alias_cb(GtkEntry
*entry
, gpointer user_data
)
9853 PidginConversation
*gtkconv
;
9854 PurpleConversation
*conv
;
9855 PurpleAccount
*account
;
9858 gtkconv
= (PidginConversation
*)user_data
;
9859 if (gtkconv
== NULL
) {
9862 conv
= gtkconv
->active_conv
;
9863 account
= purple_conversation_get_account(conv
);
9864 name
= purple_conversation_get_name(conv
);
9866 if (PURPLE_IS_IM_CONVERSATION(conv
)) {
9868 buddy
= purple_blist_find_buddy(account
, name
);
9869 if (buddy
!= NULL
) {
9870 purple_buddy_set_local_alias(buddy
, gtk_entry_get_text(entry
));
9872 purple_serv_alias_buddy(buddy
);
9873 } else if (PURPLE_IS_CHAT_CONVERSATION(conv
)) {
9874 gtk_entry_set_text(GTK_ENTRY(gtkconv
->u
.chat
->topic_text
), gtk_entry_get_text(entry
));
9875 topic_callback(NULL
, gtkconv
);
9877 remove_edit_entry(user_data
, GTK_WIDGET(entry
));
9881 infopane_entry_activate(PidginConversation
*gtkconv
)
9883 GtkWidget
*entry
= NULL
;
9884 PurpleConversation
*conv
= gtkconv
->active_conv
;
9885 const char *text
= NULL
;
9887 if (!gtk_widget_get_visible(gtkconv
->infopane
)) {
9888 /* There's already an entry for alias. Let's not create another one. */
9892 if (!purple_account_is_connected(purple_conversation_get_account(gtkconv
->active_conv
))) {
9893 /* Do not allow aliasing someone on a disconnected account. */
9897 if (PURPLE_IS_IM_CONVERSATION(conv
)) {
9898 PurpleBuddy
*buddy
= purple_blist_find_buddy(purple_conversation_get_account(gtkconv
->active_conv
), purple_conversation_get_name(gtkconv
->active_conv
));
9900 /* This buddy isn't in your buddy list, so we can't alias him */
9903 text
= purple_buddy_get_contact_alias(buddy
);
9904 } else if (PURPLE_IS_CHAT_CONVERSATION(conv
)) {
9905 PurpleConnection
*gc
;
9906 PurpleProtocol
*protocol
= NULL
;
9908 gc
= purple_conversation_get_connection(conv
);
9910 protocol
= purple_connection_get_protocol(gc
);
9911 if (protocol
&& !PURPLE_PROTOCOL_IMPLEMENTS(protocol
, CHAT_IFACE
, set_topic
))
9912 /* This protocol doesn't support setting the chat room topic */
9915 text
= purple_chat_conversation_get_topic(PURPLE_CHAT_CONVERSATION(conv
));
9919 entry
= gtk_entry_new();
9920 gtk_entry_set_has_frame(GTK_ENTRY(entry
), FALSE
);
9921 gtk_entry_set_width_chars(GTK_ENTRY(entry
), 10);
9922 gtk_entry_set_alignment(GTK_ENTRY(entry
), 0.5);
9924 gtk_box_pack_start(GTK_BOX(gtkconv
->infopane_hbox
), entry
, TRUE
, TRUE
, 0);
9925 /* after the tab label */
9926 gtk_box_reorder_child(GTK_BOX(gtkconv
->infopane_hbox
), entry
, 0);
9928 g_signal_connect(G_OBJECT(entry
), "activate", G_CALLBACK(alias_cb
), gtkconv
);
9929 g_signal_connect(G_OBJECT(entry
), "focus-out-event", G_CALLBACK(alias_focus_cb
), gtkconv
);
9930 g_signal_connect(G_OBJECT(entry
), "key-press-event", G_CALLBACK(alias_key_press_cb
), gtkconv
);
9933 gtk_entry_set_text(GTK_ENTRY(entry
), text
);
9934 gtk_widget_show(entry
);
9935 gtk_widget_hide(gtkconv
->infopane
);
9936 gtk_widget_grab_focus(entry
);
9942 window_keypress_cb(GtkWidget
*widget
, GdkEventKey
*event
, PidginConvWindow
*win
)
9944 PidginConversation
*gtkconv
= pidgin_conv_window_get_active_gtkconv(win
);
9946 return conv_keypress_common(gtkconv
, event
);
9950 switch_conv_cb(GtkNotebook
*notebook
, GtkWidget
*page
, gint page_num
,
9953 PidginConvWindow
*win
;
9954 PurpleConversation
*conv
;
9955 PidginConversation
*gtkconv
;
9956 const char *sound_method
;
9959 gtkconv
= pidgin_conv_window_get_gtkconv_at_index(win
, page_num
);
9960 conv
= gtkconv
->active_conv
;
9962 g_return_if_fail(conv
!= NULL
);
9964 /* clear unseen flag if conversation is not hidden */
9965 if(!pidgin_conv_is_hidden(gtkconv
)) {
9966 gtkconv_set_unseen(gtkconv
, PIDGIN_UNSEEN_NONE
);
9969 /* Update the menubar */
9971 gtk_toggle_action_set_active(GTK_TOGGLE_ACTION(gtkconv
->win
->menu
->logging
),
9972 purple_conversation_is_logging(conv
));
9974 generate_send_to_items(win
);
9975 generate_e2ee_controls(win
);
9976 regenerate_options_items(win
);
9977 regenerate_plugins_items(win
);
9979 pidgin_conv_switch_active_conversation(conv
);
9981 sound_method
= purple_prefs_get_string(PIDGIN_PREFS_ROOT
"/sound/method");
9982 if (strcmp(sound_method
, "none") != 0)
9983 gtk_toggle_action_set_active(GTK_TOGGLE_ACTION(win
->menu
->sounds
),
9984 gtkconv
->make_sound
);
9986 gtk_toggle_action_set_active(GTK_TOGGLE_ACTION(win
->menu
->show_formatting_toolbar
),
9987 purple_prefs_get_bool(PIDGIN_PREFS_ROOT
"/conversations/show_formatting_toolbar"));
9990 * We pause icons when they are not visible. If this icon should
9991 * be animated then start it back up again.
9993 if (PURPLE_IS_IM_CONVERSATION(conv
) &&
9994 (gtkconv
->u
.im
->animate
))
9995 start_anim(NULL
, gtkconv
);
9997 purple_signal_emit(pidgin_conversations_get_handle(), "conversation-switched", conv
);
10000 /**************************************************************************
10002 **************************************************************************/
10005 pidgin_conv_windows_get_list()
10007 return window_list
;
10011 make_status_icon_list(const char *stock
, GtkWidget
*w
)
10014 l
= g_list_append(l
,
10015 gtk_widget_render_icon(w
, stock
,
10016 gtk_icon_size_from_name(PIDGIN_ICON_SIZE_TANGO_EXTRA_SMALL
), "GtkWindow"));
10017 l
= g_list_append(l
,
10018 gtk_widget_render_icon(w
, stock
,
10019 gtk_icon_size_from_name(PIDGIN_ICON_SIZE_TANGO_SMALL
), "GtkWindow"));
10020 l
= g_list_append(l
,
10021 gtk_widget_render_icon(w
, stock
,
10022 gtk_icon_size_from_name(PIDGIN_ICON_SIZE_TANGO_MEDIUM
), "GtkWindow"));
10023 l
= g_list_append(l
,
10024 gtk_widget_render_icon(w
, stock
,
10025 gtk_icon_size_from_name(PIDGIN_ICON_SIZE_TANGO_LARGE
), "GtkWindow"));
10030 create_icon_lists(GtkWidget
*w
)
10032 available_list
= make_status_icon_list(PIDGIN_STOCK_STATUS_AVAILABLE
, w
);
10033 busy_list
= make_status_icon_list(PIDGIN_STOCK_STATUS_BUSY
, w
);
10034 xa_list
= make_status_icon_list(PIDGIN_STOCK_STATUS_XA
, w
);
10035 offline_list
= make_status_icon_list(PIDGIN_STOCK_STATUS_OFFLINE
, w
);
10036 away_list
= make_status_icon_list(PIDGIN_STOCK_STATUS_AWAY
, w
);
10037 protocol_lists
= g_hash_table_new(g_str_hash
, g_str_equal
);
10041 plugin_changed_cb(PurplePlugin
*p
, gpointer data
)
10043 regenerate_plugins_items(data
);
10046 static gboolean
gtk_conv_configure_cb(GtkWidget
*w
, GdkEventConfigure
*event
, gpointer data
) {
10049 if (gtk_widget_get_visible(w
))
10050 gtk_window_get_position(GTK_WINDOW(w
), &x
, &y
);
10052 return FALSE
; /* carry on normally */
10054 /* Workaround for GTK+ bug # 169811 - "configure_event" is fired
10055 * when the window is being maximized */
10056 if (gdk_window_get_state(gtk_widget_get_window(w
)) & GDK_WINDOW_STATE_MAXIMIZED
)
10059 /* don't save off-screen positioning */
10060 if (x
+ event
->width
< 0 ||
10061 y
+ event
->height
< 0 ||
10062 x
> gdk_screen_width() ||
10063 y
> gdk_screen_height())
10064 return FALSE
; /* carry on normally */
10066 /* store the position */
10067 purple_prefs_set_int(PIDGIN_PREFS_ROOT
"/conversations/im/x", x
);
10068 purple_prefs_set_int(PIDGIN_PREFS_ROOT
"/conversations/im/y", y
);
10069 purple_prefs_set_int(PIDGIN_PREFS_ROOT
"/conversations/im/width", event
->width
);
10070 purple_prefs_set_int(PIDGIN_PREFS_ROOT
"/conversations/im/height", event
->height
);
10072 /* continue to handle event normally */
10078 pidgin_conv_set_position_size(PidginConvWindow
*win
, int conv_x
, int conv_y
,
10079 int conv_width
, int conv_height
)
10081 /* if the window exists, is hidden, we're saving positions, and the
10082 * position is sane... */
10083 if (win
&& win
->window
&&
10084 !gtk_widget_get_visible(win
->window
) && conv_width
!= 0) {
10086 #ifdef _WIN32 /* only override window manager placement on Windows */
10087 /* ...check position is on screen... */
10088 if (conv_x
>= gdk_screen_width())
10089 conv_x
= gdk_screen_width() - 100;
10090 else if (conv_x
+ conv_width
< 0)
10093 if (conv_y
>= gdk_screen_height())
10094 conv_y
= gdk_screen_height() - 100;
10095 else if (conv_y
+ conv_height
< 0)
10098 /* ...and move it back. */
10099 gtk_window_move(GTK_WINDOW(win
->window
), conv_x
, conv_y
);
10101 gtk_window_resize(GTK_WINDOW(win
->window
), conv_width
, conv_height
);
10106 pidgin_conv_restore_position(PidginConvWindow
*win
) {
10107 pidgin_conv_set_position_size(win
,
10108 purple_prefs_get_int(PIDGIN_PREFS_ROOT
"/conversations/im/x"),
10109 purple_prefs_get_int(PIDGIN_PREFS_ROOT
"/conversations/im/y"),
10110 purple_prefs_get_int(PIDGIN_PREFS_ROOT
"/conversations/im/width"),
10111 purple_prefs_get_int(PIDGIN_PREFS_ROOT
"/conversations/im/height"));
10115 pidgin_conv_window_new()
10117 PidginConvWindow
*win
;
10118 GtkPositionType pos
;
10119 GtkWidget
*testidea
;
10120 GtkWidget
*menubar
;
10123 GdkModifierType state
;
10125 win
= g_malloc0(sizeof(PidginConvWindow
));
10126 win
->menu
= g_malloc0(sizeof(PidginConvWindowMenu
));
10128 window_list
= g_list_append(window_list
, win
);
10130 /* Create the window. */
10131 win
->window
= pidgin_create_window(NULL
, 0, "conversation", TRUE
);
10132 /*_pidgin_widget_set_accessible_name(win->window, "Conversations");*/
10133 if (!gtk_get_current_event_state(&state
))
10134 gtk_window_set_focus_on_map(GTK_WINDOW(win
->window
), FALSE
);
10136 /* Etan: I really think this entire function call should happen only
10137 * when we are on Windows but I was informed that back before we used
10138 * to save the window position we stored the window size, so I'm
10139 * leaving it for now. */
10140 #if TRUE || defined(_WIN32)
10141 pidgin_conv_restore_position(win
);
10144 if (available_list
== NULL
) {
10145 create_icon_lists(win
->window
);
10148 g_signal_connect(G_OBJECT(win
->window
), "delete_event",
10149 G_CALLBACK(close_win_cb
), win
);
10150 g_signal_connect(G_OBJECT(win
->window
), "focus_in_event",
10151 G_CALLBACK(focus_win_cb
), win
);
10153 /* Intercept keystrokes from the menu items */
10154 g_signal_connect(G_OBJECT(win
->window
), "key_press_event",
10155 G_CALLBACK(window_keypress_cb
), win
);
10158 /* Create the notebook. */
10159 win
->notebook
= gtk_notebook_new();
10161 pos
= purple_prefs_get_int(PIDGIN_PREFS_ROOT
"/conversations/tab_side");
10164 gtk_notebook_set_tab_hborder(GTK_NOTEBOOK(win
->notebook
), 0);
10165 gtk_notebook_set_tab_vborder(GTK_NOTEBOOK(win
->notebook
), 0);
10167 gtk_notebook_set_tab_pos(GTK_NOTEBOOK(win
->notebook
), pos
);
10168 gtk_notebook_set_scrollable(GTK_NOTEBOOK(win
->notebook
), TRUE
);
10169 gtk_notebook_set_show_tabs(GTK_NOTEBOOK(win
->notebook
), FALSE
);
10170 gtk_notebook_set_show_border(GTK_NOTEBOOK(win
->notebook
), TRUE
);
10172 menu
= win
->notebook_menu
= gtk_menu_new();
10174 pidgin_separator(GTK_WIDGET(menu
));
10176 item
= gtk_menu_item_new_with_label(_("Close other tabs"));
10177 gtk_widget_show(item
);
10178 gtk_menu_shell_append(GTK_MENU_SHELL(menu
), item
);
10179 g_signal_connect(G_OBJECT(item
), "activate",
10180 G_CALLBACK(close_others_cb
), win
);
10182 item
= gtk_menu_item_new_with_label(_("Close all tabs"));
10183 gtk_widget_show(item
);
10184 gtk_menu_shell_append(GTK_MENU_SHELL(menu
), item
);
10185 g_signal_connect(G_OBJECT(item
), "activate",
10186 G_CALLBACK(close_window
), win
);
10188 pidgin_separator(menu
);
10190 item
= gtk_menu_item_new_with_label(_("Detach this tab"));
10191 gtk_widget_show(item
);
10192 gtk_menu_shell_append(GTK_MENU_SHELL(menu
), item
);
10193 g_signal_connect(G_OBJECT(item
), "activate",
10194 G_CALLBACK(detach_tab_cb
), win
);
10196 item
= gtk_menu_item_new_with_label(_("Close this tab"));
10197 gtk_widget_show(item
);
10198 gtk_menu_shell_append(GTK_MENU_SHELL(menu
), item
);
10199 g_signal_connect(G_OBJECT(item
), "activate",
10200 G_CALLBACK(close_tab_cb
), win
);
10202 g_signal_connect(G_OBJECT(win
->notebook
), "page-added",
10203 G_CALLBACK(notebook_add_tab_to_menu_cb
), win
);
10204 g_signal_connect(G_OBJECT(win
->notebook
), "page-removed",
10205 G_CALLBACK(notebook_remove_tab_from_menu_cb
), win
);
10206 g_signal_connect(G_OBJECT(win
->notebook
), "page-reordered",
10207 G_CALLBACK(notebook_reorder_tab_in_menu_cb
), win
);
10209 g_signal_connect(G_OBJECT(win
->notebook
), "button-press-event",
10210 G_CALLBACK(notebook_right_click_menu_cb
), win
);
10212 gtk_widget_show(win
->notebook
);
10214 g_signal_connect(G_OBJECT(win
->notebook
), "switch_page",
10215 G_CALLBACK(before_switch_conv_cb
), win
);
10216 g_signal_connect_after(G_OBJECT(win
->notebook
), "switch_page",
10217 G_CALLBACK(switch_conv_cb
), win
);
10219 /* Setup the tab drag and drop signals. */
10220 gtk_widget_add_events(win
->notebook
,
10221 GDK_BUTTON1_MOTION_MASK
| GDK_LEAVE_NOTIFY_MASK
);
10222 g_signal_connect(G_OBJECT(win
->notebook
), "button_press_event",
10223 G_CALLBACK(notebook_press_cb
), win
);
10224 g_signal_connect(G_OBJECT(win
->notebook
), "button_release_event",
10225 G_CALLBACK(notebook_release_cb
), win
);
10227 testidea
= gtk_box_new(GTK_ORIENTATION_VERTICAL
, 0);
10229 /* Setup the menubar. */
10230 menubar
= setup_menubar(win
);
10231 gtk_box_pack_start(GTK_BOX(testidea
), menubar
, FALSE
, TRUE
, 0);
10233 gtk_box_pack_start(GTK_BOX(testidea
), win
->notebook
, TRUE
, TRUE
, 0);
10235 gtk_container_add(GTK_CONTAINER(win
->window
), testidea
);
10237 gtk_widget_show(testidea
);
10239 /* Update the plugin actions when plugins are (un)loaded */
10240 purple_signal_connect(purple_plugins_get_handle(), "plugin-load",
10241 win
, PURPLE_CALLBACK(plugin_changed_cb
), win
);
10242 purple_signal_connect(purple_plugins_get_handle(), "plugin-unload",
10243 win
, PURPLE_CALLBACK(plugin_changed_cb
), win
);
10247 g_signal_connect(G_OBJECT(win
->window
), "show",
10248 G_CALLBACK(winpidgin_ensure_onscreen
), win
->window
);
10250 if (purple_prefs_get_bool(PIDGIN_PREFS_ROOT
"/win32/minimize_new_convs")
10251 && !gtk_get_current_event_state(&state
))
10252 gtk_window_iconify(GTK_WINDOW(win
->window
));
10255 purple_signal_emit(pidgin_conversations_get_handle(),
10256 "conversation-window-created", win
);
10262 pidgin_conv_window_destroy(PidginConvWindow
*win
)
10264 if (win
->gtkconvs
) {
10265 GList
*iter
= win
->gtkconvs
;
10268 PidginConversation
*gtkconv
= iter
->data
;
10270 close_conv_cb(NULL
, gtkconv
);
10275 purple_prefs_disconnect_by_handle(win
);
10276 window_list
= g_list_remove(window_list
, win
);
10278 gtk_widget_destroy(win
->notebook_menu
);
10279 gtk_widget_destroy(win
->window
);
10281 g_object_unref(G_OBJECT(win
->menu
->ui
));
10283 purple_notify_close_with_handle(win
);
10284 purple_signals_disconnect_by_handle(win
);
10291 pidgin_conv_window_show(PidginConvWindow
*win
)
10293 gtk_widget_show(win
->window
);
10297 pidgin_conv_window_hide(PidginConvWindow
*win
)
10299 gtk_widget_hide(win
->window
);
10303 pidgin_conv_window_raise(PidginConvWindow
*win
)
10305 gdk_window_raise(GDK_WINDOW(gtk_widget_get_window(win
->window
)));
10309 pidgin_conv_window_switch_gtkconv(PidginConvWindow
*win
, PidginConversation
*gtkconv
)
10311 gtk_notebook_set_current_page(GTK_NOTEBOOK(win
->notebook
),
10312 gtk_notebook_page_num(GTK_NOTEBOOK(win
->notebook
),
10313 gtkconv
->tab_cont
));
10317 gtkconv_tab_set_tip(GtkWidget
*widget
, GdkEventCrossing
*event
, PidginConversation
*gtkconv
)
10319 /* PANGO_VERSION_CHECK macro was introduced in 1.15. So we need this double check. */
10320 #ifndef PANGO_VERSION_CHECK
10321 #define pango_layout_is_ellipsized(l) TRUE
10322 #elif !PANGO_VERSION_CHECK(1,16,0)
10323 #define pango_layout_is_ellipsized(l) TRUE
10325 PangoLayout
*layout
;
10327 layout
= gtk_label_get_layout(GTK_LABEL(gtkconv
->tab_label
));
10328 if (pango_layout_is_ellipsized(layout
))
10329 gtk_widget_set_tooltip_text(widget
, gtk_label_get_text(GTK_LABEL(gtkconv
->tab_label
)));
10331 gtk_widget_set_tooltip_text(widget
, NULL
);
10337 set_default_tab_colors(GtkWidget
*widget
)
10340 GtkCssProvider
*provider
;
10341 GError
*error
= NULL
;
10345 const char *labelname
;
10348 {"tab-label-typing", "#4e9a06"},
10349 {"tab-label-typed", "#c4a000"},
10350 {"tab-label-attention", "#006aff"},
10351 {"tab-label-unreadchat", "#cc0000"},
10352 {"tab-label-event", "#888a85"},
10356 str
= g_string_new(NULL
);
10358 for (iter
= 0; styles
[iter
].labelname
; iter
++) {
10359 g_string_append_printf(str
,
10363 styles
[iter
].labelname
,
10364 styles
[iter
].color
);
10367 provider
= gtk_css_provider_new();
10369 gtk_css_provider_load_from_data(provider
, str
->str
, str
->len
, &error
);
10371 gtk_style_context_add_provider(gtk_widget_get_style_context(widget
),
10372 GTK_STYLE_PROVIDER(provider
),
10373 GTK_STYLE_PROVIDER_PRIORITY_APPLICATION
);
10376 g_error_free(error
);
10377 g_string_free(str
, TRUE
);
10381 pidgin_conv_window_add_gtkconv(PidginConvWindow
*win
, PidginConversation
*gtkconv
)
10383 PurpleConversation
*conv
= gtkconv
->active_conv
;
10384 PidginConversation
*focus_gtkconv
;
10385 GtkWidget
*tab_cont
= gtkconv
->tab_cont
;
10386 const gchar
*tmp_lab
;
10388 win
->gtkconvs
= g_list_append(win
->gtkconvs
, gtkconv
);
10389 gtkconv
->win
= win
;
10391 if (win
->gtkconvs
&& win
->gtkconvs
->next
&& win
->gtkconvs
->next
->next
== NULL
)
10392 pidgin_conv_tab_pack(win
, ((PidginConversation
*)win
->gtkconvs
->data
));
10395 /* Close button. */
10396 gtkconv
->close
= pidgin_create_small_button(gtk_label_new("×"));
10397 gtk_widget_set_tooltip_text(gtkconv
->close
, _("Close conversation"));
10399 g_signal_connect(gtkconv
->close
, "clicked", G_CALLBACK (close_conv_cb
), gtkconv
);
10402 gtkconv
->icon
= gtk_image_new();
10403 gtkconv
->menu_icon
= gtk_image_new();
10404 g_object_set(G_OBJECT(gtkconv
->icon
),
10405 "icon-size", gtk_icon_size_from_name(PIDGIN_ICON_SIZE_TANGO_MICROSCOPIC
),
10407 g_object_set(G_OBJECT(gtkconv
->menu_icon
),
10408 "icon-size", gtk_icon_size_from_name(PIDGIN_ICON_SIZE_TANGO_MICROSCOPIC
),
10410 gtk_widget_show(gtkconv
->icon
);
10411 update_tab_icon(conv
);
10414 gtkconv
->tab_label
= gtk_label_new(tmp_lab
= purple_conversation_get_title(conv
));
10415 set_default_tab_colors(gtkconv
->tab_label
);
10416 gtk_widget_set_name(gtkconv
->tab_label
, "tab-label");
10418 gtkconv
->menu_tabby
= gtk_box_new(GTK_ORIENTATION_HORIZONTAL
, PIDGIN_HIG_BOX_SPACE
);
10419 gtkconv
->menu_label
= gtk_label_new(tmp_lab
);
10420 gtk_box_pack_start(GTK_BOX(gtkconv
->menu_tabby
), gtkconv
->menu_icon
, FALSE
, FALSE
, 0);
10422 gtk_widget_show_all(gtkconv
->menu_icon
);
10424 gtk_box_pack_start(GTK_BOX(gtkconv
->menu_tabby
), gtkconv
->menu_label
, TRUE
, TRUE
, 0);
10425 gtk_widget_show(gtkconv
->menu_label
);
10426 gtk_label_set_xalign(GTK_LABEL(gtkconv
->menu_label
), 0);
10427 gtk_label_set_yalign(GTK_LABEL(gtkconv
->menu_label
), 0);
10429 gtk_widget_show(gtkconv
->menu_tabby
);
10431 if (PURPLE_IS_IM_CONVERSATION(conv
))
10432 pidgin_conv_update_buddy_icon(PURPLE_IM_CONVERSATION(conv
));
10434 /* Build and set conversations tab */
10435 pidgin_conv_tab_pack(win
, gtkconv
);
10437 gtk_notebook_set_menu_label(GTK_NOTEBOOK(win
->notebook
), tab_cont
, gtkconv
->menu_tabby
);
10439 gtk_widget_show(tab_cont
);
10441 if (pidgin_conv_window_get_gtkconv_count(win
) == 1) {
10442 /* Er, bug in notebooks? Switch to the page manually. */
10443 gtk_notebook_set_current_page(GTK_NOTEBOOK(win
->notebook
), 0);
10445 gtk_notebook_set_show_tabs(GTK_NOTEBOOK(win
->notebook
), TRUE
);
10448 focus_gtkconv
= g_list_nth_data(pidgin_conv_window_get_gtkconvs(win
),
10449 gtk_notebook_get_current_page(GTK_NOTEBOOK(win
->notebook
)));
10450 gtk_widget_grab_focus(focus_gtkconv
->entry
);
10452 if (pidgin_conv_window_get_gtkconv_count(win
) == 1)
10453 update_send_to_selection(win
);
10457 pidgin_conv_tab_pack(PidginConvWindow
*win
, PidginConversation
*gtkconv
)
10459 gboolean tabs_side
= FALSE
;
10461 GtkWidget
*first
, *third
, *ebox
, *parent
;
10463 if (purple_prefs_get_int(PIDGIN_PREFS_ROOT
"/conversations/tab_side") == GTK_POS_LEFT
||
10464 purple_prefs_get_int(PIDGIN_PREFS_ROOT
"/conversations/tab_side") == GTK_POS_RIGHT
)
10466 else if (purple_prefs_get_int(PIDGIN_PREFS_ROOT
"/conversations/tab_side") == (GTK_POS_LEFT
|8))
10468 else if (purple_prefs_get_int(PIDGIN_PREFS_ROOT
"/conversations/tab_side") == (GTK_POS_RIGHT
|8))
10472 g_object_set(G_OBJECT(gtkconv
->tab_label
), "ellipsize", PANGO_ELLIPSIZE_END
, NULL
);
10473 gtk_label_set_width_chars(GTK_LABEL(gtkconv
->tab_label
), 4);
10475 g_object_set(G_OBJECT(gtkconv
->tab_label
), "ellipsize", PANGO_ELLIPSIZE_NONE
, NULL
);
10476 gtk_label_set_width_chars(GTK_LABEL(gtkconv
->tab_label
), -1);
10480 gtk_label_set_width_chars(
10481 GTK_LABEL(gtkconv
->tab_label
),
10482 MIN(g_utf8_strlen(gtk_label_get_text(GTK_LABEL(gtkconv
->tab_label
)), -1), 12)
10486 gtk_label_set_angle(GTK_LABEL(gtkconv
->tab_label
), angle
);
10489 gtkconv
->tabby
= gtk_box_new(GTK_ORIENTATION_VERTICAL
, PIDGIN_HIG_BOX_SPACE
);
10491 gtkconv
->tabby
= gtk_box_new(GTK_ORIENTATION_HORIZONTAL
, PIDGIN_HIG_BOX_SPACE
);
10492 gtk_widget_set_name(gtkconv
->tabby
, "tab-container");
10494 /* select the correct ordering for verticle tabs */
10496 first
= gtkconv
->close
;
10497 third
= gtkconv
->icon
;
10499 first
= gtkconv
->icon
;
10500 third
= gtkconv
->close
;
10503 ebox
= gtk_event_box_new();
10504 gtk_event_box_set_visible_window(GTK_EVENT_BOX(ebox
), FALSE
);
10505 gtk_container_add(GTK_CONTAINER(ebox
), gtkconv
->tabby
);
10506 g_signal_connect(G_OBJECT(ebox
), "enter-notify-event",
10507 G_CALLBACK(gtkconv_tab_set_tip
), gtkconv
);
10509 parent
= gtk_widget_get_parent(gtkconv
->tab_label
);
10510 if (parent
!= NULL
) {
10511 /* reparent old widgets on preference changes */
10512 g_object_ref(first
);
10513 g_object_ref(gtkconv
->tab_label
);
10514 g_object_ref(third
);
10515 gtk_container_remove(GTK_CONTAINER(parent
), first
);
10516 gtk_container_remove(GTK_CONTAINER(parent
), gtkconv
->tab_label
);
10517 gtk_container_remove(GTK_CONTAINER(parent
), third
);
10520 gtk_box_pack_start(GTK_BOX(gtkconv
->tabby
), first
, FALSE
, FALSE
, 0);
10521 gtk_box_pack_start(GTK_BOX(gtkconv
->tabby
), gtkconv
->tab_label
, TRUE
, TRUE
, 0);
10522 gtk_box_pack_start(GTK_BOX(gtkconv
->tabby
), third
, FALSE
, FALSE
, 0);
10524 if (parent
== NULL
) {
10525 /* Add this pane to the conversation's notebook. */
10526 gtk_notebook_append_page(GTK_NOTEBOOK(win
->notebook
), gtkconv
->tab_cont
, ebox
);
10528 /* reparent old widgets on preference changes */
10529 g_object_unref(first
);
10530 g_object_unref(gtkconv
->tab_label
);
10531 g_object_unref(third
);
10533 /* Reset the tabs label to the new version */
10534 gtk_notebook_set_tab_label(GTK_NOTEBOOK(win
->notebook
), gtkconv
->tab_cont
, ebox
);
10537 gtk_container_child_set(GTK_CONTAINER(win
->notebook
), gtkconv
->tab_cont
,
10538 "tab-expand", !tabs_side
&& !angle
,
10539 "tab-fill", TRUE
, NULL
);
10541 if (pidgin_conv_window_get_gtkconv_count(win
) == 1)
10542 gtk_notebook_set_show_tabs(GTK_NOTEBOOK(win
->notebook
),
10543 purple_prefs_get_bool(PIDGIN_PREFS_ROOT
"/conversations/tabs") &&
10544 (!purple_prefs_get_bool(PIDGIN_PREFS_ROOT
"/conversations/im/show_buddy_icons") ||
10545 purple_prefs_get_int(PIDGIN_PREFS_ROOT
"/conversations/tab_side") != GTK_POS_TOP
));
10547 /* show the widgets */
10548 /* gtk_widget_show(gtkconv->icon); */
10549 gtk_widget_show(gtkconv
->tab_label
);
10550 if (purple_prefs_get_bool(PIDGIN_PREFS_ROOT
"/conversations/close_on_tabs"))
10551 gtk_widget_show(gtkconv
->close
);
10552 gtk_widget_show(gtkconv
->tabby
);
10553 gtk_widget_show(ebox
);
10557 pidgin_conv_window_remove_gtkconv(PidginConvWindow
*win
, PidginConversation
*gtkconv
)
10559 unsigned int index
;
10561 index
= gtk_notebook_page_num(GTK_NOTEBOOK(win
->notebook
), gtkconv
->tab_cont
);
10563 g_object_ref_sink(G_OBJECT(gtkconv
->tab_cont
));
10565 gtk_notebook_remove_page(GTK_NOTEBOOK(win
->notebook
), index
);
10567 win
->gtkconvs
= g_list_remove(win
->gtkconvs
, gtkconv
);
10569 g_signal_handlers_disconnect_matched(win
->window
, G_SIGNAL_MATCH_DATA
,
10570 0, 0, NULL
, NULL
, gtkconv
);
10572 if (win
->gtkconvs
&& win
->gtkconvs
->next
== NULL
)
10573 pidgin_conv_tab_pack(win
, win
->gtkconvs
->data
);
10575 if (!win
->gtkconvs
&& win
!= hidden_convwin
)
10576 pidgin_conv_window_destroy(win
);
10579 PidginConversation
*
10580 pidgin_conv_window_get_gtkconv_at_index(const PidginConvWindow
*win
, int index
)
10582 GtkWidget
*tab_cont
;
10586 tab_cont
= gtk_notebook_get_nth_page(GTK_NOTEBOOK(win
->notebook
), index
);
10587 return tab_cont
? g_object_get_data(G_OBJECT(tab_cont
), "PidginConversation") : NULL
;
10590 PidginConversation
*
10591 pidgin_conv_window_get_active_gtkconv(const PidginConvWindow
*win
)
10594 GtkWidget
*tab_cont
;
10596 index
= gtk_notebook_get_current_page(GTK_NOTEBOOK(win
->notebook
));
10599 tab_cont
= gtk_notebook_get_nth_page(GTK_NOTEBOOK(win
->notebook
), index
);
10602 return g_object_get_data(G_OBJECT(tab_cont
), "PidginConversation");
10606 PurpleConversation
*
10607 pidgin_conv_window_get_active_conversation(const PidginConvWindow
*win
)
10609 PidginConversation
*gtkconv
;
10611 gtkconv
= pidgin_conv_window_get_active_gtkconv(win
);
10612 return gtkconv
? gtkconv
->active_conv
: NULL
;
10616 pidgin_conv_window_is_active_conversation(const PurpleConversation
*conv
)
10618 return conv
== pidgin_conv_window_get_active_conversation(PIDGIN_CONVERSATION(conv
)->win
);
10622 pidgin_conv_window_has_focus(PidginConvWindow
*win
)
10624 gboolean has_focus
= FALSE
;
10626 g_object_get(G_OBJECT(win
->window
), "has-toplevel-focus", &has_focus
, NULL
);
10632 pidgin_conv_window_get_at_event(GdkEvent
*event
)
10634 PidginConvWindow
*win
;
10639 gdkwin
= gdk_device_get_window_at_position(gdk_event_get_device(event
),
10643 gdkwin
= gdk_window_get_toplevel(gdkwin
);
10645 for (l
= pidgin_conv_windows_get_list(); l
!= NULL
; l
= l
->next
) {
10648 if (gdkwin
== gtk_widget_get_window(win
->window
))
10656 pidgin_conv_window_get_gtkconvs(PidginConvWindow
*win
)
10658 return win
->gtkconvs
;
10662 pidgin_conv_window_get_gtkconv_count(PidginConvWindow
*win
)
10664 return g_list_length(win
->gtkconvs
);
10668 pidgin_conv_window_first_im(void)
10670 GList
*wins
, *convs
;
10671 PidginConvWindow
*win
;
10672 PidginConversation
*conv
;
10674 for (wins
= pidgin_conv_windows_get_list(); wins
!= NULL
; wins
= wins
->next
) {
10677 for (convs
= win
->gtkconvs
;
10679 convs
= convs
->next
) {
10681 conv
= convs
->data
;
10683 if (PURPLE_IS_IM_CONVERSATION(conv
->active_conv
))
10692 pidgin_conv_window_last_im(void)
10694 GList
*wins
, *convs
;
10695 PidginConvWindow
*win
;
10696 PidginConversation
*conv
;
10698 for (wins
= g_list_last(pidgin_conv_windows_get_list());
10700 wins
= wins
->prev
) {
10704 for (convs
= win
->gtkconvs
;
10706 convs
= convs
->next
) {
10708 conv
= convs
->data
;
10710 if (PURPLE_IS_IM_CONVERSATION(conv
->active_conv
))
10719 pidgin_conv_window_first_chat(void)
10721 GList
*wins
, *convs
;
10722 PidginConvWindow
*win
;
10723 PidginConversation
*conv
;
10725 for (wins
= pidgin_conv_windows_get_list(); wins
!= NULL
; wins
= wins
->next
) {
10728 for (convs
= win
->gtkconvs
;
10730 convs
= convs
->next
) {
10732 conv
= convs
->data
;
10734 if (PURPLE_IS_CHAT_CONVERSATION(conv
->active_conv
))
10743 pidgin_conv_window_last_chat(void)
10745 GList
*wins
, *convs
;
10746 PidginConvWindow
*win
;
10747 PidginConversation
*conv
;
10749 for (wins
= g_list_last(pidgin_conv_windows_get_list());
10751 wins
= wins
->prev
) {
10755 for (convs
= win
->gtkconvs
;
10757 convs
= convs
->next
) {
10759 conv
= convs
->data
;
10761 if (PURPLE_IS_CHAT_CONVERSATION(conv
->active_conv
))
10770 /**************************************************************************
10771 * Conversation placement functions
10772 **************************************************************************/
10777 PidginConvPlacementFunc fnc
;
10779 } ConvPlacementData
;
10781 static GList
*conv_placement_fncs
= NULL
;
10782 static PidginConvPlacementFunc place_conv
= NULL
;
10784 /* This one places conversations in the last made window. */
10786 conv_placement_last_created_win(PidginConversation
*conv
)
10788 PidginConvWindow
*win
;
10790 GList
*l
= g_list_last(pidgin_conv_windows_get_list());
10791 win
= l
? l
->data
: NULL
;;
10794 win
= pidgin_conv_window_new();
10796 g_signal_connect(G_OBJECT(win
->window
), "configure_event",
10797 G_CALLBACK(gtk_conv_configure_cb
), NULL
);
10799 pidgin_conv_window_add_gtkconv(win
, conv
);
10800 pidgin_conv_window_show(win
);
10802 pidgin_conv_window_add_gtkconv(win
, conv
);
10806 /* This one places conversations in the last made window of the same type. */
10808 conv_placement_last_created_win_type_configured_cb(GtkWidget
*w
,
10809 GdkEventConfigure
*event
, PidginConversation
*conv
)
10814 if (gtk_widget_get_visible(w
))
10815 gtk_window_get_position(GTK_WINDOW(w
), &x
, &y
);
10817 return FALSE
; /* carry on normally */
10819 /* Workaround for GTK+ bug # 169811 - "configure_event" is fired
10820 * when the window is being maximized */
10821 if (gdk_window_get_state(gtk_widget_get_window(w
)) & GDK_WINDOW_STATE_MAXIMIZED
)
10824 /* don't save off-screen positioning */
10825 if (x
+ event
->width
< 0 ||
10826 y
+ event
->height
< 0 ||
10827 x
> gdk_screen_width() ||
10828 y
> gdk_screen_height())
10829 return FALSE
; /* carry on normally */
10831 for (all
= conv
->convs
; all
!= NULL
; all
= all
->next
) {
10832 if (PURPLE_IS_IM_CONVERSATION(conv
->active_conv
) != PURPLE_IS_IM_CONVERSATION(all
->data
)) {
10833 /* this window has different types of conversation, don't save */
10838 if (PURPLE_IS_IM_CONVERSATION(conv
->active_conv
)) {
10839 purple_prefs_set_int(PIDGIN_PREFS_ROOT
"/conversations/im/x", x
);
10840 purple_prefs_set_int(PIDGIN_PREFS_ROOT
"/conversations/im/y", y
);
10841 purple_prefs_set_int(PIDGIN_PREFS_ROOT
"/conversations/im/width", event
->width
);
10842 purple_prefs_set_int(PIDGIN_PREFS_ROOT
"/conversations/im/height", event
->height
);
10843 } else if (PURPLE_IS_CHAT_CONVERSATION(conv
->active_conv
)) {
10844 purple_prefs_set_int(PIDGIN_PREFS_ROOT
"/conversations/chat/x", x
);
10845 purple_prefs_set_int(PIDGIN_PREFS_ROOT
"/conversations/chat/y", y
);
10846 purple_prefs_set_int(PIDGIN_PREFS_ROOT
"/conversations/chat/width", event
->width
);
10847 purple_prefs_set_int(PIDGIN_PREFS_ROOT
"/conversations/chat/height", event
->height
);
10854 conv_placement_last_created_win_type(PidginConversation
*conv
)
10856 PidginConvWindow
*win
;
10858 if (PURPLE_IS_IM_CONVERSATION(conv
->active_conv
))
10859 win
= pidgin_conv_window_last_im();
10861 win
= pidgin_conv_window_last_chat();
10864 win
= pidgin_conv_window_new();
10866 if (PURPLE_IS_IM_CONVERSATION(conv
->active_conv
) ||
10867 purple_prefs_get_int(PIDGIN_PREFS_ROOT
"/conversations/chat/width") == 0) {
10868 pidgin_conv_set_position_size(win
,
10869 purple_prefs_get_int(PIDGIN_PREFS_ROOT
"/conversations/im/x"),
10870 purple_prefs_get_int(PIDGIN_PREFS_ROOT
"/conversations/im/y"),
10871 purple_prefs_get_int(PIDGIN_PREFS_ROOT
"/conversations/im/width"),
10872 purple_prefs_get_int(PIDGIN_PREFS_ROOT
"/conversations/im/height"));
10873 } else if (PURPLE_IS_CHAT_CONVERSATION(conv
->active_conv
)) {
10874 pidgin_conv_set_position_size(win
,
10875 purple_prefs_get_int(PIDGIN_PREFS_ROOT
"/conversations/chat/x"),
10876 purple_prefs_get_int(PIDGIN_PREFS_ROOT
"/conversations/chat/y"),
10877 purple_prefs_get_int(PIDGIN_PREFS_ROOT
"/conversations/chat/width"),
10878 purple_prefs_get_int(PIDGIN_PREFS_ROOT
"/conversations/chat/height"));
10881 pidgin_conv_window_add_gtkconv(win
, conv
);
10882 pidgin_conv_window_show(win
);
10884 g_signal_connect(G_OBJECT(win
->window
), "configure_event",
10885 G_CALLBACK(conv_placement_last_created_win_type_configured_cb
), conv
);
10887 pidgin_conv_window_add_gtkconv(win
, conv
);
10890 /* This one places each conversation in its own window. */
10892 conv_placement_new_window(PidginConversation
*conv
)
10894 PidginConvWindow
*win
;
10896 win
= pidgin_conv_window_new();
10898 g_signal_connect(G_OBJECT(win
->window
), "configure_event",
10899 G_CALLBACK(gtk_conv_configure_cb
), NULL
);
10901 pidgin_conv_window_add_gtkconv(win
, conv
);
10903 pidgin_conv_window_show(win
);
10906 static PurpleGroup
*
10907 conv_get_group(PidginConversation
*conv
)
10909 PurpleGroup
*group
= NULL
;
10911 if (PURPLE_IS_IM_CONVERSATION(conv
->active_conv
)) {
10912 PurpleBuddy
*buddy
;
10914 buddy
= purple_blist_find_buddy(purple_conversation_get_account(conv
->active_conv
),
10915 purple_conversation_get_name(conv
->active_conv
));
10918 group
= purple_buddy_get_group(buddy
);
10920 } else if (PURPLE_IS_CHAT_CONVERSATION(conv
->active_conv
)) {
10923 chat
= purple_blist_find_chat(purple_conversation_get_account(conv
->active_conv
),
10924 purple_conversation_get_name(conv
->active_conv
));
10927 group
= purple_chat_get_group(chat
);
10934 * This groups things by, well, group. Buddies from groups will always be
10935 * grouped together, and a buddy from a group not belonging to any currently
10936 * open windows will get a new window.
10939 conv_placement_by_group(PidginConversation
*conv
)
10941 PurpleGroup
*group
= NULL
;
10944 group
= conv_get_group(conv
);
10946 /* Go through the list of IMs and find one with this group. */
10947 for (wl
= pidgin_conv_windows_get_list(); wl
!= NULL
; wl
= wl
->next
) {
10948 PidginConvWindow
*win2
;
10949 PidginConversation
*conv2
;
10950 PurpleGroup
*group2
= NULL
;
10954 for (cl
= win2
->gtkconvs
;
10959 group2
= conv_get_group(conv2
);
10961 if (group
== group2
) {
10962 pidgin_conv_window_add_gtkconv(win2
, conv
);
10969 /* Make a new window. */
10970 conv_placement_new_window(conv
);
10973 /* This groups things by account. Otherwise, the same semantics as above */
10975 conv_placement_by_account(PidginConversation
*conv
)
10977 GList
*wins
, *convs
;
10978 PurpleAccount
*account
;
10980 account
= purple_conversation_get_account(conv
->active_conv
);
10982 /* Go through the list of IMs and find one with this group. */
10983 for (wins
= pidgin_conv_windows_get_list(); wins
!= NULL
; wins
= wins
->next
) {
10984 PidginConvWindow
*win2
;
10985 PidginConversation
*conv2
;
10989 for (convs
= win2
->gtkconvs
;
10991 convs
= convs
->next
) {
10992 conv2
= convs
->data
;
10994 if (account
== purple_conversation_get_account(conv2
->active_conv
)) {
10995 pidgin_conv_window_add_gtkconv(win2
, conv
);
11001 /* Make a new window. */
11002 conv_placement_new_window(conv
);
11005 static ConvPlacementData
*
11006 get_conv_placement_data(const char *id
)
11008 ConvPlacementData
*data
= NULL
;
11011 for (n
= conv_placement_fncs
; n
; n
= n
->next
) {
11013 if (!strcmp(data
->id
, id
))
11021 add_conv_placement_fnc(const char *id
, const char *name
,
11022 PidginConvPlacementFunc fnc
)
11024 ConvPlacementData
*data
;
11026 data
= g_new(ConvPlacementData
, 1);
11028 data
->id
= g_strdup(id
);
11029 data
->name
= g_strdup(name
);
11032 conv_placement_fncs
= g_list_append(conv_placement_fncs
, data
);
11036 ensure_default_funcs(void)
11038 if (conv_placement_fncs
== NULL
) {
11039 add_conv_placement_fnc("last", _("Last created window"),
11040 conv_placement_last_created_win
);
11041 add_conv_placement_fnc("im_chat", _("Separate IM and Chat windows"),
11042 conv_placement_last_created_win_type
);
11043 add_conv_placement_fnc("new", _("New window"),
11044 conv_placement_new_window
);
11045 add_conv_placement_fnc("group", _("By group"),
11046 conv_placement_by_group
);
11047 add_conv_placement_fnc("account", _("By account"),
11048 conv_placement_by_account
);
11053 pidgin_conv_placement_get_options(void)
11055 GList
*n
, *list
= NULL
;
11056 ConvPlacementData
*data
;
11058 ensure_default_funcs();
11060 for (n
= conv_placement_fncs
; n
; n
= n
->next
) {
11062 list
= g_list_append(list
, data
->name
);
11063 list
= g_list_append(list
, data
->id
);
11071 pidgin_conv_placement_add_fnc(const char *id
, const char *name
,
11072 PidginConvPlacementFunc fnc
)
11074 g_return_if_fail(id
!= NULL
);
11075 g_return_if_fail(name
!= NULL
);
11076 g_return_if_fail(fnc
!= NULL
);
11078 ensure_default_funcs();
11080 add_conv_placement_fnc(id
, name
, fnc
);
11084 pidgin_conv_placement_remove_fnc(const char *id
)
11086 ConvPlacementData
*data
= get_conv_placement_data(id
);
11091 conv_placement_fncs
= g_list_remove(conv_placement_fncs
, data
);
11094 g_free(data
->name
);
11099 pidgin_conv_placement_get_name(const char *id
)
11101 ConvPlacementData
*data
;
11103 ensure_default_funcs();
11105 data
= get_conv_placement_data(id
);
11113 PidginConvPlacementFunc
11114 pidgin_conv_placement_get_fnc(const char *id
)
11116 ConvPlacementData
*data
;
11118 ensure_default_funcs();
11120 data
= get_conv_placement_data(id
);
11129 pidgin_conv_placement_set_current_func(PidginConvPlacementFunc func
)
11131 g_return_if_fail(func
!= NULL
);
11133 /* If tabs are enabled, set the function, otherwise, NULL it out. */
11134 if (purple_prefs_get_bool(PIDGIN_PREFS_ROOT
"/conversations/tabs"))
11140 PidginConvPlacementFunc
11141 pidgin_conv_placement_get_current_func(void)
11147 pidgin_conv_placement_place(PidginConversation
*gtkconv
)
11150 place_conv(gtkconv
);
11152 conv_placement_new_window(gtkconv
);
11156 pidgin_conv_is_hidden(PidginConversation
*gtkconv
)
11158 g_return_val_if_fail(gtkconv
!= NULL
, FALSE
);
11160 return (gtkconv
->win
== hidden_convwin
);
11164 gdouble
luminance(GdkRGBA color
)
11167 gdouble rr
, gg
, bb
;
11168 gdouble cutoff
= 0.03928, scale
= 12.92;
11169 gdouble a
= 0.055, d
= 1.055, p
= 2.2;
11175 r
= (rr
> cutoff
) ? pow((rr
+a
)/d
, p
) : rr
/scale
;
11176 g
= (gg
> cutoff
) ? pow((gg
+a
)/d
, p
) : gg
/scale
;
11177 b
= (bb
> cutoff
) ? pow((bb
+a
)/d
, p
) : bb
/scale
;
11179 return (r
*0.2126 + g
*0.7152 + b
*0.0722);
11182 /* Algorithm from https://www.w3.org/TR/2008/REC-WCAG20-20081211/relative-luminance.xml */
11184 color_is_visible(GdkRGBA foreground
, GdkRGBA background
, gdouble min_contrast_ratio
)
11186 gdouble lfg
, lbg
, lmin
, lmax
;
11187 gdouble luminosity_ratio
;
11190 lfg
= luminance(foreground
);
11191 lbg
= luminance(background
);
11194 lmax
= lfg
, lmin
= lbg
;
11196 lmax
= lbg
, lmin
= lfg
;
11198 nr
= lmax
+ 0.05, dr
= lmin
- 0.05;
11202 luminosity_ratio
= nr
/dr
;
11203 if ( luminosity_ratio
< 0)
11204 luminosity_ratio
*= -1.0;
11205 return (luminosity_ratio
> min_contrast_ratio
);
11210 generate_nick_colors(guint numcolors
, GdkRGBA background
)
11212 guint i
= 0, j
= 0;
11213 GArray
*colors
= g_array_new(FALSE
, FALSE
, sizeof(GdkRGBA
));
11214 GdkRGBA nick_highlight
;
11215 GdkRGBA send_color
;
11216 time_t breakout_time
;
11218 gdk_rgba_parse(&nick_highlight
, DEFAULT_HIGHLIGHT_COLOR
);
11219 gdk_rgba_parse(&send_color
, DEFAULT_SEND_COLOR
);
11221 srand(background
.red
* 65535 + background
.green
* 65535 + background
.blue
* 65535 + 1);
11223 breakout_time
= time(NULL
) + 3;
11225 /* first we look through the list of "good" colors: colors that differ from every other color in the
11226 * list. only some of them will differ from the background color though. lets see if we can find
11227 * numcolors of them that do
11229 while (i
< numcolors
&& j
< PIDGIN_NUM_NICK_SEED_COLORS
&& time(NULL
) < breakout_time
)
11231 GdkRGBA color
= nick_seed_colors
[j
];
11233 if (color_is_visible(color
, background
, MIN_LUMINANCE_CONTRAST_RATIO
) &&
11234 color_is_visible(color
, nick_highlight
, MIN_LUMINANCE_CONTRAST_RATIO
) &&
11235 color_is_visible(color
, send_color
, MIN_LUMINANCE_CONTRAST_RATIO
))
11237 g_array_append_val(colors
, color
);
11243 /* we might not have found numcolors in the last loop. if we did, we'll never enter this one.
11244 * if we did not, lets just find some colors that don't conflict with the background. its
11245 * expensive to find colors that not only don't conflict with the background, but also do not
11246 * conflict with each other.
11248 while(i
< numcolors
&& time(NULL
) < breakout_time
)
11250 GdkRGBA color
= {rand() % 65536 / 65535.0, rand() % 65536 / 65535.0, rand() % 65536 / 65535.0, 1};
11252 if (color_is_visible(color
, background
, MIN_LUMINANCE_CONTRAST_RATIO
) &&
11253 color_is_visible(color
, nick_highlight
, MIN_LUMINANCE_CONTRAST_RATIO
) &&
11254 color_is_visible(color
, send_color
, MIN_LUMINANCE_CONTRAST_RATIO
))
11256 g_array_append_val(colors
, color
);
11261 if (i
< numcolors
) {
11262 purple_debug_warning("gtkconv", "Unable to generate enough random colors before timeout. %u colors found.\n", i
);
11266 /* To remove errors caused by an empty array. */
11267 GdkRGBA color
= {0.5, 0.5, 0.5, 1.0};
11268 g_array_append_val(colors
, color
);
11274 /**************************************************************************
11275 * PidginConvWindow GBoxed code
11276 **************************************************************************/
11277 static PidginConvWindow
*
11278 pidgin_conv_window_ref(PidginConvWindow
*win
)
11280 g_return_val_if_fail(win
!= NULL
, NULL
);
11288 pidgin_conv_window_unref(PidginConvWindow
*win
)
11290 g_return_if_fail(win
!= NULL
);
11291 g_return_if_fail(win
->box_count
>= 0);
11293 if (!win
->box_count
--)
11294 pidgin_conv_window_destroy(win
);
11298 pidgin_conv_window_get_type(void)
11300 static GType type
= 0;
11303 type
= g_boxed_type_register_static("PidginConvWindow",
11304 (GBoxedCopyFunc
)pidgin_conv_window_ref
,
11305 (GBoxedFreeFunc
)pidgin_conv_window_unref
);