* Pidgin is the legal property of its developers, whose names are too numerous * to list here. Please refer to the COPYRIGHT file distributed with this * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA #include "theme-loader.h" #include "theme-manager.h" #include "gtkcellrendererexpander.h" #include "gtkstatusbox.h" #include "gtkscrollbook.h" #include "gtksmiley-manager.h" #include "gtkblist-theme.h" #include "gtkblist-theme-loader.h" #include "pidgin/minidialog.h" #include "pidgin/pidginabout.h" #include "pidgin/pidginaccountchooser.h" #include "pidgin/pidgindebug.h" #include "pidgin/pidgindebugplugininfo.h" #include "pidgin/pidgingdkpixbuf.h" #include "pidgin/pidginlog.h" #include "pidgin/pidgintooltip.h" #include <gdk/gdkkeysyms.h> } PidginBlistRequestData ; PidginBlistRequestData rq_data ; GtkWidget * entry_for_alias ; GtkWidget * entry_for_invite ; PidginBlistRequestData rq_data ; gchar * default_chat_name ; PidginChatData chat_data ; /* GBoxed reference count */ /* Used to hold error minidialogs. Gets packed * inside PidginBuddyList.error_buttons PidginScrollBook * error_scrollbook ; /* Pointer to the mini-dialog about having signed on elsewhere, if one * is showing; %NULL otherwise. PidginMiniDialog * signed_on_elsewhere ; PidginBlistTheme * current_theme ; guint select_notebook_page_timeout ; } PidginBuddyListPrivate ; G_DEFINE_TYPE_WITH_PRIVATE ( PidginBuddyList , pidgin_buddy_list , #define PIDGIN_WINDOW_ICONIFIED(x) \ (gdk_window_get_state(gtk_widget_get_window(GTK_WIDGET(x))) & \ GDK_WINDOW_STATE_ICONIFIED) #define PIDGIN_WINDOW_MAXIMIZED(x) \ (gdk_window_get_state(gtk_widget_get_window(GTK_WIDGET(x))) & \ GDK_WINDOW_STATE_MAXIMIZED) static GtkWidget * accountmenu = NULL ; static guint visibility_manager_count = 0 ; static GdkVisibilityState gtk_blist_visibility = GDK_VISIBILITY_UNOBSCURED ; static gboolean gtk_blist_focused = FALSE ; static gboolean editing_blist = FALSE ; static GList * pidgin_blist_sort_methods = NULL ; static struct _PidginBlistSortMethod * current_sort_method = NULL ; static void sort_method_none ( PurpleBlistNode * node , PurpleBuddyList * blist , GtkTreeIter groupiter , GtkTreeIter * cur , GtkTreeIter * iter ); static void sort_method_alphabetical ( PurpleBlistNode * node , PurpleBuddyList * blist , GtkTreeIter groupiter , GtkTreeIter * cur , GtkTreeIter * iter ); static void sort_method_status ( PurpleBlistNode * node , PurpleBuddyList * blist , GtkTreeIter groupiter , GtkTreeIter * cur , GtkTreeIter * iter ); static void sort_method_log_activity ( PurpleBlistNode * node , PurpleBuddyList * blist , GtkTreeIter groupiter , GtkTreeIter * cur , GtkTreeIter * iter ); static guint sort_merge_id ; static GtkActionGroup * sort_action_group = NULL ; static PidginBuddyList * gtkblist = NULL ; static GList * groups_tree ( void ); static gboolean pidgin_blist_refresh_timer ( PurpleBuddyList * list ); static void pidgin_blist_update_buddy ( PurpleBuddyList * list , PurpleBlistNode * node , gboolean status_change ); static void pidgin_blist_selection_changed ( GtkTreeSelection * selection , gpointer data ); static void pidgin_blist_update ( PurpleBuddyList * list , PurpleBlistNode * node ); static void pidgin_blist_update_group ( PurpleBuddyList * list , PurpleBlistNode * node ); static void pidgin_blist_update_contact ( PurpleBuddyList * list , PurpleBlistNode * node ); static char * pidgin_get_tooltip_text ( PurpleBlistNode * node , gboolean full ); static gboolean get_iter_from_node ( PurpleBlistNode * node , GtkTreeIter * iter ); static gboolean buddy_is_displayable ( PurpleBuddy * buddy ); static void redo_buddy_list ( PurpleBuddyList * list , gboolean remove , gboolean rerender ); static void pidgin_blist_collapse_contact_cb ( GtkWidget * w , PurpleBlistNode * node ); static char * pidgin_get_group_title ( PurpleBlistNode * gnode , gboolean expanded ); static void pidgin_blist_expand_contact_cb ( GtkWidget * w , PurpleBlistNode * node ); static void set_urgent ( void ); PIDGIN_BLIST_NODE_HAS_PENDING_MESSAGE = 1 << 0 , /* Whether there's pending message in a conversation */ PIDGIN_BLIST_CHAT_HAS_PENDING_MESSAGE_WITH_NICK = 1 << 1 , /* Whether there's a pending message in a chat that mentions our nick */ GtkTreeRowReference * row ; gboolean contact_expanded ; gboolean recent_signonoff ; gint recent_signonoff_timer ; PurpleConversation * conv ; PidginBlistNodeFlags flags ; /*************************************************** ***************************************************/ static gboolean gtk_blist_visibility_cb ( GtkWidget * w , GdkEventVisibility * event , gpointer data ) GdkVisibilityState old_state = gtk_blist_visibility ; gtk_blist_visibility = event -> state ; if ( gtk_blist_visibility == GDK_VISIBILITY_FULLY_OBSCURED && old_state != GDK_VISIBILITY_FULLY_OBSCURED ) { /* no longer fully obscured */ pidgin_blist_refresh_timer ( purple_blist_get_default ()); /* continue to handle event normally */ static gboolean gtk_blist_window_state_cb ( GtkWidget * w , GdkEventWindowState * event , gpointer data ) if ( event -> changed_mask & GDK_WINDOW_STATE_WITHDRAWN ) { if ( event -> new_window_state & GDK_WINDOW_STATE_WITHDRAWN ) purple_prefs_set_bool ( PIDGIN_PREFS_ROOT "/blist/list_visible" , FALSE ); purple_prefs_set_bool ( PIDGIN_PREFS_ROOT "/blist/list_visible" , TRUE ); pidgin_blist_refresh_timer ( purple_blist_get_default ()); if ( event -> changed_mask & GDK_WINDOW_STATE_MAXIMIZED ) { if ( event -> new_window_state & GDK_WINDOW_STATE_MAXIMIZED ) purple_prefs_set_bool ( PIDGIN_PREFS_ROOT "/blist/list_maximized" , TRUE ); purple_prefs_set_bool ( PIDGIN_PREFS_ROOT "/blist/list_maximized" , FALSE ); /* Refresh gtkblist if un-iconifying */ if ( event -> changed_mask & GDK_WINDOW_STATE_ICONIFIED ){ if ( ! ( event -> new_window_state & GDK_WINDOW_STATE_ICONIFIED )) pidgin_blist_refresh_timer ( purple_blist_get_default ()); static gboolean gtk_blist_delete_cb ( GtkWidget * w , GdkEventAny * event , gpointer data ) if ( visibility_manager_count ) purple_blist_set_visible ( FALSE ); /* we handle everything, event should not propogate further */ gtk_blist_hide_cb ( GtkWidget * widget , PidginBuddyList * gtkblist ) purple_signal_emit ( pidgin_blist_get_handle (), "gtkblist-hiding" , gtkblist ); gtk_blist_show_cb ( GtkWidget * widget , PidginBuddyList * gtkblist ) purple_signal_emit ( pidgin_blist_get_handle (), "gtkblist-unhiding" , gtkblist ); gtk_blist_size_allocate_cb ( GtkWidget * widget , GtkAllocation * allocation , /* ignore changes when maximized */ if ( purple_prefs_get_bool ( PIDGIN_PREFS_ROOT "/blist/list_maximized" )) { gtk_window_get_size ( GTK_WINDOW ( widget ), & new_width , & new_height ); purple_prefs_set_int ( PIDGIN_PREFS_ROOT "/blist/width" , new_width ); purple_prefs_set_int ( PIDGIN_PREFS_ROOT "/blist/height" , new_height ); static void gtk_blist_menu_info_cb ( GtkWidget * w , PurpleBuddy * b ) PurpleAccount * account = purple_buddy_get_account ( b ); pidgin_retrieve_user_info ( purple_account_get_connection ( account ), purple_buddy_get_name ( b )); static void gtk_blist_menu_im_cb ( GtkWidget * w , PurpleBuddy * b ) pidgin_dialogs_im_with_user ( purple_buddy_get_account ( b ), purple_buddy_get_name ( b )); static void gtk_blist_menu_audio_call_cb ( GtkWidget * w , PurpleBuddy * b ) purple_protocol_initiate_media ( purple_buddy_get_account ( b ), purple_buddy_get_name ( b ), PURPLE_MEDIA_AUDIO ); static void gtk_blist_menu_video_call_cb ( GtkWidget * w , PurpleBuddy * b ) /* if the buddy supports both audio and video, start a combined call, otherwise start a pure video session */ if ( purple_protocol_get_media_caps ( purple_buddy_get_account ( b ), purple_buddy_get_name ( b )) & PURPLE_MEDIA_CAPS_AUDIO_VIDEO ) { purple_protocol_initiate_media ( purple_buddy_get_account ( b ), purple_buddy_get_name ( b ), PURPLE_MEDIA_AUDIO | PURPLE_MEDIA_VIDEO ); purple_protocol_initiate_media ( purple_buddy_get_account ( b ), purple_buddy_get_name ( b ), PURPLE_MEDIA_VIDEO ); static void gtk_blist_menu_send_file_cb ( GtkWidget * w , PurpleBuddy * b ) PurpleAccount * account = purple_buddy_get_account ( b ); purple_serv_send_file ( purple_account_get_connection ( account ), purple_buddy_get_name ( b ), NULL ); static void gtk_blist_menu_move_to_cb ( GtkWidget * w , PurpleBlistNode * node ) PurpleGroup * group = g_object_get_data ( G_OBJECT ( w ), "groupnode" ); purple_blist_add_contact (( PurpleContact * ) node , group , NULL ); static void gtk_blist_menu_autojoin_cb ( GtkWidget * w , PurpleChat * chat ) purple_blist_node_set_bool ( PURPLE_BLIST_NODE ( chat ), "gtk-autojoin" , gtk_check_menu_item_get_active ( GTK_CHECK_MENU_ITEM ( w ))); static void gtk_blist_menu_persistent_cb ( GtkWidget * w , PurpleChat * chat ) purple_blist_node_set_bool ( PURPLE_BLIST_NODE ( chat ), "gtk-persistent" , gtk_check_menu_item_get_active ( GTK_CHECK_MENU_ITEM ( w ))); static PurpleConversation * find_conversation_with_buddy ( PurpleBuddy * buddy ) PidginBlistNode * ui = purple_blist_node_get_ui_data ( PURPLE_BLIST_NODE ( buddy )); return PURPLE_CONVERSATION ( purple_conversations_find_im_with_account ( purple_buddy_get_name ( buddy ), purple_buddy_get_account ( buddy ))); static void gtk_blist_join_chat ( PurpleChat * chat ) PurpleConversation * conv ; PurpleProtocol * protocol ; account = purple_chat_get_account ( chat ); protocol = purple_protocols_find ( purple_account_get_protocol_id ( account )); components = purple_chat_get_components ( chat ); chat_name = purple_protocol_chat_iface_get_name ( protocol , components ); name = purple_chat_get_name ( chat ); conv = PURPLE_CONVERSATION ( purple_conversations_find_chat_with_account ( name , pidgin_conv_attach_to_conversation ( conv ); purple_conversation_present ( conv ); purple_serv_join_chat ( purple_account_get_connection ( account ), components ); static void gtk_blist_menu_join_cb ( GtkWidget * w , PurpleChat * chat ) gtk_blist_join_chat ( chat ); static void gtk_blist_renderer_editing_cancelled_cb ( GtkCellRenderer * renderer , PurpleBuddyList * list ) g_object_set ( G_OBJECT ( renderer ), "editable" , FALSE , NULL ); pidgin_blist_refresh ( list ); static void gtk_blist_renderer_editing_started_cb ( GtkCellRenderer * renderer , GtkCellEditable * editable , PidginBuddyList * gtkblist = PIDGIN_BUDDY_LIST ( user_data ); GtkTreePath * path = NULL ; path = gtk_tree_path_new_from_string ( path_str ); gtk_tree_model_get_iter ( GTK_TREE_MODEL ( gtkblist -> treemodel ), & iter , path ); gtk_tree_path_free ( path ); gtk_tree_model_get ( GTK_TREE_MODEL ( gtkblist -> treemodel ), & iter , NODE_COLUMN , & node , -1 ); if ( PURPLE_IS_CONTACT ( node )) text = purple_contact_get_alias ( PURPLE_CONTACT ( node )); else if ( PURPLE_IS_BUDDY ( node )) text = purple_buddy_get_alias ( PURPLE_BUDDY ( node )); else if ( PURPLE_IS_GROUP ( node )) text = purple_group_get_name ( PURPLE_GROUP ( node )); else if ( PURPLE_IS_CHAT ( node )) text = purple_chat_get_name ( PURPLE_CHAT ( node )); if ( GTK_IS_ENTRY ( editable )) { GtkEntry * entry = GTK_ENTRY ( editable ); gtk_entry_set_text ( entry , text ); gtk_blist_do_personize ( GList * merges ) PurpleBlistNode * contact = NULL ; /* First, we find the contact to merge the rest of the buddies into. * This will be the contact with the most buddies in it; ties are broken * by which contact is higher in the list for ( tmp = merges ; tmp ; tmp = tmp -> next ) { PurpleBlistNode * node = tmp -> data ; if ( PURPLE_IS_BUDDY ( node )) node = purple_blist_node_get_parent ( node ); if ( ! PURPLE_IS_CONTACT ( node )) for ( b = purple_blist_node_get_first_child ( node ); b = purple_blist_node_get_sibling_next ( b )) /* Merge all those buddies into this contact */ for ( tmp = merges ; tmp ; tmp = tmp -> next ) { PurpleBlistNode * node = tmp -> data ; if ( PURPLE_IS_BUDDY ( node )) node = purple_blist_node_get_parent ( node ); purple_contact_merge (( PurpleContact * ) node , contact ); /* And show the expanded contact, so the people know what's going on */ pidgin_blist_expand_contact_cb ( NULL , contact ); gtk_blist_auto_personize ( PurpleBlistNode * group , const char * alias ) PurpleBlistNode * contact ; char * a = g_utf8_casefold ( alias , -1 ); for ( contact = purple_blist_node_get_first_child ( group ); contact = purple_blist_node_get_sibling_next ( contact )) { if ( ! PURPLE_IS_CONTACT ( contact )) node_alias = g_utf8_casefold ( purple_contact_get_alias (( PurpleContact * ) contact ), -1 ); if ( node_alias && ! g_utf8_collate ( node_alias , a )) { merges = g_list_append ( merges , contact ); for ( buddy = purple_blist_node_get_first_child ( contact ); buddy = purple_blist_node_get_sibling_next ( buddy )) if ( ! PURPLE_IS_BUDDY ( buddy )) node_alias = g_utf8_casefold ( purple_buddy_get_alias ( PURPLE_BUDDY ( buddy )), -1 ); if ( node_alias && ! g_utf8_collate ( node_alias , a )) { merges = g_list_append ( merges , buddy ); char * msg = g_strdup_printf ( ngettext ( "You have %d contact named %s. Would you like to merge them?" , "You currently have %d contacts named %s. Would you like to merge them?" , i ), i , alias ); purple_request_action ( NULL , NULL , msg , _ ( "Merging these contacts will cause them to share a single entry on the buddy list and use a single conversation window. " "You can separate them again by choosing 'Expand' from the contact's context menu" ), 0 , NULL , merges , 2 , _ ( "_Yes" ), PURPLE_CALLBACK ( gtk_blist_do_personize ), _ ( "_No" ), PURPLE_CALLBACK ( g_list_free )); static void gtk_blist_renderer_edited_cb ( GtkCellRendererText * text_rend , char * arg1 , char * arg2 , PurpleBuddyList * list ) PidginBuddyList * gtkblist = PIDGIN_BUDDY_LIST ( list ); path = gtk_tree_path_new_from_string ( arg1 ); gtk_tree_model_get_iter ( GTK_TREE_MODEL ( gtkblist -> treemodel ), & iter , path ); gtk_tree_path_free ( path ); gtk_tree_model_get ( GTK_TREE_MODEL ( gtkblist -> treemodel ), & iter , NODE_COLUMN , & node , -1 ); gtk_tree_view_set_enable_search ( GTK_TREE_VIEW ( gtkblist -> treeview ), TRUE ); g_object_set ( G_OBJECT ( gtkblist -> text_rend ), "editable" , FALSE , NULL ); if ( PURPLE_IS_CONTACT ( node )) { PurpleContact * contact = PURPLE_CONTACT ( node ); PidginBlistNode * gtknode = ( PidginBlistNode * ) purple_blist_node_get_ui_data ( node ); * Using purple_contact_get_alias here breaks because we * specifically want to check the contact alias only (i.e. not * the priority buddy, which purple_contact_get_alias does). * The "alias" GObject property gives us just the alias. g_object_get ( contact , "alias" , & alias , NULL ); if ( alias || gtknode -> contact_expanded ) { purple_contact_set_alias ( contact , arg2 ); gtk_blist_auto_personize ( purple_blist_node_get_parent ( node ), arg2 ); PurpleBuddy * buddy = purple_contact_get_priority_buddy ( contact ); purple_buddy_set_local_alias ( buddy , arg2 ); purple_serv_alias_buddy ( buddy ); gtk_blist_auto_personize ( purple_blist_node_get_parent ( node ), arg2 ); } else if ( PURPLE_IS_BUDDY ( node )) { PurpleGroup * group = purple_buddy_get_group ( PURPLE_BUDDY ( node )); purple_buddy_set_local_alias ( PURPLE_BUDDY ( node ), arg2 ); purple_serv_alias_buddy ( PURPLE_BUDDY ( node )); gtk_blist_auto_personize ( PURPLE_BLIST_NODE ( group ), arg2 ); } else if ( PURPLE_IS_GROUP ( node )) { dest = purple_blist_find_group ( arg2 ); if ( dest != NULL && purple_utf8_strcasecmp ( arg2 , purple_group_get_name ( PURPLE_GROUP ( node )))) { pidgin_dialogs_merge_groups ( PURPLE_GROUP ( node ), arg2 ); purple_group_set_name ( PURPLE_GROUP ( node ), arg2 ); } else if ( PURPLE_IS_CHAT ( node )) { purple_chat_set_alias ( PURPLE_CHAT ( node ), arg2 ); pidgin_blist_refresh ( list ); chat_components_edit_ok ( PurpleChat * chat , PurpleRequestFields * allfields ) for ( groups = purple_request_fields_get_groups ( allfields ); groups ; groups = groups -> next ) { fields = purple_request_field_group_get_fields ( groups -> data ); for (; fields ; fields = fields -> next ) { PurpleRequestField * field = fields -> data ; id = purple_request_field_get_id ( field ); if ( purple_request_field_get_field_type ( field ) == PURPLE_REQUEST_FIELD_INTEGER ) val = g_strdup_printf ( "%d" , purple_request_field_int_get_value ( field )); val = g_strdup ( purple_request_field_string_get_value ( field )); g_hash_table_remove ( purple_chat_get_components ( chat ), id ); g_hash_table_replace ( purple_chat_get_components ( chat ), g_strdup ( id ), val ); /* val should not be free'd */ static void chat_components_edit ( GtkWidget * w , PurpleBlistNode * node ) PurpleRequestFields * fields = purple_request_fields_new (); PurpleRequestFieldGroup * group = purple_request_field_group_new ( NULL ); PurpleRequestField * field ; PurpleProtocolChatEntry * pce ; PurpleChat * chat = ( PurpleChat * ) node ; purple_request_fields_add_group ( fields , group ); gc = purple_account_get_connection ( purple_chat_get_account ( chat )); parts = purple_protocol_chat_iface_info ( purple_connection_get_protocol ( gc ), gc ); for ( iter = parts ; iter ; iter = iter -> next ) { const char * str = g_hash_table_lookup ( purple_chat_get_components ( chat ), pce -> identifier ); if ( ! str || sscanf ( str , "%d" , & val ) != 1 ) field = purple_request_field_int_new ( pce -> identifier , pce -> label , val , INT_MIN , INT_MAX ); field = purple_request_field_string_new ( pce -> identifier , pce -> label , g_hash_table_lookup ( purple_chat_get_components ( chat ), pce -> identifier ), FALSE ); purple_request_field_string_set_masked ( field , TRUE ); purple_request_field_set_required ( field , TRUE ); purple_request_field_group_add_field ( group , field ); purple_request_fields ( NULL , _ ( "Edit Chat" ), NULL , _ ( "Please update the necessary fields." ), fields , _ ( "Save" ), G_CALLBACK ( chat_components_edit_ok ), _ ( "Cancel" ), NULL , static void gtk_blist_menu_alias_cb ( GtkWidget * w , PurpleBlistNode * node ) if ( ! ( get_iter_from_node ( node , & iter ))) { /* This is either a bug, or the buddy is in a collapsed contact */ node = purple_blist_node_get_parent ( node ); if ( ! get_iter_from_node ( node , & iter )) /* Now it's definitely a bug */ pidgin_blist_tooltip_destroy (); path = gtk_tree_model_get_path ( GTK_TREE_MODEL ( gtkblist -> treemodel ), & iter ); g_object_set ( G_OBJECT ( gtkblist -> text_rend ), "editable" , TRUE , NULL ); gtk_tree_view_set_enable_search ( GTK_TREE_VIEW ( gtkblist -> treeview ), FALSE ); gtk_widget_grab_focus ( gtkblist -> treeview ); gtk_tree_view_set_cursor_on_cell ( GTK_TREE_VIEW ( gtkblist -> treeview ), path , gtkblist -> text_column , gtkblist -> text_rend , TRUE ); gtk_tree_path_free ( path ); static void gtk_blist_menu_bp_cb ( GtkWidget * w , PurpleBuddy * b ) pidgin_pounce_editor_show ( purple_buddy_get_account ( b ), purple_buddy_get_name ( b ), NULL ); static void gtk_blist_menu_showlog_cb ( GtkWidget * w , PurpleBlistNode * node ) pidgin_set_cursor ( gtkblist -> window , GDK_WATCH ); if ( PURPLE_IS_BUDDY ( node )) { PurpleBuddy * b = ( PurpleBuddy * ) node ; name = g_strdup ( purple_buddy_get_name ( b )); account = purple_buddy_get_account ( b ); } else if ( PURPLE_IS_CHAT ( node )) { PurpleChat * c = PURPLE_CHAT ( node ); PurpleProtocol * protocol = NULL ; account = purple_chat_get_account ( c ); protocol = purple_protocols_find ( purple_account_get_protocol_id ( account )); name = purple_protocol_chat_iface_get_name ( protocol , purple_chat_get_components ( c )); } else if ( PURPLE_IS_CONTACT ( node )) { pidgin_log_show_contact ( PURPLE_CONTACT ( node )); pidgin_clear_cursor ( gtkblist -> window ); pidgin_clear_cursor ( gtkblist -> window ); /* This callback should not have been registered for a node * that doesn't match the type of one of the blocks above. */ pidgin_log_show ( type , name , account ); pidgin_clear_cursor ( gtkblist -> window ); static void gtk_blist_menu_showoffline_cb ( GtkWidget * w , PurpleBlistNode * node ) if ( PURPLE_IS_BUDDY ( node )) purple_blist_node_set_bool ( node , "show_offline" , ! purple_blist_node_get_bool ( node , "show_offline" )); pidgin_blist_update ( purple_blist_get_default (), node ); else if ( PURPLE_IS_CONTACT ( node )) gboolean setting = ! purple_blist_node_get_bool ( node , "show_offline" ); purple_blist_node_set_bool ( node , "show_offline" , setting ); for ( bnode = purple_blist_node_get_first_child ( node ); bnode = purple_blist_node_get_sibling_next ( bnode )) purple_blist_node_set_bool ( bnode , "show_offline" , setting ); pidgin_blist_update ( purple_blist_get_default (), bnode ); } else if ( PURPLE_IS_GROUP ( node )) { PurpleBlistNode * cnode , * bnode ; gboolean setting = ! purple_blist_node_get_bool ( node , "show_offline" ); purple_blist_node_set_bool ( node , "show_offline" , setting ); for ( cnode = purple_blist_node_get_first_child ( node ); cnode = purple_blist_node_get_sibling_next ( cnode )) purple_blist_node_set_bool ( cnode , "show_offline" , setting ); for ( bnode = purple_blist_node_get_first_child ( cnode ); bnode = purple_blist_node_get_sibling_next ( bnode )) purple_blist_node_set_bool ( bnode , "show_offline" , setting ); pidgin_blist_update ( purple_blist_get_default (), static void gtk_blist_show_systemlog_cb ( void ) static void gtk_blist_show_onlinehelp_cb ( void ) purple_notify_uri ( NULL , PURPLE_WEBSITE "documentation" ); do_join_chat ( PidginChatData * data ) g_hash_table_new_full ( g_str_hash , g_str_equal , g_free , g_free ); for ( tmp = data -> entries ; tmp != NULL ; tmp = tmp -> next ) if ( g_object_get_data ( tmp -> data , "is_spin" )) g_hash_table_replace ( components , g_strdup ( g_object_get_data ( tmp -> data , "identifier" )), gtk_spin_button_get_value_as_int ( tmp -> data ))); g_hash_table_replace ( components , g_strdup ( g_object_get_data ( tmp -> data , "identifier" )), g_strdup ( gtk_entry_get_text ( tmp -> data ))); chat = purple_chat_new ( data -> rq_data . account , NULL , components ); gtk_blist_join_chat ( chat ); purple_blist_remove_chat ( chat ); do_joinchat ( GtkWidget * dialog , int id , PidginChatData * info ) pidgin_roomlist_dialog_show_with_account ( info -> rq_data . account ); gtk_widget_destroy ( GTK_WIDGET ( dialog )); g_list_free ( info -> entries ); * Check the values of all the text entry boxes. If any required input * strings are empty then don't allow the user to click on "OK." set_sensitive_if_input_chat_cb ( GtkWidget * entry , gpointer user_data ) PurpleProtocol * protocol ; gboolean sensitive = TRUE ; for ( tmp = data -> entries ; tmp != NULL ; tmp = tmp -> next ) if ( ! g_object_get_data ( tmp -> data , "is_spin" )) required = GPOINTER_TO_INT ( g_object_get_data ( tmp -> data , "required" )); text = gtk_entry_get_text ( tmp -> data ); if ( required && ( * text == '\0' )) gtk_dialog_set_response_sensitive ( GTK_DIALOG ( data -> rq_data . window ), GTK_RESPONSE_OK , sensitive ); gc = purple_account_get_connection ( data -> rq_data . account ); protocol = ( gc != NULL ) ? purple_connection_get_protocol ( gc ) : NULL ; sensitive = ( protocol != NULL && PURPLE_PROTOCOL_IMPLEMENTS ( protocol , ROOMLIST , get_list )); gtk_dialog_set_response_sensitive ( GTK_DIALOG ( data -> rq_data . window ), 1 , sensitive ); set_sensitive_if_input_buddy_cb ( GtkWidget * entry , gpointer user_data ) PurpleProtocol * protocol ; PidginAddBuddyData * data = user_data ; protocol = purple_protocols_find ( purple_account_get_protocol_id ( text = gtk_entry_get_text ( GTK_ENTRY ( entry )); gtk_dialog_set_response_sensitive ( GTK_DIALOG ( data -> rq_data . window ), GTK_RESPONSE_OK , purple_validate ( protocol , text )); pidgin_blist_update_privacy_cb ( PurpleBuddy * buddy ) PidginBlistNode * ui_data = purple_blist_node_get_ui_data ( PURPLE_BLIST_NODE ( buddy )); if ( ui_data == NULL || ui_data -> row == NULL ) pidgin_blist_update_buddy ( purple_blist_get_default (), PURPLE_BLIST_NODE ( buddy ), TRUE ); add_buddy_account_filter_func ( PurpleAccount * account ) PurpleConnection * gc = purple_account_get_connection ( account ); PurpleProtocol * protocol = purple_connection_get_protocol ( gc ); return PURPLE_PROTOCOL_IMPLEMENTS ( protocol , SERVER , add_buddy ); chat_account_filter_func ( PurpleAccount * account ) PurpleConnection * gc = purple_account_get_connection ( account ); PurpleProtocol * protocol = NULL ; protocol = purple_connection_get_protocol ( gc ); return ( PURPLE_PROTOCOL_IMPLEMENTS ( protocol , CHAT , info )); pidgin_blist_joinchat_is_showable () for ( c = purple_connections_get_all (); c != NULL ; c = c -> next ) { if ( chat_account_filter_func ( purple_connection_get_account ( gc ))) make_blist_request_dialog ( PidginBlistRequestData * data , PurpleAccount * account , const char * title , const char * window_role , const char * label_text , GCallback callback_func , PurpleFilterAccountFunc filter_func , PidginBuddyList * gtkblist ; img = gtk_image_new_from_icon_name ( "dialog-question" , gtkblist = PIDGIN_BUDDY_LIST ( purple_blist_get_default ()); blist_window = gtkblist ? GTK_WINDOW ( gtkblist -> window ) : NULL ; data -> window = gtk_dialog_new (); gtk_window_set_title ( GTK_WINDOW ( data -> window ), title ); gtk_window_set_transient_for ( GTK_WINDOW ( data -> window ), blist_window ); gtk_dialog_set_default_response ( GTK_DIALOG ( data -> window ), GTK_RESPONSE_OK ); gtk_container_set_border_width ( GTK_CONTAINER ( data -> window ), PIDGIN_HIG_BOX_SPACE ); gtk_window_set_resizable ( GTK_WINDOW ( data -> window ), FALSE ); gtk_box_set_spacing ( GTK_BOX ( gtk_dialog_get_content_area ( GTK_DIALOG ( data -> window ))), gtk_container_set_border_width ( GTK_CONTAINER ( gtk_dialog_get_content_area ( GTK_DIALOG ( data -> window ))), gtk_window_set_role ( GTK_WINDOW ( data -> window ), window_role ); hbox = gtk_box_new ( GTK_ORIENTATION_HORIZONTAL , PIDGIN_HIG_BORDER ); gtk_container_add ( GTK_CONTAINER ( gtk_dialog_get_content_area ( GTK_DIALOG ( data -> window ))), gtk_box_pack_start ( GTK_BOX ( hbox ), img , FALSE , FALSE , 0 ); gtk_widget_set_halign ( img , GTK_ALIGN_START ); gtk_widget_set_valign ( img , GTK_ALIGN_START ); vbox = gtk_box_new ( GTK_ORIENTATION_VERTICAL , 5 ); gtk_container_add ( GTK_CONTAINER ( hbox ), vbox ); label = gtk_label_new ( label_text ); gtk_widget_set_size_request ( label , 400 , -1 ); gtk_label_set_line_wrap ( GTK_LABEL ( label ), TRUE ); gtk_label_set_xalign ( GTK_LABEL ( label ), 0 ); gtk_label_set_yalign ( GTK_LABEL ( label ), 0 ); gtk_box_pack_start ( GTK_BOX ( vbox ), label , FALSE , FALSE , 0 ); data -> sg = gtk_size_group_new ( GTK_SIZE_GROUP_HORIZONTAL ); data -> account_menu = pidgin_account_chooser_new ( account , FALSE ); pidgin_account_chooser_set_filter_func ( PIDGIN_ACCOUNT_CHOOSER ( data -> account_menu ), filter_func ); pidgin_add_widget_to_vbox ( GTK_BOX ( vbox ), _ ( "A_ccount" ), data -> sg , data -> account_menu , TRUE , NULL ); g_signal_connect ( data -> account_menu , "changed" , G_CALLBACK ( callback_func ), data ); data -> vbox = GTK_BOX ( gtk_box_new ( GTK_ORIENTATION_VERTICAL , 5 )); gtk_container_set_border_width ( GTK_CONTAINER ( data -> vbox ), 0 ); gtk_box_pack_start ( GTK_BOX ( vbox ), GTK_WIDGET ( data -> vbox ), FALSE , FALSE , 0 ); g_signal_connect ( G_OBJECT ( data -> window ), "response" , response_cb , data ); g_object_unref ( data -> sg ); rebuild_chat_entries ( PidginChatData * data , const char * default_chat_name ) PurpleProtocol * protocol ; GList * list = NULL , * tmp ; GHashTable * defaults = NULL ; PurpleProtocolChatEntry * pce ; g_return_if_fail ( data -> rq_data . account != NULL ); gc = purple_account_get_connection ( data -> rq_data . account ); protocol = purple_connection_get_protocol ( gc ); gtk_container_foreach ( GTK_CONTAINER ( data -> rq_data . vbox ), ( GtkCallback ) gtk_widget_destroy , NULL ); g_list_free ( data -> entries ); list = purple_protocol_chat_iface_info ( protocol , gc ); defaults = purple_protocol_chat_iface_info_defaults ( protocol , gc , default_chat_name ); for ( tmp = list ; tmp ; tmp = tmp -> next ) adjust = GTK_ADJUSTMENT ( gtk_adjustment_new ( pce -> min , input = gtk_spin_button_new ( adjust , 1 , 0 ); gtk_widget_set_size_request ( input , 50 , -1 ); pidgin_add_widget_to_vbox ( GTK_BOX ( data -> rq_data . vbox ), pce -> label , data -> rq_data . sg , input , FALSE , NULL ); gtk_entry_set_activates_default ( GTK_ENTRY ( input ), TRUE ); value = g_hash_table_lookup ( defaults , pce -> identifier ); gtk_entry_set_text ( GTK_ENTRY ( input ), value ); gtk_entry_set_visibility ( GTK_ENTRY ( input ), FALSE ); pidgin_add_widget_to_vbox ( data -> rq_data . vbox , pce -> label , data -> rq_data . sg , input , TRUE , NULL ); g_signal_connect ( G_OBJECT ( input ), "changed" , G_CALLBACK ( set_sensitive_if_input_chat_cb ), data ); /* Do the following for any type of input widget */ gtk_widget_grab_focus ( input ); g_object_set_data ( G_OBJECT ( input ), "identifier" , ( gpointer ) pce -> identifier ); g_object_set_data ( G_OBJECT ( input ), "is_spin" , GINT_TO_POINTER ( pce -> is_int )); g_object_set_data ( G_OBJECT ( input ), "required" , GINT_TO_POINTER ( pce -> required )); data -> entries = g_list_append ( data -> entries , input ); g_hash_table_destroy ( defaults ); /* Set whether the "OK" button should be clickable initially */ set_sensitive_if_input_chat_cb ( NULL , data ); gtk_widget_show_all ( GTK_WIDGET ( data -> rq_data . vbox )); chat_select_account_cb ( GObject * w , PidginChatData * data ) pidgin_account_chooser_get_selected ( GTK_WIDGET ( w )); g_return_if_fail ( data != NULL ); g_return_if_fail ( account != NULL ); if ( purple_strequal ( purple_account_get_protocol_id ( data -> rq_data . account ), purple_account_get_protocol_id ( account ))) data -> rq_data . account = account ; data -> rq_data . account = account ; rebuild_chat_entries ( data , data -> default_chat_name ); pidgin_blist_joinchat_show ( void ) PidginChatData * data = NULL ; data = g_new0 ( PidginChatData , 1 ); make_blist_request_dialog (( PidginBlistRequestData * ) data , NULL , _ ( "Join a Chat" ), "join_chat" , _ ( "Please enter the appropriate information about the chat " "you would like to join. \n " ), G_CALLBACK ( chat_select_account_cb ), chat_account_filter_func , ( GCallback ) do_joinchat ); gtk_dialog_add_buttons ( GTK_DIALOG ( data -> rq_data . window ), GTK_STOCK_CANCEL , GTK_RESPONSE_CANCEL , PIDGIN_STOCK_CHAT , GTK_RESPONSE_OK , NULL ); gtk_dialog_set_default_response ( GTK_DIALOG ( data -> rq_data . window ), data -> default_chat_name = NULL ; pidgin_account_chooser_get_selected ( data -> rq_data . account_menu ); rebuild_chat_entries ( data , NULL ); gtk_widget_show_all ( data -> rq_data . window ); static void gtk_blist_row_expanded_cb ( GtkTreeView * tv , GtkTreeIter * iter , GtkTreePath * path , gpointer user_data ) PidginBuddyList * gtkblist = PIDGIN_BUDDY_LIST ( user_data ); gtk_tree_model_get ( GTK_TREE_MODEL ( gtkblist -> treemodel ), iter , NODE_COLUMN , & node , -1 ); if ( PURPLE_IS_GROUP ( node )) { title = pidgin_get_group_title ( node , TRUE ); gtk_tree_store_set ( gtkblist -> treemodel , iter , purple_blist_node_set_bool ( node , "collapsed" , FALSE ); pidgin_blist_tooltip_destroy (); static void gtk_blist_row_collapsed_cb ( GtkTreeView * tv , GtkTreeIter * iter , GtkTreePath * path , gpointer user_data ) PidginBuddyList * gtkblist = PIDGIN_BUDDY_LIST ( user_data ); gtk_tree_model_get ( GTK_TREE_MODEL ( gtkblist -> treemodel ), iter , NODE_COLUMN , & node , -1 ); if ( PURPLE_IS_GROUP ( node )) { PidginBlistNode * gtknode ; title = pidgin_get_group_title ( node , FALSE ); gtk_tree_store_set ( gtkblist -> treemodel , iter , purple_blist_node_set_bool ( node , "collapsed" , TRUE ); for ( cnode = purple_blist_node_get_first_child ( node ); cnode ; cnode = purple_blist_node_get_sibling_next ( cnode )) { if ( PURPLE_IS_CONTACT ( cnode )) { gtknode = purple_blist_node_get_ui_data ( cnode ); if ( ! gtknode -> contact_expanded ) gtknode -> contact_expanded = FALSE ; pidgin_blist_update_contact ( NULL , cnode ); pidgin_blist_tooltip_destroy (); } else if ( PURPLE_IS_CONTACT ( node )) { pidgin_blist_collapse_contact_cb ( NULL , node ); static void gtk_blist_row_activated_cb ( GtkTreeView * tv , GtkTreePath * path , GtkTreeViewColumn * col , gpointer data ) { PidginBuddyList * gtkblist = PIDGIN_BUDDY_LIST ( data ); gtk_tree_model_get_iter ( GTK_TREE_MODEL ( gtkblist -> treemodel ), & iter , path ); gtk_tree_model_get ( GTK_TREE_MODEL ( gtkblist -> treemodel ), & iter , NODE_COLUMN , & node , -1 ); if ( PURPLE_IS_CONTACT ( node ) || PURPLE_IS_BUDDY ( node )) { if ( PURPLE_IS_CONTACT ( node )) buddy = purple_contact_get_priority_buddy (( PurpleContact * ) node ); buddy = ( PurpleBuddy * ) node ; pidgin_dialogs_im_with_user ( purple_buddy_get_account ( buddy ), purple_buddy_get_name ( buddy )); } else if ( PURPLE_IS_CHAT ( node )) { gtk_blist_join_chat (( PurpleChat * ) node ); } else if ( PURPLE_IS_GROUP ( node )) { /* if (gtk_tree_view_row_expanded(tv, path)) gtk_tree_view_collapse_row(tv, path); gtk_tree_view_expand_row(tv,path,FALSE);*/ static void pidgin_blist_add_chat_cb ( void ) GtkTreeSelection * sel = gtk_tree_view_get_selection ( GTK_TREE_VIEW ( gtkblist -> treeview )); if ( gtk_tree_selection_get_selected ( sel , NULL , & iter )){ gtk_tree_model_get ( GTK_TREE_MODEL ( gtkblist -> treemodel ), & iter , NODE_COLUMN , & node , -1 ); if ( PURPLE_IS_BUDDY ( node )) purple_blist_request_add_chat ( NULL , purple_buddy_get_group ( PURPLE_BUDDY ( node )), NULL , NULL ); if ( PURPLE_IS_CONTACT ( node ) || PURPLE_IS_CHAT ( node )) purple_blist_request_add_chat ( NULL , purple_contact_get_group ( PURPLE_CONTACT ( node )), NULL , NULL ); else if ( PURPLE_IS_GROUP ( node )) purple_blist_request_add_chat ( NULL , ( PurpleGroup * ) node , NULL , NULL ); purple_blist_request_add_chat ( NULL , NULL , NULL , NULL ); static void pidgin_blist_add_buddy_cb ( void ) GtkTreeSelection * sel = gtk_tree_view_get_selection ( GTK_TREE_VIEW ( gtkblist -> treeview )); if ( gtk_tree_selection_get_selected ( sel , NULL , & iter )){ gtk_tree_model_get ( GTK_TREE_MODEL ( gtkblist -> treemodel ), & iter , NODE_COLUMN , & node , -1 ); if ( PURPLE_IS_BUDDY ( node )) { PurpleGroup * group = purple_buddy_get_group ( PURPLE_BUDDY ( node )); purple_blist_request_add_buddy ( NULL , NULL , purple_group_get_name ( group ), NULL ); } else if ( PURPLE_IS_CONTACT ( node ) || PURPLE_IS_CHAT ( node )) { PurpleGroup * group = purple_contact_get_group ( PURPLE_CONTACT ( node )); purple_blist_request_add_buddy ( NULL , NULL , purple_group_get_name ( group ), NULL ); } else if ( PURPLE_IS_GROUP ( node )) { purple_blist_request_add_buddy ( NULL , NULL , purple_group_get_name ( PURPLE_GROUP ( node )), NULL ); purple_blist_request_add_buddy ( NULL , NULL , NULL , NULL ); pidgin_blist_remove_cb ( GtkWidget * w , PurpleBlistNode * node ) if ( PURPLE_IS_BUDDY ( node )) { pidgin_dialogs_remove_buddy (( PurpleBuddy * ) node ); } else if ( PURPLE_IS_CHAT ( node )) { pidgin_dialogs_remove_chat (( PurpleChat * ) node ); } else if ( PURPLE_IS_GROUP ( node )) { pidgin_dialogs_remove_group (( PurpleGroup * ) node ); } else if ( PURPLE_IS_CONTACT ( node )) { pidgin_dialogs_remove_contact (( PurpleContact * ) node ); scroll_to_expanded_cell ( gpointer data ) struct _expand * ex = data ; gtk_tree_view_scroll_to_cell ( ex -> treeview , ex -> path , NULL , FALSE , 0 , 0 ); pidgin_blist_update_contact ( NULL , ex -> node ); gtk_tree_path_free ( ex -> path ); pidgin_blist_expand_contact_cb ( GtkWidget * w , PurpleBlistNode * node ) PidginBlistNode * gtknode ; GtkTreeIter iter , parent ; if ( ! PURPLE_IS_CONTACT ( node )) gtknode = purple_blist_node_get_ui_data ( node ); gtknode -> contact_expanded = TRUE ; for ( bnode = purple_blist_node_get_first_child ( node ); bnode ; bnode = purple_blist_node_get_sibling_next ( bnode )) { pidgin_blist_update ( NULL , bnode ); /* This ensures that the bottom buddy is visible, i.e. not scrolled off the alignment */ if ( get_iter_from_node ( node , & parent )) { struct _expand * ex = g_new0 ( struct _expand , 1 ); gtk_tree_model_iter_nth_child ( GTK_TREE_MODEL ( gtkblist -> treemodel ), & iter , & parent , gtk_tree_model_iter_n_children ( GTK_TREE_MODEL ( gtkblist -> treemodel ), & parent ) -1 ); path = gtk_tree_model_get_path ( GTK_TREE_MODEL ( gtkblist -> treemodel ), & iter ); /* Let the treeview draw so it knows where to scroll */ ex -> treeview = GTK_TREE_VIEW ( gtkblist -> treeview ); ex -> node = purple_blist_node_get_first_child ( node ); g_idle_add ( scroll_to_expanded_cell , ex ); pidgin_blist_collapse_contact_cb ( GtkWidget * w , PurpleBlistNode * node ) PidginBlistNode * gtknode ; if ( ! PURPLE_IS_CONTACT ( node )) gtknode = purple_blist_node_get_ui_data ( node ); gtknode -> contact_expanded = FALSE ; for ( bnode = purple_blist_node_get_first_child ( node ); bnode ; bnode = purple_blist_node_get_sibling_next ( bnode )) { pidgin_blist_update ( NULL , bnode ); toggle_privacy ( GtkWidget * widget , PurpleBlistNode * node ) if ( ! PURPLE_IS_BUDDY ( node )) buddy = ( PurpleBuddy * ) node ; account = purple_buddy_get_account ( buddy ); name = purple_buddy_get_name ( buddy ); permitted = purple_account_privacy_check ( account , name ); /* XXX: Perhaps ask whether to restore the previous lists where appropirate? */ purple_account_privacy_deny ( account , name ); purple_account_privacy_allow ( account , name ); pidgin_blist_update ( purple_blist_get_default (), node ); void pidgin_append_blist_node_privacy_menu ( GtkWidget * menu , PurpleBlistNode * node ) PurpleBuddy * buddy = ( PurpleBuddy * ) node ; account = purple_buddy_get_account ( buddy ); permitted = purple_account_privacy_check ( account , purple_buddy_get_name ( buddy )); pidgin_new_menu_item ( menu , permitted ? _ ( "_Block" ) : _ ( "Un_block" ), permitted ? PIDGIN_STOCK_TOOLBAR_BLOCK : PIDGIN_STOCK_TOOLBAR_UNBLOCK , G_CALLBACK ( toggle_privacy ), node ); pidgin_append_blist_node_proto_menu ( GtkWidget * menu , PurpleConnection * gc , PurpleProtocol * protocol = purple_connection_get_protocol ( gc ); if ( ! protocol || ! PURPLE_PROTOCOL_IMPLEMENTS ( protocol , CLIENT , blist_node_menu )) for ( l = ll = purple_protocol_client_iface_blist_node_menu ( protocol , node ); l ; l = l -> next ) { PurpleActionMenu * act = ( PurpleActionMenu * ) l -> data ; pidgin_append_menu_action ( menu , act , node ); pidgin_append_blist_node_extended_menu ( GtkWidget * menu , PurpleBlistNode * node ) for ( l = ll = purple_blist_node_get_extended_menu ( node ); l ; l = l -> next ) { PurpleActionMenu * act = ( PurpleActionMenu * ) l -> data ; pidgin_append_menu_action ( menu , act , node ); pidgin_append_blist_node_move_to_menu ( GtkWidget * menu , PurpleBlistNode * node ) menuitem = gtk_menu_item_new_with_label ( _ ( "Move to" )); gtk_menu_shell_append ( GTK_MENU_SHELL ( menu ), menuitem ); gtk_widget_show ( menuitem ); submenu = gtk_menu_new (); gtk_menu_item_set_submenu ( GTK_MENU_ITEM ( menuitem ), submenu ); for ( group = purple_blist_get_default_root (); group ; group = purple_blist_node_get_sibling_next ( group )) { if ( ! PURPLE_IS_GROUP ( group )) if ( group == purple_blist_node_get_parent ( node )) menuitem = pidgin_new_menu_item ( submenu , purple_group_get_name (( PurpleGroup * ) group ), NULL , G_CALLBACK ( gtk_blist_menu_move_to_cb ), node ); g_object_set_data ( G_OBJECT ( menuitem ), "groupnode" , group ); gtk_widget_show_all ( submenu ); pidgin_blist_make_buddy_menu ( GtkWidget * menu , PurpleBuddy * buddy , gboolean sub ) { PurpleAccount * account = NULL ; PurpleConnection * pc = NULL ; PurpleProtocol * protocol ; gboolean contact_expanded = FALSE ; account = purple_buddy_get_account ( buddy ); pc = purple_account_get_connection ( account ); protocol = purple_connection_get_protocol ( pc ); node = PURPLE_BLIST_NODE ( buddy ); contact = purple_buddy_get_contact ( buddy ); PidginBlistNode * node = purple_blist_node_get_ui_data ( PURPLE_BLIST_NODE ( contact )); contact_expanded = node -> contact_expanded ; if ( protocol && PURPLE_PROTOCOL_IMPLEMENTS ( protocol , SERVER , get_info )) { pidgin_new_menu_item ( menu , _ ( "Get _Info" ), PIDGIN_STOCK_TOOLBAR_USER_INFO , G_CALLBACK ( gtk_blist_menu_info_cb ), buddy ); pidgin_new_menu_item ( menu , _ ( "I_M" ), PIDGIN_STOCK_TOOLBAR_MESSAGE_NEW , G_CALLBACK ( gtk_blist_menu_im_cb ), buddy ); if ( protocol && PURPLE_PROTOCOL_IMPLEMENTS ( protocol , MEDIA , get_caps )) { PurpleAccount * account = purple_buddy_get_account ( buddy ); const gchar * who = purple_buddy_get_name ( buddy ); PurpleMediaCaps caps = purple_protocol_get_media_caps ( account , who ); if ( caps & PURPLE_MEDIA_CAPS_AUDIO ) { pidgin_new_menu_item ( menu , _ ( "_Audio Call" ), PIDGIN_STOCK_TOOLBAR_AUDIO_CALL , G_CALLBACK ( gtk_blist_menu_audio_call_cb ), buddy ); if ( caps & PURPLE_MEDIA_CAPS_AUDIO_VIDEO ) { pidgin_new_menu_item ( menu , _ ( "Audio/_Video Call" ), PIDGIN_STOCK_TOOLBAR_VIDEO_CALL , G_CALLBACK ( gtk_blist_menu_video_call_cb ), buddy ); } else if ( caps & PURPLE_MEDIA_CAPS_VIDEO ) { pidgin_new_menu_item ( menu , _ ( "_Video Call" ), PIDGIN_STOCK_TOOLBAR_VIDEO_CALL , G_CALLBACK ( gtk_blist_menu_video_call_cb ), buddy ); if ( protocol && PURPLE_IS_PROTOCOL_XFER ( protocol )) { if ( purple_protocol_xfer_can_receive ( PURPLE_PROTOCOL_XFER ( protocol ), purple_account_get_connection ( purple_buddy_get_account ( buddy )), purple_buddy_get_name ( buddy ) pidgin_new_menu_item ( menu , _ ( "_Send File..." ), PIDGIN_STOCK_TOOLBAR_SEND_FILE , G_CALLBACK ( gtk_blist_menu_send_file_cb ), pidgin_new_menu_item ( menu , _ ( "Add Buddy _Pounce..." ), NULL , G_CALLBACK ( gtk_blist_menu_bp_cb ), buddy ); if ( node -> parent && node -> parent -> child -> next && ! sub && ! contact_expanded ) { pidgin_new_menu_item ( menu , _ ( "View _Log" ), NULL , G_CALLBACK ( gtk_blist_menu_showlog_cb ), contact ); pidgin_new_menu_item ( menu , _ ( "View _Log" ), NULL , G_CALLBACK ( gtk_blist_menu_showlog_cb ), buddy ); if ( ! purple_blist_node_is_transient ( node )) { gboolean show_offline = purple_blist_node_get_bool ( node , "show_offline" ); pidgin_new_menu_item ( menu , show_offline ? _ ( "Hide When Offline" ) : _ ( "Show When Offline" ), NULL , G_CALLBACK ( gtk_blist_menu_showoffline_cb ), node ); pidgin_append_blist_node_proto_menu ( menu , purple_account_get_connection ( purple_buddy_get_account ( buddy )), node ); pidgin_append_blist_node_extended_menu ( menu , node ); if ( ! contact_expanded && contact != NULL ) pidgin_append_blist_node_move_to_menu ( menu , PURPLE_BLIST_NODE ( contact )); if ( node -> parent && node -> parent -> child -> next && ! sub && ! contact_expanded ) { pidgin_append_blist_node_privacy_menu ( menu , node ); pidgin_new_menu_item ( menu , _ ( "_Alias..." ), PIDGIN_STOCK_ALIAS , G_CALLBACK ( gtk_blist_menu_alias_cb ), contact ); pidgin_new_menu_item ( menu , _ ( "_Remove" ), GTK_STOCK_REMOVE , G_CALLBACK ( pidgin_blist_remove_cb ), contact ); } else if ( ! sub || contact_expanded ) { pidgin_append_blist_node_privacy_menu ( menu , node ); pidgin_new_menu_item ( menu , _ ( "_Alias..." ), PIDGIN_STOCK_ALIAS , G_CALLBACK ( gtk_blist_menu_alias_cb ), buddy ); pidgin_new_menu_item ( menu , _ ( "_Remove" ), GTK_STOCK_REMOVE , G_CALLBACK ( pidgin_blist_remove_cb ), buddy ); gtk_blist_key_press_cb ( GtkWidget * tv , GdkEventKey * event , gpointer data ) GtkTreeIter iter , parent ; sel = gtk_tree_view_get_selection ( GTK_TREE_VIEW ( tv )); if ( ! gtk_tree_selection_get_selected ( sel , NULL , & iter )) gtk_tree_model_get ( GTK_TREE_MODEL ( gtkblist -> treemodel ), & iter , NODE_COLUMN , & node , -1 ); if ( event -> state & GDK_CONTROL_MASK && ( event -> keyval == 'o' || event -> keyval == 'O' )) { if ( PURPLE_IS_CONTACT ( node )) { buddy = purple_contact_get_priority_buddy (( PurpleContact * ) node ); } else if ( PURPLE_IS_BUDDY ( node )) { buddy = ( PurpleBuddy * ) node ; pidgin_retrieve_user_info ( purple_account_get_connection ( purple_buddy_get_account ( buddy )), purple_buddy_get_name ( buddy )); gtk_blist_menu_alias_cb ( tv , node ); path = gtk_tree_model_get_path ( GTK_TREE_MODEL ( gtkblist -> treemodel ), & iter ); if ( gtk_tree_view_row_expanded ( GTK_TREE_VIEW ( tv ), path )) { gtk_tree_view_collapse_row ( GTK_TREE_VIEW ( tv ), path ); gtk_tree_path_free ( path ); if ( gtk_tree_model_get_iter ( GTK_TREE_MODEL ( gtkblist -> treemodel ), & iter , path )) { if ( gtk_tree_model_iter_parent ( GTK_TREE_MODEL ( gtkblist -> treemodel ), & parent , & iter )) { gtk_tree_path_free ( path ); path = gtk_tree_model_get_path ( GTK_TREE_MODEL ( gtkblist -> treemodel ), & parent ); gtk_tree_view_set_cursor ( GTK_TREE_VIEW ( tv ), path , NULL , FALSE ); gtk_tree_path_free ( path ); gtk_tree_path_free ( path ); path = gtk_tree_model_get_path ( GTK_TREE_MODEL ( gtkblist -> treemodel ), & iter ); if ( ! gtk_tree_view_row_expanded ( GTK_TREE_VIEW ( tv ), path )) { if ( PURPLE_IS_CONTACT ( node )) { pidgin_blist_expand_contact_cb ( NULL , node ); gtk_tree_path_free ( path ); } else if ( ! PURPLE_IS_BUDDY ( node )) { gtk_tree_view_expand_row ( GTK_TREE_VIEW ( tv ), path , FALSE ); gtk_tree_path_free ( path ); /* Select the First Child */ if ( gtk_tree_model_get_iter ( GTK_TREE_MODEL ( gtkblist -> treemodel ), & parent , path )) { if ( gtk_tree_model_iter_nth_child ( GTK_TREE_MODEL ( gtkblist -> treemodel ), & iter , & parent , 0 )) { gtk_tree_path_free ( path ); path = gtk_tree_model_get_path ( GTK_TREE_MODEL ( gtkblist -> treemodel ), & iter ); gtk_tree_view_set_cursor ( GTK_TREE_VIEW ( tv ), path , NULL , FALSE ); gtk_tree_path_free ( path ); gtk_tree_path_free ( path ); set_node_custom_icon_cb ( const gchar * filename , gpointer data ) PurpleBlistNode * node = ( PurpleBlistNode * ) data ; purple_buddy_icons_node_set_custom_icon_from_file ( node , g_object_set_data ( G_OBJECT ( data ), "buddy-icon-chooser" , NULL ); set_node_custom_icon ( GtkWidget * w , PurpleBlistNode * node ) GtkFileChooserNative * win = g_object_get_data ( G_OBJECT ( node ), "buddy-icon-chooser" ); win = pidgin_buddy_icon_chooser_new ( NULL , set_node_custom_icon_cb , g_object_set_data_full ( G_OBJECT ( node ), "buddy-icon-chooser" , win , gtk_native_dialog_show ( GTK_NATIVE_DIALOG ( win )); remove_node_custom_icon ( GtkWidget * w , PurpleBlistNode * node ) purple_buddy_icons_node_set_custom_icon ( node , NULL , 0 ); add_buddy_icon_menu_items ( GtkWidget * menu , PurpleBlistNode * node ) pidgin_new_menu_item ( menu , _ ( "Set Custom Icon" ), NULL , G_CALLBACK ( set_node_custom_icon ), node ); item = pidgin_new_menu_item ( menu , _ ( "Remove Custom Icon" ), NULL , G_CALLBACK ( remove_node_custom_icon ), node ); if ( ! purple_buddy_icons_node_has_custom_icon ( node )) gtk_widget_set_sensitive ( item , FALSE ); create_group_menu ( PurpleBlistNode * node , PurpleGroup * g ) item = pidgin_new_menu_item ( menu , _ ( "Add _Buddy..." ), GTK_STOCK_ADD , G_CALLBACK ( pidgin_blist_add_buddy_cb ), node ); gtk_widget_set_sensitive ( item , purple_connections_get_all () != NULL ); item = pidgin_new_menu_item ( menu , _ ( "Add C_hat..." ), GTK_STOCK_ADD , G_CALLBACK ( pidgin_blist_add_chat_cb ), node ); gtk_widget_set_sensitive ( item , pidgin_blist_joinchat_is_showable ()); pidgin_new_menu_item ( menu , _ ( "_Delete Group" ), GTK_STOCK_REMOVE , G_CALLBACK ( pidgin_blist_remove_cb ), node ); pidgin_new_menu_item ( menu , _ ( "_Rename" ), NULL , G_CALLBACK ( gtk_blist_menu_alias_cb ), node ); if ( ! purple_blist_node_is_transient ( node )) { gboolean show_offline = purple_blist_node_get_bool ( node , "show_offline" ); pidgin_new_menu_item ( menu , show_offline ? _ ( "Hide When Offline" ) : _ ( "Show When Offline" ), NULL , G_CALLBACK ( gtk_blist_menu_showoffline_cb ), node ); add_buddy_icon_menu_items ( menu , node ); pidgin_append_blist_node_extended_menu ( menu , node ); create_chat_menu ( PurpleBlistNode * node , PurpleChat * c ) gboolean autojoin , persistent ; autojoin = purple_blist_node_get_bool ( node , "gtk-autojoin" ); persistent = purple_blist_node_get_bool ( node , "gtk-persistent" ); pidgin_new_menu_item ( menu , _ ( "_Join" ), PIDGIN_STOCK_CHAT , G_CALLBACK ( gtk_blist_menu_join_cb ), node ); pidgin_new_check_item ( menu , _ ( "Auto-Join" ), G_CALLBACK ( gtk_blist_menu_autojoin_cb ), node , autojoin ); pidgin_new_check_item ( menu , _ ( "Persistent" ), G_CALLBACK ( gtk_blist_menu_persistent_cb ), node , persistent ); pidgin_new_menu_item ( menu , _ ( "View _Log" ), NULL , G_CALLBACK ( gtk_blist_menu_showlog_cb ), node ); pidgin_append_blist_node_proto_menu ( menu , purple_account_get_connection ( purple_chat_get_account ( c )), node ); pidgin_append_blist_node_extended_menu ( menu , node ); pidgin_new_menu_item ( menu , _ ( "_Edit Settings..." ), NULL , G_CALLBACK ( chat_components_edit ), node ); pidgin_new_menu_item ( menu , _ ( "_Alias..." ), PIDGIN_STOCK_ALIAS , G_CALLBACK ( gtk_blist_menu_alias_cb ), node ); pidgin_new_menu_item ( menu , _ ( "_Remove" ), GTK_STOCK_REMOVE , G_CALLBACK ( pidgin_blist_remove_cb ), node ); add_buddy_icon_menu_items ( menu , node ); create_contact_menu ( PurpleBlistNode * node ) pidgin_new_menu_item ( menu , _ ( "View _Log" ), NULL , G_CALLBACK ( gtk_blist_menu_showlog_cb ), pidgin_new_menu_item ( menu , _ ( "_Alias..." ), PIDGIN_STOCK_ALIAS , G_CALLBACK ( gtk_blist_menu_alias_cb ), node ); pidgin_new_menu_item ( menu , _ ( "_Remove" ), GTK_STOCK_REMOVE , G_CALLBACK ( pidgin_blist_remove_cb ), node ); add_buddy_icon_menu_items ( menu , node ); pidgin_new_menu_item ( menu , _ ( "_Collapse" ), GTK_STOCK_ZOOM_OUT , G_CALLBACK ( pidgin_blist_collapse_contact_cb ), pidgin_append_blist_node_extended_menu ( menu , node ); create_buddy_menu ( PurpleBlistNode * node , PurpleBuddy * b ) PidginBlistNode * gtknode = purple_blist_node_get_ui_data ( node ); gboolean show_offline = purple_prefs_get_bool ( PIDGIN_PREFS_ROOT "/blist/show_offline_buddies" ); pidgin_blist_make_buddy_menu ( menu , b , FALSE ); if ( PURPLE_IS_CONTACT ( node )) { add_buddy_icon_menu_items ( menu , node ); if ( gtknode -> contact_expanded ) { pidgin_new_menu_item ( menu , _ ( "_Collapse" ), G_CALLBACK ( pidgin_blist_collapse_contact_cb ), pidgin_new_menu_item ( menu , _ ( "_Expand" ), G_CALLBACK ( pidgin_blist_expand_contact_cb ), for ( bnode = node -> child ; bnode ; bnode = bnode -> next ) { PurpleBuddy * buddy = ( PurpleBuddy * ) bnode ; if ( ! purple_account_get_connection ( purple_buddy_get_account ( buddy ))) if ( ! show_offline && ! PURPLE_BUDDY_IS_ONLINE ( buddy )) menuitem = gtk_image_menu_item_new_with_label ( purple_buddy_get_name ( buddy )); buf = pidgin_create_protocol_icon ( purple_buddy_get_account ( buddy ), PIDGIN_PROTOCOL_ICON_SMALL ); image = gtk_image_new_from_pixbuf ( buf ); g_object_unref ( G_OBJECT ( buf )); gtk_image_menu_item_set_image ( GTK_IMAGE_MENU_ITEM ( menuitem ), gtk_menu_shell_append ( GTK_MENU_SHELL ( menu ), menuitem ); gtk_widget_show ( menuitem ); submenu = gtk_menu_new (); gtk_menu_item_set_submenu ( GTK_MENU_ITEM ( menuitem ), submenu ); gtk_widget_show ( submenu ); pidgin_blist_make_buddy_menu ( submenu , buddy , TRUE ); pidgin_blist_show_context_menu ( GtkWidget * tv , PurpleBlistNode * node , GdkEvent * event ) PidginBlistNode * gtknode = purple_blist_node_get_ui_data ( node ); gboolean handled = FALSE ; /* Create a menu based on the thing we right-clicked on */ if ( PURPLE_IS_GROUP ( node )) { PurpleGroup * g = ( PurpleGroup * ) node ; menu = create_group_menu ( node , g ); } else if ( PURPLE_IS_CHAT ( node )) { PurpleChat * c = ( PurpleChat * ) node ; menu = create_chat_menu ( node , c ); } else if (( PURPLE_IS_CONTACT ( node )) && ( gtknode -> contact_expanded )) { menu = create_contact_menu ( node ); } else if ( PURPLE_IS_CONTACT ( node ) || PURPLE_IS_BUDDY ( node )) { if ( PURPLE_IS_CONTACT ( node )) b = purple_contact_get_priority_buddy (( PurpleContact * ) node ); menu = create_buddy_menu ( node , b ); pidgin_blist_tooltip_destroy (); /* Unhook the tooltip-timeout since we don't want a tooltip * to appear and obscure the context menu we are about to show This is a workaround for GTK+ bug 107320. */ g_source_remove ( gtkblist -> timeout ); /* Now display the menu */ gtk_widget_show_all ( menu ); gtk_menu_popup_at_pointer ( GTK_MENU ( menu ), event ); pidgin_menu_popup_at_treeview_selection ( menu , tv ); gtk_blist_button_press_cb ( GtkWidget * tv , GdkEventButton * event , gpointer user_data ) PurpleProtocol * protocol = NULL ; PidginBlistNode * gtknode ; gboolean handled = FALSE ; /* Here we figure out which node was clicked */ if ( ! gtk_tree_view_get_path_at_pos ( GTK_TREE_VIEW ( tv ), event -> x , event -> y , & path , NULL , NULL , NULL )) gtk_tree_model_get_iter ( GTK_TREE_MODEL ( gtkblist -> treemodel ), & iter , path ); gtk_tree_model_get ( GTK_TREE_MODEL ( gtkblist -> treemodel ), & iter , NODE_COLUMN , & node , -1 ); gtknode = purple_blist_node_get_ui_data ( node ); /* Right click draws a context menu */ if ( gdk_event_triggers_context_menu (( GdkEvent * ) event )) { handled = pidgin_blist_show_context_menu ( tv , node , ( GdkEvent * ) event ); /* CTRL+middle click expands or collapse a contact */ } else if (( event -> button == GDK_BUTTON_MIDDLE ) && ( event -> type == GDK_BUTTON_PRESS ) && ( event -> state & GDK_CONTROL_MASK ) && ( PURPLE_IS_CONTACT ( node ))) { if ( gtknode -> contact_expanded ) pidgin_blist_collapse_contact_cb ( NULL , node ); pidgin_blist_expand_contact_cb ( NULL , node ); /* Double middle click gets info */ } else if (( event -> button == GDK_BUTTON_MIDDLE ) && ( event -> type == GDK_2BUTTON_PRESS ) && (( PURPLE_IS_CONTACT ( node )) || ( PURPLE_IS_BUDDY ( node )))) { if ( PURPLE_IS_CONTACT ( node )) b = purple_contact_get_priority_buddy (( PurpleContact * ) node ); protocol = purple_protocols_find ( purple_account_get_protocol_id ( purple_buddy_get_account ( b ))); if ( protocol && PURPLE_PROTOCOL_IMPLEMENTS ( protocol , SERVER , get_info )) pidgin_retrieve_user_info ( purple_account_get_connection ( purple_buddy_get_account ( b )), purple_buddy_get_name ( b )); * This code only exists because GTK+ doesn't work. If we return * FALSE here, as would be normal the event propoagates down and * somehow gets interpreted as the start of a drag event. * Um, isn't it _normal_ to return TRUE here? Since the event sel = gtk_tree_view_get_selection ( GTK_TREE_VIEW ( tv )); gtk_tree_selection_select_path ( sel , path ); gtk_tree_path_free ( path ); gtk_tree_path_free ( path ); pidgin_blist_popup_menu_cb ( GtkWidget * tv , void * user_data ) gboolean handled = FALSE ; sel = gtk_tree_view_get_selection ( GTK_TREE_VIEW ( tv )); if ( ! gtk_tree_selection_get_selected ( sel , NULL , & iter )) gtk_tree_model_get ( GTK_TREE_MODEL ( gtkblist -> treemodel ), & iter , NODE_COLUMN , & node , -1 ); /* Shift+F10 draws a context menu */ handled = pidgin_blist_show_context_menu ( tv , node , NULL ); static void gtk_blist_show_xfer_dialog_cb ( GtkAction * item , gpointer data ) pidgin_xfer_dialog_show ( NULL ); static void pidgin_blist_buddy_details_cb ( GtkToggleAction * item , gpointer data ) pidgin_set_cursor ( gtkblist -> window , GDK_WATCH ); purple_prefs_set_bool ( PIDGIN_PREFS_ROOT "/blist/show_buddy_icons" , gtk_toggle_action_get_active ( item )); pidgin_clear_cursor ( gtkblist -> window ); static void pidgin_blist_show_idle_time_cb ( GtkToggleAction * item , gpointer data ) pidgin_set_cursor ( gtkblist -> window , GDK_WATCH ); purple_prefs_set_bool ( PIDGIN_PREFS_ROOT "/blist/show_idle_time" , gtk_toggle_action_get_active ( item )); pidgin_clear_cursor ( gtkblist -> window ); static void pidgin_blist_show_protocol_icons_cb ( GtkToggleAction * item , gpointer data ) purple_prefs_set_bool ( PIDGIN_PREFS_ROOT "/blist/show_protocol_icons" , gtk_toggle_action_get_active ( item )); static void pidgin_blist_show_empty_groups_cb ( GtkToggleAction * item , gpointer data ) pidgin_set_cursor ( gtkblist -> window , GDK_WATCH ); purple_prefs_set_bool ( PIDGIN_PREFS_ROOT "/blist/show_empty_groups" , gtk_toggle_action_get_active ( item )); pidgin_clear_cursor ( gtkblist -> window ); static void pidgin_blist_edit_mode_cb ( GtkToggleAction * checkitem , gpointer data ) pidgin_set_cursor ( gtkblist -> window , GDK_WATCH ); purple_prefs_set_bool ( PIDGIN_PREFS_ROOT "/blist/show_offline_buddies" , gtk_toggle_action_get_active ( checkitem )); pidgin_clear_cursor ( gtkblist -> window ); static void pidgin_blist_mute_sounds_cb ( GtkToggleAction * item , gpointer data ) purple_prefs_set_bool ( PIDGIN_PREFS_ROOT "/sound/mute" , gtk_toggle_action_get_active ( item )); pidgin_blist_mute_pref_cb ( const char * name , PurplePrefType type , gconstpointer value , gpointer data ) gtk_toggle_action_set_active ( GTK_TOGGLE_ACTION ( gtk_ui_manager_get_action ( gtkblist -> ui , "/BList/ToolsMenu/MuteSounds" )), ( gboolean ) GPOINTER_TO_INT ( value )); pidgin_blist_sound_method_pref_cb ( const char * name , PurplePrefType type , gconstpointer value , gpointer data ) gboolean sensitive = TRUE ; if ( purple_strequal ( value , "none" )) gtk_action_set_sensitive ( gtk_ui_manager_get_action ( gtkblist -> ui , "/BList/ToolsMenu/MuteSounds" ), sensitive ); add_buddies_from_vcard ( const char * protocol_id , PurpleGroup * group , GList * list , PurpleAccount * account = NULL ; for ( l = purple_connections_get_all (); l != NULL ; l = l -> next ) gc = ( PurpleConnection * ) l -> data ; account = purple_connection_get_account ( gc ); if ( purple_strequal ( purple_account_get_protocol_id ( account ), protocol_id )) for ( l = list ; l != NULL ; l = l -> next ) purple_blist_request_add_buddy ( account , l -> data , ( group ? purple_group_get_name ( group ) : NULL ), g_list_free_full ( list , g_free ); parse_vcard ( const char * vcard , PurpleGroup * group ) s = temp_vcard = g_strdup ( vcard ); while ( * s != '\0' && strncmp ( s , "END:vCard" , strlen ( "END:vCard" ))) while ( * s != '\r' && * s != '\n' && * s != '\0' && * s != ':' ) if ( * s != '\0' ) * s ++ = '\0' ; if (( c = strchr ( field , ';' )) != NULL ) /* Proceed to the end of the line */ while ( * s != '\r' && * s != '\n' && * s != '\0' ) if ( * s == '\r' ) * s ++ = '\0' ; if ( * s == '\n' ) * s ++ = '\0' ; /* We only want to worry about a few fields here. */ if ( purple_strequal ( field , "FN" )) else if ( purple_strequal ( field , "X-AIM" ) || purple_strequal ( field , "X-ICQ" ) || purple_strequal ( field , "X-JABBER" )) char ** values = g_strsplit ( value , ":" , 0 ); for ( im = values ; * im != NULL ; im ++ ) if ( purple_strequal ( field , "X-AIM" )) aims = g_list_append ( aims , g_strdup ( * im )); else if ( purple_strequal ( field , "X-ICQ" )) icqs = g_list_append ( icqs , g_strdup ( * im )); else if ( purple_strequal ( field , "X-JABBER" )) jabbers = g_list_append ( jabbers , g_strdup ( * im )); if ( aims == NULL && icqs == NULL && jabbers == NULL ) add_buddies_from_vcard ( "prpl-aim" , group , aims , alias ); add_buddies_from_vcard ( "prpl-icq" , group , icqs , alias ); add_buddies_from_vcard ( "prpl-jabber" , group , jabbers , alias ); static void pidgin_blist_drag_begin ( GtkWidget * widget , GdkDragContext * drag_context , gpointer user_data ) pidgin_blist_tooltip_destroy (); /* Unhook the tooltip-timeout since we don't want a tooltip * to appear and obscure the dragging operation. * This is a workaround for GTK+ bug 107320. */ g_source_remove ( gtkblist -> timeout ); static void pidgin_blist_drag_data_get_cb ( GtkWidget * widget , GdkAtom target = gtk_selection_data_get_target ( data ); if ( target == gdk_atom_intern ( "PURPLE_BLIST_NODE" , FALSE )) { GtkTreeRowReference * ref = g_object_get_data ( G_OBJECT ( dc ), "gtk-tree-view-source-row" ); GtkTreePath * sourcerow = gtk_tree_row_reference_get_path ( ref ); PurpleBlistNode * node = NULL ; gtk_tree_model_get_iter ( GTK_TREE_MODEL ( gtkblist -> treemodel ), & iter , sourcerow ); gtk_tree_model_get ( GTK_TREE_MODEL ( gtkblist -> treemodel ), & iter , NODE_COLUMN , & node , -1 ); gtk_selection_data_set ( data , gdk_atom_intern ( "PURPLE_BLIST_NODE" , FALSE ), gtk_tree_path_free ( sourcerow ); } else if ( target == gdk_atom_intern ( "application/x-im-contact" , FALSE )) { GtkTreeRowReference * ref ; PurpleBlistNode * node = NULL ; ref = g_object_get_data ( G_OBJECT ( dc ), "gtk-tree-view-source-row" ); sourcerow = gtk_tree_row_reference_get_path ( ref ); gtk_tree_model_get_iter ( GTK_TREE_MODEL ( gtkblist -> treemodel ), & iter , gtk_tree_model_get ( GTK_TREE_MODEL ( gtkblist -> treemodel ), & iter , NODE_COLUMN , & node , -1 ); if ( PURPLE_IS_CONTACT ( node )) buddy = purple_contact_get_priority_buddy (( PurpleContact * ) node ); else if ( ! PURPLE_IS_BUDDY ( node )) gtk_tree_path_free ( sourcerow ); buddy = ( PurpleBuddy * ) node ; gc = purple_account_get_connection ( purple_buddy_get_account ( buddy )); gtk_tree_path_free ( sourcerow ); purple_protocol_class_list_icon ( purple_connection_get_protocol ( gc ), purple_buddy_get_account ( buddy ), buddy ); str = g_string_new ( NULL ); "Content-Type: application/x-im-contact \r\n " purple_buddy_get_name ( buddy )); if ( purple_buddy_get_local_alias ( buddy ) != NULL ) g_string_append_printf ( str , purple_buddy_get_local_alias ( buddy )); g_string_append ( str , " \r\n " ); gtk_selection_data_set ( data , gdk_atom_intern ( "application/x-im-contact" , FALSE ), ( const guchar * ) str -> str , g_string_free ( str , TRUE ); gtk_tree_path_free ( sourcerow ); static void pidgin_blist_drag_data_rcv_cb ( GtkWidget * widget , GdkDragContext * dc , guint x , guint y , GtkSelectionData * sd , guint info , guint t ) GdkAtom target = gtk_selection_data_get_target ( sd ); const guchar * data = gtk_selection_data_get_data ( sd ); if ( gtkblist -> drag_timeout ) { g_source_remove ( gtkblist -> drag_timeout ); gtkblist -> drag_timeout = 0 ; if ( target == gdk_atom_intern ( "PURPLE_BLIST_NODE" , FALSE ) && data ) { PurpleBlistNode * n = NULL ; GtkTreePath * path = NULL ; GtkTreeViewDropPosition position ; memcpy ( & n , data , sizeof ( n )); if ( gtk_tree_view_get_dest_row_at_pos ( GTK_TREE_VIEW ( widget ), x , y , & path , & position )) { /* if we're here, I think it means the drop is ok */ PidginBlistNode * gtknode ; gtk_tree_model_get_iter ( GTK_TREE_MODEL ( gtkblist -> treemodel ), gtk_tree_model_get ( GTK_TREE_MODEL ( gtkblist -> treemodel ), & iter , NODE_COLUMN , & node , -1 ); gtknode = purple_blist_node_get_ui_data ( node ); if ( PURPLE_IS_CONTACT ( n )) { PurpleContact * c = ( PurpleContact * ) n ; if ( PURPLE_IS_CONTACT ( node ) && gtknode -> contact_expanded ) { purple_contact_merge ( c , node ); } else if ( PURPLE_IS_CONTACT ( node ) || case GTK_TREE_VIEW_DROP_AFTER : case GTK_TREE_VIEW_DROP_INTO_OR_AFTER : purple_blist_add_contact ( c , ( PurpleGroup * ) node -> parent , case GTK_TREE_VIEW_DROP_BEFORE : case GTK_TREE_VIEW_DROP_INTO_OR_BEFORE : purple_blist_add_contact ( c , ( PurpleGroup * ) node -> parent , } else if ( PURPLE_IS_GROUP ( node )) { purple_blist_add_contact ( c , ( PurpleGroup * ) node , NULL ); } else if ( PURPLE_IS_BUDDY ( node )) { purple_contact_merge ( c , node ); } else if ( PURPLE_IS_BUDDY ( n )) { PurpleBuddy * b = ( PurpleBuddy * ) n ; if ( PURPLE_IS_BUDDY ( node )) { case GTK_TREE_VIEW_DROP_AFTER : case GTK_TREE_VIEW_DROP_INTO_OR_AFTER : purple_blist_add_buddy ( b , ( PurpleContact * ) node -> parent , ( PurpleGroup * ) node -> parent -> parent , node ); case GTK_TREE_VIEW_DROP_BEFORE : case GTK_TREE_VIEW_DROP_INTO_OR_BEFORE : purple_blist_add_buddy ( b , ( PurpleContact * ) node -> parent , ( PurpleGroup * ) node -> parent -> parent , } else if ( PURPLE_IS_CHAT ( node )) { purple_blist_add_buddy ( b , NULL , ( PurpleGroup * ) node -> parent , } else if ( PURPLE_IS_GROUP ( node )) { purple_blist_add_buddy ( b , NULL , ( PurpleGroup * ) node , NULL ); } else if ( PURPLE_IS_CONTACT ( node )) { if ( gtknode -> contact_expanded ) { case GTK_TREE_VIEW_DROP_INTO_OR_AFTER : case GTK_TREE_VIEW_DROP_AFTER : case GTK_TREE_VIEW_DROP_INTO_OR_BEFORE : purple_blist_add_buddy ( b , ( PurpleContact * ) node , ( PurpleGroup * ) node -> parent , NULL ); case GTK_TREE_VIEW_DROP_BEFORE : purple_blist_add_buddy ( b , NULL , ( PurpleGroup * ) node -> parent , node -> prev ); case GTK_TREE_VIEW_DROP_INTO_OR_AFTER : case GTK_TREE_VIEW_DROP_AFTER : purple_blist_add_buddy ( b , NULL , ( PurpleGroup * ) node -> parent , NULL ); case GTK_TREE_VIEW_DROP_INTO_OR_BEFORE : case GTK_TREE_VIEW_DROP_BEFORE : purple_blist_add_buddy ( b , NULL , ( PurpleGroup * ) node -> parent , node -> prev ); } else if ( PURPLE_IS_CHAT ( n )) { PurpleChat * chat = ( PurpleChat * ) n ; if ( PURPLE_IS_BUDDY ( node )) { case GTK_TREE_VIEW_DROP_AFTER : case GTK_TREE_VIEW_DROP_INTO_OR_AFTER : case GTK_TREE_VIEW_DROP_BEFORE : case GTK_TREE_VIEW_DROP_INTO_OR_BEFORE : purple_blist_add_chat ( chat , ( PurpleGroup * ) node -> parent -> parent , } else if ( PURPLE_IS_CONTACT ( node ) || case GTK_TREE_VIEW_DROP_AFTER : case GTK_TREE_VIEW_DROP_INTO_OR_AFTER : purple_blist_add_chat ( chat , ( PurpleGroup * ) node -> parent , node ); case GTK_TREE_VIEW_DROP_BEFORE : case GTK_TREE_VIEW_DROP_INTO_OR_BEFORE : purple_blist_add_chat ( chat , ( PurpleGroup * ) node -> parent , node -> prev ); } else if ( PURPLE_IS_GROUP ( node )) { purple_blist_add_chat ( chat , ( PurpleGroup * ) node , NULL ); } else if ( PURPLE_IS_GROUP ( n )) { PurpleGroup * g = ( PurpleGroup * ) n ; if ( PURPLE_IS_GROUP ( node )) { case GTK_TREE_VIEW_DROP_INTO_OR_AFTER : case GTK_TREE_VIEW_DROP_AFTER : purple_blist_add_group ( g , node ); case GTK_TREE_VIEW_DROP_INTO_OR_BEFORE : case GTK_TREE_VIEW_DROP_BEFORE : purple_blist_add_group ( g , node -> prev ); } else if ( PURPLE_IS_BUDDY ( node )) { purple_blist_add_group ( g , node -> parent -> parent ); } else if ( PURPLE_IS_CONTACT ( node ) || purple_blist_add_group ( g , node -> parent ); gtk_tree_path_free ( path ); gtk_drag_finish ( dc , TRUE , ( gdk_drag_context_get_actions ( dc ) == GDK_ACTION_MOVE ), t ); } else if ( target == gdk_atom_intern ( "application/x-im-contact" , PurpleGroup * group = NULL ; GtkTreePath * path = NULL ; GtkTreeViewDropPosition position ; if ( gtk_tree_view_get_dest_row_at_pos ( GTK_TREE_VIEW ( widget ), gtk_tree_model_get_iter ( GTK_TREE_MODEL ( gtkblist -> treemodel ), gtk_tree_model_get ( GTK_TREE_MODEL ( gtkblist -> treemodel ), & iter , NODE_COLUMN , & node , -1 ); if ( PURPLE_IS_BUDDY ( node )) group = ( PurpleGroup * ) node -> parent -> parent ; else if ( PURPLE_IS_CHAT ( node ) || group = ( PurpleGroup * ) node -> parent ; else if ( PURPLE_IS_GROUP ( node )) group = ( PurpleGroup * ) node ; if ( pidgin_parse_x_im_contact (( const char * ) data , FALSE , & account , & protocol , & username , & alias )) purple_notify_error ( NULL , NULL , _ ( "You are not currently signed on with an account that " "can add that buddy." ), NULL , NULL ); purple_blist_request_add_buddy ( account , username , ( group ? purple_group_get_name ( group ) : NULL ), gtk_tree_path_free ( path ); gtk_drag_finish ( dc , TRUE , gdk_drag_context_get_actions ( dc ) == GDK_ACTION_MOVE , t ); else if ( target == gdk_atom_intern ( "text/x-vcard" , FALSE ) && data ) PurpleGroup * group = NULL ; GtkTreePath * path = NULL ; GtkTreeViewDropPosition position ; if ( gtk_tree_view_get_dest_row_at_pos ( GTK_TREE_VIEW ( widget ), gtk_tree_model_get_iter ( GTK_TREE_MODEL ( gtkblist -> treemodel ), gtk_tree_model_get ( GTK_TREE_MODEL ( gtkblist -> treemodel ), & iter , NODE_COLUMN , & node , -1 ); if ( PURPLE_IS_BUDDY ( node )) group = ( PurpleGroup * ) node -> parent -> parent ; else if ( PURPLE_IS_CHAT ( node ) || group = ( PurpleGroup * ) node -> parent ; else if ( PURPLE_IS_GROUP ( node )) group = ( PurpleGroup * ) node ; result = parse_vcard (( const gchar * ) data , group ); gtk_drag_finish ( dc , result , gdk_drag_context_get_actions ( dc ) == GDK_ACTION_MOVE , t ); } else if ( target == gdk_atom_intern ( "text/uri-list" , FALSE ) && data ) { GtkTreePath * path = NULL ; GtkTreeViewDropPosition position ; if ( gtk_tree_view_get_dest_row_at_pos ( GTK_TREE_VIEW ( widget ), gtk_tree_model_get_iter ( GTK_TREE_MODEL ( gtkblist -> treemodel ), gtk_tree_model_get ( GTK_TREE_MODEL ( gtkblist -> treemodel ), & iter , NODE_COLUMN , & node , -1 ); if ( PURPLE_IS_BUDDY ( node ) || PURPLE_IS_CONTACT ( node )) { PurpleBuddy * b = PURPLE_IS_BUDDY ( node ) ? PURPLE_BUDDY ( node ) : purple_contact_get_priority_buddy ( PURPLE_CONTACT ( node )); pidgin_dnd_file_manage ( sd , purple_buddy_get_account ( b ), purple_buddy_get_name ( b )); gtk_drag_finish ( dc , TRUE , gdk_drag_context_get_actions ( dc ) == GDK_ACTION_MOVE , t ); gtk_drag_finish ( dc , FALSE , FALSE , t ); /* Altered from do_colorshift in gnome-panel */ do_alphashift ( GdkPixbuf * pixbuf , int shift ) gint width , height , padding ; if ( ! gdk_pixbuf_get_has_alpha ( pixbuf )) width = gdk_pixbuf_get_width ( pixbuf ); height = gdk_pixbuf_get_height ( pixbuf ); padding = gdk_pixbuf_get_rowstride ( pixbuf ) - width * 4 ; pixels = gdk_pixbuf_get_pixels ( pixbuf ); for ( i = 0 ; i < height ; i ++ ) { for ( j = 0 ; j < width ; j ++ ) { * ( pixels ++ ) = CLAMP ( val , 0 , 255 ); static GdkPixbuf * pidgin_blist_get_buddy_icon ( PurpleBlistNode * node , gboolean scaled , gboolean greyed ) PurpleBuddy * buddy = NULL ; PurpleGroup * group = NULL ; const guchar * data = NULL ; GdkPixbuf * buf , * ret = NULL ; PurpleBuddyIcon * icon = NULL ; PurpleAccount * account = NULL ; PurpleContact * contact = NULL ; PurpleProtocol * protocol = NULL ; PurpleBuddyIconSpec * icon_spec = NULL ; gint orig_width , orig_height , scale_width , scale_height ; if ( PURPLE_IS_CONTACT ( node )) { buddy = purple_contact_get_priority_buddy (( PurpleContact * ) node ); contact = ( PurpleContact * ) node ; } else if ( PURPLE_IS_BUDDY ( node )) { buddy = ( PurpleBuddy * ) node ; contact = purple_buddy_get_contact ( buddy ); } else if ( PURPLE_IS_GROUP ( node )) { group = ( PurpleGroup * ) node ; } else if ( PURPLE_IS_CHAT ( node )) { /* We don't need to do anything here. We just need to not fall * into the else block and return. */ account = purple_buddy_get_account ( buddy ); if ( account && purple_account_get_connection ( account )) { protocol = purple_connection_get_protocol ( purple_account_get_connection ( account )); if (!purple_prefs_get_bool(PIDGIN_PREFS_ROOT "/blist/show_buddy_icons")) /* If we have a contact then this is either a contact or a buddy and * we want to fetch the custom icon for the contact. If we don't have * a contact then this is a group or some other type of node and we * want to use that directly. */ custom_img = purple_buddy_icons_node_find_custom_icon ( PURPLE_BLIST_NODE ( contact )); custom_img = purple_buddy_icons_node_find_custom_icon ( node ); data = purple_image_get_data ( custom_img ); len = purple_image_get_data_size ( custom_img ); /* Not sure I like this...*/ if ( ! ( icon = purple_buddy_icons_find ( purple_buddy_get_account ( buddy ), purple_buddy_get_name ( buddy )))) data = purple_buddy_icon_get_data ( icon , & len ); buf = pidgin_pixbuf_from_data ( data , len ); purple_buddy_icon_unref ( icon ); purple_debug_warning ( "gtkblist" , "Couldn't load buddy icon on " "account %s (%s); buddyname=%s; custom_img_size=%" G_GSIZE_FORMAT , account ? purple_account_get_username ( account ) : "(no account)" , account ? purple_account_get_protocol_id ( account ) : "(no account)" , buddy ? purple_buddy_get_name ( buddy ) : "(no buddy)" , custom_img ? purple_image_get_data_size ( custom_img ) : 0 ); g_object_unref ( custom_img ); g_object_unref ( custom_img ); gboolean offline = FALSE , idle = FALSE ; PurplePresence * presence = purple_buddy_get_presence ( buddy ); if ( ! PURPLE_BUDDY_IS_ONLINE ( buddy )) if ( purple_presence_is_idle ( presence )) if ( purple_counting_node_get_online_count ( PURPLE_COUNTING_NODE ( group )) == 0 ) gdk_pixbuf_saturate_and_pixelate ( buf , buf , 0.0 , FALSE ); gdk_pixbuf_saturate_and_pixelate ( buf , buf , 0.25 , FALSE ); /* I'd use the pidgin_buddy_icon_get_scale_size() thing, but it won't * tell me the original size, which I need for scaling purposes. */ scale_width = orig_width = gdk_pixbuf_get_width ( buf ); scale_height = orig_height = gdk_pixbuf_get_height ( buf ); icon_spec = purple_protocol_get_icon_spec ( protocol ); if ( icon_spec && icon_spec -> scale_rules & PURPLE_ICON_SCALE_DISPLAY ) purple_buddy_icon_spec_get_scaled_size ( purple_protocol_get_icon_spec ( protocol ), & scale_width , & scale_height ); if ( scaled || scale_height > 200 || scale_width > 200 ) { float scale_size = scaled ? 32.0 : 200.0 ; if ( scale_height > scale_width ) { scale_width = scale_size * ( double ) scale_width / ( double ) scale_height ; scale_height = scale_size ; scale_height = scale_size * ( double ) scale_height / ( double ) scale_width ; scale_width = scale_size ; /* Scale & round before making square, so rectangular (but * non-square) images get rounded corners too. */ tmpbuf = gdk_pixbuf_new ( GDK_COLORSPACE_RGB , TRUE , 8 , scale_width , scale_height ); gdk_pixbuf_fill ( tmpbuf , 0x00000000 ); gdk_pixbuf_scale ( buf , tmpbuf , 0 , 0 , scale_width , scale_height , 0 , 0 , ( double ) scale_width / ( double ) orig_width , ( double ) scale_height / ( double ) orig_height , GDK_INTERP_BILINEAR ); if ( pidgin_gdk_pixbuf_is_opaque ( tmpbuf )) pidgin_gdk_pixbuf_make_round ( tmpbuf ); ret = gdk_pixbuf_new ( GDK_COLORSPACE_RGB , TRUE , 8 , scale_size , scale_size ); gdk_pixbuf_fill ( ret , 0x00000000 ); gdk_pixbuf_copy_area ( tmpbuf , 0 , 0 , scale_width , scale_height , ret , ( scale_size - scale_width ) / 2 , ( scale_size - scale_height ) / 2 ); g_object_unref ( G_OBJECT ( tmpbuf )); ret = gdk_pixbuf_scale_simple ( buf , scale_width , scale_height , GDK_INTERP_BILINEAR ); g_object_unref ( G_OBJECT ( buf )); * +--- STATUS_SIZE +--- td->avatar_width * +----+ +-------+ +---------+ * +-------------------------------------------+ * | [ = [ |--- TOOLTIP_BORDER *name_height --+-| ######[BuddyName = PP [ AAAAAAAAAAA |--+ * | | ######[ = PP [ AAAAAAAAAAA | | * STATUS SIZE -| | ######[[[[[[[[[[[[[[[[[[[[[ AAAAAAAAAAA | | * +--+-| ######[Account: So-and-so [ AAAAAAAAAAA | |-- td->avatar_height * | | [Idle: 4h 15m [ AAAAAAAAAAA | | * height --+ | [Foo: Bar, Baz [ AAAAAAAAAAA | | * | | [Status: Awesome [ AAAAAAAAAAA |--+ * +----| [Stop: Hammer Time [ | * | [ [ |--- TOOLTIP_BORDER * +-------------------------------------------+ * +---- TOOLTIP_BORDER +---- TOOLTIP_BORDER #define TOOLTIP_BORDER 12 PangoLayout * name_layout ; GdkPixbuf * protocol_icon ; gboolean avatar_is_protocol_icon ; static PangoLayout * create_pango_layout ( const char * markup , int * width , int * height ) layout = gtk_widget_create_pango_layout ( gtkblist -> tipwindow , NULL ); pango_layout_set_markup ( layout , markup , -1 ); pango_layout_set_wrap ( layout , PANGO_WRAP_WORD ); pango_layout_set_width ( layout , 300000 ); pango_layout_get_size ( layout , & w , & h ); * width = PANGO_PIXELS ( w ); * height = PANGO_PIXELS ( h ); static struct tooltip_data * create_tip_for_account ( PurpleAccount * account ) struct tooltip_data * td = g_new0 ( struct tooltip_data , 1 ); td -> status_icon = pidgin_create_protocol_icon ( account , PIDGIN_PROTOCOL_ICON_SMALL ); /* Yes, status_icon, not protocol_icon */ if ( purple_account_is_disconnected ( account )) gdk_pixbuf_saturate_and_pixelate ( td -> status_icon , td -> status_icon , 0.0 , FALSE ); td -> layout = create_pango_layout ( purple_account_get_username ( account ), & td -> width , & td -> height ); td -> padding = SMALL_SPACE ; static struct tooltip_data * create_tip_for_node ( PurpleBlistNode * node , gboolean full ) struct tooltip_data * td = g_new0 ( struct tooltip_data , 1 ); PurpleAccount * account = NULL ; char * tmp = NULL , * node_name = NULL , * tooltip_text = NULL ; if ( PURPLE_IS_BUDDY ( node )) { account = purple_buddy_get_account (( PurpleBuddy * )( node )); } else if ( PURPLE_IS_CHAT ( node )) { account = purple_chat_get_account (( PurpleChat * )( node )); td -> padding = TOOLTIP_BORDER ; td -> status_icon = pidgin_blist_get_status_icon ( node , PIDGIN_STATUS_ICON_LARGE ); td -> avatar = pidgin_blist_get_buddy_icon ( node , ! full , FALSE ); td -> protocol_icon = pidgin_create_protocol_icon ( account , PIDGIN_PROTOCOL_ICON_SMALL ); tooltip_text = pidgin_get_tooltip_text ( node , full ); if ( tooltip_text && * tooltip_text ) { td -> layout = create_pango_layout ( tooltip_text , & td -> width , & td -> height ); if ( PURPLE_IS_BUDDY ( node )) { tmp = g_markup_escape_text ( purple_buddy_get_name (( PurpleBuddy * ) node ), -1 ); } else if ( PURPLE_IS_CHAT ( node )) { tmp = g_markup_escape_text ( purple_chat_get_name (( PurpleChat * ) node ), -1 ); } else if ( PURPLE_IS_GROUP ( node )) { tmp = g_markup_escape_text ( purple_group_get_name (( PurpleGroup * ) node ), -1 ); /* I don't believe this can happen currently, I think * everything that calls this function checks for one of the * above node types first. */ tmp = g_strdup ( _ ( "Unknown node type" )); node_name = g_strdup_printf ( "<span size='x-large' weight='bold'>%s</span>" , td -> name_layout = create_pango_layout ( node_name , & td -> name_width , & td -> name_height ); td -> name_width += SMALL_SPACE + PROTOCOL_SIZE ; td -> name_height = MAX ( td -> name_height , PROTOCOL_SIZE + SMALL_SPACE ); #if 0 /* Protocol Icon as avatar */ if(!td->avatar && full) { td->avatar = pidgin_create_protocol_icon(account, PIDGIN_PROTOCOL_ICON_LARGE); td->avatar_is_protocol_icon = TRUE; td -> avatar_width = gdk_pixbuf_get_width ( td -> avatar ); td -> avatar_height = gdk_pixbuf_get_height ( td -> avatar ); pidgin_blist_paint_tip ( GtkWidget * widget , cairo_t * cr , gpointer null ) GtkStyleContext * context ; int current_height , max_width ; GtkTextDirection dir = gtk_widget_get_direction ( widget ); if ( gtkblist -> tooltipdata == NULL ) context = gtk_widget_get_style_context ( gtkblist -> tipwindow ); gtk_style_context_add_class ( context , GTK_STYLE_CLASS_TOOLTIP ); for ( l = gtkblist -> tooltipdata ; l ; l = l -> next ) struct tooltip_data * td = l -> data ; max_text_width = MAX ( max_text_width , MAX ( td -> width , td -> name_width )); max_avatar_width = MAX ( max_avatar_width , td -> avatar_width ); status_size = STATUS_SIZE ; max_width = TOOLTIP_BORDER + status_size + SMALL_SPACE + max_text_width + SMALL_SPACE + max_avatar_width + TOOLTIP_BORDER ; if ( dir == GTK_TEXT_DIR_RTL ) protocol_col = TOOLTIP_BORDER + max_avatar_width + SMALL_SPACE ; protocol_col = TOOLTIP_BORDER + status_size + SMALL_SPACE + max_text_width - PROTOCOL_SIZE ; for ( l = gtkblist -> tooltipdata ; l ; l = l -> next ) struct tooltip_data * td = l -> data ; if ( td -> avatar && pidgin_gdk_pixbuf_is_opaque ( td -> avatar )) gtk_style_context_save ( context ); gtk_style_context_add_class ( context , GTK_STYLE_CLASS_FRAME ); if ( dir == GTK_TEXT_DIR_RTL ) { gtk_render_frame ( context , cr , TOOLTIP_BORDER - 1 , current_height - 1 , td -> avatar_width + 2 , td -> avatar_height + 2 ); gtk_render_frame ( context , cr , max_width - ( td -> avatar_width + TOOLTIP_BORDER ) - 1 , td -> avatar_width + 2 , td -> avatar_height + 2 ); gtk_style_context_restore ( context ); if ( dir == GTK_TEXT_DIR_RTL ) { gdk_cairo_set_source_pixbuf ( cr , td -> status_icon , max_width - TOOLTIP_BORDER - status_size , gdk_cairo_set_source_pixbuf ( cr , td -> status_icon , TOOLTIP_BORDER , current_height ); if ( dir == GTK_TEXT_DIR_RTL ) { gdk_cairo_set_source_pixbuf ( cr , td -> avatar , TOOLTIP_BORDER , current_height ); gdk_cairo_set_source_pixbuf ( cr , td -> avatar , max_width - ( td -> avatar_width + TOOLTIP_BORDER ), if ( ! td -> avatar_is_protocol_icon && td -> protocol_icon ) { gdk_cairo_set_source_pixbuf ( cr , td -> protocol_icon , protocol_col , ( td -> name_height - PROTOCOL_SIZE ) / 2 ); if ( dir == GTK_TEXT_DIR_RTL ) { gtk_render_layout ( context , cr , max_width - ( TOOLTIP_BORDER + status_size + SMALL_SPACE ) - PANGO_PIXELS ( 300000 ), current_height , td -> name_layout ); gtk_render_layout ( context , cr , TOOLTIP_BORDER + status_size + SMALL_SPACE , current_height , td -> name_layout ); if ( dir != GTK_TEXT_DIR_RTL ) { gtk_render_layout ( context , cr , TOOLTIP_BORDER + status_size + SMALL_SPACE , current_height + td -> name_height , gtk_render_layout ( context , cr , max_width - ( TOOLTIP_BORDER + status_size + SMALL_SPACE ) - PANGO_PIXELS ( 300000 ), current_height + td -> name_height , current_height += MAX ( td -> name_height + td -> height , td -> avatar_height ) + td -> padding ; tooltip_data_free ( struct tooltip_data * td ) g_return_if_fail ( td != NULL ); g_clear_object ( & td -> avatar ); g_clear_object ( & td -> status_icon ); g_clear_object ( & td -> protocol_icon ); g_clear_object ( & td -> layout ); g_clear_object ( & td -> name_layout ); void pidgin_blist_tooltip_destroy () g_list_free_full ( gtkblist -> tooltipdata , ( GDestroyNotify ) tooltip_data_free ); pidgin_tooltip_destroy (); pidgin_blist_align_tooltip ( struct tooltip_data * td , GtkWidget * widget ) GtkTextDirection dir = gtk_widget_get_direction ( widget ); if ( dir == GTK_TEXT_DIR_RTL ) char * layout_name = purple_markup_strip_html ( pango_layout_get_text ( td -> name_layout )); PangoDirection dir = pango_find_base_dir ( layout_name , -1 ); if ( dir == PANGO_DIRECTION_RTL || dir == PANGO_DIRECTION_NEUTRAL ) pango_layout_set_alignment ( td -> name_layout , PANGO_ALIGN_RIGHT ); pango_layout_set_alignment ( td -> layout , PANGO_ALIGN_RIGHT ); pidgin_blist_create_tooltip_for_node ( GtkWidget * widget , gpointer data , int * w , int * h ) PurpleBlistNode * node = data ; int max_avatar_width = 0 ; if ( gtkblist -> tooltipdata ) { gtkblist -> tipwindow = NULL ; g_list_free_full ( gtkblist -> tooltipdata , ( GDestroyNotify ) tooltip_data_free ); gtkblist -> tipwindow = widget ; if ( PURPLE_IS_CHAT ( node ) || struct tooltip_data * td = create_tip_for_node ( node , TRUE ); pidgin_blist_align_tooltip ( td , gtkblist -> tipwindow ); gtkblist -> tooltipdata = g_list_append ( gtkblist -> tooltipdata , td ); } else if ( PURPLE_IS_GROUP ( node )) { PurpleGroup * group = ( PurpleGroup * ) node ; struct tooltip_data * td = create_tip_for_node ( node , TRUE ); pidgin_blist_align_tooltip ( td , gtkblist -> tipwindow ); gtkblist -> tooltipdata = g_list_append ( gtkblist -> tooltipdata , td ); /* Accounts with buddies in group */ accounts = purple_group_get_accounts ( group ); accounts = g_slist_delete_link ( accounts , accounts )) { PurpleAccount * account = accounts -> data ; td = create_tip_for_account ( account ); gtkblist -> tooltipdata = g_list_append ( gtkblist -> tooltipdata , td ); } else if ( PURPLE_IS_CONTACT ( node )) { PurpleBuddy * b = purple_contact_get_priority_buddy (( PurpleContact * ) node ); for ( child = node -> child ; child ; child = child -> next ) if ( PURPLE_IS_BUDDY ( child ) && buddy_is_displayable (( PurpleBuddy * ) child )) { struct tooltip_data * td = create_tip_for_node ( child , ( b == ( PurpleBuddy * ) child )); pidgin_blist_align_tooltip ( td , gtkblist -> tipwindow ); if ( b == ( PurpleBuddy * ) child ) { gtkblist -> tooltipdata = g_list_prepend ( gtkblist -> tooltipdata , td ); gtkblist -> tooltipdata = g_list_append ( gtkblist -> tooltipdata , td ); for ( list = gtkblist -> tooltipdata ; list ; list = list -> next ) { struct tooltip_data * td = list -> data ; max_text_width = MAX ( max_text_width , MAX ( td -> width , td -> name_width )); max_avatar_width = MAX ( max_avatar_width , td -> avatar_width ); height += MAX ( MAX ( STATUS_SIZE , td -> avatar_height ), td -> height + td -> name_height ) + td -> padding ; status_size = MAX ( status_size , STATUS_SIZE ); height += TOOLTIP_BORDER ; width = TOOLTIP_BORDER + status_size + SMALL_SPACE + max_text_width + SMALL_SPACE + max_avatar_width + TOOLTIP_BORDER ; static gboolean pidgin_blist_expand_timeout ( GtkWidget * tv ) PidginBlistNode * gtknode ; if ( ! gtk_tree_view_get_path_at_pos ( GTK_TREE_VIEW ( tv ), gtkblist -> tip_rect . x , gtkblist -> tip_rect . y + ( gtkblist -> tip_rect . height / 2 ), & path , NULL , NULL , NULL )) gtk_tree_model_get_iter ( GTK_TREE_MODEL ( gtkblist -> treemodel ), & iter , path ); gtk_tree_model_get ( GTK_TREE_MODEL ( gtkblist -> treemodel ), & iter , NODE_COLUMN , & node , -1 ); if ( ! PURPLE_IS_CONTACT ( node )) { gtk_tree_path_free ( path ); gtknode = purple_blist_node_get_ui_data ( node ); if ( ! gtknode -> contact_expanded ) { pidgin_blist_expand_contact_cb ( NULL , node ); gtk_tree_view_get_cell_area ( GTK_TREE_VIEW ( tv ), path , NULL , & gtkblist -> contact_rect ); gtkblist -> contact_rect . width = gdk_window_get_width ( gtk_widget_get_window ( tv )); gtkblist -> mouseover_contact = node ; gtk_tree_path_down ( path ); while ( gtk_tree_model_get_iter ( GTK_TREE_MODEL ( gtkblist -> treemodel ), & i , path )) { gtk_tree_view_get_cell_area ( GTK_TREE_VIEW ( tv ), path , NULL , & rect ); gtkblist -> contact_rect . height += rect . height ; gtk_tree_path_next ( path ); gtk_tree_path_free ( path ); static gboolean buddy_is_displayable ( PurpleBuddy * buddy ) PidginBlistNode * gtknode ; gtknode = purple_blist_node_get_ui_data ( PURPLE_BLIST_NODE ( buddy )); return ( purple_account_is_connected ( purple_buddy_get_account ( buddy )) && ( purple_presence_is_online ( purple_buddy_get_presence ( buddy )) || ( gtknode && gtknode -> recent_signonoff ) || purple_prefs_get_bool ( PIDGIN_PREFS_ROOT "/blist/show_offline_buddies" ) || purple_blist_node_get_bool ( PURPLE_BLIST_NODE ( buddy ), "show_offline" ))); void pidgin_blist_draw_tooltip ( PurpleBlistNode * node , GtkWidget * widget ) pidgin_tooltip_show ( widget , node , pidgin_blist_create_tooltip_for_node , pidgin_blist_paint_tip ); static gboolean pidgin_blist_drag_motion_cb ( GtkWidget * tv , GdkDragContext * drag_context , gint x , gint y , guint time , gpointer user_data ) * When dragging a buddy into a contact, this is the delay before * the contact auto-expands. if ( gtkblist -> drag_timeout ) { if (( y > gtkblist -> tip_rect . y ) && (( y - gtkblist -> tip_rect . height ) < gtkblist -> tip_rect . y )) /* We've left the cell. Remove the timeout and create a new one below */ g_source_remove ( gtkblist -> drag_timeout ); gtk_tree_view_get_path_at_pos ( GTK_TREE_VIEW ( tv ), x , y , & path , NULL , NULL , NULL ); gtk_tree_view_get_cell_area ( GTK_TREE_VIEW ( tv ), path , NULL , & rect ); gtk_tree_path_free ( path ); /* Only autoexpand when in the middle of the cell to avoid annoying un-intended expands */ if ( y < rect . y + ( rect . height / 3 ) || y > rect . y + ( 2 * ( rect . height / 3 ))) rect . height = rect . height / 3 ; gtkblist -> tip_rect = rect ; gtkblist -> drag_timeout = g_timeout_add ( delay , ( GSourceFunc ) pidgin_blist_expand_timeout , tv ); if ( gtkblist -> mouseover_contact ) { if (( y < gtkblist -> contact_rect . y ) || (( y - gtkblist -> contact_rect . height ) > gtkblist -> contact_rect . y )) { pidgin_blist_collapse_contact_cb ( NULL , gtkblist -> mouseover_contact ); gtkblist -> mouseover_contact = NULL ; pidgin_blist_create_tooltip ( GtkWidget * widget , GtkTreePath * path , gpointer null , int * w , int * h ) gboolean editable = FALSE ; /* If we're editing a cell (e.g. alias editing), don't show the tooltip */ g_object_get ( G_OBJECT ( gtkblist -> text_rend ), "editable" , & editable , NULL ); if ( gtkblist -> tooltipdata ) { gtkblist -> tipwindow = NULL ; g_list_free_full ( gtkblist -> tooltipdata , ( GDestroyNotify ) tooltip_data_free ); gtk_tree_model_get_iter ( GTK_TREE_MODEL ( gtkblist -> treemodel ), & iter , path ); gtk_tree_model_get ( GTK_TREE_MODEL ( gtkblist -> treemodel ), & iter , NODE_COLUMN , & node , -1 ); return pidgin_blist_create_tooltip_for_node ( widget , node , w , h ); static gboolean pidgin_blist_motion_cb ( GtkWidget * tv , GdkEventMotion * event , gpointer null ) if ( gtkblist -> mouseover_contact ) { if (( event -> y < gtkblist -> contact_rect . y ) || (( event -> y - gtkblist -> contact_rect . height ) > gtkblist -> contact_rect . y )) { pidgin_blist_collapse_contact_cb ( NULL , gtkblist -> mouseover_contact ); gtkblist -> mouseover_contact = NULL ; static gboolean pidgin_blist_leave_cb ( GtkWidget * w , GdkEventCrossing * e , gpointer n ) g_source_remove ( gtkblist -> timeout ); if ( gtkblist -> drag_timeout ) { g_source_remove ( gtkblist -> drag_timeout ); gtkblist -> drag_timeout = 0 ; if ( gtkblist -> mouseover_contact && ! (( e -> x > gtkblist -> contact_rect . x ) && ( e -> x < ( gtkblist -> contact_rect . x + gtkblist -> contact_rect . width )) && ( e -> y > gtkblist -> contact_rect . y ) && ( e -> y < ( gtkblist -> contact_rect . y + gtkblist -> contact_rect . height )))) { pidgin_blist_collapse_contact_cb ( NULL , gtkblist -> mouseover_contact ); gtkblist -> mouseover_contact = NULL ; purple_prefs_set_bool ( PIDGIN_PREFS_ROOT "/debug/enabled" , ! purple_prefs_get_bool ( PIDGIN_PREFS_ROOT "/debug/enabled" )); static char * get_mood_icon_path ( const char * mood ) if ( purple_strequal ( mood , "busy" )) { path = g_build_filename ( PURPLE_DATADIR , "pidgin" , "icons" , "hicolor" , "16x16" , "status" , "user-busy.png" , NULL ); } else if ( purple_strequal ( mood , "hiptop" )) { path = g_build_filename ( PURPLE_DATADIR , "pidgin" , "icons" , "hicolor" , "16x16" , "emblems" , "emblem-hiptop.png" , char * filename = g_strdup_printf ( "%s.png" , mood ); path = g_build_filename ( PURPLE_DATADIR , "pixmaps" , "pidgin" , "emotes" , "small" , filename , NULL ); update_status_with_mood ( PurpleAccount * account , const gchar * mood , purple_account_set_status ( account , "mood" , TRUE , PURPLE_MOOD_COMMENT , text , purple_account_set_status ( account , "mood" , TRUE , purple_account_set_status ( account , "mood" , FALSE , NULL ); edit_mood_cb ( PurpleConnection * gc , PurpleRequestFields * fields ) PurpleRequestField * mood_field ; mood_field = purple_request_fields_get_field ( fields , "mood" ); l = purple_request_field_list_get_selected ( mood_field ); const char * mood = purple_request_field_list_get_data ( mood_field , l -> data ); PurpleAccount * account = purple_connection_get_account ( gc ); if ( purple_connection_get_flags ( gc ) & PURPLE_CONNECTION_FLAG_SUPPORT_MOOD_MESSAGES ) { PurpleRequestField * text_field ; text_field = purple_request_fields_get_field ( fields , "text" ); text = purple_request_field_string_get_value ( text_field ); update_status_with_mood ( account , mood , text ); GList * accounts = purple_accounts_get_all_active (); for (; accounts ; accounts = g_list_delete_link ( accounts , accounts )) { PurpleAccount * account = ( PurpleAccount * ) accounts -> data ; PurpleConnection * gc = purple_account_get_connection ( account ); if ( gc && ( purple_connection_get_flags ( gc ) & PURPLE_CONNECTION_FLAG_SUPPORT_MOODS )) { update_status_with_mood ( account , mood , NULL ); global_moods_for_each ( gpointer key , gpointer value , gpointer user_data ) GList ** out_moods = ( GList ** ) user_data ; PurpleMood * mood = ( PurpleMood * ) value ; * out_moods = g_list_append ( * out_moods , mood ); GHashTable * global_moods = g_hash_table_new_full ( g_str_hash , g_str_equal , NULL , NULL ); GHashTable * mood_counts = g_hash_table_new_full ( g_str_hash , g_str_equal , NULL , NULL ); GList * accounts = purple_accounts_get_all_active (); PurpleMood * result = NULL ; for (; accounts ; accounts = g_list_delete_link ( accounts , accounts )) { PurpleAccount * account = ( PurpleAccount * ) accounts -> data ; if ( purple_account_is_connected ( account )) { PurpleConnection * gc = purple_account_get_connection ( account ); if ( purple_connection_get_flags ( gc ) & PURPLE_CONNECTION_FLAG_SUPPORT_MOODS ) { PurpleProtocol * protocol = purple_connection_get_protocol ( gc ); for ( mood = purple_protocol_client_iface_get_moods ( protocol , account ) ; mood -> mood != NULL ; mood ++ ) { GPOINTER_TO_INT ( g_hash_table_lookup ( mood_counts , mood -> mood )); if ( ! g_hash_table_lookup ( global_moods , mood -> mood )) { g_hash_table_insert ( global_moods , ( gpointer ) mood -> mood , mood ); g_hash_table_insert ( mood_counts , ( gpointer ) mood -> mood , GINT_TO_POINTER ( mood_count + 1 )); g_hash_table_foreach ( global_moods , global_moods_for_each , & out_moods ); result = g_new0 ( PurpleMood , g_hash_table_size ( global_moods ) + 1 ); PurpleMood * mood = ( PurpleMood * ) out_moods -> data ; GPOINTER_TO_INT ( g_hash_table_lookup ( mood_counts , mood -> mood )); if ( in_num_accounts == num_accounts ) { /* mood is present in all accounts supporting moods */ result [ i ]. mood = mood -> mood ; result [ i ]. description = mood -> description ; out_moods = g_list_delete_link ( out_moods , out_moods ); g_hash_table_destroy ( global_moods ); g_hash_table_destroy ( mood_counts ); /* get current set mood for all mood-supporting accounts, or NULL if not set or not set to the same on all */ get_global_mood_status ( void ) GList * accounts = purple_accounts_get_all_active (); const gchar * found_mood = NULL ; for (; accounts ; accounts = g_list_delete_link ( accounts , accounts )) { PurpleAccount * account = ( PurpleAccount * ) accounts -> data ; if ( purple_account_is_connected ( account ) && ( purple_connection_get_flags ( purple_account_get_connection ( account )) & PURPLE_CONNECTION_FLAG_SUPPORT_MOODS )) { PurplePresence * presence = purple_account_get_presence ( account ); PurpleStatus * status = purple_presence_get_status ( presence , "mood" ); const gchar * curr_mood = purple_status_get_attr_string ( status , PURPLE_MOOD_NAME ); if ( found_mood != NULL && ! purple_strequal ( curr_mood , found_mood )) { /* found a different mood */ set_mood_cb ( GtkWidget * widget , PurpleAccount * account ) const char * current_mood ; PurpleRequestFields * fields ; PurpleRequestFieldGroup * g ; PurpleConnection * gc = NULL ; PurpleProtocol * protocol = NULL ; PurpleMood * global_moods = NULL ; PurplePresence * presence = purple_account_get_presence ( account ); PurpleStatus * status = purple_presence_get_status ( presence , "mood" ); gc = purple_account_get_connection ( account ); g_return_if_fail ( purple_connection_get_protocol ( gc ) != NULL ); protocol = purple_connection_get_protocol ( gc ); current_mood = purple_status_get_attr_string ( status , PURPLE_MOOD_NAME ); current_mood = get_global_mood_status (); fields = purple_request_fields_new (); g = purple_request_field_group_new ( NULL ); f = purple_request_field_list_new ( "mood" , _ ( "Please select your mood from the list" )); purple_request_field_list_add_icon ( f , _ ( "None" ), NULL , "" ); if ( current_mood == NULL ) purple_request_field_list_add_selected ( f , _ ( "None" )); /* TODO: rlaager wants this sorted. */ /* TODO: darkrain wants it sorted post-translation */ if ( account && PURPLE_PROTOCOL_IMPLEMENTS ( protocol , CLIENT , get_moods )) { mood = purple_protocol_client_iface_get_moods ( protocol , account ); mood = global_moods = get_global_moods (); for ( ; mood -> mood != NULL ; mood ++ ) { if ( mood -> description == NULL ) { path = get_mood_icon_path ( mood -> mood ); purple_request_field_list_add_icon ( f , _ ( mood -> description ), path , ( gpointer ) mood -> mood ); if ( current_mood && purple_strequal ( current_mood , mood -> mood )) purple_request_field_list_add_selected ( f , _ ( mood -> description )); purple_request_field_group_add_field ( g , f ); purple_request_fields_add_group ( fields , g ); /* if the connection allows setting a mood message */ if ( gc && ( purple_connection_get_flags ( gc ) & PURPLE_CONNECTION_FLAG_SUPPORT_MOOD_MESSAGES )) { g = purple_request_field_group_new ( NULL ); f = purple_request_field_string_new ( "text" , _ ( "Message (optional)" ), NULL , FALSE ); purple_request_field_group_add_field ( g , f ); purple_request_fields_add_group ( fields , g ); purple_request_fields ( gc , _ ( "Edit User Mood" ), _ ( "Edit User Mood" ), _ ( "OK" ), G_CALLBACK ( edit_mood_cb ), purple_request_cpar_from_connection ( gc ), gc ); /*************************************************** ***************************************************/ _pidgin_about_cb ( GtkAction * action , GtkWidget * window ) { GtkWidget * about = pidgin_about_dialog_new (); gtk_window_set_transient_for ( GTK_WINDOW ( about ), GTK_WINDOW ( window )); gtk_widget_show_all ( about ); /* TODO: fill out tooltips... */ static const GtkActionEntry blist_menu_entries [] = { /* NOTE: Do not set any accelerator to Control+O. It is mapped by gtk_blist_key_press_cb to "Get User Info" on the selected buddy. */ { "BuddiesMenu" , NULL , N_ ( "_Buddies" ), NULL , NULL , NULL }, { "NewInstantMessage" , PIDGIN_STOCK_TOOLBAR_MESSAGE_NEW , N_ ( "New Instant _Message..." ), "<control>M" , NULL , pidgin_dialogs_im }, { "JoinAChat" , PIDGIN_STOCK_CHAT , N_ ( "Join a _Chat..." ), "<control>C" , NULL , pidgin_blist_joinchat_show }, { "GetUserInfo" , PIDGIN_STOCK_TOOLBAR_USER_INFO , N_ ( "Get User _Info..." ), "<control>I" , NULL , pidgin_dialogs_info }, { "ViewUserLog" , NULL , N_ ( "View User _Log..." ), "<control>L" , NULL , pidgin_dialogs_log }, { "ShowMenu" , NULL , N_ ( "Sh_ow" ), NULL , NULL , NULL }, { "SortMenu" , NULL , N_ ( "_Sort Buddies" ), NULL , NULL , NULL }, { "AddBuddy" , GTK_STOCK_ADD , N_ ( "_Add Buddy..." ), "<control>B" , NULL , pidgin_blist_add_buddy_cb }, { "AddChat" , GTK_STOCK_ADD , N_ ( "Add C_hat..." ), NULL , NULL , pidgin_blist_add_chat_cb }, { "AddGroup" , GTK_STOCK_ADD , N_ ( "Add _Group..." ), NULL , NULL , purple_blist_request_add_group }, { "Quit" , GTK_STOCK_QUIT , N_ ( "_Quit" ), "<control>Q" , NULL , purple_core_quit }, { "AccountsMenu" , NULL , N_ ( "_Accounts" ), NULL , NULL , NULL }, { "ManageAccounts" , NULL , N_ ( "Manage Accounts" ), "<control>A" , NULL , pidgin_accounts_window_show }, { "ToolsMenu" , NULL , N_ ( "_Tools" ), NULL , NULL , NULL }, { "BuddyPounces" , NULL , N_ ( "Buddy _Pounces" ), NULL , NULL , pidgin_pounces_manager_show }, { "CustomSmileys" , PIDGIN_STOCK_TOOLBAR_SMILEY , N_ ( "Custom Smile_ys" ), "<control>Y" , NULL , pidgin_smiley_manager_show }, { "Plugins" , PIDGIN_STOCK_TOOLBAR_PLUGINS , N_ ( "Plu_gins" ), "<control>U" , NULL , pidgin_plugin_dialog_show }, { "Preferences" , GTK_STOCK_PREFERENCES , N_ ( "Pr_eferences" ), "<control>P" , NULL , pidgin_prefs_show }, { "Privacy" , NULL , N_ ( "Pr_ivacy" ), NULL , NULL , pidgin_privacy_dialog_show }, { "SetMood" , NULL , N_ ( "Set _Mood" ), "<control>D" , NULL , set_mood_show }, { "FileTransfers" , PIDGIN_STOCK_TOOLBAR_TRANSFER , N_ ( "_File Transfers" ), "<control>T" , NULL , G_CALLBACK ( gtk_blist_show_xfer_dialog_cb ) }, { "RoomList" , NULL , N_ ( "R_oom List" ), NULL , NULL , pidgin_roomlist_dialog_show }, { "SystemLog" , NULL , N_ ( "System _Log" ), NULL , NULL , gtk_blist_show_systemlog_cb }, { "HelpMenu" , NULL , N_ ( "_Help" ), NULL , NULL , NULL }, { "OnlineHelp" , GTK_STOCK_HELP , N_ ( "Online _Help" ), "F1" , NULL , gtk_blist_show_onlinehelp_cb }, { "DebugWindow" , NULL , N_ ( "_Debug Window" ), NULL , NULL , toggle_debug }, { "PluginInformation" , NULL , N_ ( "_Plugin Information" ), NULL , NULL , pidgin_debug_plugin_info_show }, { "About" , GTK_STOCK_ABOUT , N_ ( "_About" ), NULL , NULL , G_CALLBACK ( _pidgin_about_cb ) }, static const GtkToggleActionEntry blist_menu_toggle_entries [] = { { "ShowOffline" , NULL , N_ ( "_Offline Buddies" ), NULL , NULL , G_CALLBACK ( pidgin_blist_edit_mode_cb ), FALSE }, { "ShowEmptyGroups" , NULL , N_ ( "_Empty Groups" ), NULL , NULL , G_CALLBACK ( pidgin_blist_show_empty_groups_cb ), FALSE }, { "ShowBuddyDetails" , NULL , N_ ( "Buddy _Details" ), NULL , NULL , G_CALLBACK ( pidgin_blist_buddy_details_cb ), FALSE }, { "ShowIdleTimes" , NULL , N_ ( "Idle _Times" ), NULL , NULL , G_CALLBACK ( pidgin_blist_show_idle_time_cb ), FALSE }, { "ShowProtocolIcons" , NULL , N_ ( "_Protocol Icons" ), NULL , NULL , G_CALLBACK ( pidgin_blist_show_protocol_icons_cb ), FALSE }, { "MuteSounds" , NULL , N_ ( "Mute _Sounds" ), NULL , NULL , G_CALLBACK ( pidgin_blist_mute_sounds_cb ), FALSE }, static const char * blist_menu = "<menu action='BuddiesMenu'>" "<menuitem action='NewInstantMessage'/>" "<menuitem action='JoinAChat'/>" "<menuitem action='GetUserInfo'/>" "<menuitem action='ViewUserLog'/>" "<menu action='ShowMenu'>" "<menuitem action='ShowOffline'/>" "<menuitem action='ShowEmptyGroups'/>" "<menuitem action='ShowBuddyDetails'/>" "<menuitem action='ShowIdleTimes'/>" "<menuitem action='ShowProtocolIcons'/>" "<menu action='SortMenu'/>" "<menuitem action='AddBuddy'/>" "<menuitem action='AddChat'/>" "<menuitem action='AddGroup'/>" "<menuitem action='Quit'/>" "<menu action='AccountsMenu'>" "<menuitem action='ManageAccounts'/>" "<menu action='ToolsMenu'>" "<menuitem action='BuddyPounces'/>" "<menuitem action='CustomSmileys'/>" "<menuitem action='Plugins'/>" "<menuitem action='Preferences'/>" "<menuitem action='Privacy'/>" "<menuitem action='SetMood'/>" "<menuitem action='FileTransfers'/>" "<menuitem action='RoomList'/>" "<menuitem action='SystemLog'/>" "<menuitem action='MuteSounds'/>" "<placeholder name='PluginActions'/>" "<menu action='HelpMenu'>" "<menuitem action='OnlineHelp'/>" "<menuitem action='DebugWindow'/>" "<menuitem action='PluginInformation'/>" "<menuitem action='About'/>" /********************************************************* * Private Utility functions * *********************************************************/ static char * pidgin_get_tooltip_text ( PurpleBlistNode * node , gboolean full ) GString * str = g_string_new ( "" ); PurpleProtocol * protocol = NULL ; if ( PURPLE_IS_CHAT ( node )) PurpleProtocolChatEntry * pce ; PurpleChatConversation * conv ; PidginBlistNode * bnode = purple_blist_node_get_ui_data ( node ); chat = ( PurpleChat * ) node ; protocol = purple_protocols_find ( purple_account_get_protocol_id ( purple_chat_get_account ( chat ))); connections = purple_connections_get_all (); if ( connections && connections -> next ) tmp = g_markup_escape_text ( purple_account_get_username ( purple_chat_get_account ( chat )), -1 ); g_string_append_printf ( str , _ ( "<b>Account:</b> %s" ), tmp ); if ( bnode && bnode -> conv . conv ) { conv = PURPLE_CHAT_CONVERSATION ( bnode -> conv . conv ); if ( protocol && PURPLE_PROTOCOL_IMPLEMENTS ( protocol , CHAT , get_name )) chat_name = purple_protocol_chat_iface_get_name ( protocol , purple_chat_get_components ( chat )); chat_name = g_strdup ( purple_chat_get_name ( chat )); conv = purple_conversations_find_chat_with_account ( chat_name , purple_chat_get_account ( chat )); if ( conv && ! purple_chat_conversation_has_left ( conv )) { g_string_append_printf ( str , _ ( " \n <b>Occupants:</b> %d" ), purple_chat_conversation_get_users_count ( conv )); if ( protocol && ( purple_protocol_get_options ( protocol ) & OPT_PROTO_CHAT_TOPIC )) { const char * chattopic = purple_chat_conversation_get_topic ( conv ); char * topic = chattopic ? g_markup_escape_text ( chattopic , -1 ) : NULL ; g_string_append_printf ( str , _ ( " \n <b>Topic:</b> %s" ), topic ? topic : _ ( "(no topic set)" )); cur = purple_protocol_chat_iface_info ( protocol , purple_account_get_connection ( purple_chat_get_account ( chat ))); tmp = purple_text_strip_mnemonic ( pce -> label ); name = g_markup_escape_text ( tmp , -1 ); value = g_markup_escape_text ( g_hash_table_lookup ( purple_chat_get_components ( chat ), pce -> identifier ), -1 ); g_string_append_printf ( str , " \n <b>%s</b> %s" , cur = g_list_delete_link ( cur , cur ); else if ( PURPLE_IS_CONTACT ( node ) || PURPLE_IS_BUDDY ( node )) /* NOTE: THIS FUNCTION IS NO LONGER CALLED FOR CONTACTS. * It is only called by create_tip_for_node(), and create_tip_for_node() is never called for a contact. PurplePresence * presence ; PurpleNotifyUserInfo * user_info ; time_t idle_secs , signon ; if ( PURPLE_IS_CONTACT ( node )) c = ( PurpleContact * ) node ; b = purple_contact_get_priority_buddy ( c ); c = purple_buddy_get_contact ( b ); protocol = purple_protocols_find ( purple_account_get_protocol_id ( purple_buddy_get_account ( b ))); presence = purple_buddy_get_presence ( b ); user_info = purple_notify_user_info_new (); connections = purple_connections_get_all (); if ( full && connections && connections -> next ) purple_notify_user_info_add_pair_plaintext ( user_info , _ ( "Account" ), purple_account_get_username ( purple_buddy_get_account ( b ))); /* If there's not a contact alias, the node is being displayed with * this alias, so there's no point in showing it in the tooltip. */ g_object_get ( c , "alias" , & alias , NULL ); if ( full && c && purple_buddy_get_local_alias ( b ) != NULL && purple_buddy_get_local_alias ( b )[ 0 ] != '\0' && ( alias != NULL && alias [ 0 ] != '\0' ) && ! purple_strequal ( alias , purple_buddy_get_local_alias ( b ))) purple_notify_user_info_add_pair_plaintext ( user_info , _ ( "Buddy Alias" ), purple_buddy_get_local_alias ( b )); /* Nickname/Server Alias */ /* I'd like to only show this if there's a contact or buddy * alias, but people often set long nicknames, which * get ellipsized, so the only way to see the whole thing is * to look at the tooltip. */ if ( full && purple_buddy_get_server_alias ( b )) purple_notify_user_info_add_pair_plaintext ( user_info , _ ( "Nickname" ), purple_buddy_get_server_alias ( b )); signon = purple_presence_get_login_time ( presence ); if ( full && PURPLE_BUDDY_IS_ONLINE ( b ) && signon > 0 ) if ( signon > time ( NULL )) { * They signed on in the future?! Our local clock * must be wrong, show the actual date instead of tmp = g_strdup ( purple_date_format_long ( localtime ( & signon ))); tmp = purple_str_seconds_to_string ( time ( NULL ) - signon ); purple_notify_user_info_add_pair_plaintext ( user_info , _ ( "Logged In" ), tmp ); if ( purple_presence_is_idle ( presence )) idle_secs = purple_presence_get_idle_time ( presence ); tmp = purple_str_seconds_to_string ( time ( NULL ) - idle_secs ); purple_notify_user_info_add_pair_plaintext ( user_info , _ ( "Idle" ), tmp ); if ( full && c && ! PURPLE_BUDDY_IS_ONLINE ( b )) PidginBlistNode * gtknode = purple_blist_node_get_ui_data ( PURPLE_BLIST_NODE ( c )); if ( gtknode && ( ! gtknode -> contact_expanded || PURPLE_IS_CONTACT ( node ))) /* We're either looking at a buddy for a collapsed contact or * an expanded contact itself so we show the most recent * (largest) last_seen time for any of the buddies under for ( bnode = (( PurpleBlistNode * ) c ) -> child ; bnode != NULL ; bnode = bnode -> next ) int value = purple_blist_node_get_int ( bnode , "last_seen" ); /* We're dealing with a buddy under an expanded contact, * so we show the last_seen time for the buddy. */ lastseen = purple_blist_node_get_int ( & b -> node , "last_seen" ); tmp = purple_str_seconds_to_string ( time ( NULL ) - lastseen ); purple_notify_user_info_add_pair_plaintext ( user_info , _ ( "Last Seen" ), tmp ); /* FIXME: Why is this status special-cased by the core? --rlaager * FIXME: Alternatively, why not have the core do all of them? --rlaager */ if ( ! PURPLE_BUDDY_IS_ONLINE ( b )) { purple_notify_user_info_add_pair_plaintext ( user_info , _ ( "Status" ), _ ( "Offline" )); if ( purple_account_is_connected ( purple_buddy_get_account ( b )) && /* Additional text from the protocol */ purple_protocol_client_iface_tooltip_text ( protocol , b , user_info , full ); /* These are Easter Eggs. Patches to remove them will be rejected. */ if ( ! g_ascii_strcasecmp ( purple_buddy_get_name ( b ), "robflynn" )) purple_notify_user_info_add_pair_plaintext ( user_info , _ ( "Description" ), _ ( "Spooky" )); if ( ! g_ascii_strcasecmp ( purple_buddy_get_name ( b ), "seanegn" )) purple_notify_user_info_add_pair_plaintext ( user_info , _ ( "Status" ), _ ( "Awesome" )); if ( ! g_ascii_strcasecmp ( purple_buddy_get_name ( b ), "chipx86" )) purple_notify_user_info_add_pair_plaintext ( user_info , _ ( "Status" ), _ ( "Rockin'" )); tmp = purple_notify_user_info_get_text_with_newline ( user_info , " \n " ); g_string_append ( str , tmp ); purple_notify_user_info_destroy ( user_info ); } else if ( PURPLE_IS_GROUP ( node )) { PurpleGroup * group = ( PurpleGroup * ) node ; PurpleNotifyUserInfo * user_info ; user_info = purple_notify_user_info_new (); count = purple_counting_node_get_online_count ( PURPLE_COUNTING_NODE ( group )); /* Online buddies in group */ sprintf ( tmp2 , "%d" , count ); purple_notify_user_info_add_pair_plaintext ( user_info , _ ( "Online Buddies" ), tmp2 ); count = purple_counting_node_get_current_size ( PURPLE_COUNTING_NODE ( group )); /* Total buddies (from online accounts) in group */ sprintf ( tmp2 , "%d" , count ); purple_notify_user_info_add_pair_html ( user_info , _ ( "Total Buddies" ), tmp2 ); tmp = purple_notify_user_info_get_text_with_newline ( user_info , " \n " ); g_string_append ( str , tmp ); purple_notify_user_info_destroy ( user_info ); purple_signal_emit ( pidgin_blist_get_handle (), "drawing-tooltip" , return g_string_free ( str , FALSE ); static GHashTable * cached_emblems ; static void _cleanup_cached_emblem ( gpointer data , GObject * obj ) { g_hash_table_remove ( cached_emblems , data ); static GdkPixbuf * _pidgin_blist_get_cached_emblem ( gchar * path ) { GdkPixbuf * pb = g_hash_table_lookup ( cached_emblems , path ); /* The caller gets a reference */ pb = pidgin_pixbuf_new_from_file ( path ); /* We don't want to own a ref to the pixbuf, but we need to keep clean up. */ /* I'm not sure if it would be better to just keep our ref and not let the emblem ever be destroyed */ g_object_weak_ref ( G_OBJECT ( pb ), _cleanup_cached_emblem , path ); g_hash_table_insert ( cached_emblems , path , pb ); pidgin_blist_get_emblem ( PurpleBlistNode * node ) PurpleBuddy * buddy = NULL ; PidginBlistNode * gtknode = purple_blist_node_get_ui_data ( node ); PurpleProtocol * protocol ; PurplePresence * p = NULL ; if ( PURPLE_IS_CONTACT ( node )) { if ( ! gtknode -> contact_expanded ) { buddy = purple_contact_get_priority_buddy (( PurpleContact * ) node ); } else if ( PURPLE_IS_BUDDY ( node )) { buddy = ( PurpleBuddy * ) node ; p = purple_buddy_get_presence ( buddy ); if ( purple_presence_is_status_primitive_active ( p , PURPLE_STATUS_MOBILE )) { /* This emblem comes from the small emoticon set now, * to reduce duplication. */ path = g_build_filename ( PURPLE_DATADIR , "pixmaps" , "pidgin" , "emotes" , "small" , "mobile.png" , NULL ); return _pidgin_blist_get_cached_emblem ( path ); if ((( PidginBlistNode * ) purple_blist_node_get_ui_data ( node -> parent )) -> contact_expanded ) { if ( purple_prefs_get_bool ( PIDGIN_PREFS_ROOT "/blist/show_protocol_icons" )) return pidgin_create_protocol_icon ( purple_buddy_get_account (( PurpleBuddy * ) node ), PIDGIN_PROTOCOL_ICON_SMALL ); g_return_val_if_fail ( buddy != NULL , NULL ); if ( ! purple_account_privacy_check ( purple_buddy_get_account ( buddy ), purple_buddy_get_name ( buddy ))) { path = g_build_filename ( PURPLE_DATADIR , "pidgin" , "icons" , "hicolor" , "16x16" , "emblems" , "emblem-blocked.png" , return _pidgin_blist_get_cached_emblem ( path ); /* If we came through the contact code flow above, we didn't need * to get the presence until now. */ p = purple_buddy_get_presence ( buddy ); if ( purple_presence_is_status_primitive_active ( p , PURPLE_STATUS_MOBILE )) { /* This emblem comes from the small emoticon set now, to reduce duplication. */ path = g_build_filename ( PURPLE_DATADIR , "pixmaps" , "pidgin" , "emotes" , "small" , "mobile.png" , NULL ); return _pidgin_blist_get_cached_emblem ( path ); tune = purple_presence_get_status ( p , "tune" ); if ( tune && purple_status_is_active ( tune )) { /* TODO: Replace "Tune" with generalized "Media" in 3.0. */ if ( purple_status_get_attr_string ( tune , "game" ) != NULL ) { path = g_build_filename ( PURPLE_DATADIR , "pidgin" , "icons" , "hicolor" , "16x16" , "emblems" , "emblem-game.png" , NULL ); return _pidgin_blist_get_cached_emblem ( path ); /* TODO: Replace "Tune" with generalized "Media" in 3.0. */ if ( purple_status_get_attr_string ( tune , "office" ) != NULL ) { path = g_build_filename ( PURPLE_DATADIR , "pidgin" , "icons" , "hicolor" , "16x16" , "emblems" , "emblem-office.png" , NULL ); return _pidgin_blist_get_cached_emblem ( path ); /* Regular old "tune" is the only one in all protocols. */ /* This emblem comes from the small emoticon set now, to reduce duplication. */ path = g_build_filename ( PURPLE_DATADIR , "pixmaps" , "pidgin" , "emotes" , "small" , "music.png" , NULL ); return _pidgin_blist_get_cached_emblem ( path ); protocol = purple_protocols_find ( purple_account_get_protocol_id ( purple_buddy_get_account ( buddy ))); name = purple_protocol_client_iface_list_emblem ( protocol , buddy ); if ( ! purple_presence_is_status_primitive_active ( p , PURPLE_STATUS_MOOD )) status = purple_presence_get_status ( p , "mood" ); name = purple_status_get_attr_string ( status , PURPLE_MOOD_NAME ); path = get_mood_icon_path ( name ); filename = g_strdup_printf ( "emblem-%s.png" , name ); path = g_build_filename ( PURPLE_DATADIR , "pidgin" , "icons" , "hicolor" , "16x16" , "emblems" , filename , NULL ); /* _pidgin_blist_get_cached_emblem() assumes ownership of path */ return _pidgin_blist_get_cached_emblem ( path ); pidgin_blist_get_status_icon ( PurpleBlistNode * node , PidginStatusIconSize size ) PidginBlistNode * gtknode = purple_blist_node_get_ui_data ( node ); PidginBlistNode * gtkbuddynode = NULL ; PurpleBuddy * buddy = NULL ; GtkIconSize icon_size = gtk_icon_size_from_name (( size == PIDGIN_STATUS_ICON_LARGE ) ? PIDGIN_ICON_SIZE_TANGO_EXTRA_SMALL : PIDGIN_ICON_SIZE_TANGO_MICROSCOPIC ); if ( PURPLE_IS_CONTACT ( node )) { if ( ! gtknode -> contact_expanded ) { buddy = purple_contact_get_priority_buddy (( PurpleContact * ) node ); gtkbuddynode = purple_blist_node_get_ui_data ( PURPLE_BLIST_NODE ( buddy )); } else if ( PURPLE_IS_BUDDY ( node )) { buddy = ( PurpleBuddy * ) node ; gtkbuddynode = purple_blist_node_get_ui_data ( node ); } else if ( PURPLE_IS_CHAT ( node )) { chat = ( PurpleChat * ) node ; PurpleProtocol * protocol ; account = purple_buddy_get_account ( buddy ); account = purple_chat_get_account ( chat ); protocol = purple_protocols_find ( purple_account_get_protocol_id ( account )); PurpleConversation * conv = find_conversation_with_buddy ( buddy ); PidginConversation * gtkconv = PIDGIN_CONVERSATION ( conv ); if ( gtkconv == NULL && size == PIDGIN_STATUS_ICON_SMALL ) { PidginBlistNode * ui = purple_blist_node_get_ui_data ( & ( buddy -> node )); if ( ui == NULL || ( ui -> conv . flags & PIDGIN_BLIST_NODE_HAS_PENDING_MESSAGE )) return gtk_widget_render_icon ( GTK_WIDGET ( gtkblist -> treeview ), PIDGIN_STOCK_STATUS_MESSAGE , icon_size , "GtkTreeView" ); p = purple_buddy_get_presence ( buddy ); trans = purple_presence_is_idle ( p ); if ( PURPLE_BUDDY_IS_ONLINE ( buddy ) && gtkbuddynode && gtkbuddynode -> recent_signonoff ) icon = PIDGIN_STOCK_STATUS_LOGIN ; else if ( gtkbuddynode && gtkbuddynode -> recent_signonoff ) icon = PIDGIN_STOCK_STATUS_LOGOUT ; else if ( purple_presence_is_status_primitive_active ( p , PURPLE_STATUS_UNAVAILABLE )) icon = PIDGIN_STOCK_STATUS_BUSY_I ; icon = PIDGIN_STOCK_STATUS_BUSY ; else if ( purple_presence_is_status_primitive_active ( p , PURPLE_STATUS_AWAY )) icon = PIDGIN_STOCK_STATUS_AWAY_I ; icon = PIDGIN_STOCK_STATUS_AWAY ; else if ( purple_presence_is_status_primitive_active ( p , PURPLE_STATUS_EXTENDED_AWAY )) icon = PIDGIN_STOCK_STATUS_XA_I ; icon = PIDGIN_STOCK_STATUS_XA ; else if ( purple_presence_is_status_primitive_active ( p , PURPLE_STATUS_OFFLINE )) icon = PIDGIN_STOCK_STATUS_OFFLINE ; icon = PIDGIN_STOCK_STATUS_AVAILABLE_I ; else if ( purple_presence_is_status_primitive_active ( p , PURPLE_STATUS_INVISIBLE )) icon = PIDGIN_STOCK_STATUS_INVISIBLE ; icon = PIDGIN_STOCK_STATUS_AVAILABLE ; icon = PIDGIN_STOCK_STATUS_CHAT ; icon = PIDGIN_STOCK_STATUS_PERSON ; ret = gtk_widget_render_icon ( GTK_WIDGET ( gtkblist -> treeview ), icon , icon_size , "GtkTreeView" ); theme_font_get_color_default ( PidginThemeFont * font , const char * def ) if ( ! font || ! ( ret = pidgin_theme_font_get_color_describe ( font ))) theme_font_get_face_default ( PidginThemeFont * font , const char * def ) if ( ! font || ! ( ret = pidgin_theme_font_get_font_face ( font ))) pidgin_blist_get_name_markup ( PurpleBuddy * b , gboolean selected , gboolean aliased ) const char * name , * name_color , * name_font , * status_color , * status_font , * dim_grey ; PurpleProtocol * protocol = NULL ; PurplePresence * presence ; PidginBlistNode * gtkcontactnode = NULL ; char * idletime = NULL , * statustext = NULL , * nametext = NULL ; PurpleConversation * conv = find_conversation_with_buddy ( b ); gboolean hidden_conv = FALSE ; gboolean biglist = purple_prefs_get_bool ( PIDGIN_PREFS_ROOT "/blist/show_buddy_icons" ); PidginThemeFont * statusfont = NULL , * namefont = NULL ; PidginBlistNode * ui = purple_blist_node_get_ui_data ( & ( b -> node )); if ( ui -> conv . flags & PIDGIN_BLIST_NODE_HAS_PENDING_MESSAGE ) if ( PIDGIN_CONVERSATION ( conv ) == NULL ) /* XXX Good luck cleaning up this crap */ contact = PURPLE_CONTACT ( PURPLE_BLIST_NODE ( b ) -> parent ); gtkcontactnode = purple_blist_node_get_ui_data ( PURPLE_BLIST_NODE ( contact )); g_object_get ( contact , "alias" , & contact_alias , NULL ); if ( gtkcontactnode && ! gtkcontactnode -> contact_expanded && contact_alias ) name = purple_buddy_get_alias ( b ); /* Raise a contact pre-draw signal here. THe callback will return an * escaped version of the name. */ nametext = purple_signal_emit_return_1 ( pidgin_blist_get_handle (), "drawing-buddy" , b ); nametext = g_markup_escape_text ( name , strlen ( name )); presence = purple_buddy_get_presence ( b ); /* Name is all that is needed */ if ( ! aliased || biglist ) { protocol = purple_protocols_find ( purple_account_get_protocol_id ( purple_buddy_get_account ( b ))); if ( protocol && PURPLE_PROTOCOL_IMPLEMENTS ( protocol , CLIENT , status_text ) && purple_account_get_connection ( purple_buddy_get_account ( b ))) { char * tmp = purple_protocol_client_iface_status_text ( protocol , b ); if ( tmp && ! g_utf8_validate ( tmp , -1 , & end )) { char * new = g_strndup ( tmp , g_utf8_pointer_to_offset ( tmp , end )); g_strdelimit ( tmp , " \n " , ' ' ); purple_str_strip_char ( tmp , '\r' ); if ( ! purple_presence_is_online ( presence ) && ! statustext ) statustext = g_strdup ( _ ( "Offline" )); if ( purple_presence_is_idle ( presence ) && purple_prefs_get_bool ( PIDGIN_PREFS_ROOT "/blist/show_idle_time" )) { time_t idle_secs = purple_presence_get_idle_time ( presence ); iday = ( t - idle_secs ) / ( 24 * 60 * 60 ); ihrs = (( t - idle_secs ) / 60 / 60 ) % 24 ; imin = (( t - idle_secs ) / 60 ) % 60 ; idletime = g_strdup_printf ( _ ( "Idle %dd %dh %02dm" ), iday , ihrs , imin ); idletime = g_strdup_printf ( _ ( "Idle %dh %02dm" ), ihrs , imin ); idletime = g_strdup_printf ( _ ( "Idle %dm" ), imin ); idletime = g_strdup ( _ ( "Idle" )); /* choose the colors of the text */ theme = pidgin_blist_get_theme (); dim_grey = pidgin_style_is_dark ( NULL ) ? "light slate grey" : "dim grey" ; if ( purple_presence_is_idle ( presence )) { namefont = statusfont = pidgin_blist_theme_get_idle_text_info ( theme ); } else if ( ! purple_presence_is_online ( presence )) { namefont = pidgin_blist_theme_get_offline_text_info ( theme ); statusfont = pidgin_blist_theme_get_status_text_info ( theme ); } else if ( purple_presence_is_available ( presence )) { namefont = pidgin_blist_theme_get_online_text_info ( theme ); statusfont = pidgin_blist_theme_get_status_text_info ( theme ); namefont = pidgin_blist_theme_get_away_text_info ( theme ); statusfont = pidgin_blist_theme_get_status_text_info ( theme ); && ( purple_presence_is_idle ( presence ) || ! purple_presence_is_online ( presence ))) name_color = theme_font_get_color_default ( namefont , name_color ); name_font = theme_font_get_face_default ( namefont , "" ); status_color = theme_font_get_color_default ( statusfont , dim_grey ); status_font = theme_font_get_face_default ( statusfont , "" ); if ( aliased && selected ) { nametext = g_strdup_printf ( "<b>%s</b>" , tmp ); /* Put it all together */ if (( ! aliased || biglist ) && ( statustext || idletime )) { /* using <span size='smaller'> breaks the status, so it must be seperated into <small><span>*/ text = g_strdup_printf ( "<span font_desc='%s' foreground='%s'>%s</span> \n " "<small><span font_desc='%s' foreground='%s'>%s%s%s</span></small>" , name_font , name_color , nametext , status_font , status_color , idletime != NULL ? idletime : "" , ( idletime != NULL && statustext != NULL ) ? " - " : "" , statustext != NULL ? statustext : "" ); } else if ( status_color ) { text = g_strdup_printf ( "<span font_desc='%s'>%s</span> \n " "<small><span font_desc='%s' foreground='%s'>%s%s%s</span></small>" , name_font , nametext , status_font , status_color , idletime != NULL ? idletime : "" , ( idletime != NULL && statustext != NULL ) ? " - " : "" , statustext != NULL ? statustext : "" ); text = g_strdup_printf ( "<span font_desc='%s'>%s</span> \n " "<small><span font_desc='%s'>%s%s%s</span></small>" , name_font , nametext , status_font , idletime != NULL ? idletime : "" , ( idletime != NULL && statustext != NULL ) ? " - " : "" , statustext != NULL ? statustext : "" ); text = g_strdup_printf ( "<span font_desc='%s' color='%s'>%s</span>" , name_font , name_color , nametext ); text = g_strdup_printf ( "<span font_desc='%s'>%s</span>" , name_font , text = g_strdup_printf ( "<b>%s</b>" , tmp ); static void pidgin_blist_restore_window_state ( void ) int blist_width , blist_height ; blist_width = purple_prefs_get_int ( PIDGIN_PREFS_ROOT "/blist/width" ); /* if the window exists, is hidden, we're saving sizes, and the if ( gtkblist && gtkblist -> window && ! gtk_widget_get_visible ( gtkblist -> window ) && blist_width != 0 ) { blist_height = purple_prefs_get_int ( PIDGIN_PREFS_ROOT "/blist/height" ); gtk_window_set_default_size ( GTK_WINDOW ( gtkblist -> window ), blist_width , blist_height ); if ( purple_prefs_get_bool ( PIDGIN_PREFS_ROOT "/blist/list_maximized" )) gtk_window_maximize ( GTK_WINDOW ( gtkblist -> window )); static gboolean pidgin_blist_refresh_timer ( PurpleBuddyList * list ) PurpleBlistNode * gnode , * cnode ; if ( gtk_blist_visibility == GDK_VISIBILITY_FULLY_OBSCURED || ! gtk_widget_get_visible ( gtkblist -> window )) for ( gnode = purple_blist_get_root ( list ); gnode ; gnode = gnode -> next ) { if ( ! PURPLE_IS_GROUP ( gnode )) for ( cnode = gnode -> child ; cnode ; cnode = cnode -> next ) { if ( PURPLE_IS_CONTACT ( cnode )) { buddy = purple_contact_get_priority_buddy (( PurpleContact * ) cnode ); purple_presence_is_idle ( purple_buddy_get_presence ( buddy ))) pidgin_blist_update_contact ( list , PURPLE_BLIST_NODE ( buddy )); static void pidgin_blist_hide_node ( PurpleBuddyList * list , PurpleBlistNode * node , gboolean update ) PidginBlistNode * gtknode = purple_blist_node_get_ui_data ( node ); if ( ! gtknode || ! gtknode -> row || ! gtkblist ) if ( gtkblist -> selected_node == node ) gtkblist -> selected_node = NULL ; if ( get_iter_from_node ( node , & iter )) { gtk_tree_store_remove ( gtkblist -> treemodel , & iter ); if ( update && ( PURPLE_IS_CONTACT ( node ) || PURPLE_IS_BUDDY ( node ) || PURPLE_IS_CHAT ( node ))) { pidgin_blist_update ( list , node -> parent ); gtk_tree_row_reference_free ( gtknode -> row ); static const char * require_connection [] = "/BList/BuddiesMenu/NewInstantMessage" , "/BList/BuddiesMenu/JoinAChat" , "/BList/BuddiesMenu/GetUserInfo" , "/BList/BuddiesMenu/AddBuddy" , "/BList/BuddiesMenu/AddChat" , "/BList/BuddiesMenu/AddGroup" , "/BList/ToolsMenu/Privacy" , static const int require_connection_size = sizeof ( require_connection ) / sizeof ( * require_connection ); * Rebuild dynamic menus and make menu items sensitive/insensitive update_menu_bar ( PidginBuddyList * gtkblist ) g_return_if_fail ( gtkblist != NULL ); pidgin_blist_update_accounts_menu (); sensitive = ( purple_connections_get_all () != NULL ); for ( i = 0 ; i < require_connection_size ; i ++ ) action = gtk_ui_manager_get_action ( gtkblist -> ui , require_connection [ i ]); gtk_action_set_sensitive ( action , sensitive ); action = gtk_ui_manager_get_action ( gtkblist -> ui , "/BList/BuddiesMenu/JoinAChat" ); gtk_action_set_sensitive ( action , pidgin_blist_joinchat_is_showable ()); action = gtk_ui_manager_get_action ( gtkblist -> ui , "/BList/BuddiesMenu/AddChat" ); gtk_action_set_sensitive ( action , pidgin_blist_joinchat_is_showable ()); action = gtk_ui_manager_get_action ( gtkblist -> ui , "/BList/ToolsMenu/RoomList" ); gtk_action_set_sensitive ( action , pidgin_roomlist_is_showable ()); sign_on_off_cb ( PurpleConnection * gc , PurpleBuddyList * blist ) PidginBuddyList * gtkblist = PIDGIN_BUDDY_LIST ( blist ); update_menu_bar ( gtkblist ); plugin_changed_cb ( PurplePlugin * p , gpointer data ) pidgin_blist_update_plugin_actions (); unseen_conv_menu ( GdkEvent * event ) static GtkWidget * menu = NULL ; gtk_widget_destroy ( menu ); ims = pidgin_conversations_get_unseen_ims ( PIDGIN_UNSEEN_TEXT , FALSE , 0 ); chats = pidgin_conversations_get_unseen_chats ( PIDGIN_UNSEEN_NICK , FALSE , 0 ); convs = g_list_concat ( ims , chats ); /* no conversations added, don't show the menu */ pidgin_conversations_fill_menu ( menu , convs ); gtk_widget_show_all ( menu ); gtk_menu_popup_at_pointer ( GTK_MENU ( menu ), event ); menutray_press_cb ( GtkWidget * widget , GdkEventButton * event ) if ( event -> button == GDK_BUTTON_PRIMARY ) { convs = pidgin_conversations_get_unseen_ims ( PIDGIN_UNSEEN_TEXT , FALSE , 1 ); convs = pidgin_conversations_get_unseen_chats ( PIDGIN_UNSEEN_NICK , FALSE , 1 ); pidgin_conv_present_conversation (( PurpleConversation * ) convs -> data ); } else if ( gdk_event_triggers_context_menu (( GdkEvent * ) event )) { unseen_conv_menu (( GdkEvent * ) event ); conversation_updated_cb ( PurpleConversation * conv , PurpleConversationUpdateType type , PidginBuddyList * gtkblist ) PurpleAccount * account = purple_conversation_get_account ( conv ); if ( type != PURPLE_CONVERSATION_UPDATE_UNSEEN ) if ( account != NULL && purple_conversation_get_name ( conv ) != NULL ) { PurpleBuddy * buddy = purple_blist_find_buddy ( account , purple_conversation_get_name ( conv )); pidgin_blist_update_buddy ( NULL , PURPLE_BLIST_NODE ( buddy ), TRUE ); if ( gtkblist -> menutrayicon ) { gtk_widget_destroy ( gtkblist -> menutrayicon ); gtkblist -> menutrayicon = NULL ; ims = pidgin_conversations_get_unseen_ims ( PIDGIN_UNSEEN_TEXT , FALSE , 0 ); chats = pidgin_conversations_get_unseen_chats ( PIDGIN_UNSEEN_NICK , FALSE , 0 ); convs = g_list_concat ( ims , chats ); GString * tooltip_text = NULL ; tooltip_text = g_string_new ( "" ); PidginConversation * gtkconv = PIDGIN_CONVERSATION (( PurpleConversation * ) l -> data ); count = gtkconv -> unseen_count ; else if ( g_object_get_data ( G_OBJECT ( l -> data ), "unseen-count" )) count = GPOINTER_TO_INT ( g_object_get_data ( G_OBJECT ( l -> data ), "unseen-count" )); g_string_append_printf ( tooltip_text , ngettext ( "%d unread message from %s \n " , "%d unread messages from %s \n " , count ), count , purple_conversation_get_title ( l -> data )); if ( tooltip_text -> len > 0 ) { /* get rid of the last newline */ g_string_truncate ( tooltip_text , tooltip_text -> len -1 ); img = gtk_image_new_from_stock ( PIDGIN_STOCK_TOOLBAR_PENDING , gtk_icon_size_from_name ( PIDGIN_ICON_SIZE_TANGO_EXTRA_SMALL )); gtkblist -> menutrayicon = gtk_event_box_new (); gtk_container_add ( GTK_CONTAINER ( gtkblist -> menutrayicon ), img ); gtk_widget_show ( gtkblist -> menutrayicon ); g_signal_connect ( G_OBJECT ( gtkblist -> menutrayicon ), "button-press-event" , G_CALLBACK ( menutray_press_cb ), NULL ); pidgin_menu_tray_append ( PIDGIN_MENU_TRAY ( gtkblist -> menutray ), gtkblist -> menutrayicon , tooltip_text -> str ); g_string_free ( tooltip_text , TRUE ); conversation_deleting_cb ( PurpleConversation * conv , PidginBuddyList * gtkblist ) conversation_updated_cb ( conv , PURPLE_CONVERSATION_UPDATE_UNSEEN , gtkblist ); conversation_deleted_update_ui_cb ( PurpleConversation * conv , PidginBlistNode * ui ) if ( ui -> conv . conv != conv ) written_msg_update_ui_cb ( PurpleConversation * conv , PurpleMessage * msg , PurpleBlistNode * node ) PidginBlistNode * ui = purple_blist_node_get_ui_data ( node ); if ( ui -> conv . conv != conv ) if ( ! pidgin_conv_is_hidden ( PIDGIN_CONVERSATION ( conv ))) if ( ! ( purple_message_get_flags ( msg ) & ( PURPLE_MESSAGE_SEND | PURPLE_MESSAGE_RECV ))) ui -> conv . flags |= PIDGIN_BLIST_NODE_HAS_PENDING_MESSAGE ; if ( PURPLE_IS_CHAT_CONVERSATION ( conv ) && ( purple_message_get_flags ( msg ) & PURPLE_MESSAGE_NICK )) ui -> conv . flags |= PIDGIN_BLIST_CHAT_HAS_PENDING_MESSAGE_WITH_NICK ; pidgin_blist_update ( purple_blist_get_default (), node ); displayed_msg_update_ui_cb ( PidginConversation * gtkconv , PurpleBlistNode * node ) PidginBlistNode * ui = purple_blist_node_get_ui_data ( node ); if ( ui -> conv . conv != gtkconv -> active_conv ) ui -> conv . flags &= ~ ( PIDGIN_BLIST_NODE_HAS_PENDING_MESSAGE | PIDGIN_BLIST_CHAT_HAS_PENDING_MESSAGE_WITH_NICK ); pidgin_blist_update ( purple_blist_get_default (), node ); conversation_created_cb ( PurpleConversation * conv , PidginBuddyList * gtkblist ) PurpleAccount * account = purple_conversation_get_account ( conv ); if ( PURPLE_IS_IM_CONVERSATION ( conv )) { GSList * buddies = purple_blist_find_buddies ( account , purple_conversation_get_name ( conv )); PurpleBlistNode * buddy = buddies -> data ; PidginBlistNode * ui = purple_blist_node_get_ui_data ( buddy ); buddies = g_slist_delete_link ( buddies , buddies ); purple_signal_connect ( purple_conversations_get_handle (), "deleting-conversation" , ui , PURPLE_CALLBACK ( conversation_deleted_update_ui_cb ), ui ); purple_signal_connect ( purple_conversations_get_handle (), "wrote-im-msg" , ui , PURPLE_CALLBACK ( written_msg_update_ui_cb ), buddy ); purple_signal_connect ( pidgin_conversations_get_handle (), "conversation-displayed" , ui , PURPLE_CALLBACK ( displayed_msg_update_ui_cb ), buddy ); } else if ( PURPLE_IS_CHAT_CONVERSATION ( conv )) { PurpleChat * chat = purple_blist_find_chat ( account , purple_conversation_get_name ( conv )); ui = purple_blist_node_get_ui_data ( & ( chat -> node )); purple_signal_connect ( purple_conversations_get_handle (), "deleting-conversation" , ui , PURPLE_CALLBACK ( conversation_deleted_update_ui_cb ), ui ); purple_signal_connect ( purple_conversations_get_handle (), "wrote-chat-msg" , ui , PURPLE_CALLBACK ( written_msg_update_ui_cb ), chat ); purple_signal_connect ( pidgin_conversations_get_handle (), "conversation-displayed" , ui , PURPLE_CALLBACK ( displayed_msg_update_ui_cb ), chat ); /********************************************************************************** **********************************************************************************/ pidgin_blist_new_node ( PurpleBuddyList * list , PurpleBlistNode * node ) purple_blist_node_set_ui_data ( node , g_new0 ( PidginBlistNode , 1 )); gboolean pidgin_blist_node_is_contact_expanded ( PurpleBlistNode * node ) if ( PURPLE_IS_BUDDY ( node )) { g_return_val_if_fail ( PURPLE_IS_CONTACT ( node ), FALSE ); return (( PidginBlistNode * ) purple_blist_node_get_ui_data ( node )) -> contact_expanded ; void pidgin_blist_setup_sort_methods () pidgin_blist_sort_method_reg ( "none" , _ ( "Manually" ), sort_method_none ); pidgin_blist_sort_method_reg ( "alphabetical" , _ ( "Alphabetically" ), sort_method_alphabetical ); pidgin_blist_sort_method_reg ( "status" , _ ( "By status" ), sort_method_status ); pidgin_blist_sort_method_reg ( "log_size" , _ ( "By recent log activity" ), sort_method_log_activity ); id = purple_prefs_get_string ( PIDGIN_PREFS_ROOT "/blist/sort_type" ); purple_debug_warning ( "gtkblist" , "Sort method was NULL, resetting to alphabetical \n " ); pidgin_blist_sort_method_set ( id ); static void _prefs_change_redo_list ( const char * name , PurplePrefType type , gconstpointer val , gpointer data ) PurpleBlistNode * node = NULL ; sel = gtk_tree_view_get_selection ( GTK_TREE_VIEW ( gtkblist -> treeview )); if ( gtk_tree_selection_get_selected ( sel , NULL , & iter )) gtk_tree_model_get ( GTK_TREE_MODEL ( gtkblist -> treemodel ), & iter , NODE_COLUMN , & node , -1 ); redo_buddy_list ( purple_blist_get_default (), FALSE , FALSE ); gtk_tree_view_columns_autosize ( GTK_TREE_VIEW ( gtkblist -> treeview )); PidginBlistNode * gtknode ; gtknode = purple_blist_node_get_ui_data ( node ); if ( gtknode && gtknode -> row ) path = gtk_tree_row_reference_get_path ( gtknode -> row ); gtk_tree_selection_select_path ( sel , path ); gtk_tree_view_scroll_to_cell ( GTK_TREE_VIEW ( gtkblist -> treeview ), path , NULL , FALSE , 0 , 0 ); gtk_tree_path_free ( path ); static void _prefs_change_sort_method ( const char * pref_name , PurplePrefType type , gconstpointer val , gpointer data ) if ( purple_strequal ( pref_name , PIDGIN_PREFS_ROOT "/blist/sort_type" )) pidgin_blist_sort_method_set ( val ); static gboolean pidgin_blist_select_notebook_page_cb ( gpointer user_data ) PidginBuddyList * gtkblist = ( PidginBuddyList * ) user_data ; PidginBuddyListPrivate * priv ; priv = pidgin_buddy_list_get_instance_private ( gtkblist ); priv -> select_notebook_page_timeout = 0 ; /* this is far too ugly thanks to me not wanting to fix #3989 properly right now */ if ( priv -> error_scrollbook != NULL ) { errors = gtk_notebook_get_n_pages ( GTK_NOTEBOOK ( priv -> error_scrollbook -> notebook )); if (( list = purple_accounts_get_all_active ()) != NULL || errors ) { gtk_notebook_set_current_page ( GTK_NOTEBOOK ( gtkblist -> notebook ), 1 ); gtk_notebook_set_current_page ( GTK_NOTEBOOK ( gtkblist -> notebook ), 0 ); priv -> select_notebook_page_timeout = 0 ; static void pidgin_blist_select_notebook_page ( PidginBuddyList * gtkblist ) PidginBuddyListPrivate * priv = pidgin_buddy_list_get_instance_private ( gtkblist ); priv -> select_notebook_page_timeout = g_timeout_add ( 0 , pidgin_blist_select_notebook_page_cb , gtkblist ); static void account_modified ( PurpleAccount * account , PidginBuddyList * gtkblist ) pidgin_blist_select_notebook_page ( gtkblist ); update_menu_bar ( gtkblist ); account_actions_changed ( PurpleAccount * account , gpointer data ) pidgin_blist_update_accounts_menu (); account_status_changed ( PurpleAccount * account , PurpleStatus * old , PurpleStatus * new , PidginBuddyList * gtkblist ) account_modified ( account , gtkblist ); gtk_blist_window_key_press_cb ( GtkWidget * w , GdkEventKey * event , PidginBuddyList * gtkblist ) pidgin_blist_tooltip_destroy (); reset_headline ( PidginBuddyList * gtkblist ) gtkblist -> headline_callback = NULL ; gtkblist -> headline_data = NULL ; gtkblist -> headline_destroy = NULL ; pidgin_set_urgent ( GTK_WINDOW ( gtkblist -> window ), FALSE ); headline_click_callback ( gpointer unused ) if ( gtkblist -> headline_callback ) (( GSourceFunc ) gtkblist -> headline_callback )( gtkblist -> headline_data ); reset_headline ( gtkblist ); headline_response_cb ( GtkInfoBar * infobar , int resp , PidginBuddyList * gtkblist ) gtk_widget_hide ( gtkblist -> headline ); if ( resp == GTK_RESPONSE_OK ) { if ( gtkblist -> headline_callback ) g_idle_add ( headline_click_callback , NULL ); if ( gtkblist -> headline_destroy ) gtkblist -> headline_destroy ( gtkblist -> headline_data ); reset_headline ( gtkblist ); if ( gtkblist -> headline_destroy ) gtkblist -> headline_destroy ( gtkblist -> headline_data ); reset_headline ( gtkblist ); headline_realize_cb ( GtkWidget * widget , gpointer data ) GdkWindow * window = gtk_widget_get_window ( widget ); GdkDisplay * display = gdk_window_get_display ( window ); GdkCursor * hand_cursor = gdk_cursor_new_for_display ( display , GDK_HAND2 ); gdk_window_set_cursor ( window , hand_cursor ); g_object_unref ( hand_cursor ); headline_press_cb ( GtkWidget * widget , GdkEventButton * event , GtkInfoBar * infobar ) gtk_info_bar_response ( infobar , GTK_RESPONSE_OK ); /***********************************/ /* Connection error handling stuff */ /***********************************/ #define OBJECT_DATA_KEY_ACCOUNT "account" #define DO_NOT_CLEAR_ERROR "do-not-clear-error" find_account_widget ( GObject * widget , if ( g_object_get_data ( widget , OBJECT_DATA_KEY_ACCOUNT ) == account ) pack_protocol_icon_start ( GtkWidget * box , pixbuf = pidgin_create_protocol_icon ( account , PIDGIN_PROTOCOL_ICON_SMALL ); image = gtk_image_new_from_pixbuf ( pixbuf ); gtk_box_pack_start ( GTK_BOX ( box ), image , FALSE , FALSE , 0 ); add_error_dialog ( PidginBuddyList * gtkblist , PidginBuddyListPrivate * priv = pidgin_buddy_list_get_instance_private ( gtkblist ); gtk_container_add ( GTK_CONTAINER ( priv -> error_scrollbook ), dialog ); find_child_widget_by_account ( GtkContainer * container , /* XXX: Workaround for the currently incomplete implementation of PidginScrollBook */ if ( PIDGIN_IS_SCROLL_BOOK ( container )) container = GTK_CONTAINER ( PIDGIN_SCROLL_BOOK ( container ) -> notebook ); children = gtk_container_get_children ( container ); l = g_list_find_custom ( children , account , ( GCompareFunc ) find_account_widget ); ret = GTK_WIDGET ( l -> data ); remove_child_widget_by_account ( GtkContainer * container , GtkWidget * widget = find_child_widget_by_account ( container , account ); /* Since we are destroying the widget in response to a change in * error, we should not clear the error. g_object_set_data ( G_OBJECT ( widget ), DO_NOT_CLEAR_ERROR , gtk_widget_destroy ( widget ); /* Generic error buttons */ generic_error_modify_cb ( PurpleAccount * account ) purple_account_clear_current_error ( account ); pidgin_account_dialog_show ( PIDGIN_MODIFY_ACCOUNT_DIALOG , account ); generic_error_enable_cb ( PurpleAccount * account ) purple_account_clear_current_error ( account ); purple_account_set_enabled ( account , purple_core_get_ui (), TRUE ); generic_error_destroy_cb ( GtkWidget * dialog , /* If the error dialog is being destroyed in response to the * account-error-changed signal, we don't want to clear the current if ( g_object_get_data ( G_OBJECT ( dialog ), DO_NOT_CLEAR_ERROR ) == NULL ) purple_account_clear_current_error ( account ); #define SSL_FAQ_URI "https: //developer.pidgin.im/wiki/FAQssl" ssl_faq_clicked_cb ( PidginMiniDialog * mini_dialog , purple_notify_uri ( NULL , SSL_FAQ_URI ); add_generic_error_dialog ( PurpleAccount * account , const PurpleConnectionErrorInfo * err ) const char * username = purple_account_get_username ( account ); purple_account_get_enabled ( account , purple_core_get_ui ()); primary = g_strdup_printf ( _ ( "%s disconnected" ), username ); primary = g_strdup_printf ( _ ( "%s disabled" ), username ); mini_dialog = pidgin_make_mini_dialog ( NULL , "dialog-error" , primary , err -> description , account , ( enabled ? _ ( "Reconnect" ) : _ ( "Re-enable" )), ( enabled ? PURPLE_CALLBACK ( purple_account_connect ) : PURPLE_CALLBACK ( generic_error_enable_cb )), _ ( "Modify Account" ), PURPLE_CALLBACK ( generic_error_modify_cb ), g_object_set_data ( G_OBJECT ( mini_dialog ), OBJECT_DATA_KEY_ACCOUNT , if ( err -> type == PURPLE_CONNECTION_ERROR_NO_SSL_SUPPORT ) pidgin_mini_dialog_add_non_closing_button ( PIDGIN_MINI_DIALOG ( mini_dialog ), _ ( "SSL FAQs" ), ssl_faq_clicked_cb , NULL ); g_signal_connect_after ( mini_dialog , "destroy" , ( GCallback ) generic_error_destroy_cb , add_error_dialog ( gtkblist , mini_dialog ); remove_generic_error_dialog ( PurpleAccount * account ) PidginBuddyListPrivate * priv = pidgin_buddy_list_get_instance_private ( gtkblist ); remove_child_widget_by_account ( GTK_CONTAINER ( priv -> error_scrollbook ), account ); update_generic_error_message ( PurpleAccount * account , PidginBuddyListPrivate * priv = pidgin_buddy_list_get_instance_private ( gtkblist ); GtkWidget * mini_dialog = find_child_widget_by_account ( GTK_CONTAINER ( priv -> error_scrollbook ), account ); pidgin_mini_dialog_set_description ( PIDGIN_MINI_DIALOG ( mini_dialog ), /* Notifications about accounts which were disconnected with * PURPLE_CONNECTION_ERROR_NAME_IN_USE typedef void ( * AccountFunction )( PurpleAccount * ); elsewhere_foreach_account ( PidginMiniDialog * mini_dialog , GList * labels = gtk_container_get_children ( GTK_CONTAINER ( mini_dialog -> contents )); for ( l = labels ; l ; l = l -> next ) { account = g_object_get_data ( G_OBJECT ( l -> data ), OBJECT_DATA_KEY_ACCOUNT ); purple_debug_warning ( "gtkblist" , "mini_dialog's child " "didn't have an account stored in it!" ); enable_account ( PurpleAccount * account ) purple_account_set_enabled ( account , purple_core_get_ui (), TRUE ); reconnect_elsewhere_accounts ( PidginMiniDialog * mini_dialog , elsewhere_foreach_account ( mini_dialog , enable_account ); clear_elsewhere_errors ( PidginMiniDialog * mini_dialog , elsewhere_foreach_account ( mini_dialog , purple_account_clear_current_error ); ensure_signed_on_elsewhere_minidialog ( PidginBuddyList * gtkblist ) PidginBuddyListPrivate * priv = pidgin_buddy_list_get_instance_private ( gtkblist ); PidginMiniDialog * mini_dialog ; if ( priv -> signed_on_elsewhere ) mini_dialog = priv -> signed_on_elsewhere = pidgin_mini_dialog_new ( _ ( "Welcome back!" ), NULL , PIDGIN_STOCK_DISCONNECT ); pidgin_mini_dialog_add_button ( mini_dialog , _ ( "Re-enable" ), reconnect_elsewhere_accounts , NULL ); /* Make dismissing the dialog clear the errors. The "destroy" signal * does not appear to fire at quit, which is fortunate! g_signal_connect ( G_OBJECT ( mini_dialog ), "destroy" , ( GCallback ) clear_elsewhere_errors , NULL ); add_error_dialog ( gtkblist , GTK_WIDGET ( mini_dialog )); /* Set priv->signed_on_elsewhere to NULL when the dialog is destroyed */ g_signal_connect ( G_OBJECT ( mini_dialog ), "destroy" , ( GCallback ) gtk_widget_destroyed , & ( priv -> signed_on_elsewhere )); update_signed_on_elsewhere_minidialog_title ( void ) PidginBuddyListPrivate * priv = pidgin_buddy_list_get_instance_private ( gtkblist ); PidginMiniDialog * mini_dialog = priv -> signed_on_elsewhere ; accounts = pidgin_mini_dialog_get_num_children ( mini_dialog ); gtk_widget_destroy ( GTK_WIDGET ( mini_dialog )); ngettext ( "%d account was disabled because you signed on from another location:" , "%d accounts were disabled because you signed on from another location:" , pidgin_mini_dialog_set_description ( mini_dialog , title ); create_account_label ( PurpleAccount * account ) const char * username = purple_account_get_username ( account ); hbox = gtk_box_new ( GTK_ORIENTATION_HORIZONTAL , 6 ); g_object_set_data ( G_OBJECT ( hbox ), OBJECT_DATA_KEY_ACCOUNT , account ); pack_protocol_icon_start ( hbox , account ); label = gtk_label_new ( NULL ); markup = g_strdup_printf ( "<span size= \" smaller \" >%s</span>" , username ); gtk_label_set_markup ( GTK_LABEL ( label ), markup ); gtk_label_set_xalign ( GTK_LABEL ( label ), 0 ); gtk_label_set_yalign ( GTK_LABEL ( label ), 0 ); g_object_set ( G_OBJECT ( label ), "ellipsize" , PANGO_ELLIPSIZE_END , NULL ); description = purple_account_get_current_error ( account ) -> description ; if ( description != NULL && * description != '\0' ) gtk_widget_set_tooltip_text ( label , description ); gtk_box_pack_start ( GTK_BOX ( hbox ), label , TRUE , TRUE , 0 ); add_to_signed_on_elsewhere ( PurpleAccount * account ) PidginBuddyListPrivate * priv = pidgin_buddy_list_get_instance_private ( gtkblist ); PidginMiniDialog * mini_dialog ; GtkWidget * account_label ; ensure_signed_on_elsewhere_minidialog ( gtkblist ); mini_dialog = priv -> signed_on_elsewhere ; if ( find_child_widget_by_account ( GTK_CONTAINER ( mini_dialog -> contents ), account )) account_label = create_account_label ( account ); gtk_box_pack_start ( mini_dialog -> contents , account_label , FALSE , FALSE , 0 ); gtk_widget_show_all ( account_label ); update_signed_on_elsewhere_minidialog_title (); remove_from_signed_on_elsewhere ( PurpleAccount * account ) PidginBuddyListPrivate * priv = pidgin_buddy_list_get_instance_private ( gtkblist ); PidginMiniDialog * mini_dialog = priv -> signed_on_elsewhere ; remove_child_widget_by_account ( GTK_CONTAINER ( mini_dialog -> contents ), account ); update_signed_on_elsewhere_minidialog_title (); update_signed_on_elsewhere_tooltip ( PurpleAccount * account , PidginBuddyListPrivate * priv = pidgin_buddy_list_get_instance_private ( gtkblist ); GtkContainer * c = GTK_CONTAINER ( priv -> signed_on_elsewhere -> contents ); GtkWidget * label = find_child_widget_by_account ( c , account ); gtk_widget_set_tooltip_text ( label , description ); /* Call appropriate error notification code based on error types */ update_account_error_state ( PurpleAccount * account , const PurpleConnectionErrorInfo * old , const PurpleConnectionErrorInfo * new , PidginBuddyList * gtkblist ) gboolean descriptions_differ ; if ( old == NULL && new == NULL ) pidgin_blist_select_notebook_page ( gtkblist ); if ( old != NULL && new == NULL ) { if ( old -> type == PURPLE_CONNECTION_ERROR_NAME_IN_USE ) remove_from_signed_on_elsewhere ( account ); remove_generic_error_dialog ( account ); if ( old == NULL && new != NULL ) { if ( new -> type == PURPLE_CONNECTION_ERROR_NAME_IN_USE ) add_to_signed_on_elsewhere ( account ); add_generic_error_dialog ( account , new ); /* else, new and old are both non-NULL */ descriptions_differ = ! purple_strequal ( old -> description , new -> description ); case PURPLE_CONNECTION_ERROR_NAME_IN_USE : if ( old -> type == PURPLE_CONNECTION_ERROR_NAME_IN_USE && descriptions_differ ) { update_signed_on_elsewhere_tooltip ( account , desc ); remove_generic_error_dialog ( account ); add_to_signed_on_elsewhere ( account ); if ( old -> type == PURPLE_CONNECTION_ERROR_NAME_IN_USE ) { remove_from_signed_on_elsewhere ( account ); add_generic_error_dialog ( account , new ); } else if ( descriptions_differ ) { update_generic_error_message ( account , desc ); /* In case accounts are loaded before the blist (which they currently are), * let's call update_account_error_state ourselves on every account's current * state when the blist starts. show_initial_account_errors ( PidginBuddyList * gtkblist ) GList * l = purple_accounts_get_all (); const PurpleConnectionErrorInfo * err ; err = purple_account_get_current_error ( account ); update_account_error_state ( account , NULL , err , gtkblist ); /* This assumes there are not things like groupless buddies or multi-leveled groups. * I'm sure other things in this code assumes that also. treeview_style_set ( GtkWidget * widget , PurpleBuddyList * list = data ; PurpleBlistNode * node = purple_blist_get_root ( list ); pidgin_blist_update_group ( list , node ); /******************************************/ /* End of connection error handling stuff */ /******************************************/ blist_focus_cb ( GtkWidget * widget , GdkEventFocus * event , PidginBuddyList * gtkblist ) gtk_blist_focused = TRUE ; pidgin_set_urgent ( GTK_WINDOW ( gtkblist -> window ), FALSE ); gtk_blist_focused = FALSE ; GtkWidget *ret = gtk_box_new(GTK_ORIENTATION_VERTICAL, PIDGIN_HIG_BOX_SPACE); label = gtk_label_new(NULL); gtk_box_pack_start(GTK_BOX(ret), label, TRUE, TRUE, 0); label = gtk_label_new(NULL); gtk_label_set_markup(GTK_LABEL(label), _("<b>Username:</b>")); gtk_label_set_xalign(GTK_LABEL(label), 0.0); gtk_box_pack_start(GTK_BOX(ret), label, FALSE, FALSE, 0); gtk_box_pack_start(GTK_BOX(ret), entry, FALSE, FALSE, 0); label = gtk_label_new(NULL); gtk_label_set_markup(GTK_LABEL(label), _("<b>Password:</b>")); gtk_label_set_xalign(GTK_LABEL(label), 0.0); gtk_box_pack_start(GTK_BOX(ret), label, FALSE, FALSE, 0); gtk_entry_set_visibility(GTK_ENTRY(entry), FALSE); gtk_box_pack_start(GTK_BOX(ret), entry, FALSE, FALSE, 0); label = gtk_label_new(" "); gtk_box_pack_start(GTK_BOX(ret), label, FALSE, FALSE, 0); bbox = gtk_button_box_new(GTK_ORIENTATION_HORIZONTAL); button = gtk_button_new_with_mnemonic(_("_Login")); gtk_box_pack_start(GTK_BOX(ret), bbox, FALSE, FALSE, 0); gtk_container_add(GTK_CONTAINER(bbox), button); label = gtk_label_new(NULL); gtk_box_pack_start(GTK_BOX(ret), label, TRUE, TRUE, 0); gtk_container_set_border_width(GTK_CONTAINER(ret), PIDGIN_HIG_BORDER); gtk_widget_show_all(ret); /* builds the blist layout according to to the current theme */ pidgin_blist_build_layout ( PurpleBuddyList * list ) GtkTreeViewColumn * column ; PidginBlistLayout * layout ; gint i , status_icon = 0 , text = 1 , emblem = 2 , protocol_icon = 3 , buddy_icon = 4 ; column = gtkblist -> text_column ; if (( theme = pidgin_blist_get_theme ()) != NULL && ( layout = pidgin_blist_theme_get_layout ( theme )) != NULL ) { status_icon = layout -> status_icon ; protocol_icon = layout -> protocol_icon ; buddy_icon = layout -> buddy_icon ; gtk_tree_view_column_clear ( column ); rend = pidgin_cell_renderer_expander_new (); gtk_tree_view_column_pack_start ( column , rend , FALSE ); gtk_tree_view_column_set_attributes ( column , rend , "visible" , GROUP_EXPANDER_VISIBLE_COLUMN , "expander-visible" , GROUP_EXPANDER_COLUMN , "sensitive" , GROUP_EXPANDER_COLUMN , "cell-background-rgba" , BGCOLOR_COLUMN , rend = pidgin_cell_renderer_expander_new (); gtk_tree_view_column_pack_start ( column , rend , FALSE ); gtk_tree_view_column_set_attributes ( column , rend , "visible" , CONTACT_EXPANDER_VISIBLE_COLUMN , "expander-visible" , CONTACT_EXPANDER_COLUMN , "sensitive" , CONTACT_EXPANDER_COLUMN , "cell-background-rgba" , BGCOLOR_COLUMN , for ( i = 0 ; i < 5 ; i ++ ) { rend = gtk_cell_renderer_pixbuf_new (); gtk_tree_view_column_pack_start ( column , rend , FALSE ); gtk_tree_view_column_set_attributes ( column , rend , "pixbuf" , STATUS_ICON_COLUMN , "visible" , STATUS_ICON_VISIBLE_COLUMN , "cell-background-rgba" , BGCOLOR_COLUMN , g_object_set ( rend , "xalign" , 0.0 , "xpad" , 6 , "ypad" , 0 , NULL ); gtkblist -> text_rend = rend = gtk_cell_renderer_text_new (); gtk_tree_view_column_pack_start ( column , rend , TRUE ); gtk_tree_view_column_set_attributes ( column , rend , "cell-background-rgba" , BGCOLOR_COLUMN , G_OBJECT ( rend ), "editing-started" , G_CALLBACK ( gtk_blist_renderer_editing_started_cb ), g_signal_connect ( G_OBJECT ( rend ), "editing-canceled" , G_CALLBACK ( gtk_blist_renderer_editing_cancelled_cb ), list ); g_signal_connect ( G_OBJECT ( rend ), "edited" , G_CALLBACK ( gtk_blist_renderer_edited_cb ), list ); g_object_set ( rend , "ypad" , 0 , "yalign" , 0.5 , NULL ); g_object_set ( rend , "ellipsize" , PANGO_ELLIPSIZE_END , NULL ); rend = gtk_cell_renderer_text_new (); g_object_set ( rend , "xalign" , 1.0 , "ypad" , 0 , NULL ); gtk_tree_view_column_pack_start ( column , rend , FALSE ); gtk_tree_view_column_set_attributes ( column , rend , "visible" , IDLE_VISIBLE_COLUMN , "cell-background-rgba" , BGCOLOR_COLUMN , } else if ( emblem == i ) { rend = gtk_cell_renderer_pixbuf_new (); g_object_set ( rend , "xalign" , 1.0 , "yalign" , 0.5 , "ypad" , 0 , "xpad" , 3 , NULL ); gtk_tree_view_column_pack_start ( column , rend , FALSE ); gtk_tree_view_column_set_attributes ( column , rend , "pixbuf" , EMBLEM_COLUMN , "cell-background-rgba" , BGCOLOR_COLUMN , "visible" , EMBLEM_VISIBLE_COLUMN , NULL ); } else if ( protocol_icon == i ) { rend = gtk_cell_renderer_pixbuf_new (); gtk_tree_view_column_pack_start ( column , rend , FALSE ); gtk_tree_view_column_set_attributes ( column , rend , "pixbuf" , PROTOCOL_ICON_COLUMN , "visible" , PROTOCOL_ICON_VISIBLE_COLUMN , "cell-background-rgba" , BGCOLOR_COLUMN , g_object_set ( rend , "xalign" , 0.0 , "xpad" , 3 , "ypad" , 0 , NULL ); } else if ( buddy_icon == i ) { rend = gtk_cell_renderer_pixbuf_new (); g_object_set ( rend , "xalign" , 1.0 , "ypad" , 0 , NULL ); gtk_tree_view_column_pack_start ( column , rend , FALSE ); gtk_tree_view_column_set_attributes ( column , rend , "pixbuf" , BUDDY_ICON_COLUMN , "cell-background-rgba" , BGCOLOR_COLUMN , "visible" , BUDDY_ICON_VISIBLE_COLUMN , pidgin_blist_search_equal_func ( GtkTreeModel * model , gint column , const gchar * key , GtkTreeIter * iter , gpointer data ) PurpleBlistNode * node = NULL ; const char * compare = NULL ; if ( ! pidgin_tree_view_search_equal_func ( model , column , key , iter , data )) /* If the search string does not match the displayed label, then look * at the alternate labels for the nodes and search in them. Currently, * alternate labels that make sense are usernames/email addresses for * buddies (but only for the ones who don't have a local alias). gtk_tree_model_get ( model , iter , NODE_COLUMN , & node , -1 ); if ( PURPLE_IS_CONTACT ( node )) { PurpleBuddy * b = purple_contact_get_priority_buddy ( PURPLE_CONTACT ( node )); if ( ! purple_buddy_get_local_alias ( b )) compare = purple_buddy_get_name ( b ); } else if ( PURPLE_IS_BUDDY ( node )) { if ( ! purple_buddy_get_local_alias ( PURPLE_BUDDY ( node ))) compare = purple_buddy_get_name ( PURPLE_BUDDY ( node )); char * tmp , * enteredstring ; tmp = g_utf8_normalize ( key , -1 , G_NORMALIZE_DEFAULT ); enteredstring = g_utf8_casefold ( tmp , -1 ); if ( purple_str_has_prefix ( compare , enteredstring )) static void pidgin_blist_show ( PurpleBuddyList * list ) PidginBuddyListPrivate * priv ; GtkTreeViewColumn * column ; GtkActionGroup * action_group ; GtkAccelGroup * accel_group ; GtkTreeSelection * selection ; GtkTargetEntry dte [] = {{ "PURPLE_BLIST_NODE" , GTK_TARGET_SAME_APP , DRAG_ROW }, { "application/x-im-contact" , 0 , DRAG_BUDDY }, { "text/x-vcard" , 0 , DRAG_VCARD }, { "text/uri-list" , 0 , DRAG_URI }, { "text/plain" , 0 , DRAG_TEXT }}; GtkTargetEntry ste [] = {{ "PURPLE_BLIST_NODE" , GTK_TARGET_SAME_APP , DRAG_ROW }, { "application/x-im-contact" , 0 , DRAG_BUDDY }, { "text/x-vcard" , 0 , DRAG_VCARD }}; if ( gtkblist && gtkblist -> window ) { purple_blist_set_visible ( purple_prefs_get_bool ( PIDGIN_PREFS_ROOT "/blist/list_visible" )); gtkblist = PIDGIN_BUDDY_LIST ( list ); priv = pidgin_buddy_list_get_instance_private ( gtkblist ); g_object_unref ( priv -> current_theme ); theme_name = purple_prefs_get_string ( PIDGIN_PREFS_ROOT "/blist/theme" ); if ( theme_name && * theme_name ) priv -> current_theme = g_object_ref ( PIDGIN_BLIST_THEME ( purple_theme_manager_find_theme ( theme_name , "blist" ))); priv -> current_theme = NULL ; gtkblist -> empty_avatar = gdk_pixbuf_new ( GDK_COLORSPACE_RGB , TRUE , 8 , 32 , 32 ); gdk_pixbuf_fill ( gtkblist -> empty_avatar , 0x00000000 ); gtkblist -> window = pidgin_create_window ( _ ( "Buddy List" ), 0 , "buddy_list" , TRUE ); g_signal_connect ( G_OBJECT ( gtkblist -> window ), "focus-in-event" , G_CALLBACK ( blist_focus_cb ), gtkblist ); g_signal_connect ( G_OBJECT ( gtkblist -> window ), "focus-out-event" , G_CALLBACK ( blist_focus_cb ), gtkblist ); gtkblist -> main_vbox = gtk_box_new ( GTK_ORIENTATION_VERTICAL , 0 ); gtk_widget_show ( gtkblist -> main_vbox ); gtk_container_add ( GTK_CONTAINER ( gtkblist -> window ), gtkblist -> main_vbox ); g_signal_connect ( G_OBJECT ( gtkblist -> window ), "delete_event" , G_CALLBACK ( gtk_blist_delete_cb ), NULL ); g_signal_connect ( G_OBJECT ( gtkblist -> window ), "hide" , G_CALLBACK ( gtk_blist_hide_cb ), gtkblist ); g_signal_connect ( G_OBJECT ( gtkblist -> window ), "show" , G_CALLBACK ( gtk_blist_show_cb ), gtkblist ); g_signal_connect ( G_OBJECT ( gtkblist -> window ), "size-allocate" , G_CALLBACK ( gtk_blist_size_allocate_cb ), NULL ); g_signal_connect ( G_OBJECT ( gtkblist -> window ), "visibility_notify_event" , G_CALLBACK ( gtk_blist_visibility_cb ), NULL ); g_signal_connect ( G_OBJECT ( gtkblist -> window ), "window_state_event" , G_CALLBACK ( gtk_blist_window_state_cb ), NULL ); g_signal_connect ( G_OBJECT ( gtkblist -> window ), "key_press_event" , G_CALLBACK ( gtk_blist_window_key_press_cb ), gtkblist ); gtk_widget_add_events ( gtkblist -> window , GDK_VISIBILITY_NOTIFY_MASK ); /******************************* Menu bar *************************************/ action_group = gtk_action_group_new ( "BListActions" ); gtk_action_group_set_translation_domain ( action_group , PACKAGE ); gtk_action_group_add_actions ( action_group , G_N_ELEMENTS ( blist_menu_entries ), GTK_WINDOW ( gtkblist -> window )); gtk_action_group_add_toggle_actions ( action_group , blist_menu_toggle_entries , G_N_ELEMENTS ( blist_menu_toggle_entries ), GTK_WINDOW ( gtkblist -> window )); gtkblist -> ui = gtk_ui_manager_new (); gtk_ui_manager_insert_action_group ( gtkblist -> ui , action_group , 0 ); accel_group = gtk_ui_manager_get_accel_group ( gtkblist -> ui ); gtk_window_add_accel_group ( GTK_WINDOW ( gtkblist -> window ), accel_group ); g_signal_connect ( G_OBJECT ( accel_group ), "accel-changed" , G_CALLBACK ( pidgin_save_accels_cb ), NULL ); if ( ! gtk_ui_manager_add_ui_from_string ( gtkblist -> ui , blist_menu , -1 , & error )) g_message ( "building menus failed: %s" , error -> message ); menu = gtk_ui_manager_get_widget ( gtkblist -> ui , "/BList" ); gtkblist -> menutray = pidgin_menu_tray_new (); gtk_menu_shell_append ( GTK_MENU_SHELL ( menu ), gtkblist -> menutray ); gtk_widget_show ( gtkblist -> menutray ); gtk_box_pack_start ( GTK_BOX ( gtkblist -> main_vbox ), menu , FALSE , FALSE , 0 ); menu = gtk_ui_manager_get_widget ( gtkblist -> ui , "/BList/AccountsMenu" ); accountmenu = gtk_menu_item_get_submenu ( GTK_MENU_ITEM ( menu )); /****************************** Notebook *************************************/ gtkblist -> notebook = gtk_notebook_new (); gtk_notebook_set_show_tabs ( GTK_NOTEBOOK ( gtkblist -> notebook ), FALSE ); gtk_notebook_set_show_border ( GTK_NOTEBOOK ( gtkblist -> notebook ), FALSE ); gtk_box_pack_start ( GTK_BOX ( gtkblist -> main_vbox ), gtkblist -> notebook , TRUE , TRUE , 0 ); gtk_notebook_append_page(GTK_NOTEBOOK(gtkblist->notebook), kiosk_page(), NULL); /* Translators: Please maintain the use of ⇦ or ⇨ to refer to menu hierarchy */ text = g_strdup_printf ( _ ( "<span weight='bold' size='larger'>Welcome to %s!</span> \n\n " "You have no accounts enabled. Enable your IM accounts from the " "<b>Accounts</b> window at <b>Accounts⇨Manage Accounts</b>. Once you " "enable accounts, you'll be able to sign on, set your status, " "and talk to your friends." ), PIDGIN_NAME ); label = gtk_label_new ( NULL ); gtk_label_set_line_wrap ( GTK_LABEL ( label ), TRUE ); gtk_label_set_yalign ( GTK_LABEL ( label ), 0.2 ); gtk_label_set_markup ( GTK_LABEL ( label ), text ); gtk_notebook_append_page ( GTK_NOTEBOOK ( gtkblist -> notebook ), label , NULL ); gtkblist -> vbox = gtk_box_new ( GTK_ORIENTATION_VERTICAL , 0 ); gtk_notebook_append_page ( GTK_NOTEBOOK ( gtkblist -> notebook ), gtkblist -> vbox , NULL ); gtk_widget_show_all ( gtkblist -> notebook ); pidgin_blist_select_notebook_page ( gtkblist ); /****************************** Headline **********************************/ gtkblist -> headline = gtk_event_box_new (); gtk_box_pack_start ( GTK_BOX ( gtkblist -> vbox ), gtkblist -> headline , infobar = gtk_info_bar_new (); gtk_container_add ( GTK_CONTAINER ( gtkblist -> headline ), infobar ); gtk_info_bar_set_default_response ( GTK_INFO_BAR ( infobar ), GTK_RESPONSE_OK ); gtk_info_bar_set_message_type ( GTK_INFO_BAR ( infobar ), GTK_MESSAGE_INFO ); content_area = gtk_info_bar_get_content_area ( GTK_INFO_BAR ( infobar )); gtkblist -> headline_image = gtk_image_new_from_pixbuf ( NULL ); gtk_widget_set_halign ( gtkblist -> headline_image , GTK_ALIGN_CENTER ); gtk_widget_set_valign ( gtkblist -> headline_image , GTK_ALIGN_CENTER ); gtkblist -> headline_label = gtk_label_new ( NULL ); gtk_label_set_line_wrap ( GTK_LABEL ( gtkblist -> headline_label ), TRUE ); gtk_box_pack_start ( GTK_BOX ( content_area ), gtkblist -> headline_image , gtk_box_pack_start ( GTK_BOX ( content_area ), gtkblist -> headline_label , close = gtk_image_new_from_stock ( GTK_STOCK_CLOSE , GTK_ICON_SIZE_MENU ); close = pidgin_create_small_button ( close ); gtk_widget_set_tooltip_text ( close , _ ( "Close" )); gtk_info_bar_add_action_widget ( GTK_INFO_BAR ( infobar ), close , g_signal_connect ( infobar , "response" , G_CALLBACK ( headline_response_cb ), g_signal_connect ( infobar , "close" , G_CALLBACK ( gtk_info_bar_response ), GINT_TO_POINTER ( GTK_RESPONSE_CLOSE )); g_signal_connect ( gtkblist -> headline , "realize" , G_CALLBACK ( headline_realize_cb ), NULL ); g_signal_connect ( gtkblist -> headline , "button-press-event" , G_CALLBACK ( headline_press_cb ), infobar ); /****************************** GtkTreeView **********************************/ gtkblist -> treemodel = gtk_tree_store_new ( BLIST_COLUMNS , GDK_TYPE_PIXBUF , /* Status icon */ G_TYPE_BOOLEAN , /* Status icon visible */ G_TYPE_STRING , /* Name */ G_TYPE_STRING , /* Idle */ G_TYPE_BOOLEAN , /* Idle visible */ GDK_TYPE_PIXBUF , /* Buddy icon */ G_TYPE_BOOLEAN , /* Buddy icon visible */ G_TYPE_POINTER , /* Node */ GDK_TYPE_RGBA , /* bgcolor */ G_TYPE_BOOLEAN , /* Group expander */ G_TYPE_BOOLEAN , /* Group expander visible */ G_TYPE_BOOLEAN , /* Contact expander */ G_TYPE_BOOLEAN , /* Contact expander visible */ GDK_TYPE_PIXBUF , /* Emblem */ G_TYPE_BOOLEAN , /* Emblem visible */ GDK_TYPE_PIXBUF , /* Protocol icon */ G_TYPE_BOOLEAN /* Protocol visible */ gtkblist -> treeview = gtk_tree_view_new_with_model ( GTK_TREE_MODEL ( gtkblist -> treemodel )); gtk_widget_show ( gtkblist -> treeview ); gtk_widget_set_name ( gtkblist -> treeview , "pidgin_blist_treeview" ); g_signal_connect ( gtkblist -> treeview , G_CALLBACK ( treeview_style_set ), list ); /* Set up selection stuff */ selection = gtk_tree_view_get_selection ( GTK_TREE_VIEW ( gtkblist -> treeview )); g_signal_connect ( G_OBJECT ( selection ), "changed" , G_CALLBACK ( pidgin_blist_selection_changed ), NULL ); gtk_tree_view_enable_model_drag_source ( GTK_TREE_VIEW ( gtkblist -> treeview ), GDK_BUTTON1_MASK , ste , 3 , gtk_tree_view_enable_model_drag_dest ( GTK_TREE_VIEW ( gtkblist -> treeview ), GDK_ACTION_COPY | GDK_ACTION_MOVE ); g_signal_connect ( G_OBJECT ( gtkblist -> treeview ), "drag-data-received" , G_CALLBACK ( pidgin_blist_drag_data_rcv_cb ), NULL ); g_signal_connect ( G_OBJECT ( gtkblist -> treeview ), "drag-data-get" , G_CALLBACK ( pidgin_blist_drag_data_get_cb ), NULL ); g_signal_connect ( G_OBJECT ( gtkblist -> treeview ), "drag-begin" , G_CALLBACK ( pidgin_blist_drag_begin ), NULL ); g_signal_connect ( G_OBJECT ( gtkblist -> treeview ), "drag-motion" , G_CALLBACK ( pidgin_blist_drag_motion_cb ), NULL ); g_signal_connect ( G_OBJECT ( gtkblist -> treeview ), "motion-notify-event" , G_CALLBACK ( pidgin_blist_motion_cb ), NULL ); g_signal_connect ( G_OBJECT ( gtkblist -> treeview ), "leave-notify-event" , G_CALLBACK ( pidgin_blist_leave_cb ), NULL ); pidgin_tooltip_setup_for_treeview ( gtkblist -> treeview , NULL , pidgin_blist_create_tooltip , gtk_tree_view_set_headers_visible ( GTK_TREE_VIEW ( gtkblist -> treeview ), FALSE ); column = gtk_tree_view_column_new (); gtk_tree_view_append_column ( GTK_TREE_VIEW ( gtkblist -> treeview ), column ); gtk_tree_view_column_set_visible ( column , FALSE ); gtk_tree_view_set_expander_column ( GTK_TREE_VIEW ( gtkblist -> treeview ), column ); /* everything else column */ gtkblist -> text_column = gtk_tree_view_column_new (); gtk_tree_view_append_column ( GTK_TREE_VIEW ( gtkblist -> treeview ), gtkblist -> text_column ); pidgin_blist_build_layout ( list ); g_signal_connect ( G_OBJECT ( gtkblist -> treeview ), "row-activated" , G_CALLBACK ( gtk_blist_row_activated_cb ), gtkblist ); g_signal_connect ( G_OBJECT ( gtkblist -> treeview ), "row-expanded" , G_CALLBACK ( gtk_blist_row_expanded_cb ), gtkblist ); g_signal_connect ( G_OBJECT ( gtkblist -> treeview ), "row-collapsed" , G_CALLBACK ( gtk_blist_row_collapsed_cb ), gtkblist ); g_signal_connect ( G_OBJECT ( gtkblist -> treeview ), "button-press-event" , G_CALLBACK ( gtk_blist_button_press_cb ), NULL ); g_signal_connect ( G_OBJECT ( gtkblist -> treeview ), "key-press-event" , G_CALLBACK ( gtk_blist_key_press_cb ), NULL ); g_signal_connect ( G_OBJECT ( gtkblist -> treeview ), "popup-menu" , G_CALLBACK ( pidgin_blist_popup_menu_cb ), NULL ); /* Enable CTRL+F searching */ gtk_tree_view_set_search_column ( GTK_TREE_VIEW ( gtkblist -> treeview ), NAME_COLUMN ); gtk_tree_view_set_search_equal_func ( GTK_TREE_VIEW ( gtkblist -> treeview ), pidgin_blist_search_equal_func , NULL , NULL ); gtk_box_pack_start ( GTK_BOX ( gtkblist -> vbox ), pidgin_make_scrollable ( gtkblist -> treeview , GTK_POLICY_AUTOMATIC , GTK_POLICY_AUTOMATIC , GTK_SHADOW_NONE , -1 , -1 ), sep = gtk_separator_new ( GTK_ORIENTATION_HORIZONTAL ); gtk_box_pack_start ( GTK_BOX ( gtkblist -> vbox ), sep , FALSE , FALSE , 0 ); gtkblist -> scrollbook = pidgin_scroll_book_new (); gtk_box_pack_start ( GTK_BOX ( gtkblist -> vbox ), gtkblist -> scrollbook , FALSE , FALSE , 0 ); priv -> error_scrollbook = PIDGIN_SCROLL_BOOK ( pidgin_scroll_book_new ()); gtk_box_pack_start ( GTK_BOX ( gtkblist -> vbox ), GTK_WIDGET ( priv -> error_scrollbook ), FALSE , FALSE , 0 ); gtkblist -> statusbox = pidgin_status_box_new (); gtk_box_pack_start ( GTK_BOX ( gtkblist -> vbox ), gtkblist -> statusbox , FALSE , TRUE , 0 ); gtk_widget_set_name ( gtkblist -> statusbox , "pidgin_blist_statusbox" ); gtk_widget_show ( gtkblist -> statusbox ); /* set the Show Offline Buddies option. must be done * after the treeview or faceprint gets mad. -Robot101 gtk_toggle_action_set_active ( GTK_TOGGLE_ACTION ( gtk_ui_manager_get_action ( gtkblist -> ui , "/BList/BuddiesMenu/ShowMenu/ShowOffline" )), purple_prefs_get_bool ( PIDGIN_PREFS_ROOT "/blist/show_offline_buddies" )); gtk_toggle_action_set_active ( GTK_TOGGLE_ACTION ( gtk_ui_manager_get_action ( gtkblist -> ui , "/BList/BuddiesMenu/ShowMenu/ShowEmptyGroups" )), purple_prefs_get_bool ( PIDGIN_PREFS_ROOT "/blist/show_empty_groups" )); gtk_toggle_action_set_active ( GTK_TOGGLE_ACTION ( gtk_ui_manager_get_action ( gtkblist -> ui , "/BList/ToolsMenu/MuteSounds" )), purple_prefs_get_bool ( PIDGIN_PREFS_ROOT "/sound/mute" )); gtk_toggle_action_set_active ( GTK_TOGGLE_ACTION ( gtk_ui_manager_get_action ( gtkblist -> ui , "/BList/BuddiesMenu/ShowMenu/ShowBuddyDetails" )), purple_prefs_get_bool ( PIDGIN_PREFS_ROOT "/blist/show_buddy_icons" )); gtk_toggle_action_set_active ( GTK_TOGGLE_ACTION ( gtk_ui_manager_get_action ( gtkblist -> ui , "/BList/BuddiesMenu/ShowMenu/ShowIdleTimes" )), purple_prefs_get_bool ( PIDGIN_PREFS_ROOT "/blist/show_idle_time" )); gtk_toggle_action_set_active ( GTK_TOGGLE_ACTION ( gtk_ui_manager_get_action ( gtkblist -> ui , "/BList/BuddiesMenu/ShowMenu/ShowProtocolIcons" )), purple_prefs_get_bool ( PIDGIN_PREFS_ROOT "/blist/show_protocol_icons" )); if ( purple_strequal ( purple_prefs_get_string ( PIDGIN_PREFS_ROOT "/sound/method" ), "none" )) gtk_action_set_sensitive ( gtk_ui_manager_get_action ( gtkblist -> ui , "/BList/ToolsMenu/MuteSounds" ), FALSE ); /* Update some dynamic things */ update_menu_bar ( gtkblist ); pidgin_blist_update_plugin_actions (); pidgin_blist_update_sort_methods (); /* OK... let's show this bad boy. */ pidgin_blist_refresh ( list ); pidgin_blist_restore_window_state (); gtk_widget_show_all ( GTK_WIDGET ( gtkblist -> vbox )); gtk_widget_realize ( GTK_WIDGET ( gtkblist -> window )); purple_blist_set_visible ( purple_prefs_get_bool ( PIDGIN_PREFS_ROOT "/blist/list_visible" )); /* start the refresh timer */ gtkblist -> refresh_timer = g_timeout_add_seconds ( 30 , ( GSourceFunc ) pidgin_blist_refresh_timer , list ); handle = pidgin_blist_get_handle (); /* things that affect how buddies are displayed */ purple_prefs_connect_callback ( handle , PIDGIN_PREFS_ROOT "/blist/show_buddy_icons" , _prefs_change_redo_list , NULL ); purple_prefs_connect_callback ( handle , PIDGIN_PREFS_ROOT "/blist/show_idle_time" , _prefs_change_redo_list , NULL ); purple_prefs_connect_callback ( handle , PIDGIN_PREFS_ROOT "/blist/show_empty_groups" , _prefs_change_redo_list , NULL ); purple_prefs_connect_callback ( handle , PIDGIN_PREFS_ROOT "/blist/show_offline_buddies" , _prefs_change_redo_list , NULL ); purple_prefs_connect_callback ( handle , PIDGIN_PREFS_ROOT "/blist/show_protocol_icons" , _prefs_change_redo_list , NULL ); purple_prefs_connect_callback ( handle , PIDGIN_PREFS_ROOT "/blist/sort_type" , _prefs_change_sort_method , NULL ); purple_prefs_connect_callback ( handle , PIDGIN_PREFS_ROOT "/sound/mute" , pidgin_blist_mute_pref_cb , NULL ); purple_prefs_connect_callback ( handle , PIDGIN_PREFS_ROOT "/sound/method" , pidgin_blist_sound_method_pref_cb , NULL ); /* Setup some purple signal handlers. */ handle = purple_accounts_get_handle (); purple_signal_connect ( handle , "account-enabled" , gtkblist , PURPLE_CALLBACK ( account_modified ), gtkblist ); purple_signal_connect ( handle , "account-disabled" , gtkblist , PURPLE_CALLBACK ( account_modified ), gtkblist ); purple_signal_connect ( handle , "account-removed" , gtkblist , PURPLE_CALLBACK ( account_modified ), gtkblist ); purple_signal_connect ( handle , "account-status-changed" , gtkblist , PURPLE_CALLBACK ( account_status_changed ), purple_signal_connect ( handle , "account-error-changed" , gtkblist , PURPLE_CALLBACK ( update_account_error_state ), purple_signal_connect ( handle , "account-actions-changed" , gtkblist , PURPLE_CALLBACK ( account_actions_changed ), NULL ); handle = pidgin_accounts_get_handle (); purple_signal_connect ( handle , "account-modified" , gtkblist , PURPLE_CALLBACK ( account_modified ), gtkblist ); handle = purple_connections_get_handle (); purple_signal_connect ( handle , "signed-on" , gtkblist , PURPLE_CALLBACK ( sign_on_off_cb ), list ); purple_signal_connect ( handle , "signed-off" , gtkblist , PURPLE_CALLBACK ( sign_on_off_cb ), list ); handle = purple_plugins_get_handle (); purple_signal_connect ( handle , "plugin-load" , gtkblist , PURPLE_CALLBACK ( plugin_changed_cb ), NULL ); purple_signal_connect ( handle , "plugin-unload" , gtkblist , PURPLE_CALLBACK ( plugin_changed_cb ), NULL ); handle = purple_conversations_get_handle (); purple_signal_connect ( handle , "conversation-updated" , gtkblist , PURPLE_CALLBACK ( conversation_updated_cb ), purple_signal_connect ( handle , "deleting-conversation" , gtkblist , PURPLE_CALLBACK ( conversation_deleting_cb ), purple_signal_connect ( handle , "conversation-created" , gtkblist , PURPLE_CALLBACK ( conversation_created_cb ), purple_signal_connect ( handle , "chat-joined" , gtkblist , PURPLE_CALLBACK ( conversation_created_cb ), gtk_widget_hide ( gtkblist -> headline ); show_initial_account_errors ( gtkblist ); /* emit our created signal */ handle = pidgin_blist_get_handle (); purple_signal_emit ( handle , "gtkblist-created" , list ); static void redo_buddy_list ( PurpleBuddyList * list , gboolean remove , gboolean rerender ) gtkblist = PIDGIN_BUDDY_LIST ( list ); if ( ! gtkblist || ! gtkblist -> treeview ) node = purple_blist_get_root ( list ); /* This is only needed when we're reverting to a non-GTK+ sorted * status. We shouldn't need to remove otherwise. if ( remove && ! PURPLE_IS_GROUP ( node )) pidgin_blist_hide_node ( list , node , FALSE ); if ( PURPLE_IS_BUDDY ( node )) pidgin_blist_update_buddy ( list , node , rerender ); else if ( PURPLE_IS_CHAT ( node )) pidgin_blist_update ( list , node ); else if ( PURPLE_IS_GROUP ( node )) pidgin_blist_update ( list , node ); node = purple_blist_node_next ( node , FALSE ); void pidgin_blist_refresh ( PurpleBuddyList * list ) redo_buddy_list ( list , FALSE , TRUE ); pidgin_blist_update_refresh_timeout () PidginBuddyList * gtkblist ; blist = purple_blist_get_default (); gtkblist = PIDGIN_BUDDY_LIST ( blist ); gtkblist -> refresh_timer = g_timeout_add_seconds ( 30 ,( GSourceFunc ) pidgin_blist_refresh_timer , blist ); static gboolean get_iter_from_node ( PurpleBlistNode * node , GtkTreeIter * iter ) { PidginBlistNode * gtknode = purple_blist_node_get_ui_data ( node ); purple_debug_error ( "gtkblist" , "get_iter_from_node was called, but we don't seem to have a blist \n " ); if (( path = gtk_tree_row_reference_get_path ( gtknode -> row )) == NULL ) if ( ! gtk_tree_model_get_iter ( GTK_TREE_MODEL ( gtkblist -> treemodel ), iter , path )) { gtk_tree_path_free ( path ); gtk_tree_path_free ( path ); static void pidgin_blist_remove ( PurpleBuddyList * list , PurpleBlistNode * node ) PidginBlistNode * gtknode = purple_blist_node_get_ui_data ( node ); purple_request_close_with_handle ( node ); pidgin_blist_hide_node ( list , node , TRUE ); pidgin_blist_update ( list , node -> parent ); /* There's something I don't understand here - Ethan */ /* Ethan said that back in 2003, but this g_free has been left commented * out ever since. I can't find any reason at all why this is bad and * valgrind found several reasons why it's good. If this causes problems * comment it out again. Stu */ /* Of course it still causes problems - this breaks dragging buddies into * contacts, the dragged buddy mysteriously 'disappears'. Stu. */ /* I think it's fixed now. Stu. */ if ( gtknode -> recent_signonoff_timer > 0 ) g_source_remove ( gtknode -> recent_signonoff_timer ); purple_signals_disconnect_by_handle ( gtknode ); purple_blist_node_set_ui_data ( node , NULL ); static gboolean do_selection_changed ( PurpleBlistNode * new_selection ) PurpleBlistNode * old_selection = NULL ; /* test for gtkblist because crazy timeout means we can be called after the blist is gone */ if ( gtkblist && new_selection != gtkblist -> selected_node ) { old_selection = gtkblist -> selected_node ; gtkblist -> selected_node = new_selection ; pidgin_blist_update ( NULL , new_selection ); pidgin_blist_update ( NULL , old_selection ); static void pidgin_blist_selection_changed ( GtkTreeSelection * selection , gpointer data ) PurpleBlistNode * new_selection = NULL ; if ( gtk_tree_selection_get_selected ( selection , NULL , & iter )){ gtk_tree_model_get ( GTK_TREE_MODEL ( gtkblist -> treemodel ), & iter , NODE_COLUMN , & new_selection , -1 ); /* we set this up as a timeout, otherwise the blist flickers ... * but we don't do it for groups, because it causes total bizarness - * the previously selected buddy node might rendered at half height. if (( new_selection != NULL ) && PURPLE_IS_GROUP ( new_selection )) { do_selection_changed ( new_selection ); g_timeout_add ( 0 , ( GSourceFunc ) do_selection_changed , new_selection ); static gboolean insert_node ( PurpleBuddyList * list , PurpleBlistNode * node , GtkTreeIter * iter ) GtkTreeIter parent_iter = { 0 , NULL , NULL , NULL }, cur , * curptr = NULL ; PidginBlistNode * gtknode = purple_blist_node_get_ui_data ( node ); /* XXX: it's not necessary, but let's silence a warning*/ memset ( & parent_iter , 0 , sizeof ( parent_iter )); if ( node -> parent && ! get_iter_from_node ( node -> parent , & parent_iter )) if ( get_iter_from_node ( node , & cur )) if ( PURPLE_IS_CONTACT ( node ) || PURPLE_IS_CHAT ( node )) { current_sort_method -> func ( node , list , parent_iter , curptr , iter ); sort_method_none ( node , list , parent_iter , curptr , iter ); gtk_tree_row_reference_free ( gtknode -> row ); pidgin_blist_new_node ( list , node ); gtknode = purple_blist_node_get_ui_data ( node ); newpath = gtk_tree_model_get_path ( GTK_TREE_MODEL ( gtkblist -> treemodel ), gtk_tree_row_reference_new ( GTK_TREE_MODEL ( gtkblist -> treemodel ), gtk_tree_path_free ( newpath ); gtk_tree_store_set ( gtkblist -> treemodel , iter , GtkTreePath * expand = NULL ; PidginBlistNode * gtkparentnode = purple_blist_node_get_ui_data ( node -> parent ); if ( PURPLE_IS_GROUP ( node -> parent )) { if ( ! purple_blist_node_get_bool ( node -> parent , "collapsed" )) expand = gtk_tree_model_get_path ( GTK_TREE_MODEL ( gtkblist -> treemodel ), & parent_iter ); } else if ( PURPLE_IS_CONTACT ( node -> parent ) && gtkparentnode -> contact_expanded ) { expand = gtk_tree_model_get_path ( GTK_TREE_MODEL ( gtkblist -> treemodel ), & parent_iter ); gtk_tree_view_expand_row ( GTK_TREE_VIEW ( gtkblist -> treeview ), expand , FALSE ); gtk_tree_path_free ( expand ); static gboolean pidgin_blist_group_has_show_offline_buddy ( PurpleGroup * group ) PurpleBlistNode * gnode , * cnode , * bnode ; gnode = PURPLE_BLIST_NODE ( group ); for ( cnode = gnode -> child ; cnode ; cnode = cnode -> next ) { if ( PURPLE_IS_CONTACT ( cnode )) { for ( bnode = cnode -> child ; bnode ; bnode = bnode -> next ) { PurpleBuddy * buddy = ( PurpleBuddy * ) bnode ; if ( purple_account_is_connected ( purple_buddy_get_account ( buddy )) && purple_blist_node_get_bool ( bnode , "show_offline" )) /* This version of pidgin_blist_update_group can take the original buddy or a * group, but has much better algorithmic performance with a pre-known buddy. static void pidgin_blist_update_group ( PurpleBuddyList * list , gboolean show = FALSE , show_offline = FALSE ; g_return_if_fail ( node != NULL ); if ( PURPLE_IS_GROUP ( node )) else if ( PURPLE_IS_BUDDY ( node )) gnode = node -> parent -> parent ; else if ( PURPLE_IS_CONTACT ( node ) || PURPLE_IS_CHAT ( node )) group = ( PurpleGroup * ) gnode ; show_offline = purple_prefs_get_bool ( PIDGIN_PREFS_ROOT "/blist/show_offline_buddies" ); count = purple_counting_node_get_current_size ( PURPLE_COUNTING_NODE ( group )); count = purple_counting_node_get_online_count ( PURPLE_COUNTING_NODE ( group )); if ( count > 0 || purple_prefs_get_bool ( PIDGIN_PREFS_ROOT "/blist/show_empty_groups" )) else if ( PURPLE_IS_BUDDY ( node ) && buddy_is_displayable (( PurpleBuddy * ) node )) { /* Or chat? */ } else if ( ! show_offline ) { show = pidgin_blist_group_has_show_offline_buddy ( group ); GdkPixbuf * avatar = NULL ; PidginBlistTheme * theme = NULL ; if ( ! insert_node ( list , gnode , & iter )) if (( theme = pidgin_blist_get_theme ()) == NULL ) else if ( purple_blist_node_get_bool ( gnode , "collapsed" ) || count <= 0 ) bgcolor = pidgin_blist_theme_get_collapsed_background_color ( theme ); bgcolor = pidgin_blist_theme_get_expanded_background_color ( theme ); path = gtk_tree_model_get_path ( GTK_TREE_MODEL ( gtkblist -> treemodel ), & iter ); expanded = gtk_tree_view_row_expanded ( GTK_TREE_VIEW ( gtkblist -> treeview ), path ); gtk_tree_path_free ( path ); title = pidgin_get_group_title ( gnode , expanded ); biglist = purple_prefs_get_bool ( PIDGIN_PREFS_ROOT "/blist/show_buddy_icons" ); avatar = pidgin_blist_get_buddy_icon ( gnode , TRUE , TRUE ); gtk_tree_store_set ( gtkblist -> treemodel , & iter , STATUS_ICON_VISIBLE_COLUMN , FALSE , STATUS_ICON_COLUMN , NULL , GROUP_EXPANDER_COLUMN , TRUE , GROUP_EXPANDER_VISIBLE_COLUMN , TRUE , CONTACT_EXPANDER_VISIBLE_COLUMN , FALSE , BUDDY_ICON_COLUMN , avatar , BUDDY_ICON_VISIBLE_COLUMN , biglist , IDLE_VISIBLE_COLUMN , FALSE , EMBLEM_VISIBLE_COLUMN , FALSE , pidgin_blist_hide_node ( list , gnode , TRUE ); static char * pidgin_get_group_title ( PurpleBlistNode * gnode , gboolean expanded ) char group_count [ 12 ] = "" ; PurpleBlistNode * selected_node = NULL ; gchar const * text_color , * text_font ; group = ( PurpleGroup * ) gnode ; if ( gtk_tree_selection_get_selected ( gtk_tree_view_get_selection ( GTK_TREE_VIEW ( gtkblist -> treeview )), NULL , & iter )) { gtk_tree_model_get ( GTK_TREE_MODEL ( gtkblist -> treemodel ), & iter , NODE_COLUMN , & selected_node , -1 ); selected = ( gnode == selected_node ); g_snprintf ( group_count , sizeof ( group_count ), "%d/%d" , purple_counting_node_get_online_count ( PURPLE_COUNTING_NODE ( group )), purple_counting_node_get_current_size ( PURPLE_COUNTING_NODE ( group ))); theme = pidgin_blist_get_theme (); pair = pidgin_blist_theme_get_expanded_text_info ( theme ); pair = pidgin_blist_theme_get_collapsed_text_info ( theme ); text_color = selected ? NULL : theme_font_get_color_default ( pair , NULL ); text_font = theme_font_get_face_default ( pair , "" ); esc = g_markup_escape_text ( purple_group_get_name ( group ), -1 ); mark = g_strdup_printf ( "<span foreground='%s' font_desc='%s'><b>%s</b>%s%s%s</span>" , ! expanded ? " <span weight='light'>(</span>" : "" , ! expanded ? "<span weight='light'>)</span>" : "" ); mark = g_strdup_printf ( "<span font_desc='%s'><b>%s</b>%s%s%s</span>" , text_font , esc ? esc : "" , ! expanded ? " <span weight='light'>(</span>" : "" , ! expanded ? "<span weight='light'>)</span>" : "" ); static void buddy_node ( PurpleBuddy * buddy , GtkTreeIter * iter , PurpleBlistNode * node ) PurplePresence * presence = purple_buddy_get_presence ( buddy ); GdkPixbuf * status , * avatar , * emblem , * protocol_icon ; gboolean expanded = (( PidginBlistNode * ) purple_blist_node_get_ui_data ( node -> parent )) -> contact_expanded ; gboolean selected = ( gtkblist -> selected_node == node ); gboolean biglist = purple_prefs_get_bool ( PIDGIN_PREFS_ROOT "/blist/show_buddy_icons" ); status = pidgin_blist_get_status_icon ( PURPLE_BLIST_NODE ( buddy ), biglist ? PIDGIN_STATUS_ICON_LARGE : PIDGIN_STATUS_ICON_SMALL ); /* Speed it up if we don't want buddy icons. */ avatar = pidgin_blist_get_buddy_icon ( PURPLE_BLIST_NODE ( buddy ), TRUE , TRUE ); g_object_ref ( G_OBJECT ( gtkblist -> empty_avatar )); avatar = gtkblist -> empty_avatar ; } else if (( ! PURPLE_BUDDY_IS_ONLINE ( buddy ) || purple_presence_is_idle ( presence ))) { do_alphashift ( avatar , 77 ); emblem = pidgin_blist_get_emblem ( PURPLE_BLIST_NODE ( buddy )); mark = pidgin_blist_get_name_markup ( buddy , selected , TRUE ); theme = pidgin_blist_get_theme (); if ( purple_prefs_get_bool ( PIDGIN_PREFS_ROOT "/blist/show_idle_time" ) && purple_presence_is_idle ( presence ) && ! biglist ) time_t idle_secs = purple_presence_get_idle_time ( presence ); PidginThemeFont * pair = NULL ; ihrs = ( t - idle_secs ) / 3600 ; imin = (( t - idle_secs ) / 60 ) % 60 ; else if ( theme != NULL && ( pair = pidgin_blist_theme_get_idle_text_info ( theme )) != NULL ) textcolor = pidgin_theme_font_get_color_describe ( pair ); /* If no theme them default to making idle buddy names grey */ textcolor = pidgin_style_is_dark ( NULL ) ? "light slate grey" : "dim grey" ; idle = g_strdup_printf ( "<span color='%s' font_desc='%s'>%d:%02d</span>" , textcolor , theme_font_get_face_default ( pair , "" ), idle = g_strdup_printf ( "<span font_desc='%s'>%d:%02d</span>" , theme_font_get_face_default ( pair , "" ), protocol_icon = pidgin_create_protocol_icon ( purple_buddy_get_account ( buddy ), PIDGIN_PROTOCOL_ICON_SMALL ); color = pidgin_blist_theme_get_contact_color ( theme ); gtk_tree_store_set ( gtkblist -> treemodel , iter , STATUS_ICON_COLUMN , status , STATUS_ICON_VISIBLE_COLUMN , TRUE , IDLE_VISIBLE_COLUMN , ! biglist && idle , BUDDY_ICON_COLUMN , avatar , BUDDY_ICON_VISIBLE_COLUMN , biglist , EMBLEM_VISIBLE_COLUMN , ( emblem != NULL ), PROTOCOL_ICON_COLUMN , protocol_icon , PROTOCOL_ICON_VISIBLE_COLUMN , purple_prefs_get_bool ( PIDGIN_PREFS_ROOT "/blist/show_protocol_icons" ), CONTACT_EXPANDER_COLUMN , NULL , CONTACT_EXPANDER_VISIBLE_COLUMN , expanded , GROUP_EXPANDER_VISIBLE_COLUMN , FALSE , g_object_unref ( protocol_icon ); /* This is a variation on the original gtk_blist_update_contact. Here we can know in advance which buddy has changed so we can just update that */ static void pidgin_blist_update_contact ( PurpleBuddyList * list , PurpleBlistNode * node ) gboolean biglist = purple_prefs_get_bool ( PIDGIN_PREFS_ROOT "/blist/show_buddy_icons" ); PidginBlistNode * gtknode ; if ( PURPLE_IS_BUDDY ( node )) g_return_if_fail ( PURPLE_IS_CONTACT ( cnode )); /* First things first, update the group */ if ( PURPLE_IS_BUDDY ( node )) pidgin_blist_update_group ( list , node ); pidgin_blist_update_group ( list , cnode -> parent ); contact = ( PurpleContact * ) cnode ; buddy = purple_contact_get_priority_buddy ( contact ); if ( buddy_is_displayable ( buddy )) if ( ! insert_node ( list , cnode , & iter )) gtknode = purple_blist_node_get_ui_data ( cnode ); if ( gtknode -> contact_expanded ) { const gchar * fg_color , * font ; gboolean selected = ( gtkblist -> selected_node == cnode ); mark = g_markup_escape_text ( purple_contact_get_alias ( contact ), -1 ); theme = pidgin_blist_get_theme (); pair = pidgin_blist_theme_get_contact_text_info ( theme ); color = pidgin_blist_theme_get_contact_color ( theme ); font = theme_font_get_face_default ( pair , "" ); fg_color = selected ? NULL : theme_font_get_color_default ( pair , NULL ); tmp = g_strdup_printf ( "<span font_desc='%s' color='%s'>%s</span>" , tmp = g_strdup_printf ( "<span font_desc='%s'>%s</span>" , font , status = pidgin_blist_get_status_icon ( cnode , biglist ? PIDGIN_STATUS_ICON_LARGE : PIDGIN_STATUS_ICON_SMALL ); gtk_tree_store_set ( gtkblist -> treemodel , & iter , STATUS_ICON_COLUMN , status , STATUS_ICON_VISIBLE_COLUMN , TRUE , IDLE_VISIBLE_COLUMN , FALSE , CONTACT_EXPANDER_COLUMN , TRUE , CONTACT_EXPANDER_VISIBLE_COLUMN , TRUE , GROUP_EXPANDER_VISIBLE_COLUMN , FALSE , buddy_node ( buddy , & iter , cnode ); pidgin_blist_hide_node ( list , cnode , TRUE ); static void pidgin_blist_update_buddy ( PurpleBuddyList * list , PurpleBlistNode * node , gboolean status_change ) PidginBlistNode * gtkparentnode ; g_return_if_fail ( PURPLE_IS_BUDDY ( node )); if ( node -> parent == NULL ) buddy = ( PurpleBuddy * ) node ; /* First things first, update the contact */ pidgin_blist_update_contact ( list , node ); gtkparentnode = purple_blist_node_get_ui_data ( node -> parent ); if ( gtkparentnode -> contact_expanded && buddy_is_displayable ( buddy )) if ( ! insert_node ( list , node , & iter )) buddy_node ( buddy , & iter , node ); pidgin_blist_hide_node ( list , node , TRUE ); static void pidgin_blist_update_chat ( PurpleBuddyList * list , PurpleBlistNode * node ) g_return_if_fail ( PURPLE_IS_CHAT ( node )); /* First things first, update the group */ pidgin_blist_update_group ( list , node -> parent ); chat = ( PurpleChat * ) node ; if ( purple_account_is_connected ( purple_chat_get_account ( chat ))) { GdkPixbuf * status , * avatar , * emblem , * protocol_icon ; const gchar * color , * font ; gboolean showicons = purple_prefs_get_bool ( PIDGIN_PREFS_ROOT "/blist/show_buddy_icons" ); gboolean biglist = purple_prefs_get_bool ( PIDGIN_PREFS_ROOT "/blist/show_buddy_icons" ); PurpleConversation * conv ; gboolean selected = ( gtkblist -> selected_node == node ); gboolean nick_said = FALSE ; if ( ! insert_node ( list , node , & iter )) ui = purple_blist_node_get_ui_data ( node ); if ( conv && pidgin_conv_is_hidden ( PIDGIN_CONVERSATION ( conv ))) { hidden = ( ui -> conv . flags & PIDGIN_BLIST_NODE_HAS_PENDING_MESSAGE ); nick_said = ( ui -> conv . flags & PIDGIN_BLIST_CHAT_HAS_PENDING_MESSAGE_WITH_NICK ); status = pidgin_blist_get_status_icon ( node , biglist ? PIDGIN_STATUS_ICON_LARGE : PIDGIN_STATUS_ICON_SMALL ); emblem = pidgin_blist_get_emblem ( node ); /* Speed it up if we don't want buddy icons. */ avatar = pidgin_blist_get_buddy_icon ( node , TRUE , FALSE ); mark = g_markup_escape_text ( purple_chat_get_name ( chat ), -1 ); theme = pidgin_blist_get_theme (); pair = pidgin_blist_theme_get_unread_message_nick_said_text_info ( theme ); pair = pidgin_blist_theme_get_unread_message_text_info ( theme ); else pair = pidgin_blist_theme_get_online_text_info ( theme ); font = theme_font_get_face_default ( pair , "" ); if ( selected || ! ( color = theme_font_get_color_default ( pair , NULL ))) /* nick_said color is the same as gtkconv:tab-label-attention */ color = ( nick_said ? "#006aff" : NULL ); tmp = g_strdup_printf ( "<span font_desc='%s' color='%s' weight='%s'>%s</span>" , font , color , hidden ? "bold" : "normal" , mark ); tmp = g_strdup_printf ( "<span font_desc='%s' weight='%s'>%s</span>" , font , hidden ? "bold" : "normal" , mark ); protocol_icon = pidgin_create_protocol_icon ( purple_chat_get_account ( chat ), PIDGIN_PROTOCOL_ICON_SMALL ); bgcolor = pidgin_blist_theme_get_contact_color ( theme ); gtk_tree_store_set ( gtkblist -> treemodel , & iter , STATUS_ICON_COLUMN , status , STATUS_ICON_VISIBLE_COLUMN , TRUE , BUDDY_ICON_COLUMN , avatar ? avatar : gtkblist -> empty_avatar , BUDDY_ICON_VISIBLE_COLUMN , showicons , EMBLEM_VISIBLE_COLUMN , emblem != NULL , PROTOCOL_ICON_COLUMN , protocol_icon , PROTOCOL_ICON_VISIBLE_COLUMN , purple_prefs_get_bool ( PIDGIN_PREFS_ROOT "/blist/show_protocol_icons" ), GROUP_EXPANDER_VISIBLE_COLUMN , FALSE , g_object_unref ( protocol_icon ); pidgin_blist_hide_node ( list , node , TRUE ); static void pidgin_blist_update ( PurpleBuddyList * list , PurpleBlistNode * node ) gtkblist = PIDGIN_BUDDY_LIST ( list ); if ( ! gtkblist || ! gtkblist -> treeview || ! node ) if ( purple_blist_node_get_ui_data ( node ) == NULL ) pidgin_blist_new_node ( list , node ); if ( PURPLE_IS_GROUP ( node )) pidgin_blist_update_group ( list , node ); else if ( PURPLE_IS_CONTACT ( node )) pidgin_blist_update_contact ( list , node ); else if ( PURPLE_IS_BUDDY ( node )) pidgin_blist_update_buddy ( list , node , TRUE ); else if ( PURPLE_IS_CHAT ( node )) pidgin_blist_update_chat ( list , node ); static void pidgin_blist_set_visible ( PurpleBuddyList * list , gboolean show ) if ( ! ( gtkblist && gtkblist -> window )) gtk_widget_show ( gtkblist -> window ); if ( visibility_manager_count ) { gtk_widget_hide ( gtkblist -> window ); if ( ! gtk_widget_get_visible ( gtkblist -> window )) gtk_widget_show ( gtkblist -> window ); gtk_window_iconify ( GTK_WINDOW ( gtkblist -> window )); static GList * list = NULL ; gnode = purple_blist_get_default_root (); list = g_list_append ( list , ( gpointer ) PURPLE_BLIST_DEFAULT_GROUP_NAME ); for (; gnode != NULL ; gnode = gnode -> next ) { if ( PURPLE_IS_GROUP ( gnode )) g = ( PurpleGroup * ) gnode ; list = g_list_append ( list , ( char * ) purple_group_get_name ( g )); add_buddy_select_account_cb ( GObject * w , PidginAddBuddyData * data ) pidgin_account_chooser_get_selected ( GTK_WIDGET ( w )); PurpleConnection * pc = NULL ; PurpleProtocol * protocol = NULL ; gboolean invite_enabled = TRUE ; data -> rq_data . account = account ; pc = purple_account_get_connection ( account ); protocol = purple_connection_get_protocol ( pc ); if ( protocol && ! ( purple_protocol_get_options ( protocol ) & OPT_PROTO_INVITE_MESSAGE )) gtk_widget_set_sensitive ( data -> entry_for_invite , invite_enabled ); set_sensitive_if_input_buddy_cb ( data -> entry , data ); destroy_add_buddy_dialog_cb ( GtkWidget * win , PidginAddBuddyData * data ) add_buddy_cb ( GtkWidget * w , int resp , PidginAddBuddyData * data ) const char * grp , * who , * whoalias , * invite ; PurpleIMConversation * im ; if ( resp == GTK_RESPONSE_OK ) who = gtk_entry_get_text ( GTK_ENTRY ( data -> entry )); grp = pidgin_text_combo_box_entry_get_text ( data -> combo ); whoalias = gtk_entry_get_text ( GTK_ENTRY ( data -> entry_for_alias )); invite = gtk_entry_get_text ( GTK_ENTRY ( data -> entry_for_invite )); account = data -> rq_data . account ; if (( grp != NULL ) && ( * grp != '\0' )) if (( g = purple_blist_find_group ( grp )) == NULL ) g = purple_group_new ( grp ); purple_blist_add_group ( g , NULL ); b = purple_blist_find_buddy_in_group ( account , who , g ); else if (( b = purple_blist_find_buddy ( account , who )) != NULL ) g = purple_buddy_get_group ( b ); b = purple_buddy_new ( account , who , whoalias ); purple_blist_add_buddy ( b , NULL , g , NULL ); purple_account_add_buddy ( account , b , invite ); /* Offer to merge people with the same alias. */ if ( whoalias != NULL && g != NULL ) gtk_blist_auto_personize ( PURPLE_BLIST_NODE ( g ), whoalias ); * It really seems like it would be better if the call to * purple_account_add_buddy() and purple_conversation_update() were done in * blist.c, possibly in the purple_blist_add_buddy() function. Maybe * purple_account_add_buddy() should be renamed to * purple_blist_add_new_buddy() or something, and have it call * purple_blist_add_buddy() after it creates it. --Mark * No that's not good. blist.c should only deal with adding nodes to the * local list. We need a new, non-gtk file that calls both * purple_account_add_buddy and purple_blist_add_buddy(). im = purple_conversations_find_im_with_account ( who , data -> rq_data . account ); icon = purple_im_conversation_get_icon ( im ); purple_buddy_icon_update ( icon ); gtk_widget_destroy ( data -> rq_data . window ); pidgin_blist_request_add_buddy ( PurpleBuddyList * list , PurpleAccount * account , const char * username , const char * group , PidginAddBuddyData * data = g_new0 ( PidginAddBuddyData , 1 ); PidginBlistRequestData * blist_req_data = & data -> rq_data ; account = purple_connection_get_account ( purple_connections_get_all () -> data ); make_blist_request_dialog ( blist_req_data , _ ( "Add Buddy" ), "add_buddy" , G_CALLBACK ( add_buddy_select_account_cb ), add_buddy_account_filter_func , G_CALLBACK ( add_buddy_cb )); gtk_dialog_add_buttons ( GTK_DIALOG ( data -> rq_data . window ), GTK_STOCK_CANCEL , GTK_RESPONSE_CANCEL , GTK_STOCK_ADD , GTK_RESPONSE_OK , gtk_dialog_set_default_response ( GTK_DIALOG ( data -> rq_data . window ), g_signal_connect ( G_OBJECT ( data -> rq_data . window ), "destroy" , G_CALLBACK ( destroy_add_buddy_dialog_cb ), data ); data -> entry = gtk_entry_new (); pidgin_add_widget_to_vbox ( data -> rq_data . vbox , _ ( "Buddy's _username:" ), data -> rq_data . sg , data -> entry , TRUE , NULL ); gtk_widget_grab_focus ( data -> entry ); gtk_entry_set_text ( GTK_ENTRY ( data -> entry ), username ); gtk_dialog_set_response_sensitive ( GTK_DIALOG ( data -> rq_data . window ), gtk_entry_set_activates_default ( GTK_ENTRY ( data -> entry ), TRUE ); g_signal_connect ( G_OBJECT ( data -> entry ), "changed" , G_CALLBACK ( set_sensitive_if_input_buddy_cb ), data -> entry_for_alias = gtk_entry_new (); pidgin_add_widget_to_vbox ( data -> rq_data . vbox , _ ( "(Optional) A_lias:" ), data -> rq_data . sg , data -> entry_for_alias , TRUE , gtk_entry_set_text ( GTK_ENTRY ( data -> entry_for_alias ), alias ); gtk_widget_grab_focus ( GTK_WIDGET ( data -> entry_for_alias )); data -> entry_for_invite = gtk_entry_new (); pidgin_add_widget_to_vbox ( data -> rq_data . vbox , _ ( "(Optional) _Invite message:" ), data -> rq_data . sg , data -> entry_for_invite , TRUE , data -> combo = pidgin_text_combo_box_entry_new ( group , groups_tree ()); pidgin_add_widget_to_vbox ( data -> rq_data . vbox , _ ( "Add buddy to _group:" ), data -> rq_data . sg , data -> combo , TRUE , NULL ); gtk_widget_show_all ( data -> rq_data . window ); /* Force update of invite message entry sensitivity */ pidgin_account_chooser_set_selected ( blist_req_data -> account_menu , add_chat_cb ( GtkWidget * w , PidginAddChatData * data ) components = g_hash_table_new_full ( g_str_hash , g_str_equal , for ( tmp = data -> chat_data . entries ; tmp ; tmp = tmp -> next ) if ( g_object_get_data ( tmp -> data , "is_spin" )) g_hash_table_replace ( components , g_strdup ( g_object_get_data ( tmp -> data , "identifier" )), gtk_spin_button_get_value_as_int ( tmp -> data ))); const char * value = gtk_entry_get_text ( tmp -> data ); g_hash_table_replace ( components , g_strdup ( g_object_get_data ( tmp -> data , "identifier" )), chat = purple_chat_new ( data -> chat_data . rq_data . account , gtk_entry_get_text ( GTK_ENTRY ( data -> alias_entry )), group_name = pidgin_text_combo_box_entry_get_text ( data -> group_combo ); if (( group_name != NULL ) && ( * group_name != '\0' ) && (( group = purple_blist_find_group ( group_name )) == NULL )) group = purple_group_new ( group_name ); purple_blist_add_group ( group , NULL ); purple_blist_add_chat ( chat , group , NULL ); if ( gtk_toggle_button_get_active ( GTK_TOGGLE_BUTTON ( data -> autojoin ))) purple_blist_node_set_bool ( PURPLE_BLIST_NODE ( chat ), "gtk-autojoin" , TRUE ); if ( gtk_toggle_button_get_active ( GTK_TOGGLE_BUTTON ( data -> persistent ))) purple_blist_node_set_bool ( PURPLE_BLIST_NODE ( chat ), "gtk-persistent" , TRUE ); gtk_widget_destroy ( data -> chat_data . rq_data . window ); g_free ( data -> chat_data . default_chat_name ); g_list_free ( data -> chat_data . entries ); add_chat_resp_cb ( GtkWidget * w , int resp , PidginAddChatData * data ) if ( resp == GTK_RESPONSE_OK ) pidgin_roomlist_dialog_show_with_account ( data -> chat_data . rq_data . account ); gtk_widget_destroy ( data -> chat_data . rq_data . window ); g_free ( data -> chat_data . default_chat_name ); g_list_free ( data -> chat_data . entries ); pidgin_blist_request_add_chat ( PurpleBuddyList * list , PurpleAccount * account , PurpleGroup * group , const char * alias , PurpleProtocol * protocol = NULL ; gc = purple_account_get_connection ( account ); protocol = purple_connection_get_protocol ( gc ); if ( ! PURPLE_PROTOCOL_IMPLEMENTS ( protocol , CHAT , join )) { purple_notify_error ( gc , NULL , _ ( "This protocol does not" " support chat rooms." ), NULL , purple_request_cpar_from_account ( account )); /* Find an account with chat capabilities */ for ( l = purple_connections_get_all (); l != NULL ; l = l -> next ) { gc = ( PurpleConnection * ) l -> data ; protocol = purple_connection_get_protocol ( gc ); if ( PURPLE_PROTOCOL_IMPLEMENTS ( protocol , CHAT , join )) { account = purple_connection_get_account ( gc ); purple_notify_error ( NULL , NULL , _ ( "You are not currently signed on with any " "protocols that have the ability to chat." ), NULL , NULL ); data = g_new0 ( PidginAddChatData , 1 ); vbox = GTK_BOX ( make_blist_request_dialog (( PidginBlistRequestData * ) data , account , _ ( "Add Chat" ), "add_chat" , _ ( "Please enter an alias, and the appropriate information " "about the chat you would like to add to your buddy list. \n " ), G_CALLBACK ( chat_select_account_cb ), chat_account_filter_func , G_CALLBACK ( add_chat_resp_cb ))); gtk_dialog_add_buttons ( GTK_DIALOG ( data -> chat_data . rq_data . window ), GTK_STOCK_CANCEL , GTK_RESPONSE_CANCEL , GTK_STOCK_ADD , GTK_RESPONSE_OK , gtk_dialog_set_default_response ( GTK_DIALOG ( data -> chat_data . rq_data . window ), data -> chat_data . default_chat_name = g_strdup ( name ); rebuild_chat_entries (( PidginChatData * ) data , name ); data -> alias_entry = gtk_entry_new (); gtk_entry_set_text ( GTK_ENTRY ( data -> alias_entry ), alias ); gtk_entry_set_activates_default ( GTK_ENTRY ( data -> alias_entry ), TRUE ); pidgin_add_widget_to_vbox ( GTK_BOX ( vbox ), _ ( "A_lias:" ), data -> chat_data . rq_data . sg , data -> alias_entry , gtk_widget_grab_focus ( data -> alias_entry ); data -> group_combo = pidgin_text_combo_box_entry_new ( group ? purple_group_get_name ( group ) : NULL , groups_tree ()); pidgin_add_widget_to_vbox ( GTK_BOX ( vbox ), _ ( "_Group:" ), data -> chat_data . rq_data . sg , data -> group_combo , data -> autojoin = gtk_check_button_new_with_mnemonic ( _ ( "Automatically _join when account connects" )); data -> persistent = gtk_check_button_new_with_mnemonic ( _ ( "_Remain in chat after window is closed" )); gtk_box_pack_start ( GTK_BOX ( vbox ), data -> autojoin , FALSE , FALSE , 0 ); gtk_box_pack_start ( GTK_BOX ( vbox ), data -> persistent , FALSE , FALSE , 0 ); gtk_widget_show_all ( data -> chat_data . rq_data . window ); add_group_cb ( PurpleConnection * gc , const char * group_name ) if (( group_name == NULL ) || ( * group_name == '\0' )) group = purple_group_new ( group_name ); purple_blist_add_group ( group , NULL ); pidgin_blist_request_add_group ( PurpleBuddyList * list ) purple_request_input ( NULL , _ ( "Add Group" ), NULL , _ ( "Please enter the name of the group to be added." ), NULL , FALSE , FALSE , NULL , _ ( "Add" ), G_CALLBACK ( add_group_cb ), pidgin_blist_toggle_visibility () if ( gtkblist && gtkblist -> window ) { if ( gtk_widget_get_visible ( gtkblist -> window )) { /* make the buddy list visible if it is iconified or if it is * obscured and not currently focused (the focus part ensures * that we do something reasonable if the buddy list is obscured * by a window set to always be on top), otherwise hide the purple_blist_set_visible ( PIDGIN_WINDOW_ICONIFIED ( gtkblist -> window ) || (( gtk_blist_visibility != GDK_VISIBILITY_UNOBSCURED ) && purple_blist_set_visible ( TRUE ); pidgin_blist_visibility_manager_add () visibility_manager_count ++ ; purple_debug_info ( "gtkblist" , "added visibility manager: %d \n " , visibility_manager_count ); pidgin_blist_visibility_manager_remove () if ( visibility_manager_count ) visibility_manager_count -- ; if ( ! visibility_manager_count ) purple_blist_set_visible ( TRUE ); purple_debug_info ( "gtkblist" , "removed visibility manager: %d \n " , visibility_manager_count ); void pidgin_blist_add_alert ( GtkWidget * widget ) gtk_container_add ( GTK_CONTAINER ( gtkblist -> scrollbook ), widget ); pidgin_blist_set_headline ( const char * text , const gchar * icon_name , GCallback callback , gpointer user_data , GDestroyNotify destroy ) /* Destroy any existing headline first */ if ( gtkblist -> headline_destroy ) gtkblist -> headline_destroy ( gtkblist -> headline_data ); gtk_label_set_markup ( GTK_LABEL ( gtkblist -> headline_label ), text ); gtk_image_set_from_icon_name ( GTK_IMAGE ( gtkblist -> headline_image ), icon_name , GTK_ICON_SIZE_SMALL_TOOLBAR ); gtkblist -> headline_callback = callback ; gtkblist -> headline_data = user_data ; gtkblist -> headline_destroy = destroy ; if ( text != NULL || icon_name != NULL ) { gtk_widget_show_all ( gtkblist -> headline ); gtk_widget_hide ( gtkblist -> headline ); if ( gtkblist -> window && ! gtk_widget_has_focus ( gtkblist -> window )) pidgin_set_urgent ( GTK_WINDOW ( gtkblist -> window ), TRUE ); pidgin_blist_get_default_gtk_blist ( void ) static gboolean autojoin_cb ( PurpleConnection * gc , gpointer data ) PurpleAccount * account = purple_connection_get_account ( gc ); PurpleBlistNode * gnode , * cnode ; for ( gnode = purple_blist_get_default_root (); gnode ; if ( ! PURPLE_IS_GROUP ( gnode )) for ( cnode = gnode -> child ; cnode ; cnode = cnode -> next ) if ( ! PURPLE_IS_CHAT ( cnode )) chat = ( PurpleChat * ) cnode ; if ( purple_chat_get_account ( chat ) != account ) if ( purple_blist_node_get_bool ( PURPLE_BLIST_NODE ( chat ), "gtk-autojoin" )) purple_serv_join_chat ( gc , purple_chat_get_components ( chat )); /* Stop processing; we handled the autojoins. */ pidgin_blist_get_handle () { static gboolean buddy_signonoff_timeout_cb ( PurpleBuddy * buddy ) PidginBlistNode * gtknode = purple_blist_node_get_ui_data ( PURPLE_BLIST_NODE ( buddy )); gtknode -> recent_signonoff = FALSE ; gtknode -> recent_signonoff_timer = 0 ; pidgin_blist_update ( NULL , PURPLE_BLIST_NODE ( buddy )); static void buddy_signonoff_cb ( PurpleBuddy * buddy ) PidginBlistNode * gtknode = purple_blist_node_get_ui_data ( PURPLE_BLIST_NODE ( buddy )); pidgin_blist_new_node ( purple_blist_get_default (), PURPLE_BLIST_NODE ( buddy )); gtknode = purple_blist_node_get_ui_data ( PURPLE_BLIST_NODE ( buddy )); gtknode -> recent_signonoff = TRUE ; if ( gtknode -> recent_signonoff_timer > 0 ) g_source_remove ( gtknode -> recent_signonoff_timer ); gtknode -> recent_signonoff_timer = g_timeout_add_seconds ( 10 , ( GSourceFunc ) buddy_signonoff_timeout_cb , buddy ); pidgin_blist_set_theme ( PidginBlistTheme * theme ) PidginBuddyListPrivate * priv = pidgin_buddy_list_get_instance_private ( gtkblist ); PurpleBuddyList * list = purple_blist_get_default (); purple_prefs_set_string ( PIDGIN_PREFS_ROOT "/blist/theme" , purple_theme_get_name ( PURPLE_THEME ( theme ))); purple_prefs_set_string ( PIDGIN_PREFS_ROOT "/blist/theme" , "" ); g_object_unref ( priv -> current_theme ); priv -> current_theme = theme ? g_object_ref ( theme ) : NULL ; pidgin_blist_build_layout ( list ); pidgin_blist_refresh ( list ); PidginBuddyListPrivate * priv = pidgin_buddy_list_get_instance_private ( gtkblist ); return priv -> current_theme ; void pidgin_blist_init ( void ) void * gtk_blist_handle = pidgin_blist_get_handle (); cached_emblems = g_hash_table_new_full ( g_str_hash , g_str_equal , g_free , NULL ); purple_prefs_add_none ( PIDGIN_PREFS_ROOT "/blist" ); purple_prefs_add_bool ( PIDGIN_PREFS_ROOT "/blist/show_buddy_icons" , TRUE ); purple_prefs_add_bool ( PIDGIN_PREFS_ROOT "/blist/show_empty_groups" , FALSE ); purple_prefs_add_bool ( PIDGIN_PREFS_ROOT "/blist/show_idle_time" , TRUE ); purple_prefs_add_bool ( PIDGIN_PREFS_ROOT "/blist/show_offline_buddies" , FALSE ); purple_prefs_add_bool ( PIDGIN_PREFS_ROOT "/blist/show_protocol_icons" , FALSE ); purple_prefs_add_bool ( PIDGIN_PREFS_ROOT "/blist/list_visible" , FALSE ); purple_prefs_add_bool ( PIDGIN_PREFS_ROOT "/blist/list_maximized" , FALSE ); purple_prefs_add_string ( PIDGIN_PREFS_ROOT "/blist/sort_type" , "alphabetical" ); purple_prefs_add_int ( PIDGIN_PREFS_ROOT "/blist/width" , 250 ); /* Golden ratio, baby */ purple_prefs_add_int ( PIDGIN_PREFS_ROOT "/blist/height" , 405 ); /* Golden ratio, baby */ purple_prefs_add_string ( PIDGIN_PREFS_ROOT "/blist/theme" , "" ); purple_theme_manager_register_type ( g_object_new ( PIDGIN_TYPE_BLIST_THEME_LOADER , "type" , "blist" , NULL )); /* Register our signals */ purple_signal_register ( gtk_blist_handle , "gtkblist-hiding" , purple_marshal_VOID__POINTER , G_TYPE_NONE , 1 , purple_signal_register ( gtk_blist_handle , "gtkblist-unhiding" , purple_marshal_VOID__POINTER , G_TYPE_NONE , 1 , purple_signal_register ( gtk_blist_handle , "gtkblist-created" , purple_marshal_VOID__POINTER , G_TYPE_NONE , 1 , purple_signal_register ( gtk_blist_handle , "drawing-tooltip" , purple_marshal_VOID__POINTER_POINTER_UINT , G_TYPE_NONE , 3 , PURPLE_TYPE_BLIST_NODE , G_TYPE_POINTER , /* pointer to a (GString *) */ purple_signal_register ( gtk_blist_handle , "drawing-buddy" , purple_marshal_POINTER__POINTER , G_TYPE_STRING , 1 , PURPLE_TYPE_BUDDY ); purple_signal_connect ( purple_blist_get_handle (), "buddy-signed-on" , gtk_blist_handle , PURPLE_CALLBACK ( buddy_signonoff_cb ), NULL ); purple_signal_connect ( purple_blist_get_handle (), "buddy-signed-off" , gtk_blist_handle , PURPLE_CALLBACK ( buddy_signonoff_cb ), NULL ); purple_signal_connect ( purple_blist_get_handle (), "buddy-privacy-changed" , gtk_blist_handle , PURPLE_CALLBACK ( pidgin_blist_update_privacy_cb ), NULL ); purple_signal_connect_priority ( purple_connections_get_handle (), "autojoin" , gtk_blist_handle , PURPLE_CALLBACK ( autojoin_cb ), NULL , PURPLE_SIGNAL_PRIORITY_HIGHEST ); pidgin_blist_uninit ( void ) { g_hash_table_destroy ( cached_emblems ); purple_signals_unregister_by_instance ( pidgin_blist_get_handle ()); purple_signals_disconnect_by_handle ( pidgin_blist_get_handle ()); /************************************************************************** * GTK Buddy list GObject code **************************************************************************/ pidgin_buddy_list_init ( PidginBuddyList * self ) pidgin_buddy_list_finalize ( GObject * obj ) PidginBuddyList * gtkblist = PIDGIN_BUDDY_LIST ( obj ); PidginBuddyListPrivate * priv = pidgin_buddy_list_get_instance_private ( gtkblist ); purple_signals_disconnect_by_handle ( gtkblist ); gtk_widget_destroy ( gtkblist -> window ); pidgin_blist_tooltip_destroy (); if ( gtkblist -> refresh_timer ) { g_source_remove ( gtkblist -> refresh_timer ); gtkblist -> refresh_timer = 0 ; g_source_remove ( gtkblist -> timeout ); if ( gtkblist -> drag_timeout ) { g_source_remove ( gtkblist -> drag_timeout ); gtkblist -> drag_timeout = 0 ; gtkblist -> window = gtkblist -> vbox = gtkblist -> treeview = NULL ; g_clear_object ( & gtkblist -> treemodel ); g_object_unref ( G_OBJECT ( gtkblist -> ui )); g_object_unref ( G_OBJECT ( gtkblist -> empty_avatar )); g_clear_object ( & priv -> current_theme ); if ( priv -> select_notebook_page_timeout ) { g_source_remove ( priv -> select_notebook_page_timeout ); purple_prefs_disconnect_by_handle ( pidgin_blist_get_handle ()); G_OBJECT_CLASS ( pidgin_buddy_list_parent_class ) -> finalize ( obj ); pidgin_buddy_list_class_init ( PidginBuddyListClass * klass ) GObjectClass * obj_class = G_OBJECT_CLASS ( klass ); PurpleBuddyListClass * purple_blist_class ; obj_class -> finalize = pidgin_buddy_list_finalize ; purple_blist_class = PURPLE_BUDDY_LIST_CLASS ( klass ); purple_blist_class -> new_node = pidgin_blist_new_node ; purple_blist_class -> show = pidgin_blist_show ; purple_blist_class -> update = pidgin_blist_update ; purple_blist_class -> remove = pidgin_blist_remove ; purple_blist_class -> set_visible = pidgin_blist_set_visible ; purple_blist_class -> request_add_buddy = pidgin_blist_request_add_buddy ; purple_blist_class -> request_add_chat = pidgin_blist_request_add_chat ; purple_blist_class -> request_add_group = pidgin_blist_request_add_group ; /********************************************************************* * Buddy List sorting functions * *********************************************************************/ GList * pidgin_blist_get_sort_methods () return pidgin_blist_sort_methods ; void pidgin_blist_sort_method_reg ( const char * id , const char * name , pidgin_blist_sort_function func ) struct _PidginBlistSortMethod * method ; g_return_if_fail ( id != NULL ); g_return_if_fail ( name != NULL ); g_return_if_fail ( func != NULL ); method = g_new0 ( struct _PidginBlistSortMethod , 1 ); method -> id = g_strdup ( id ); method -> name = g_strdup ( name ); pidgin_blist_sort_methods = g_list_append ( pidgin_blist_sort_methods , method ); pidgin_blist_update_sort_methods (); void pidgin_blist_sort_method_unreg ( const char * id ) GList * l = pidgin_blist_sort_methods ; g_return_if_fail ( id != NULL ); struct _PidginBlistSortMethod * method = l -> data ; if ( purple_strequal ( method -> id , id )) { pidgin_blist_sort_methods = g_list_delete_link ( pidgin_blist_sort_methods , l ); pidgin_blist_update_sort_methods (); void pidgin_blist_sort_method_set ( const char * id ){ GList * l = pidgin_blist_sort_methods ; while ( l && ! purple_strequal ((( struct _PidginBlistSortMethod * ) l -> data ) -> id , id )) current_sort_method = l -> data ; } else if ( ! current_sort_method ) { pidgin_blist_sort_method_set ( "none" ); if ( purple_strequal ( id , "none" )) { redo_buddy_list ( purple_blist_get_default (), TRUE , FALSE ); redo_buddy_list ( purple_blist_get_default (), FALSE , FALSE ); /****************************************** ******************************************/ static void sort_method_none ( PurpleBlistNode * node , PurpleBuddyList * blist , GtkTreeIter parent_iter , GtkTreeIter * cur , GtkTreeIter * iter ) PurpleBlistNode * sibling = node -> prev ; GtkTreeIter sibling_iter ; while ( sibling && ! get_iter_from_node ( sibling , & sibling_iter )) { gtk_tree_store_insert_after ( gtkblist -> treemodel , iter , node -> parent ? & parent_iter : NULL , sibling ? & sibling_iter : NULL ); static void sort_method_alphabetical ( PurpleBlistNode * node , PurpleBuddyList * blist , GtkTreeIter groupiter , GtkTreeIter * cur , GtkTreeIter * iter ) if ( PURPLE_IS_CONTACT ( node )) { my_name = purple_contact_get_alias (( PurpleContact * ) node ); } else if ( PURPLE_IS_CHAT ( node )) { my_name = purple_chat_get_name (( PurpleChat * ) node ); sort_method_none ( node , blist , groupiter , cur , iter ); if ( ! gtk_tree_model_iter_children ( GTK_TREE_MODEL ( gtkblist -> treemodel ), & more_z , & groupiter )) { gtk_tree_store_insert ( gtkblist -> treemodel , iter , & groupiter , 0 ); gtk_tree_model_get ( GTK_TREE_MODEL ( gtkblist -> treemodel ), & more_z , NODE_COLUMN , & n , -1 ); if ( PURPLE_IS_CONTACT ( n )) { this_name = purple_contact_get_alias (( PurpleContact * ) n ); } else if ( PURPLE_IS_CHAT ( n )) { this_name = purple_chat_get_name (( PurpleChat * ) n ); cmp = purple_utf8_strcasecmp ( my_name , this_name ); if ( this_name && ( cmp < 0 || ( cmp == 0 && node < n ))) { gtk_tree_store_move_before ( gtkblist -> treemodel , cur , & more_z ); gtk_tree_store_insert_before ( gtkblist -> treemodel , iter , } while ( gtk_tree_model_iter_next ( GTK_TREE_MODEL ( gtkblist -> treemodel ), & more_z )); gtk_tree_store_move_before ( gtkblist -> treemodel , cur , NULL ); gtk_tree_store_append ( gtkblist -> treemodel , iter , & groupiter ); static void sort_method_status ( PurpleBlistNode * node , PurpleBuddyList * blist , GtkTreeIter groupiter , GtkTreeIter * cur , GtkTreeIter * iter ) PurpleBuddy * my_buddy , * this_buddy ; if ( PURPLE_IS_CONTACT ( node )) { my_buddy = purple_contact_get_priority_buddy (( PurpleContact * ) node ); } else if ( PURPLE_IS_CHAT ( node )) { gtk_tree_store_append ( gtkblist -> treemodel , iter , & groupiter ); sort_method_alphabetical ( node , blist , groupiter , cur , iter ); if ( ! gtk_tree_model_iter_children ( GTK_TREE_MODEL ( gtkblist -> treemodel ), & more_z , & groupiter )) { gtk_tree_store_insert ( gtkblist -> treemodel , iter , & groupiter , 0 ); gtk_tree_model_get ( GTK_TREE_MODEL ( gtkblist -> treemodel ), & more_z , NODE_COLUMN , & n , -1 ); if ( PURPLE_IS_CONTACT ( n )) { this_buddy = purple_contact_get_priority_buddy (( PurpleContact * ) n ); name_cmp = purple_utf8_strcasecmp ( purple_contact_get_alias ( purple_buddy_get_contact ( my_buddy )), ? purple_contact_get_alias ( purple_buddy_get_contact ( this_buddy )) presence_cmp = purple_buddy_presence_compare ( PURPLE_BUDDY_PRESENCE ( purple_buddy_get_presence ( my_buddy )), this_buddy ? PURPLE_BUDDY_PRESENCE ( purple_buddy_get_presence ( this_buddy )) : NULL ); if ( this_buddy == NULL || ( name_cmp < 0 || ( name_cmp == 0 && node < n ))))) gtk_tree_store_move_before ( gtkblist -> treemodel , cur , & more_z ); gtk_tree_store_insert_before ( gtkblist -> treemodel , iter , while ( gtk_tree_model_iter_next ( GTK_TREE_MODEL ( gtkblist -> treemodel ), gtk_tree_store_move_before ( gtkblist -> treemodel , cur , NULL ); gtk_tree_store_append ( gtkblist -> treemodel , iter , & groupiter ); static void sort_method_log_activity ( PurpleBlistNode * node , PurpleBuddyList * blist , GtkTreeIter groupiter , GtkTreeIter * cur , GtkTreeIter * iter ) int activity_score = 0 , this_log_activity_score = 0 ; const char * buddy_name , * this_buddy_name ; if ( cur && ( gtk_tree_model_iter_n_children ( GTK_TREE_MODEL ( gtkblist -> treemodel ), & groupiter ) == 1 )) { if ( PURPLE_IS_CONTACT ( node )) { for ( n = node -> child ; n ; n = n -> next ) { activity_score += purple_log_get_activity_score ( PURPLE_LOG_IM , purple_buddy_get_name ( buddy ), purple_buddy_get_account ( buddy )); buddy_name = purple_contact_get_alias (( PurpleContact * ) node ); } else if ( PURPLE_IS_CHAT ( node )) { /* we don't have a reliable way of getting the log filename * from the chat info in the blist, yet */ gtk_tree_store_append ( gtkblist -> treemodel , iter , & groupiter ); sort_method_none ( node , blist , groupiter , cur , iter ); if ( ! gtk_tree_model_iter_children ( GTK_TREE_MODEL ( gtkblist -> treemodel ), & more_z , & groupiter )) { gtk_tree_store_insert ( gtkblist -> treemodel , iter , & groupiter , 0 ); gtk_tree_model_get ( GTK_TREE_MODEL ( gtkblist -> treemodel ), & more_z , NODE_COLUMN , & n , -1 ); this_log_activity_score = 0 ; if ( PURPLE_IS_CONTACT ( n )) { for ( n2 = n -> child ; n2 ; n2 = n2 -> next ) { buddy = ( PurpleBuddy * ) n2 ; this_log_activity_score += purple_log_get_activity_score ( PURPLE_LOG_IM , purple_buddy_get_name ( buddy ), purple_buddy_get_account ( buddy )); this_buddy_name = purple_contact_get_alias (( PurpleContact * ) n ); cmp = purple_utf8_strcasecmp ( buddy_name , this_buddy_name ); if ( ! PURPLE_IS_CONTACT ( n ) || activity_score > this_log_activity_score || (( activity_score == this_log_activity_score ) && ( cmp < 0 || ( cmp == 0 && node < n )))) { gtk_tree_store_move_before ( gtkblist -> treemodel , cur , & more_z ); gtk_tree_store_insert_before ( gtkblist -> treemodel , iter , } while ( gtk_tree_model_iter_next ( GTK_TREE_MODEL ( gtkblist -> treemodel ), & more_z )); gtk_tree_store_move_before ( gtkblist -> treemodel , cur , NULL ); gtk_tree_store_append ( gtkblist -> treemodel , iter , & groupiter ); plugin_act ( GSimpleAction * action , GVariant * param , PurplePluginAction * pam ) if ( pam && pam -> callback ) build_plugin_actions ( GActionMap * action_map , const gchar * parent , PurplePluginActionsCb actions_cb ; PurplePluginAction * action = NULL ; purple_plugin_info_get_actions_cb ( purple_plugin_get_info ( plugin )); actions = actions_cb ( plugin ); for ( l = actions ; l != NULL ; l = l -> next ) { action = ( PurplePluginAction * ) l -> data ; /* Close and append section if any */ g_menu_append_section ( menu , NULL , g_clear_object ( & section ); name = g_strdup_printf ( "plugin.%s-action-%d" , parent , count ++ ); /* +7 to skip "plugin." prefix */ menuaction = G_ACTION ( g_simple_action_new ( name + 7 , NULL )); g_signal_connect_data ( G_OBJECT ( menuaction ), "activate" , G_CALLBACK ( plugin_act ), action , ( GClosureNotify ) purple_plugin_action_free , 0 ); g_action_map_add_action ( action_map , menuaction ); g_object_unref ( menuaction ); g_menu_append ( section , action -> label , name ); /* Close and append final section if any */ g_menu_append_section ( menu , NULL , G_MENU_MODEL ( section )); g_clear_object ( & section ); ret = gtk_menu_new_from_model ( G_MENU_MODEL ( menu )); modify_account_cb ( GtkWidget * widget , gpointer data ) pidgin_account_dialog_show ( PIDGIN_MODIFY_ACCOUNT_DIALOG , data ); enable_account_cb ( GtkCheckMenuItem * widget , gpointer data ) PurpleAccount * account = data ; const PurpleSavedStatus * saved_status ; saved_status = purple_savedstatus_get_current (); purple_savedstatus_activate_for_account ( saved_status , account ); purple_account_set_enabled ( account , PIDGIN_UI , TRUE ); disable_account_cb ( GtkCheckMenuItem * widget , gpointer data ) PurpleAccount * account = data ; purple_account_set_enabled ( account , PIDGIN_UI , FALSE ); protocol_act ( GtkWidget * obj , PurpleProtocolAction * pam ) if ( pam && pam -> callback ) pidgin_blist_update_accounts_menu ( void ) GtkWidget * menuitem , * submenu ; GtkAccelGroup * accel_group ; gboolean disabled_accounts = FALSE ; gboolean enabled_accounts = FALSE ; /* Clear the old Accounts menu */ for ( l = gtk_container_get_children ( GTK_CONTAINER ( accountmenu )); l ; l = g_list_delete_link ( l , l )) { if ( menuitem != gtk_ui_manager_get_widget ( gtkblist -> ui , "/BList/AccountsMenu/ManageAccounts" )) gtk_widget_destroy ( menuitem ); accel_group = gtk_menu_get_accel_group ( GTK_MENU ( accountmenu )); for ( accounts = purple_accounts_get_all (); accounts ; accounts = accounts -> next ) { PurpleAccount * account = NULL ; GdkPixbuf * pixbuf = NULL ; account = accounts -> data ; if ( ! purple_account_get_enabled ( account , PIDGIN_UI )) { if ( ! disabled_accounts ) { menuitem = gtk_menu_item_new_with_label ( _ ( "Enable Account" )); gtk_menu_shell_append ( GTK_MENU_SHELL ( accountmenu ), menuitem ); submenu = gtk_menu_new (); gtk_menu_set_accel_group ( GTK_MENU ( submenu ), accel_group ); gtk_menu_set_accel_path ( GTK_MENU ( submenu ), "<Actions>/BListActions/EnableAccount" ); gtk_menu_item_set_submenu ( GTK_MENU_ITEM ( menuitem ), submenu ); disabled_accounts = TRUE ; buf = g_strconcat ( purple_account_get_username ( account ), " (" , purple_account_get_protocol_name ( account ), ")" , NULL ); menuitem = gtk_image_menu_item_new_with_label ( buf ); pixbuf = pidgin_create_protocol_icon ( account , PIDGIN_PROTOCOL_ICON_SMALL ); if ( ! purple_account_is_connected ( account )) gdk_pixbuf_saturate_and_pixelate ( pixbuf , pixbuf , 0.0 , FALSE ); image = gtk_image_new_from_pixbuf ( pixbuf ); g_object_unref ( G_OBJECT ( pixbuf )); gtk_image_menu_item_set_image ( GTK_IMAGE_MENU_ITEM ( menuitem ), image ); g_signal_connect ( G_OBJECT ( menuitem ), "activate" , G_CALLBACK ( enable_account_cb ), account ); gtk_menu_shell_append ( GTK_MENU_SHELL ( submenu ), menuitem ); gtk_widget_show_all ( accountmenu ); pidgin_separator ( accountmenu ); for ( accounts = purple_accounts_get_all (); accounts ; accounts = accounts -> next ) { char * accel_path_buf = NULL ; PurpleConnection * gc = NULL ; PurpleAccount * account = NULL ; GdkPixbuf * pixbuf = NULL ; PurpleProtocol * protocol ; account = accounts -> data ; if ( ! purple_account_get_enabled ( account , PIDGIN_UI )) buf = g_strconcat ( purple_account_get_username ( account ), " (" , purple_account_get_protocol_name ( account ), ")" , NULL ); menuitem = gtk_image_menu_item_new_with_label ( buf ); accel_path_buf = g_strconcat ( "<Actions>/AccountActions/" , buf , NULL ); pixbuf = pidgin_create_protocol_icon ( account , PIDGIN_PROTOCOL_ICON_SMALL ); if ( ! purple_account_is_connected ( account )) gdk_pixbuf_saturate_and_pixelate ( pixbuf , pixbuf , image = gtk_image_new_from_pixbuf ( pixbuf ); g_object_unref ( G_OBJECT ( pixbuf )); gtk_image_menu_item_set_image ( GTK_IMAGE_MENU_ITEM ( menuitem ), image ); gtk_menu_shell_append ( GTK_MENU_SHELL ( accountmenu ), menuitem ); submenu = gtk_menu_new (); gtk_menu_set_accel_group ( GTK_MENU ( submenu ), accel_group ); gtk_menu_set_accel_path ( GTK_MENU ( submenu ), accel_path_buf ); gtk_menu_item_set_submenu ( GTK_MENU_ITEM ( menuitem ), submenu ); menuitem = gtk_menu_item_new_with_mnemonic ( _ ( "_Edit Account" )); g_signal_connect ( G_OBJECT ( menuitem ), "activate" , G_CALLBACK ( modify_account_cb ), account ); gtk_menu_shell_append ( GTK_MENU_SHELL ( submenu ), menuitem ); pidgin_separator ( submenu ); gc = purple_account_get_connection ( account ); protocol = gc && PURPLE_CONNECTION_IS_CONNECTED ( gc ) ? purple_connection_get_protocol ( gc ) : NULL ; ( PURPLE_PROTOCOL_IMPLEMENTS ( protocol , CLIENT , get_moods ) || PURPLE_PROTOCOL_IMPLEMENTS ( protocol , CLIENT , get_actions ))) { if ( PURPLE_PROTOCOL_IMPLEMENTS ( protocol , CLIENT , get_moods ) && ( purple_connection_get_flags ( gc ) & PURPLE_CONNECTION_FLAG_SUPPORT_MOODS )) { if ( purple_account_get_status ( account , "mood" )) { menuitem = gtk_menu_item_new_with_mnemonic ( _ ( "Set _Mood..." )); g_signal_connect ( G_OBJECT ( menuitem ), "activate" , G_CALLBACK ( set_mood_cb ), account ); gtk_menu_shell_append ( GTK_MENU_SHELL ( submenu ), menuitem ); if ( PURPLE_PROTOCOL_IMPLEMENTS ( protocol , CLIENT , get_actions )) { PurpleProtocolAction * action = NULL ; actions = purple_protocol_client_iface_get_actions ( protocol , gc ); for ( l = actions ; l != NULL ; l = l -> next ) action = ( PurpleProtocolAction * ) l -> data ; menuitem = gtk_menu_item_new_with_label ( action -> label ); gtk_menu_shell_append ( GTK_MENU_SHELL ( submenu ), menuitem ); g_signal_connect ( G_OBJECT ( menuitem ), "activate" , G_CALLBACK ( protocol_act ), action ); g_object_set_data_full ( G_OBJECT ( menuitem ), "protocol_action" , ( GDestroyNotify ) purple_protocol_action_free ); gtk_widget_show ( menuitem ); pidgin_separator ( submenu ); menuitem = gtk_menu_item_new_with_label ( _ ( "No actions available" )); gtk_menu_shell_append ( GTK_MENU_SHELL ( submenu ), menuitem ); gtk_widget_set_sensitive ( menuitem , FALSE ); pidgin_separator ( submenu ); menuitem = gtk_menu_item_new_with_mnemonic ( _ ( "_Disable" )); g_signal_connect ( G_OBJECT ( menuitem ), "activate" , G_CALLBACK ( disable_account_cb ), account ); gtk_menu_shell_append ( GTK_MENU_SHELL ( submenu ), menuitem ); gtk_widget_show_all ( accountmenu ); static GSList * plugin_menu_items ; pidgin_blist_update_plugin_actions ( void ) GSimpleActionGroup * action_group ; PurplePlugin * plugin = NULL ; if (( gtkblist == NULL ) || ( gtkblist -> ui == NULL )) g_slist_free_full ( plugin_menu_items , ( GDestroyNotify ) gtk_widget_destroy ); plugin_menu_items = NULL ; toolsmenu = gtk_ui_manager_get_widget ( gtkblist -> ui , toolsmenu = gtk_menu_item_get_submenu ( GTK_MENU_ITEM ( toolsmenu )); action_group = g_simple_action_group_new (); /* Add a submenu for each plugin with custom actions */ for ( l = purple_plugins_get_loaded (); l ; l = l -> next ) { plugin = ( PurplePlugin * ) l -> data ; info = purple_plugin_get_info ( plugin ); if ( ! purple_plugin_info_get_actions_cb ( info )) name = g_strdup_printf ( "plugin%d" , count ); submenu = build_plugin_actions ( G_ACTION_MAP ( action_group ), menuitem = gtk_menu_item_new_with_mnemonic ( _ ( gplugin_plugin_info_get_name ( GPLUGIN_PLUGIN_INFO ( info )))); gtk_menu_item_set_submenu ( GTK_MENU_ITEM ( menuitem ), submenu ); gtk_widget_show ( menuitem ); plugin_menu_items = g_slist_prepend ( plugin_menu_items , gtk_menu_shell_append ( GTK_MENU_SHELL ( toolsmenu ), menuitem ); /* Replaces existing "plugin" group if any */ gtk_widget_insert_action_group ( toolsmenu , "plugin" , G_ACTION_GROUP ( action_group )); g_object_unref ( action_group ); sortmethod_act ( GtkRadioAction * action , GtkRadioAction * current , char * id ) pidgin_set_cursor ( gtkblist -> window , GDK_WATCH ); /* This is redundant. I think. */ /* pidgin_blist_sort_method_set(id); */ purple_prefs_set_string ( PIDGIN_PREFS_ROOT "/blist/sort_type" , id ); pidgin_clear_cursor ( gtkblist -> window ); pidgin_blist_update_sort_methods ( void ) PidginBlistSortMethod * method = NULL ; const char * m = purple_prefs_get_string ( PIDGIN_PREFS_ROOT "/blist/sort_type" ); if (( gtkblist == NULL ) || ( gtkblist -> ui == NULL )) gtk_ui_manager_remove_ui ( gtkblist -> ui , sort_merge_id ); gtk_ui_manager_remove_action_group ( gtkblist -> ui , sort_action_group ); g_object_unref ( G_OBJECT ( sort_action_group )); sort_action_group = gtk_action_group_new ( "SortMethods" ); gtk_action_group_set_translation_domain ( sort_action_group , PACKAGE ); ui_string = g_string_new ( "<ui><menubar name='BList'>" "<menu action='BuddiesMenu'><menu action='SortMenu'>" ); for ( l = pidgin_blist_sort_methods ; l ; l = l -> next ) { method = ( PidginBlistSortMethod * ) l -> data ; g_string_append_printf ( ui_string , "<menuitem action='%s'/>" , method -> id ); action = gtk_radio_action_new ( method -> id , gtk_action_group_add_action_with_accel ( sort_action_group , GTK_ACTION ( action ), NULL ); gtk_radio_action_set_group ( action , sl ); sl = gtk_radio_action_get_group ( action ); if ( purple_strequal ( m , method -> id )) gtk_toggle_action_set_active ( GTK_TOGGLE_ACTION ( action ), TRUE ); gtk_toggle_action_set_active ( GTK_TOGGLE_ACTION ( action ), FALSE ); g_signal_connect ( G_OBJECT ( action ), "changed" , G_CALLBACK ( sortmethod_act ), method -> id ); g_string_append ( ui_string , "</menu></menu></menubar></ui>" ); gtk_ui_manager_insert_action_group ( gtkblist -> ui , sort_action_group , 1 ); sort_merge_id = gtk_ui_manager_add_ui_from_string ( gtkblist -> ui , ui_string -> str , -1 , NULL ); g_string_free ( ui_string , TRUE );