* @file gtkstatusbox.c GTK+ Status Selection Widget * 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 * The status box is made up of two main pieces: * - The box that displays the current status, which is made * of a GtkListStore ("status_box->store") and GtkCellView * ("status_box->cell_view"). There is always exactly 1 row * in this list store. Only the TYPE_ICON and TYPE_TEXT * columns are used in this list store. * - The dropdown menu that lets users select a status, which * is made of a GtkComboBox ("status_box") and GtkListStore * ("status_box->dropdown_store"). This dropdown is shown * when the user clicks on the box that displays the current * status. This list store contains one row for Available, * one row for Away, etc., a few rows for popular statuses, * and the "New..." and "Saved..." options. #include <gdk/gdkkeysyms.h> #include "savedstatuses.h" #include "gtksavedstatuses.h" #include "gtkstatusbox.h" # include <gtkspell/gtkspell.h> /* Timeout for typing notifications in seconds */ static void imhtml_changed_cb ( GtkTextBuffer * buffer , void * data ); static void imhtml_format_changed_cb ( GtkIMHtml * imhtml , GtkIMHtmlButtons buttons , void * data ); static void remove_typing_cb ( PidginStatusBox * box ); static void update_size ( PidginStatusBox * box ); static gint get_statusbox_index ( PidginStatusBox * box , PurpleSavedStatus * saved_status ); static PurpleAccount * check_active_accounts_for_identical_statuses ( void ); static void pidgin_status_box_pulse_typing ( PidginStatusBox * status_box ); static void pidgin_status_box_refresh ( PidginStatusBox * status_box ); static void status_menu_refresh_iter ( PidginStatusBox * status_box , gboolean status_changed ); static void pidgin_status_box_regenerate ( PidginStatusBox * status_box , gboolean status_changed ); static void pidgin_status_box_changed ( PidginStatusBox * box ); static void pidgin_status_box_size_request ( GtkWidget * widget , GtkRequisition * requisition ); static void pidgin_status_box_size_allocate ( GtkWidget * widget , GtkAllocation * allocation ); static gboolean pidgin_status_box_expose_event ( GtkWidget * widget , GdkEventExpose * event ); static void pidgin_status_box_redisplay_buddy_icon ( PidginStatusBox * status_box ); static void pidgin_status_box_forall ( GtkContainer * container , gboolean include_internals , GtkCallback callback , gpointer callback_data ); static void pidgin_status_box_popup ( PidginStatusBox * box ); static void pidgin_status_box_popdown ( PidginStatusBox * box ); static void do_colorshift ( GdkPixbuf * dest , GdkPixbuf * src , int shift ); static void icon_choose_cb ( const char * filename , gpointer data ); static void remove_buddy_icon_cb ( GtkWidget * w , PidginStatusBox * box ); static void choose_buddy_icon_cb ( GtkWidget * w , PidginStatusBox * box ); /** A PidginStatusBoxItemType */ /** This is the stock-id for the icon. */ * This is a GdkPixbuf (the other columns are strings). * This column is visible. /** The text displayed on the status box. This column is visible. */ /** The plain-English title of this item */ /** A plain-English description of this item */ * This value depends on TYPE_COLUMN. For POPULAR types, * this is the creation time. For PRIMITIVE types, * this is the PurpleStatusPrimitive. * This column stores the GdkPixbuf for the status emblem. Currently only 'saved' is stored. * In the GtkTreeModel for the dropdown, this is the stock-id (gchararray), and for the * GtkTreeModel for the cell_view (for the account-specific statusbox), this is the prpl-icon * (GdkPixbuf) of the account. * This column stores whether to show the emblem. static char * typing_stock_ids [ 7 ] = { PIDGIN_STOCK_ANIMATION_TYPING0 , PIDGIN_STOCK_ANIMATION_TYPING1 , PIDGIN_STOCK_ANIMATION_TYPING2 , PIDGIN_STOCK_ANIMATION_TYPING3 , PIDGIN_STOCK_ANIMATION_TYPING4 , static char * connecting_stock_ids [] = { PIDGIN_STOCK_ANIMATION_CONNECT0 , PIDGIN_STOCK_ANIMATION_CONNECT1 , PIDGIN_STOCK_ANIMATION_CONNECT2 , PIDGIN_STOCK_ANIMATION_CONNECT3 , PIDGIN_STOCK_ANIMATION_CONNECT4 , PIDGIN_STOCK_ANIMATION_CONNECT5 , PIDGIN_STOCK_ANIMATION_CONNECT6 , PIDGIN_STOCK_ANIMATION_CONNECT7 , PIDGIN_STOCK_ANIMATION_CONNECT8 , PIDGIN_STOCK_ANIMATION_CONNECT9 , PIDGIN_STOCK_ANIMATION_CONNECT10 , PIDGIN_STOCK_ANIMATION_CONNECT11 , PIDGIN_STOCK_ANIMATION_CONNECT12 , PIDGIN_STOCK_ANIMATION_CONNECT13 , PIDGIN_STOCK_ANIMATION_CONNECT14 , PIDGIN_STOCK_ANIMATION_CONNECT15 , PIDGIN_STOCK_ANIMATION_CONNECT16 , PIDGIN_STOCK_ANIMATION_CONNECT17 , PIDGIN_STOCK_ANIMATION_CONNECT18 , PIDGIN_STOCK_ANIMATION_CONNECT19 , PIDGIN_STOCK_ANIMATION_CONNECT20 , PIDGIN_STOCK_ANIMATION_CONNECT21 , PIDGIN_STOCK_ANIMATION_CONNECT22 , PIDGIN_STOCK_ANIMATION_CONNECT23 , PIDGIN_STOCK_ANIMATION_CONNECT24 , PIDGIN_STOCK_ANIMATION_CONNECT25 , PIDGIN_STOCK_ANIMATION_CONNECT26 , PIDGIN_STOCK_ANIMATION_CONNECT27 , PIDGIN_STOCK_ANIMATION_CONNECT28 , PIDGIN_STOCK_ANIMATION_CONNECT29 , PIDGIN_STOCK_ANIMATION_CONNECT30 , static GtkContainerClass * parent_class = NULL ; static void pidgin_status_box_class_init ( PidginStatusBoxClass * klass ); static void pidgin_status_box_init ( PidginStatusBox * status_box ); pidgin_status_box_get_type ( void ) static GType status_box_type = 0 ; static const GTypeInfo status_box_info = sizeof ( PidginStatusBoxClass ), NULL , /* base_finalize */ ( GClassInitFunc ) pidgin_status_box_class_init , NULL , /* class_finalize */ sizeof ( PidginStatusBox ), ( GInstanceInitFunc ) pidgin_status_box_init , status_box_type = g_type_register_static ( GTK_TYPE_CONTAINER , pidgin_status_box_get_property ( GObject * object , guint param_id , GValue * value , GParamSpec * psec ) PidginStatusBox * statusbox = PIDGIN_STATUS_BOX ( object ); g_value_set_pointer ( value , statusbox -> account ); g_value_set_boolean ( value , statusbox -> icon_box != NULL ); G_OBJECT_WARN_INVALID_PROPERTY_ID ( object , param_id , psec ); update_to_reflect_account_status ( PidginStatusBox * status_box , PurpleAccount * account , PurpleStatus * newstatus ) const PurpleStatusType * statustype = NULL ; statustype = purple_status_type_find_with_id (( GList * ) purple_account_get_status_types ( account ), ( char * ) purple_status_type_get_id ( purple_status_get_type ( newstatus ))); for ( l = purple_account_get_status_types ( account ); l != NULL ; l = l -> next ) { PurpleStatusType * status_type = ( PurpleStatusType * ) l -> data ; if ( ! purple_status_type_is_user_settable ( status_type ) || purple_status_type_is_independent ( status_type )) if ( statustype == status_type ) gtk_imhtml_set_populate_primary_clipboard ( GTK_IMHTML ( status_box -> imhtml ), TRUE ); gtk_widget_set_sensitive ( GTK_WIDGET ( status_box ), FALSE ); path = gtk_tree_path_new_from_indices ( status_no , -1 ); if ( status_box -> active_row ) gtk_tree_row_reference_free ( status_box -> active_row ); status_box -> active_row = gtk_tree_row_reference_new ( GTK_TREE_MODEL ( status_box -> dropdown_store ), path ); gtk_tree_path_free ( path ); message = purple_status_get_attr_string ( newstatus , "message" ); if ( ! message || !* message ) gtk_widget_hide_all ( status_box -> vbox ); status_box -> imhtml_visible = FALSE ; gtk_widget_show_all ( status_box -> vbox ); status_box -> imhtml_visible = TRUE ; gtk_imhtml_clear ( GTK_IMHTML ( status_box -> imhtml )); gtk_imhtml_clear_formatting ( GTK_IMHTML ( status_box -> imhtml )); gtk_imhtml_append_text ( GTK_IMHTML ( status_box -> imhtml ), message , 0 ); gtk_widget_set_sensitive ( GTK_WIDGET ( status_box ), TRUE ); pidgin_status_box_refresh ( status_box ); account_status_changed_cb ( PurpleAccount * account , PurpleStatus * oldstatus , PurpleStatus * newstatus , PidginStatusBox * status_box ) if ( status_box -> account == account ) update_to_reflect_account_status ( status_box , account , newstatus ); else if ( status_box -> token_status_account == account ) status_menu_refresh_iter ( status_box , TRUE ); icon_box_press_cb ( GtkWidget * widget , GdkEventButton * event , PidginStatusBox * box ) if ( event -> button == 3 ) { gtk_widget_destroy ( box -> icon_box_menu ); box -> icon_box_menu = gtk_menu_new (); pidgin_new_item_from_stock ( box -> icon_box_menu , _ ( "Select Buddy Icon" ), GTK_STOCK_ADD , G_CALLBACK ( choose_buddy_icon_cb ), box , 0 , 0 , NULL ); menu_item = pidgin_new_item_from_stock ( box -> icon_box_menu , _ ( "Remove" ), GTK_STOCK_REMOVE , G_CALLBACK ( remove_buddy_icon_cb ), if ( ! ( path = purple_prefs_get_path ( PIDGIN_PREFS_ROOT "/accounts/buddyicon" )) gtk_widget_set_sensitive ( menu_item , FALSE ); gtk_menu_popup ( GTK_MENU ( box -> icon_box_menu ), NULL , NULL , NULL , NULL , event -> button , event -> time ); choose_buddy_icon_cb ( widget , box ); icon_box_dnd_cb ( GtkWidget * widget , GdkDragContext * dc , gint x , gint y , GtkSelectionData * sd , guint info , guint t , PidginStatusBox * box ) gchar * name = ( gchar * ) sd -> data ; if (( sd -> length >= 0 ) && ( sd -> format == 8 )) { /* Well, it looks like the drag event was cool. * Let's do something with it */ if ( ! g_ascii_strncasecmp ( name , "file://" , 7 )) { if ( ! ( tmp = g_filename_from_uri ( name , NULL , & converr ))) { purple_debug ( PURPLE_DEBUG_ERROR , "buddyicon" , "%s \n " , ( converr ? converr -> message : "g_filename_from_uri error" )); if (( rtmp = strchr ( tmp , '\r' )) || ( rtmp = strchr ( tmp , '\n' ))) icon_choose_cb ( tmp , box ); gtk_drag_finish ( dc , TRUE , FALSE , t ); gtk_drag_finish ( dc , FALSE , FALSE , t ); statusbox_got_url ( PurpleUtilFetchUrlData * url_data , gpointer user_data , const gchar * themedata , size_t len , const gchar * error_message ) if (( error_message != NULL ) || ( len == 0 )) f = purple_mkstemp ( & path , TRUE ); wc = fwrite ( themedata , len , 1 , f ); purple_debug_warning ( "theme_got_url" , "Unable to write theme data. \n " ); icon_choose_cb ( path , user_data ); statusbox_uri_handler ( const char * proto , const char * cmd , GHashTable * params , void * data ) if ( g_ascii_strcasecmp ( proto , "aim" )) if ( g_ascii_strcasecmp ( cmd , "buddyicon" )) src = g_hash_table_lookup ( params , "account" ); purple_util_fetch_url ( src , TRUE , NULL , FALSE , statusbox_got_url , data ); icon_box_enter_cb ( GtkWidget * widget , GdkEventCrossing * event , PidginStatusBox * box ) gdk_window_set_cursor ( widget -> window , box -> hand_cursor ); gtk_image_set_from_pixbuf ( GTK_IMAGE ( box -> icon ), box -> buddy_icon_hover ); icon_box_leave_cb ( GtkWidget * widget , GdkEventCrossing * event , PidginStatusBox * box ) gdk_window_set_cursor ( widget -> window , box -> arrow_cursor ); gtk_image_set_from_pixbuf ( GTK_IMAGE ( box -> icon ), box -> buddy_icon ) ; static const GtkTargetEntry dnd_targets [] = { setup_icon_box ( PidginStatusBox * status_box ) if ( status_box -> icon_box != NULL ) status_box -> icon = gtk_image_new (); status_box -> icon_box = gtk_event_box_new (); gtk_widget_set_parent ( status_box -> icon_box , GTK_WIDGET ( status_box )); gtk_widget_show ( status_box -> icon_box ); #if GTK_CHECK_VERSION(2,12,0) gtk_widget_set_tooltip_text ( status_box -> icon_box , status_box -> account ? _ ( "Click to change your buddyicon for this account." ) : _ ( "Click to change your buddyicon for all accounts." )); if ( status_box -> account && ! purple_account_get_bool ( status_box -> account , "use-global-buddyicon" , TRUE )) PurpleStoredImage * img = purple_buddy_icons_find_account_icon ( status_box -> account ); pidgin_status_box_set_buddy_icon ( status_box , img ); purple_imgstore_unref ( img ); const char * filename = purple_prefs_get_path ( PIDGIN_PREFS_ROOT "/accounts/buddyicon" ); PurpleStoredImage * img = NULL ; if ( filename && * filename ) img = purple_imgstore_new_from_file ( filename ); pidgin_status_box_set_buddy_icon ( status_box , img ); * purple_imgstore_new gives us a reference and * pidgin_status_box_set_buddy_icon also takes one. purple_imgstore_unref ( img ); status_box -> hand_cursor = gdk_cursor_new ( GDK_HAND2 ); status_box -> arrow_cursor = gdk_cursor_new ( GDK_LEFT_PTR ); gtk_drag_dest_set ( status_box -> icon_box , GTK_DEST_DEFAULT_MOTION | sizeof ( dnd_targets ) / sizeof ( GtkTargetEntry ), g_signal_connect ( G_OBJECT ( status_box -> icon_box ), "drag_data_received" , G_CALLBACK ( icon_box_dnd_cb ), status_box ); g_signal_connect ( G_OBJECT ( status_box -> icon_box ), "enter-notify-event" , G_CALLBACK ( icon_box_enter_cb ), status_box ); g_signal_connect ( G_OBJECT ( status_box -> icon_box ), "leave-notify-event" , G_CALLBACK ( icon_box_leave_cb ), status_box ); g_signal_connect ( G_OBJECT ( status_box -> icon_box ), "button-press-event" , G_CALLBACK ( icon_box_press_cb ), status_box ); gtk_container_add ( GTK_CONTAINER ( status_box -> icon_box ), status_box -> icon ); gtk_widget_show ( status_box -> icon ); destroy_icon_box ( PidginStatusBox * statusbox ) if ( statusbox -> icon_box == NULL ) gtk_widget_destroy ( statusbox -> icon_box ); gdk_cursor_unref ( statusbox -> hand_cursor ); gdk_cursor_unref ( statusbox -> arrow_cursor ); purple_imgstore_unref ( statusbox -> buddy_icon_img ); g_object_unref ( G_OBJECT ( statusbox -> buddy_icon )); g_object_unref ( G_OBJECT ( statusbox -> buddy_icon_hover )); if ( statusbox -> buddy_icon_sel ) gtk_widget_destroy ( statusbox -> buddy_icon_sel ); if ( statusbox -> icon_box_menu ) gtk_widget_destroy ( statusbox -> icon_box_menu ); statusbox -> icon_box = NULL ; statusbox -> icon_box_menu = NULL ; statusbox -> buddy_icon_img = NULL ; statusbox -> buddy_icon = NULL ; statusbox -> buddy_icon_hover = NULL ; statusbox -> hand_cursor = NULL ; statusbox -> arrow_cursor = NULL ; pidgin_status_box_set_property ( GObject * object , guint param_id , const GValue * value , GParamSpec * pspec ) PidginStatusBox * statusbox = PIDGIN_STATUS_BOX ( object ); if ( g_value_get_boolean ( value )) { if ( statusbox -> account ) { PurplePlugin * plug = purple_plugins_find_with_id ( purple_account_get_protocol_id ( statusbox -> account )); PurplePluginProtocolInfo * prplinfo = PURPLE_PLUGIN_PROTOCOL_INFO ( plug ); if ( prplinfo && prplinfo -> icon_spec . format != NULL ) setup_icon_box ( statusbox ); setup_icon_box ( statusbox ); destroy_icon_box ( statusbox ); statusbox -> account = g_value_get_pointer ( value ); statusbox -> token_status_account = NULL ; statusbox -> token_status_account = check_active_accounts_for_identical_statuses (); pidgin_status_box_regenerate ( statusbox , TRUE ); G_OBJECT_WARN_INVALID_PROPERTY_ID ( object , param_id , pspec ); pidgin_status_box_finalize ( GObject * obj ) PidginStatusBox * statusbox = PIDGIN_STATUS_BOX ( obj ); purple_signals_disconnect_by_handle ( statusbox ); purple_prefs_disconnect_by_handle ( statusbox ); destroy_icon_box ( statusbox ); if ( statusbox -> active_row ) gtk_tree_row_reference_free ( statusbox -> active_row ); for ( i = 0 ; i < G_N_ELEMENTS ( statusbox -> connecting_pixbufs ); i ++ ) { if ( statusbox -> connecting_pixbufs [ i ] != NULL ) g_object_unref ( G_OBJECT ( statusbox -> connecting_pixbufs [ i ])); for ( i = 0 ; i < G_N_ELEMENTS ( statusbox -> typing_pixbufs ); i ++ ) { if ( statusbox -> typing_pixbufs [ i ] != NULL ) g_object_unref ( G_OBJECT ( statusbox -> typing_pixbufs [ i ])); g_object_unref ( G_OBJECT ( statusbox -> store )); g_object_unref ( G_OBJECT ( statusbox -> dropdown_store )); G_OBJECT_CLASS ( parent_class ) -> finalize ( obj ); pidgin_status_box_child_type ( GtkContainer * container ) pidgin_status_box_class_init ( PidginStatusBoxClass * klass ) GObjectClass * object_class ; GtkWidgetClass * widget_class ; GtkContainerClass * container_class = ( GtkContainerClass * ) klass ; parent_class = g_type_class_peek_parent ( klass ); widget_class = ( GtkWidgetClass * ) klass ; widget_class -> size_request = pidgin_status_box_size_request ; widget_class -> size_allocate = pidgin_status_box_size_allocate ; widget_class -> expose_event = pidgin_status_box_expose_event ; container_class -> child_type = pidgin_status_box_child_type ; container_class -> forall = pidgin_status_box_forall ; container_class -> remove = NULL ; object_class = ( GObjectClass * ) klass ; object_class -> finalize = pidgin_status_box_finalize ; object_class -> get_property = pidgin_status_box_get_property ; object_class -> set_property = pidgin_status_box_set_property ; g_object_class_install_property ( object_class , g_param_spec_pointer ( "account" , "The account, or NULL for all accounts" , g_object_class_install_property ( object_class , g_param_spec_boolean ( "iconsel" , "Whether the icon selector should be displayed or not." , * This updates the text displayed on the status box so that it shows * the current status. This is the only function in this file that * should modify status_box->store pidgin_status_box_refresh ( PidginStatusBox * status_box ) PurpleSavedStatus * saved_status ; char * primary , * secondary , * text ; const char * stock = NULL ; GdkPixbuf * emblem = NULL ; gboolean account_status = FALSE ; PurpleAccount * acct = ( status_box -> account ) ? status_box -> account : status_box -> token_status_account ; style = gtk_widget_get_style ( GTK_WIDGET ( status_box )); snprintf ( aa_color , sizeof ( aa_color ), "#%02x%02x%02x" , style -> text_aa [ GTK_STATE_NORMAL ]. red >> 8 , style -> text_aa [ GTK_STATE_NORMAL ]. green >> 8 , style -> text_aa [ GTK_STATE_NORMAL ]. blue >> 8 ); saved_status = purple_savedstatus_get_current (); if ( status_box -> account || ( status_box -> token_status_account && purple_savedstatus_is_transient ( saved_status ))) if ( status_box -> typing != 0 ) PidginStatusBoxItemType type ; /* Primary (get the status selected in the dropdown) */ path = gtk_tree_row_reference_get_path ( status_box -> active_row ); if ( ! gtk_tree_model_get_iter ( GTK_TREE_MODEL ( status_box -> dropdown_store ), & iter , path )) gtk_tree_path_free ( path ); gtk_tree_model_get ( GTK_TREE_MODEL ( status_box -> dropdown_store ), & iter , if ( type == PIDGIN_STATUS_BOX_TYPE_PRIMITIVE ) primary = g_strdup ( purple_primitive_get_name_from_type ( GPOINTER_TO_INT ( data ))); /* This should never happen, but just in case... */ primary = g_strdup ( "New status" ); primary = g_strdup ( purple_status_get_name ( purple_account_get_active_status ( acct ))); else if ( purple_savedstatus_is_transient ( saved_status )) primary = g_strdup ( purple_primitive_get_name_from_type ( purple_savedstatus_get_type ( saved_status ))); primary = g_markup_escape_text ( purple_savedstatus_get_title ( saved_status ), -1 ); if ( status_box -> typing != 0 ) secondary = g_strdup ( _ ( "Typing" )); else if ( status_box -> connecting ) secondary = g_strdup ( _ ( "Connecting" )); else if ( ! status_box -> network_available ) secondary = g_strdup ( _ ( "Waiting for network connection" )); else if ( purple_savedstatus_is_transient ( saved_status )) message = purple_savedstatus_get_message ( saved_status ); tmp = purple_markup_strip_html ( message ); purple_util_chrreplace ( tmp , '\n' , ' ' ); secondary = g_markup_escape_text ( tmp , -1 ); if ( status_box -> typing != 0 ) stock = typing_stock_ids [ status_box -> typing_index ]; else if ( status_box -> connecting ) stock = connecting_stock_ids [ status_box -> connecting_index ]; PurpleStatusType * status_type ; PurpleStatusPrimitive prim ; status_type = purple_status_get_type ( purple_account_get_active_status ( acct )); prim = purple_status_type_get_primitive ( status_type ); prim = purple_savedstatus_get_type ( saved_status ); stock = pidgin_stock_id_from_status_primitive ( prim ); if ( status_box -> account != NULL ) { text = g_strdup_printf ( "%s - <span size= \" smaller \" color= \" %s \" >%s</span>" , purple_account_get_username ( status_box -> account ), aa_color , secondary ? secondary : primary ); emblem = pidgin_create_prpl_icon ( status_box -> account , PIDGIN_PRPL_ICON_SMALL ); } else if ( secondary != NULL ) { text = g_strdup_printf ( "%s<span size= \" smaller \" color= \" %s \" > - %s</span>" , primary , aa_color , secondary ); text = g_strdup ( primary ); * Only two columns are used in this list store (does it * really need to be a list store?) gtk_list_store_set ( status_box -> store , & ( status_box -> iter ), ICON_STOCK_COLUMN , ( gpointer ) stock , EMBLEM_VISIBLE_COLUMN , ( emblem != NULL ), /* Make sure to activate the only row in the tree view */ path = gtk_tree_path_new_from_string ( "0" ); gtk_cell_view_set_displayed_row ( GTK_CELL_VIEW ( status_box -> cell_view ), path ); gtk_tree_path_free ( path ); static PurpleStatusType * find_status_type_by_index ( const PurpleAccount * account , gint active ) GList * l = purple_account_get_status_types ( account ); for ( i = 0 ; l ; l = l -> next ) { PurpleStatusType * status_type = l -> data ; if ( ! purple_status_type_is_user_settable ( status_type ) || purple_status_type_is_independent ( status_type )) * This updates the GtkTreeView so that it correctly shows the state * we are currently using. It is used when the current state is * updated from somewhere other than the GtkStatusBox (from a plugin, * or when signing on with the "-n" option, for example). It is * also used when the user selects the "New..." option. * Maybe we could accomplish this by triggering off the mouse and * keyboard signals instead of the changed signal? status_menu_refresh_iter ( PidginStatusBox * status_box , gboolean status_changed ) PurpleSavedStatus * saved_status ; PurpleStatusPrimitive primitive ; GtkTreePath * path = NULL ; /* this function is inappropriate for ones with accounts */ saved_status = purple_savedstatus_get_current (); * Suppress the "changed" signal because the status * was changed programmatically. gtk_widget_set_sensitive ( GTK_WIDGET ( status_box ), FALSE ); * If there is a token-account, then select the primitive from the * dropdown using a loop. Otherwise select from the default list. primitive = purple_savedstatus_get_type ( saved_status ); if ( ! status_box -> token_status_account && purple_savedstatus_is_transient ( saved_status ) && (( primitive == PURPLE_STATUS_AVAILABLE ) || ( primitive == PURPLE_STATUS_AWAY ) || ( primitive == PURPLE_STATUS_INVISIBLE ) || ( primitive == PURPLE_STATUS_OFFLINE ) || ( primitive == PURPLE_STATUS_UNAVAILABLE )) && ( ! purple_savedstatus_has_substatuses ( saved_status ))) index = get_statusbox_index ( status_box , saved_status ); path = gtk_tree_path_new_from_indices ( index , -1 ); PidginStatusBoxItemType type ; /* If this saved status is in the list store, then set it as the active item */ if ( gtk_tree_model_get_iter_first ( GTK_TREE_MODEL ( status_box -> dropdown_store ), & iter )) gtk_tree_model_get ( GTK_TREE_MODEL ( status_box -> dropdown_store ), & iter , /* This is a special case because Primitives for the token_status_account are actually * saved statuses with substatuses for the enabled accounts */ if ( status_box -> token_status_account && purple_savedstatus_is_transient ( saved_status ) && type == PIDGIN_STATUS_BOX_TYPE_PRIMITIVE && primitive == GPOINTER_TO_INT ( data )) const char * acct_status_name = purple_status_get_name ( purple_account_get_active_status ( status_box -> token_status_account )); gtk_tree_model_get ( GTK_TREE_MODEL ( status_box -> dropdown_store ), & iter , if ( ! purple_savedstatus_has_substatuses ( saved_status ) || ! strcmp ( name , acct_status_name )) path = gtk_tree_model_get_path ( GTK_TREE_MODEL ( status_box -> dropdown_store ), & iter ); } else if (( type == PIDGIN_STATUS_BOX_TYPE_POPULAR ) && ( GPOINTER_TO_INT ( data ) == purple_savedstatus_get_creation_time ( saved_status ))) path = gtk_tree_model_get_path ( GTK_TREE_MODEL ( status_box -> dropdown_store ), & iter ); } while ( gtk_tree_model_iter_next ( GTK_TREE_MODEL ( status_box -> dropdown_store ), & iter )); if ( status_box -> active_row ) gtk_tree_row_reference_free ( status_box -> active_row ); if ( path ) { /* path should never be NULL */ status_box -> active_row = gtk_tree_row_reference_new ( GTK_TREE_MODEL ( status_box -> dropdown_store ), path ); gtk_tree_path_free ( path ); status_box -> active_row = NULL ; message = purple_savedstatus_get_message ( saved_status ); * If we are going to hide the imhtml, don't retain the * message because showing the old message later is * confusing. If we are going to set the message to a pre-set, * then we need to do this anyway * Suppress the "changed" signal because the status * was changed programmatically. gtk_widget_set_sensitive ( GTK_WIDGET ( status_box -> imhtml ), FALSE ); gtk_imhtml_clear ( GTK_IMHTML ( status_box -> imhtml )); gtk_imhtml_clear_formatting ( GTK_IMHTML ( status_box -> imhtml )); if ( ! purple_savedstatus_is_transient ( saved_status ) || ! message || !* message ) status_box -> imhtml_visible = FALSE ; gtk_widget_hide_all ( status_box -> vbox ); status_box -> imhtml_visible = TRUE ; gtk_widget_show_all ( status_box -> vbox ); gtk_imhtml_append_text ( GTK_IMHTML ( status_box -> imhtml ), message , 0 ); gtk_widget_set_sensitive ( GTK_WIDGET ( status_box -> imhtml ), TRUE ); /* Stop suppressing the "changed" signal. */ gtk_widget_set_sensitive ( GTK_WIDGET ( status_box ), TRUE ); add_popular_statuses ( PidginStatusBox * statusbox ) list = purple_savedstatuses_get_popular ( 6 ); /* Odd... oh well, nothing we can do about it. */ pidgin_status_box_add_separator ( statusbox ); for ( cur = list ; cur != NULL ; cur = cur -> next ) PurpleSavedStatus * saved = cur -> data ; PidginStatusBoxItemType type ; if ( purple_savedstatus_is_transient ( saved )) * Transient statuses do not have a title, so the savedstatus * API returns the message when purple_savedstatus_get_title() is * called, so we don't need to get the message a second time. type = PIDGIN_STATUS_BOX_TYPE_POPULAR ; type = PIDGIN_STATUS_BOX_TYPE_SAVED_POPULAR ; message = purple_savedstatus_get_message ( saved ); stripped = purple_markup_strip_html ( message ); purple_util_chrreplace ( stripped , '\n' , ' ' ); pidgin_status_box_add ( statusbox , type , NULL , purple_savedstatus_get_title ( saved ), stripped , GINT_TO_POINTER ( purple_savedstatus_get_creation_time ( saved ))); /* This returns NULL if the active accounts don't have identical * statuses and a token account if they do */ static PurpleAccount * check_active_accounts_for_identical_statuses ( void ) GList * iter , * active_accts = purple_accounts_get_all_active (); PurpleAccount * acct1 = NULL ; const char * prpl1 = NULL ; acct1 = active_accts -> data ; prpl1 = purple_account_get_protocol_id ( acct1 ); /* there's no enabled account */ /* start at the second account */ for ( iter = active_accts -> next ; iter ; iter = iter -> next ) { PurpleAccount * acct2 = iter -> data ; if ( ! g_str_equal ( prpl1 , purple_account_get_protocol_id ( acct2 ))) { for ( s1 = purple_account_get_status_types ( acct1 ), s2 = purple_account_get_status_types ( acct2 ); s1 && s2 ; s1 = s1 -> next , s2 = s2 -> next ) { PurpleStatusType * st1 = s1 -> data , * st2 = s2 -> data ; /* TODO: Are these enough to consider the statuses identical? */ if ( purple_status_type_get_primitive ( st1 ) != purple_status_type_get_primitive ( st2 ) || strcmp ( purple_status_type_get_id ( st1 ), purple_status_type_get_id ( st2 )) || strcmp ( purple_status_type_get_name ( st1 ), purple_status_type_get_name ( st2 ))) { if ( s1 != s2 ) { /* Will both be NULL if matched */ g_list_free ( active_accts ); add_account_statuses ( PidginStatusBox * status_box , PurpleAccount * account ) for ( l = purple_account_get_status_types ( account ); l != NULL ; l = l -> next ) PurpleStatusType * status_type = ( PurpleStatusType * ) l -> data ; PurpleStatusPrimitive prim ; if ( ! purple_status_type_is_user_settable ( status_type ) || purple_status_type_is_independent ( status_type )) prim = purple_status_type_get_primitive ( status_type ); pidgin_status_box_add ( PIDGIN_STATUS_BOX ( status_box ), PIDGIN_STATUS_BOX_TYPE_PRIMITIVE , NULL , purple_status_type_get_name ( status_type ), pidgin_status_box_regenerate ( PidginStatusBox * status_box , gboolean status_changed ) /* Unset the model while clearing it */ gtk_tree_view_set_model ( GTK_TREE_VIEW ( status_box -> tree_view ), NULL ); gtk_list_store_clear ( status_box -> dropdown_store ); /* Don't set the model until the new statuses have been added to the box. * What is presumably a bug in Gtk < 2.4 causes things to get all confused /* gtk_combo_box_set_model(GTK_COMBO_BOX(status_box), GTK_TREE_MODEL(status_box->dropdown_store)); */ if ( status_box -> account == NULL ) /* Do all the currently enabled accounts have the same statuses? * If so, display them instead of our global list. if ( status_box -> token_status_account ) { add_account_statuses ( status_box , status_box -> token_status_account ); pidgin_status_box_add ( PIDGIN_STATUS_BOX ( status_box ), PIDGIN_STATUS_BOX_TYPE_PRIMITIVE , NULL , _ ( "Available" ), NULL , GINT_TO_POINTER ( PURPLE_STATUS_AVAILABLE )); pidgin_status_box_add ( PIDGIN_STATUS_BOX ( status_box ), PIDGIN_STATUS_BOX_TYPE_PRIMITIVE , NULL , _ ( "Away" ), NULL , GINT_TO_POINTER ( PURPLE_STATUS_AWAY )); pidgin_status_box_add ( PIDGIN_STATUS_BOX ( status_box ), PIDGIN_STATUS_BOX_TYPE_PRIMITIVE , NULL , _ ( "Do not disturb" ), NULL , GINT_TO_POINTER ( PURPLE_STATUS_UNAVAILABLE )); pidgin_status_box_add ( PIDGIN_STATUS_BOX ( status_box ), PIDGIN_STATUS_BOX_TYPE_PRIMITIVE , NULL , _ ( "Invisible" ), NULL , GINT_TO_POINTER ( PURPLE_STATUS_INVISIBLE )); pidgin_status_box_add ( PIDGIN_STATUS_BOX ( status_box ), PIDGIN_STATUS_BOX_TYPE_PRIMITIVE , NULL , _ ( "Offline" ), NULL , GINT_TO_POINTER ( PURPLE_STATUS_OFFLINE )); add_popular_statuses ( status_box ); pidgin_status_box_add_separator ( PIDGIN_STATUS_BOX ( status_box )); pidgin_status_box_add ( PIDGIN_STATUS_BOX ( status_box ), PIDGIN_STATUS_BOX_TYPE_CUSTOM , NULL , _ ( "New status..." ), NULL , NULL ); pidgin_status_box_add ( PIDGIN_STATUS_BOX ( status_box ), PIDGIN_STATUS_BOX_TYPE_SAVED , NULL , _ ( "Saved statuses..." ), NULL , NULL ); status_menu_refresh_iter ( status_box , status_changed ); pidgin_status_box_refresh ( status_box ); add_account_statuses ( status_box , status_box -> account ); update_to_reflect_account_status ( status_box , status_box -> account , purple_account_get_active_status ( status_box -> account )); gtk_tree_view_set_model ( GTK_TREE_VIEW ( status_box -> tree_view ), GTK_TREE_MODEL ( status_box -> dropdown_store )); gtk_tree_view_set_search_column ( GTK_TREE_VIEW ( status_box -> tree_view ), TEXT_COLUMN ); static gboolean combo_box_scroll_event_cb ( GtkWidget * w , GdkEventScroll * event , GtkIMHtml * imhtml ) pidgin_status_box_popup ( PIDGIN_STATUS_BOX ( w )); static gboolean imhtml_scroll_event_cb ( GtkWidget * w , GdkEventScroll * event , GtkIMHtml * imhtml ) if ( event -> direction == GDK_SCROLL_UP ) gtk_imhtml_page_up ( imhtml ); else if ( event -> direction == GDK_SCROLL_DOWN ) gtk_imhtml_page_down ( imhtml ); static gboolean imhtml_remove_focus ( GtkWidget * w , GdkEventKey * event , PidginStatusBox * status_box ) if ( event -> keyval == GDK_Tab || event -> keyval == GDK_KP_Tab || event -> keyval == GDK_ISO_Left_Tab ) /* If last inserted character is a tab, then remove the focus from here */ GtkWidget * top = gtk_widget_get_toplevel ( w ); g_signal_emit_by_name ( G_OBJECT ( top ), "move_focus" , ( event -> state & GDK_SHIFT_MASK ) ? GTK_DIR_TAB_BACKWARD : GTK_DIR_TAB_FORWARD ); if ( status_box -> typing == 0 ) /* Reset the status if Escape was pressed */ if ( event -> keyval == GDK_Escape ) purple_timeout_remove ( status_box -> typing ); gtk_imhtml_set_populate_primary_clipboard ( GTK_IMHTML ( status_box -> imhtml ), TRUE ); if ( status_box -> account != NULL ) update_to_reflect_account_status ( status_box , status_box -> account , purple_account_get_active_status ( status_box -> account )); status_menu_refresh_iter ( status_box , TRUE ); pidgin_status_box_refresh ( status_box ); pidgin_status_box_pulse_typing ( status_box ); purple_timeout_remove ( status_box -> typing ); status_box -> typing = purple_timeout_add_seconds ( TYPING_TIMEOUT , ( GSourceFunc ) remove_typing_cb , status_box ); dropdown_store_row_separator_func ( GtkTreeModel * model , GtkTreeIter * iter , gpointer data ) PidginStatusBoxItemType type ; gtk_tree_model_get ( model , iter , TYPE_COLUMN , & type , -1 ); if ( type == PIDGIN_STATUS_BOX_TYPE_SEPARATOR ) cache_pixbufs ( PidginStatusBox * status_box ) g_object_set ( G_OBJECT ( status_box -> icon_rend ), "xpad" , 3 , NULL ); icon_size = gtk_icon_size_from_name ( PIDGIN_ICON_SIZE_TANGO_EXTRA_SMALL ); for ( i = 0 ; i < G_N_ELEMENTS ( status_box -> connecting_pixbufs ); i ++ ) { if ( status_box -> connecting_pixbufs [ i ] != NULL ) g_object_unref ( G_OBJECT ( status_box -> connecting_pixbufs [ i ])); if ( connecting_stock_ids [ i ]) status_box -> connecting_pixbufs [ i ] = gtk_widget_render_icon ( GTK_WIDGET ( status_box -> vbox ), connecting_stock_ids [ i ], icon_size , "PidginStatusBox" ); status_box -> connecting_pixbufs [ i ] = NULL ; status_box -> connecting_index = 0 ; for ( i = 0 ; i < G_N_ELEMENTS ( status_box -> typing_pixbufs ); i ++ ) { if ( status_box -> typing_pixbufs [ i ] != NULL ) g_object_unref ( G_OBJECT ( status_box -> typing_pixbufs [ i ])); status_box -> typing_pixbufs [ i ] = gtk_widget_render_icon ( GTK_WIDGET ( status_box -> vbox ), typing_stock_ids [ i ], icon_size , "PidginStatusBox" ); status_box -> typing_pixbufs [ i ] = NULL ; status_box -> typing_index = 0 ; static void account_enabled_cb ( PurpleAccount * acct , PidginStatusBox * status_box ) PurpleAccount * initial_token_acct = status_box -> token_status_account ; status_box -> token_status_account = check_active_accounts_for_identical_statuses (); /* Regenerate the list if it has changed */ if ( initial_token_acct != status_box -> token_status_account ) { pidgin_status_box_regenerate ( status_box , TRUE ); current_savedstatus_changed_cb ( PurpleSavedStatus * now , PurpleSavedStatus * old , PidginStatusBox * status_box ) /* Make sure our current status is added to the list of popular statuses */ pidgin_status_box_regenerate ( status_box , TRUE ); saved_status_updated_cb ( PurpleSavedStatus * status , PidginStatusBox * status_box ) pidgin_status_box_regenerate ( status_box , purple_savedstatus_get_current () == status ); spellcheck_prefs_cb ( const char * name , PurplePrefType type , gconstpointer value , gpointer data ) PidginStatusBox * status_box = ( PidginStatusBox * ) data ; pidgin_setup_gtkspell ( GTK_TEXT_VIEW ( status_box -> imhtml )); spell = gtkspell_get_from_text_view ( GTK_TEXT_VIEW ( status_box -> imhtml )); static gboolean button_released_cb(GtkWidget *widget, GdkEventButton *event, PidginStatusBox *box) gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(box->toggle_button), FALSE); if (!box->imhtml_visible) g_signal_emit_by_name(G_OBJECT(box), "changed", NULL, NULL); static gboolean button_pressed_cb(GtkWidget *widget, GdkEventButton *event, PidginStatusBox *box) gtk_combo_box_popup(GTK_COMBO_BOX(box)); /* Disabled until button_released_cb works */ gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(box->toggle_button), TRUE); pidgin_status_box_list_position ( PidginStatusBox * status_box , int * x , int * y , int * width , int * height ) GtkRequisition popup_req ; GtkPolicyType hpolicy , vpolicy ; gdk_window_get_origin ( GTK_WIDGET ( status_box ) -> window , x , y ); * x += GTK_WIDGET ( status_box ) -> allocation . x ; * y += GTK_WIDGET ( status_box ) -> allocation . y ; * width = GTK_WIDGET ( status_box ) -> allocation . width ; hpolicy = vpolicy = GTK_POLICY_NEVER ; g_object_set ( G_OBJECT ( status_box -> scrolled_window ), "hscrollbar-policy" , hpolicy , "vscrollbar-policy" , vpolicy , gtk_widget_size_request ( status_box -> popup_frame , & popup_req ); if ( popup_req . width > * width ) hpolicy = GTK_POLICY_ALWAYS ; g_object_set ( G_OBJECT ( status_box -> scrolled_window ), "hscrollbar-policy" , hpolicy , "vscrollbar-policy" , vpolicy , gtk_widget_size_request ( status_box -> popup_frame , & popup_req ); * height = popup_req . height ; screen = gtk_widget_get_screen ( GTK_WIDGET ( status_box )); monitor_num = gdk_screen_get_monitor_at_window ( screen , GTK_WIDGET ( status_box ) -> window ); gdk_screen_get_monitor_geometry ( screen , monitor_num , & monitor ); else if ( * x + * width > monitor . x + monitor . width ) * x = monitor . x + monitor . width - * width ; if ( * y + GTK_WIDGET ( status_box ) -> allocation . height + * height <= monitor . y + monitor . height ) * y += GTK_WIDGET ( status_box ) -> allocation . height ; else if ( * y - * height >= monitor . y ) else if ( monitor . y + monitor . height - ( * y + GTK_WIDGET ( status_box ) -> allocation . height ) > * y - monitor . y ) * y += GTK_WIDGET ( status_box ) -> allocation . height ; * height = monitor . y + monitor . height - * y ; * height = * y - monitor . y ; if ( popup_req . height > * height ) vpolicy = GTK_POLICY_ALWAYS ; g_object_set ( G_OBJECT ( status_box -> scrolled_window ), "hscrollbar-policy" , hpolicy , "vscrollbar-policy" , vpolicy , popup_grab_on_window ( GdkWindow * window , if (( gdk_pointer_grab ( window , TRUE , GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK | NULL , NULL , activate_time ) == 0 )) if ( ! grab_keyboard || gdk_keyboard_grab ( window , TRUE , activate_time ) == 0 ) gdk_display_pointer_ungrab ( gdk_drawable_get_display ( window ), activate_time ); pidgin_status_box_popup ( PidginStatusBox * box ) pidgin_status_box_list_position ( box , & x , & y , & width , & height ); gtk_widget_set_size_request ( box -> popup_window , width , height ); gtk_window_move ( GTK_WINDOW ( box -> popup_window ), x , y ); gtk_widget_show ( box -> popup_window ); gtk_widget_grab_focus ( box -> tree_view ); if ( ! popup_grab_on_window ( box -> popup_window -> window , GDK_CURRENT_TIME , TRUE )) { gtk_widget_hide ( box -> popup_window ); gtk_grab_add ( box -> popup_window ); /*box->popup_in_progress = TRUE;*/ gtk_toggle_button_set_active ( GTK_TOGGLE_BUTTON ( box -> toggle_button ), GtkTreePath * path = gtk_tree_row_reference_get_path ( box -> active_row ); gtk_tree_view_set_cursor ( GTK_TREE_VIEW ( box -> tree_view ), path , NULL , FALSE ); gtk_tree_path_free ( path ); pidgin_status_box_popdown ( PidginStatusBox * box ) gtk_widget_hide ( box -> popup_window ); box -> popup_in_progress = FALSE ; gtk_toggle_button_set_active ( GTK_TOGGLE_BUTTON ( box -> toggle_button ), gtk_grab_remove ( box -> popup_window ); toggle_key_press_cb ( GtkWidget * widget , GdkEventKey * event , PidginStatusBox * box ) if ( ! box -> popup_in_progress ) { pidgin_status_box_popup ( box ); box -> popup_in_progress = TRUE ; pidgin_status_box_popdown ( box ); toggled_cb ( GtkWidget * widget , GdkEventButton * event , PidginStatusBox * box ) if ( ! box -> popup_in_progress ) pidgin_status_box_popup ( box ); pidgin_status_box_popdown ( box ); buddy_icon_set_cb ( const char * filename , PidginStatusBox * box ) PurpleStoredImage * img = NULL ; PurplePlugin * plug = purple_find_prpl ( purple_account_get_protocol_id ( box -> account )); PurplePluginProtocolInfo * prplinfo = PURPLE_PLUGIN_PROTOCOL_INFO ( plug ); if ( prplinfo && prplinfo -> icon_spec . format ) { data = pidgin_convert_buddy_icon ( plug , filename , & len ); img = purple_buddy_icons_set_account_icon ( box -> account , data , len ); * set_account_icon doesn't give us a reference, but we * unref one below (for the other code path) purple_imgstore_ref ( img ); purple_account_set_buddy_icon_path ( box -> account , filename ); purple_account_set_bool ( box -> account , "use-global-buddyicon" , ( filename != NULL )); for ( accounts = purple_accounts_get_all (); accounts != NULL ; accounts = accounts -> next ) { PurpleAccount * account = accounts -> data ; PurplePlugin * plug = purple_find_prpl ( purple_account_get_protocol_id ( account )); PurplePluginProtocolInfo * prplinfo = PURPLE_PLUGIN_PROTOCOL_INFO ( plug ); purple_account_get_bool ( account , "use-global-buddyicon" , TRUE ) && prplinfo -> icon_spec . format ) { data = pidgin_convert_buddy_icon ( plug , filename , & len ); purple_buddy_icons_set_account_icon ( account , data , len ); purple_account_set_buddy_icon_path ( account , filename ); /* Even if no accounts were processed, load the icon that was set. */ img = purple_imgstore_new_from_file ( filename ); pidgin_status_box_set_buddy_icon ( box , img ); purple_imgstore_unref ( img ); remove_buddy_icon_cb ( GtkWidget * w , PidginStatusBox * box ) if ( box -> account == NULL ) /* The pref-connect callback does the actual work */ purple_prefs_set_path ( PIDGIN_PREFS_ROOT "/accounts/buddyicon" , NULL ); buddy_icon_set_cb ( NULL , box ); gtk_widget_destroy ( box -> icon_box_menu ); box -> icon_box_menu = NULL ; choose_buddy_icon_cb ( GtkWidget * w , PidginStatusBox * box ) if ( box -> buddy_icon_sel ) { gtk_window_present ( GTK_WINDOW ( box -> buddy_icon_sel )); box -> buddy_icon_sel = pidgin_buddy_icon_chooser_new ( GTK_WINDOW ( gtk_widget_get_toplevel ( w )), icon_choose_cb , box ); gtk_widget_show_all ( box -> buddy_icon_sel ); icon_choose_cb ( const char * filename , gpointer data ) PidginStatusBox * box = data ; if ( box -> account == NULL ) /* The pref-connect callback does the actual work */ purple_prefs_set_path ( PIDGIN_PREFS_ROOT "/accounts/buddyicon" , filename ); buddy_icon_set_cb ( filename , box ); box -> buddy_icon_sel = NULL ; update_buddyicon_cb ( const char * name , PurplePrefType type , gconstpointer value , gpointer data ) buddy_icon_set_cb ( value , ( PidginStatusBox * ) data ); treeview_activate_current_selection ( PidginStatusBox * status_box , GtkTreePath * path ) if ( status_box -> active_row ) gtk_tree_row_reference_free ( status_box -> active_row ); status_box -> active_row = gtk_tree_row_reference_new ( GTK_TREE_MODEL ( status_box -> dropdown_store ), path ); pidgin_status_box_popdown ( status_box ); pidgin_status_box_changed ( status_box ); static void tree_view_delete_current_selection_cb ( gpointer data ) PurpleSavedStatus * saved ; saved = purple_savedstatus_find_by_creation_time ( GPOINTER_TO_INT ( data )); g_return_if_fail ( saved != NULL ); if ( purple_savedstatus_get_current () != saved ) purple_savedstatus_delete_by_status ( saved ); tree_view_delete_current_selection ( PidginStatusBox * status_box , GtkTreePath * path ) PurpleSavedStatus * saved ; if ( status_box -> active_row ) { /* don't delete active status */ if ( gtk_tree_path_compare ( path , gtk_tree_row_reference_get_path ( status_box -> active_row )) == 0 ) if ( ! gtk_tree_model_get_iter ( GTK_TREE_MODEL ( status_box -> dropdown_store ), & iter , path )) gtk_tree_model_get ( GTK_TREE_MODEL ( status_box -> dropdown_store ), & iter , saved = purple_savedstatus_find_by_creation_time ( GPOINTER_TO_INT ( data )); g_return_if_fail ( saved != NULL ); if ( saved == purple_savedstatus_get_current ()) msg = g_strdup_printf ( _ ( "Are you sure you want to delete %s?" ), purple_savedstatus_get_title ( saved )); purple_request_action ( saved , NULL , msg , NULL , 0 , _ ( "Delete" ), tree_view_delete_current_selection_cb , pidgin_status_box_popdown ( status_box ); treeview_button_release_cb ( GtkWidget * widget , GdkEventButton * event , PidginStatusBox * status_box ) GtkTreePath * path = NULL ; GtkWidget * ewidget = gtk_get_event_widget (( GdkEvent * ) event ); if ( ewidget != status_box -> tree_view ) { if ( ewidget == status_box -> toggle_button && status_box -> popup_in_progress && gtk_toggle_button_get_active ( GTK_TOGGLE_BUTTON ( status_box -> toggle_button ))) { pidgin_status_box_popdown ( status_box ); } else if ( ewidget == status_box -> toggle_button ) { status_box -> popup_in_progress = TRUE ; /* released outside treeview */ if ( ewidget != status_box -> toggle_button ) { pidgin_status_box_popdown ( status_box ); ret = gtk_tree_view_get_path_at_pos ( GTK_TREE_VIEW ( status_box -> tree_view ), return TRUE ; /* clicked outside window? */ treeview_activate_current_selection ( status_box , path ); gtk_tree_path_free ( path ); treeview_key_press_event ( GtkWidget * widget , GdkEventKey * event , PidginStatusBox * box ) if ( box -> popup_in_progress ) { if ( event -> keyval == GDK_Escape ) { pidgin_status_box_popdown ( box ); GtkTreeSelection * sel = gtk_tree_view_get_selection ( GTK_TREE_VIEW ( box -> tree_view )); if ( gtk_tree_selection_get_selected ( sel , NULL , & iter )) { path = gtk_tree_model_get_path ( GTK_TREE_MODEL ( box -> dropdown_store ), & iter ); if ( event -> keyval == GDK_Return ) { treeview_activate_current_selection ( box , path ); } else if ( event -> keyval == GDK_Delete ) { tree_view_delete_current_selection ( box , path ); gtk_tree_path_free ( path ); imhtml_cursor_moved_cb ( gpointer data , GtkMovementStep step , gint count , gboolean extend , /* Restart the typing timeout if arrow keys are pressed while editing the message */ PidginStatusBox * status_box = data ; if ( status_box -> typing == 0 ) imhtml_changed_cb ( NULL , status_box ); treeview_cursor_changed_cb ( GtkTreeView * treeview , gpointer data ) GtkTreeSelection * sel = gtk_tree_view_get_selection ( treeview ); GtkTreeModel * model = GTK_TREE_MODEL ( data ); if ( gtk_tree_selection_get_selected ( sel , NULL , & iter )) { if (( selection = gtk_tree_model_get_path ( model , & iter )) == NULL ) { /* Shouldn't happen, but ignore anyway */ /* I don't think this can happen, but we'll just ignore it */ gtk_tree_view_get_cursor ( treeview , & cursor , NULL ); /* Probably won't happen in a 'cursor-changed' event? */ gtk_tree_path_free ( selection ); cmp = gtk_tree_path_compare ( cursor , selection ); /* The cursor moved up without moving the selection, so move it up again */ gtk_tree_path_prev ( cursor ); gtk_tree_view_set_cursor ( treeview , cursor , NULL , FALSE ); /* The cursor moved down without moving the selection, so move it down again */ gtk_tree_path_next ( cursor ); gtk_tree_view_set_cursor ( treeview , cursor , NULL , FALSE ); gtk_tree_path_free ( selection ); gtk_tree_path_free ( cursor ); pidgin_status_box_init ( PidginStatusBox * status_box ) GtkCellRenderer * text_rend ; GtkCellRenderer * icon_rend ; GtkCellRenderer * emblem_rend ; GTK_WIDGET_SET_FLAGS ( status_box , GTK_NO_WINDOW ); status_box -> imhtml_visible = FALSE ; status_box -> network_available = purple_network_is_available (); status_box -> connecting = FALSE ; status_box -> toggle_button = gtk_toggle_button_new (); status_box -> hbox = gtk_hbox_new ( FALSE , 6 ); status_box -> cell_view = gtk_cell_view_new (); status_box -> vsep = gtk_vseparator_new (); status_box -> arrow = gtk_arrow_new ( GTK_ARROW_DOWN , GTK_SHADOW_NONE ); status_box -> store = gtk_list_store_new ( NUM_COLUMNS , G_TYPE_INT , G_TYPE_STRING , GDK_TYPE_PIXBUF , G_TYPE_STRING , G_TYPE_STRING , G_TYPE_STRING , G_TYPE_POINTER , GDK_TYPE_PIXBUF , G_TYPE_BOOLEAN ); status_box -> dropdown_store = gtk_list_store_new ( NUM_COLUMNS , G_TYPE_INT , G_TYPE_STRING , GDK_TYPE_PIXBUF , G_TYPE_STRING , G_TYPE_STRING , G_TYPE_STRING , G_TYPE_POINTER , G_TYPE_STRING , G_TYPE_BOOLEAN ); gtk_cell_view_set_model ( GTK_CELL_VIEW ( status_box -> cell_view ), GTK_TREE_MODEL ( status_box -> store )); gtk_list_store_append ( status_box -> store , & ( status_box -> iter )); atk_object_set_name ( gtk_widget_get_accessible ( status_box -> toggle_button ), _ ( "Status Selector" )); gtk_container_add ( GTK_CONTAINER ( status_box -> toggle_button ), status_box -> hbox ); gtk_box_pack_start ( GTK_BOX ( status_box -> hbox ), status_box -> cell_view , TRUE , TRUE , 0 ); gtk_box_pack_start ( GTK_BOX ( status_box -> hbox ), status_box -> vsep , FALSE , FALSE , 0 ); gtk_box_pack_start ( GTK_BOX ( status_box -> hbox ), status_box -> arrow , FALSE , FALSE , 0 ); gtk_widget_show_all ( status_box -> toggle_button ); gtk_button_set_focus_on_click ( GTK_BUTTON ( status_box -> toggle_button ), FALSE ); text_rend = gtk_cell_renderer_text_new (); icon_rend = gtk_cell_renderer_pixbuf_new (); emblem_rend = gtk_cell_renderer_pixbuf_new (); status_box -> popup_window = gtk_window_new ( GTK_WINDOW_POPUP ); toplevel = gtk_widget_get_toplevel ( GTK_WIDGET ( status_box )); if ( GTK_IS_WINDOW ( toplevel )) { gtk_window_set_transient_for ( GTK_WINDOW ( status_box -> popup_window ), gtk_window_set_resizable ( GTK_WINDOW ( status_box -> popup_window ), FALSE ); gtk_window_set_type_hint ( GTK_WINDOW ( status_box -> popup_window ), GDK_WINDOW_TYPE_HINT_POPUP_MENU ); gtk_window_set_screen ( GTK_WINDOW ( status_box -> popup_window ), gtk_widget_get_screen ( GTK_WIDGET ( status_box ))); status_box -> popup_frame = gtk_frame_new ( NULL ); gtk_frame_set_shadow_type ( GTK_FRAME ( status_box -> popup_frame ), gtk_container_add ( GTK_CONTAINER ( status_box -> popup_window ), status_box -> popup_frame ); gtk_widget_show ( status_box -> popup_frame ); status_box -> tree_view = gtk_tree_view_new (); sel = gtk_tree_view_get_selection ( GTK_TREE_VIEW ( status_box -> tree_view )); gtk_tree_selection_set_mode ( sel , GTK_SELECTION_BROWSE ); gtk_tree_view_set_headers_visible ( GTK_TREE_VIEW ( status_box -> tree_view ), gtk_tree_view_set_hover_selection ( GTK_TREE_VIEW ( status_box -> tree_view ), gtk_tree_view_set_model ( GTK_TREE_VIEW ( status_box -> tree_view ), GTK_TREE_MODEL ( status_box -> dropdown_store )); status_box -> column = gtk_tree_view_column_new (); gtk_tree_view_append_column ( GTK_TREE_VIEW ( status_box -> tree_view ), gtk_tree_view_column_pack_start ( status_box -> column , icon_rend , FALSE ); gtk_tree_view_column_pack_start ( status_box -> column , text_rend , TRUE ); gtk_tree_view_column_pack_start ( status_box -> column , emblem_rend , FALSE ); gtk_tree_view_column_set_attributes ( status_box -> column , icon_rend , "stock-id" , ICON_STOCK_COLUMN , NULL ); gtk_tree_view_column_set_attributes ( status_box -> column , text_rend , "markup" , TEXT_COLUMN , NULL ); gtk_tree_view_column_set_attributes ( status_box -> column , emblem_rend , "stock-id" , EMBLEM_COLUMN , "visible" , EMBLEM_VISIBLE_COLUMN , NULL ); status_box -> scrolled_window = pidgin_make_scrollable ( status_box -> tree_view , GTK_POLICY_NEVER , GTK_POLICY_NEVER , GTK_SHADOW_NONE , -1 , -1 ); gtk_container_add ( GTK_CONTAINER ( status_box -> popup_frame ), status_box -> scrolled_window ); gtk_widget_show ( status_box -> tree_view ); gtk_tree_view_set_search_column ( GTK_TREE_VIEW ( status_box -> tree_view ), TEXT_COLUMN ); gtk_tree_view_set_search_equal_func ( GTK_TREE_VIEW ( status_box -> tree_view ), pidgin_tree_view_search_equal_func , NULL , NULL ); g_object_set ( text_rend , "ellipsize" , PANGO_ELLIPSIZE_END , NULL ); status_box -> icon_rend = gtk_cell_renderer_pixbuf_new (); status_box -> text_rend = gtk_cell_renderer_text_new (); emblem_rend = gtk_cell_renderer_pixbuf_new (); gtk_cell_layout_pack_start ( GTK_CELL_LAYOUT ( status_box -> cell_view ), status_box -> icon_rend , FALSE ); gtk_cell_layout_pack_start ( GTK_CELL_LAYOUT ( status_box -> cell_view ), status_box -> text_rend , TRUE ); gtk_cell_layout_pack_start ( GTK_CELL_LAYOUT ( status_box -> cell_view ), emblem_rend , FALSE ); gtk_cell_layout_set_attributes ( GTK_CELL_LAYOUT ( status_box -> cell_view ), status_box -> icon_rend , "stock-id" , ICON_STOCK_COLUMN , NULL ); gtk_cell_layout_set_attributes ( GTK_CELL_LAYOUT ( status_box -> cell_view ), status_box -> text_rend , "markup" , TEXT_COLUMN , NULL ); gtk_cell_layout_set_attributes ( GTK_CELL_LAYOUT ( status_box -> cell_view ), emblem_rend , "pixbuf" , EMBLEM_COLUMN , "visible" , EMBLEM_VISIBLE_COLUMN , NULL ); g_object_set ( status_box -> text_rend , "ellipsize" , PANGO_ELLIPSIZE_END , NULL ); status_box -> vbox = gtk_vbox_new ( 0 , FALSE ); status_box -> sw = pidgin_create_imhtml ( FALSE , & status_box -> imhtml , NULL , NULL ); gtk_imhtml_set_editable ( GTK_IMHTML ( status_box -> imhtml ), TRUE ); buffer = gtk_text_view_get_buffer ( GTK_TEXT_VIEW ( status_box -> imhtml )); g_signal_connect(G_OBJECT(status_box->toggle_button), "button-press-event", G_CALLBACK(button_pressed_cb), status_box); g_signal_connect(G_OBJECT(status_box->toggle_button), "button-release-event", G_CALLBACK(button_released_cb), status_box); g_signal_connect ( G_OBJECT ( status_box -> toggle_button ), "key-press-event" , G_CALLBACK ( toggle_key_press_cb ), status_box ); g_signal_connect ( G_OBJECT ( status_box -> toggle_button ), "button-press-event" , G_CALLBACK ( toggled_cb ), status_box ); g_signal_connect ( G_OBJECT ( buffer ), "changed" , G_CALLBACK ( imhtml_changed_cb ), status_box ); g_signal_connect ( G_OBJECT ( status_box -> imhtml ), "format_function_toggle" , G_CALLBACK ( imhtml_format_changed_cb ), status_box ); g_signal_connect_swapped ( G_OBJECT ( status_box -> imhtml ), "move_cursor" , G_CALLBACK ( imhtml_cursor_moved_cb ), status_box ); g_signal_connect ( G_OBJECT ( status_box -> imhtml ), "key_press_event" , G_CALLBACK ( imhtml_remove_focus ), status_box ); g_signal_connect_swapped ( G_OBJECT ( status_box -> imhtml ), "message_send" , G_CALLBACK ( remove_typing_cb ), status_box ); if ( purple_prefs_get_bool ( PIDGIN_PREFS_ROOT "/conversations/spellcheck" )) pidgin_setup_gtkspell ( GTK_TEXT_VIEW ( status_box -> imhtml )); gtk_widget_set_parent ( status_box -> vbox , GTK_WIDGET ( status_box )); gtk_widget_show_all ( status_box -> vbox ); gtk_widget_set_parent ( status_box -> toggle_button , GTK_WIDGET ( status_box )); gtk_box_pack_start ( GTK_BOX ( status_box -> vbox ), status_box -> sw , TRUE , TRUE , 0 ); g_signal_connect ( G_OBJECT ( status_box ), "scroll_event" , G_CALLBACK ( combo_box_scroll_event_cb ), NULL ); g_signal_connect ( G_OBJECT ( status_box -> imhtml ), "scroll_event" , G_CALLBACK ( imhtml_scroll_event_cb ), status_box -> imhtml ); g_signal_connect ( G_OBJECT ( status_box -> popup_window ), "button_release_event" , G_CALLBACK ( treeview_button_release_cb ), status_box ); g_signal_connect ( G_OBJECT ( status_box -> popup_window ), "key_press_event" , G_CALLBACK ( treeview_key_press_event ), status_box ); g_signal_connect ( G_OBJECT ( status_box -> tree_view ), "cursor-changed" , G_CALLBACK ( treeview_cursor_changed_cb ), status_box -> dropdown_store ); gtk_tree_view_set_row_separator_func ( GTK_TREE_VIEW ( status_box -> tree_view ), dropdown_store_row_separator_func , NULL , NULL ); status_box -> token_status_account = check_active_accounts_for_identical_statuses (); cache_pixbufs ( status_box ); pidgin_status_box_regenerate ( status_box , TRUE ); purple_signal_connect ( purple_savedstatuses_get_handle (), "savedstatus-changed" , PURPLE_CALLBACK ( current_savedstatus_changed_cb ), purple_signal_connect ( purple_savedstatuses_get_handle (), "savedstatus-added" , status_box , PURPLE_CALLBACK ( saved_status_updated_cb ), status_box ); purple_signal_connect ( purple_savedstatuses_get_handle (), "savedstatus-deleted" , status_box , PURPLE_CALLBACK ( saved_status_updated_cb ), status_box ); purple_signal_connect ( purple_savedstatuses_get_handle (), "savedstatus-modified" , status_box , PURPLE_CALLBACK ( saved_status_updated_cb ), status_box ); purple_signal_connect ( purple_accounts_get_handle (), "account-enabled" , status_box , PURPLE_CALLBACK ( account_enabled_cb ), purple_signal_connect ( purple_accounts_get_handle (), "account-disabled" , status_box , PURPLE_CALLBACK ( account_enabled_cb ), purple_signal_connect ( purple_accounts_get_handle (), "account-status-changed" , status_box , PURPLE_CALLBACK ( account_status_changed_cb ), purple_prefs_connect_callback ( status_box , PIDGIN_PREFS_ROOT "/conversations/spellcheck" , spellcheck_prefs_cb , status_box ); purple_prefs_connect_callback ( status_box , PIDGIN_PREFS_ROOT "/accounts/buddyicon" , update_buddyicon_cb , status_box ); purple_signal_connect ( purple_get_core (), "uri-handler" , status_box , PURPLE_CALLBACK ( statusbox_uri_handler ), status_box ); pidgin_status_box_size_request ( GtkWidget * widget , GtkRequisition * requisition ) gint border_width = GTK_CONTAINER ( widget ) -> border_width ; gtk_widget_size_request ( PIDGIN_STATUS_BOX ( widget ) -> toggle_button , requisition ); /* Make this icon the same size as other buddy icons in the list; unless it already wants to be bigger */ requisition -> height = MAX ( requisition -> height , 34 ); requisition -> height += border_width * 2 ; /* If the gtkimhtml is visible, then add some additional padding */ gtk_widget_size_request ( PIDGIN_STATUS_BOX ( widget ) -> vbox , & box_req ); requisition -> height += box_req . height + border_width * 2 ; do_colorshift ( GdkPixbuf * dest , GdkPixbuf * src , int shift ) gint width , height , has_alpha , srcrowstride , destrowstride ; has_alpha = gdk_pixbuf_get_has_alpha ( src ); width = gdk_pixbuf_get_width ( src ); height = gdk_pixbuf_get_height ( src ); srcrowstride = gdk_pixbuf_get_rowstride ( src ); destrowstride = gdk_pixbuf_get_rowstride ( dest ); target_pixels = gdk_pixbuf_get_pixels ( dest ); original_pixels = gdk_pixbuf_get_pixels ( src ); for ( i = 0 ; i < height ; i ++ ) { pixdest = target_pixels + i * destrowstride ; pixsrc = original_pixels + i * srcrowstride ; for ( j = 0 ; j < width ; j ++ ) { * ( pixdest ++ ) = CLAMP ( val , 0 , 255 ); * ( pixdest ++ ) = CLAMP ( val , 0 , 255 ); * ( pixdest ++ ) = CLAMP ( val , 0 , 255 ); * ( pixdest ++ ) = * ( pixsrc ++ ); pidgin_status_box_size_allocate ( GtkWidget * widget , GtkAllocation * allocation ) PidginStatusBox * status_box = PIDGIN_STATUS_BOX ( widget ); GtkRequisition req = { 0 , 0 }; GtkAllocation parent_alc , box_alc , icon_alc ; gint border_width = GTK_CONTAINER ( widget ) -> border_width ; gtk_widget_size_request ( status_box -> toggle_button , & req ); /* Make this icon the same size as other buddy icons in the list; unless it already wants to be bigger */ req . height = MAX ( req . height , 34 ); req . height += border_width * 2 ; box_alc . width -= ( border_width * 2 ); box_alc . height = MAX ( 1 , (( allocation -> height - req . height ) - ( border_width * 2 ))); box_alc . x += border_width ; box_alc . y += req . height + border_width ; gtk_widget_size_allocate ( status_box -> vbox , & box_alc ); parent_alc = * allocation ; parent_alc . height = MAX ( 1 , req . height - ( border_width * 2 )); parent_alc . width -= ( border_width * 2 ); parent_alc . x += border_width ; parent_alc . y += border_width ; if ( status_box -> icon_box ) parent_alc . width -= ( parent_alc . height + border_width ); icon_alc . height = MAX ( 1 , icon_alc . height ) - 2 ; icon_alc . width = icon_alc . height ; icon_alc . x += allocation -> width - ( icon_alc . width + border_width + 1 ); if ( status_box -> icon_size != icon_alc . height ) status_box -> icon_size = icon_alc . height ; pidgin_status_box_redisplay_buddy_icon ( status_box ); gtk_widget_size_allocate ( status_box -> icon_box , & icon_alc ); gtk_widget_size_allocate ( status_box -> toggle_button , & parent_alc ); widget -> allocation = * allocation ; pidgin_status_box_expose_event ( GtkWidget * widget , PidginStatusBox * status_box = PIDGIN_STATUS_BOX ( widget ); gtk_container_propagate_expose ( GTK_CONTAINER ( widget ), status_box -> vbox , event ); gtk_container_propagate_expose ( GTK_CONTAINER ( widget ), status_box -> toggle_button , event ); if ( status_box -> icon_box && status_box -> icon_opaque ) { gtk_paint_box ( widget -> style , widget -> window , GTK_STATE_NORMAL , GTK_SHADOW_OUT , NULL , status_box -> icon_box , "button" , status_box -> icon_box -> allocation . x -1 , status_box -> icon_box -> allocation . y -1 , pidgin_status_box_forall ( GtkContainer * container , gboolean include_internals , PidginStatusBox * status_box = PIDGIN_STATUS_BOX ( container ); ( * callback ) ( status_box -> vbox , callback_data ); ( * callback ) ( status_box -> toggle_button , callback_data ); ( * callback ) ( status_box -> arrow , callback_data ); if ( status_box -> icon_box ) ( * callback ) ( status_box -> icon_box , callback_data ); return g_object_new ( PIDGIN_TYPE_STATUS_BOX , "account" , NULL , pidgin_status_box_new_with_account ( PurpleAccount * account ) return g_object_new ( PIDGIN_TYPE_STATUS_BOX , "account" , account , * Add a row to the dropdown menu. * @param status_box The status box itself. * @param type A PidginStatusBoxItemType. * @param pixbuf The icon to associate with this row in the menu. The * function will try to decide a pixbuf if none is given. * @param title The title of this item. For the primitive entries, * this is something like "Available" or "Away." For * the saved statuses, this is something like * "My favorite away message!" This should be * plaintext (non-markedup) (this function escapes it). * @param desc The secondary text for this item. This will be * placed on the row below the title, in a dimmer * font (generally gray). This text should be plaintext * (non-markedup) (this function escapes it). * @param data Data to be associated with this row in the dropdown * menu. For primitives this is the value of the * PurpleStatusPrimitive. For saved statuses this is the pidgin_status_box_add ( PidginStatusBox * status_box , PidginStatusBoxItemType type , GdkPixbuf * pixbuf , const char * title , const char * desc , gpointer data ) const char * stock = NULL ; text = g_markup_escape_text ( title , -1 ); gchar * escaped_title , * escaped_desc ; style = gtk_widget_get_style ( GTK_WIDGET ( status_box )); snprintf ( aa_color , sizeof ( aa_color ), "#%02x%02x%02x" , style -> text_aa [ GTK_STATE_NORMAL ]. red >> 8 , style -> text_aa [ GTK_STATE_NORMAL ]. green >> 8 , style -> text_aa [ GTK_STATE_NORMAL ]. blue >> 8 ); escaped_title = g_markup_escape_text ( title , -1 ); escaped_desc = g_markup_escape_text ( desc , -1 ); text = g_strdup_printf ( "%s - <span color= \" %s \" size= \" smaller \" >%s</span>" , PurpleStatusPrimitive prim = PURPLE_STATUS_UNSET ; if ( type == PIDGIN_STATUS_BOX_TYPE_PRIMITIVE ) { prim = GPOINTER_TO_INT ( data ); } else if ( type == PIDGIN_STATUS_BOX_TYPE_SAVED_POPULAR || type == PIDGIN_STATUS_BOX_TYPE_POPULAR ) { PurpleSavedStatus * saved = purple_savedstatus_find_by_creation_time ( GPOINTER_TO_INT ( data )); prim = purple_savedstatus_get_type ( saved ); stock = pidgin_stock_id_from_status_primitive ( prim ); gtk_list_store_append ( status_box -> dropdown_store , & iter ); gtk_list_store_set ( status_box -> dropdown_store , & iter , ICON_STOCK_COLUMN , stock , EMBLEM_VISIBLE_COLUMN , type == PIDGIN_STATUS_BOX_TYPE_SAVED_POPULAR , EMBLEM_COLUMN , GTK_STOCK_SAVE , pidgin_status_box_add_separator ( PidginStatusBox * status_box ) /* Don't do anything unless GTK actually supports * gtk_combo_box_set_row_separator_func */ gtk_list_store_append ( status_box -> dropdown_store , & iter ); gtk_list_store_set ( status_box -> dropdown_store , & iter , TYPE_COLUMN , PIDGIN_STATUS_BOX_TYPE_SEPARATOR , pidgin_status_box_set_network_available ( PidginStatusBox * status_box , gboolean available ) status_box -> network_available = available ; pidgin_status_box_refresh ( status_box ); pidgin_status_box_set_connecting ( PidginStatusBox * status_box , gboolean connecting ) status_box -> connecting = connecting ; pidgin_status_box_refresh ( status_box ); pixbuf_size_prepared_cb ( GdkPixbufLoader * loader , int width , int height , gpointer data ) GtkIconSize icon_size = gtk_icon_size_from_name ( PIDGIN_ICON_SIZE_TANGO_MEDIUM ); gtk_icon_size_lookup ( icon_size , & w , & h ); gdk_pixbuf_loader_set_size ( loader , w , h ); pidgin_status_box_redisplay_buddy_icon ( PidginStatusBox * status_box ) /* This is sometimes called before the box is shown, and we will not have a size */ if ( status_box -> icon_size <= 0 ) if ( status_box -> buddy_icon ) g_object_unref ( status_box -> buddy_icon ); if ( status_box -> buddy_icon_hover ) g_object_unref ( status_box -> buddy_icon_hover ); status_box -> buddy_icon = NULL ; status_box -> buddy_icon_hover = NULL ; if ( status_box -> buddy_icon_img != NULL ) loader = gdk_pixbuf_loader_new (); g_signal_connect ( G_OBJECT ( loader ), "size-prepared" , G_CALLBACK ( pixbuf_size_prepared_cb ), NULL ); if ( ! gdk_pixbuf_loader_write ( loader , purple_imgstore_get_data ( status_box -> buddy_icon_img ), purple_imgstore_get_size ( status_box -> buddy_icon_img ), purple_debug_warning ( "gtkstatusbox" , "gdk_pixbuf_loader_write() " "failed with size=%zu: %s \n " , purple_imgstore_get_size ( status_box -> buddy_icon_img ), error ? error -> message : "(no error message)" ); } else if ( ! gdk_pixbuf_loader_close ( loader , & error ) || error ) { purple_debug_warning ( "gtkstatusbox" , "gdk_pixbuf_loader_close() " "failed for image of size %zu: %s \n " , purple_imgstore_get_size ( status_box -> buddy_icon_img ), error ? error -> message : "(no error message)" ); int scale_width , scale_height ; buf = gdk_pixbuf_loader_get_pixbuf ( loader ); scale_width = gdk_pixbuf_get_width ( buf ); scale_height = gdk_pixbuf_get_height ( buf ); scale = gdk_pixbuf_new ( GDK_COLORSPACE_RGB , TRUE , 8 , scale_width , scale_height ); gdk_pixbuf_fill ( scale , 0x00000000 ); gdk_pixbuf_copy_area ( buf , 0 , 0 , scale_width , scale_height , scale , 0 , 0 ); if ( pidgin_gdk_pixbuf_is_opaque ( scale )) pidgin_gdk_pixbuf_make_round ( scale ); status_box -> buddy_icon = scale ; if ( status_box -> buddy_icon == NULL ) /* Show a placeholder icon */ GtkIconSize icon_size = gtk_icon_size_from_name ( PIDGIN_ICON_SIZE_TANGO_SMALL ); status_box -> buddy_icon = gtk_widget_render_icon ( GTK_WIDGET ( status_box ), PIDGIN_STOCK_TOOLBAR_SELECT_AVATAR , icon_size , "PidginStatusBox" ); if ( status_box -> buddy_icon != NULL ) { status_box -> icon_opaque = pidgin_gdk_pixbuf_is_opaque ( status_box -> buddy_icon ); gtk_image_set_from_pixbuf ( GTK_IMAGE ( status_box -> icon ), status_box -> buddy_icon ); status_box -> buddy_icon_hover = gdk_pixbuf_copy ( status_box -> buddy_icon ); do_colorshift ( status_box -> buddy_icon_hover , status_box -> buddy_icon_hover , 32 ); gtk_widget_queue_resize ( GTK_WIDGET ( status_box )); pidgin_status_box_set_buddy_icon ( PidginStatusBox * status_box , PurpleStoredImage * img ) purple_imgstore_unref ( status_box -> buddy_icon_img ); status_box -> buddy_icon_img = img ; if ( status_box -> buddy_icon_img != NULL ) purple_imgstore_ref ( status_box -> buddy_icon_img ); pidgin_status_box_redisplay_buddy_icon ( status_box ); pidgin_status_box_pulse_connecting ( PidginStatusBox * status_box ) if ( ! connecting_stock_ids [ ++ status_box -> connecting_index ]) status_box -> connecting_index = 0 ; pidgin_status_box_refresh ( status_box ); pidgin_status_box_pulse_typing ( PidginStatusBox * status_box ) if ( ! typing_stock_ids [ ++ status_box -> typing_index ]) status_box -> typing_index = 0 ; pidgin_status_box_refresh ( status_box ); activate_currently_selected_status ( PidginStatusBox * status_box ) PidginStatusBoxItemType type ; PurpleSavedStatus * saved_status = NULL ; path = gtk_tree_row_reference_get_path ( status_box -> active_row ); if ( ! gtk_tree_model_get_iter ( GTK_TREE_MODEL ( status_box -> dropdown_store ), & iter , path )) gtk_tree_path_free ( path ); gtk_tree_model_get ( GTK_TREE_MODEL ( status_box -> dropdown_store ), & iter , * If the currently selected status is "New..." or * "Saved..." or a popular status then do nothing. * activated elsewhere, and we update the status_box * accordingly by connecting to the savedstatus-changed * signal and then calling status_menu_refresh_iter() if ( type != PIDGIN_STATUS_BOX_TYPE_PRIMITIVE ) gtk_tree_model_get ( GTK_TREE_MODEL ( status_box -> dropdown_store ), & iter , TITLE_COLUMN , & title , -1 ); message = pidgin_status_box_get_message ( status_box ); if ( ! message || !* message ) gtk_widget_hide_all ( status_box -> vbox ); status_box -> imhtml_visible = FALSE ; if ( status_box -> account == NULL ) { PurpleStatusType * acct_status_type = NULL ; const char * id = NULL ; /* id of acct_status_type */ PurpleStatusPrimitive primitive = GPOINTER_TO_INT ( data ); /* Save the newly selected status to prefs.xml and status.xml */ /* Has the status really been changed? */ if ( status_box -> token_status_account ) { GtkTreePath * path = gtk_tree_row_reference_get_path ( status_box -> active_row ); active = gtk_tree_path_get_indices ( path )[ 0 ]; gtk_tree_path_free ( path ); status = purple_account_get_active_status ( status_box -> token_status_account ); acct_status_type = find_status_type_by_index ( status_box -> token_status_account , active ); id = purple_status_type_get_id ( acct_status_type ); if ( g_str_equal ( id , purple_status_get_id ( status )) && purple_strequal ( message , purple_status_get_attr_string ( status , "message" ))) /* Selected status and previous status is the same */ PurpleSavedStatus * ss = purple_savedstatus_get_current (); /* Make sure that statusbox displays the correct thing. * It can get messed up if the previous selection was a * saved status that wasn't supported by this account */ if (( purple_savedstatus_get_type ( ss ) == primitive ) && purple_savedstatus_is_transient ( ss ) && purple_savedstatus_has_substatuses ( ss )) saved_status = purple_savedstatus_get_current (); if ( purple_savedstatus_get_type ( saved_status ) == primitive && ! purple_savedstatus_has_substatuses ( saved_status ) && purple_strequal ( purple_savedstatus_get_message ( saved_status ), message )) /* Manually find the appropriate transient status */ if ( status_box -> token_status_account ) { GList * iter = purple_savedstatuses_get_all (); GList * tmp , * active_accts = purple_accounts_get_all_active (); for (; iter != NULL ; iter = iter -> next ) { PurpleSavedStatus * ss = iter -> data ; const char * ss_msg = purple_savedstatus_get_message ( ss ); /* find a known transient status that is the same as the if (( purple_savedstatus_get_type ( ss ) == primitive ) && purple_savedstatus_is_transient ( ss ) && purple_savedstatus_has_substatuses ( ss ) && /* Must have substatuses */ purple_strequal ( ss_msg , message )) /* this status must have substatuses for all the active accts */ for ( tmp = active_accts ; tmp != NULL ; tmp = tmp -> next ) { PurpleAccount * acct = tmp -> data ; PurpleSavedStatusSub * sub = purple_savedstatus_get_substatus ( ss , acct ); const PurpleStatusType * sub_type = purple_savedstatus_substatus_get_type ( sub ); const char * subtype_status_id = purple_status_type_get_id ( sub_type ); if ( purple_strequal ( subtype_status_id , id )) { g_list_free ( active_accts ); /* If we've used this type+message before, lookup the transient status */ saved_status = purple_savedstatus_find_transient_by_type_and_message ( primitive , message ); /* If this type+message is unique then create a new transient saved status */ if ( saved_status == NULL ) saved_status = purple_savedstatus_new ( NULL , primitive ); purple_savedstatus_set_message ( saved_status , message ); if ( status_box -> token_status_account ) { GList * tmp , * active_accts = purple_accounts_get_all_active (); for ( tmp = active_accts ; tmp != NULL ; tmp = tmp -> next ) { purple_savedstatus_set_substatus ( saved_status , ( PurpleAccount * ) tmp -> data , acct_status_type , message ); g_list_free ( active_accts ); /* Set the status for each account */ purple_savedstatus_activate ( saved_status ); PurpleStatusType * status_type ; status = purple_account_get_active_status ( status_box -> account ); active = GPOINTER_TO_INT ( g_object_get_data ( G_OBJECT ( status_box ), "active" )); status_type = find_status_type_by_index ( status_box -> account , active ); id = purple_status_type_get_id ( status_type ); if ( g_str_equal ( id , purple_status_get_id ( status )) && purple_strequal ( message , purple_status_get_attr_string ( status , "message" ))) /* Selected status and previous status is the same */ purple_account_set_status ( status_box -> account , id , TRUE , "message" , message , NULL ); purple_account_set_status ( status_box -> account , id , saved_status = purple_savedstatus_get_current (); if ( purple_savedstatus_is_transient ( saved_status )) purple_savedstatus_set_substatus ( saved_status , status_box -> account , static void update_size ( PidginStatusBox * status_box ) int pad_top , pad_inside , pad_bottom ; if ( ! status_box -> imhtml_visible ) if ( status_box -> vbox != NULL ) gtk_widget_set_size_request ( status_box -> vbox , -1 , -1 ); buffer = gtk_text_view_get_buffer ( GTK_TEXT_VIEW ( status_box -> imhtml )); gtk_text_buffer_get_start_iter ( buffer , & iter ); gtk_text_view_get_iter_location ( GTK_TEXT_VIEW ( status_box -> imhtml ), & iter , & oneline ); height += oneline . height ; } while ( display_lines <= 4 && gtk_text_view_forward_display_line ( GTK_TEXT_VIEW ( status_box -> imhtml ), & iter )); * This check fixes the case where the last character entered is a * newline (shift+return). For some reason the * gtk_text_view_forward_display_line() function doesn't treat this * like a new line, and so we think the input box only needs to be * two lines instead of three, for example. So we check if the * last character was a newline and add some extra height if so. && gtk_text_iter_backward_char ( & iter ) && gtk_text_iter_get_char ( & iter ) == '\n' ) gtk_text_view_get_iter_location ( GTK_TEXT_VIEW ( status_box -> imhtml ), & iter , & oneline ); height += oneline . height ; lines = gtk_text_buffer_get_line_count ( buffer ); /* Show a maximum of 4 lines */ display_lines = MIN ( display_lines , 4 ); pad_top = gtk_text_view_get_pixels_above_lines ( GTK_TEXT_VIEW ( status_box -> imhtml )); pad_bottom = gtk_text_view_get_pixels_below_lines ( GTK_TEXT_VIEW ( status_box -> imhtml )); pad_inside = gtk_text_view_get_pixels_inside_wrap ( GTK_TEXT_VIEW ( status_box -> imhtml )); height += ( pad_top + pad_bottom ) * lines ; height += ( pad_inside ) * ( display_lines - lines ); gtk_widget_style_get ( status_box -> imhtml , "interior-focus" , & interior_focus , "focus-line-width" , & focus_width , height += 2 * focus_width ; gtk_widget_set_size_request ( status_box -> vbox , -1 , height + PIDGIN_HIG_BOX_SPACE ); static void remove_typing_cb ( PidginStatusBox * status_box ) if ( status_box -> typing == 0 ) /* Nothing has changed, so we don't need to do anything */ status_menu_refresh_iter ( status_box , FALSE ); gtk_imhtml_set_populate_primary_clipboard ( GTK_IMHTML ( status_box -> imhtml ), TRUE ); purple_timeout_remove ( status_box -> typing ); activate_currently_selected_status ( status_box ); pidgin_status_box_refresh ( status_box ); static void pidgin_status_box_changed ( PidginStatusBox * status_box ) GtkTreePath * path = gtk_tree_row_reference_get_path ( status_box -> active_row ); PidginStatusBoxItemType type ; GList * accounts = NULL , * node ; gboolean wastyping = FALSE ; if ( ! gtk_tree_model_get_iter ( GTK_TREE_MODEL ( status_box -> dropdown_store ), & iter , path )) active = gtk_tree_path_get_indices ( path )[ 0 ]; gtk_tree_path_free ( path ); g_object_set_data ( G_OBJECT ( status_box ), "active" , GINT_TO_POINTER ( active )); gtk_tree_model_get ( GTK_TREE_MODEL ( status_box -> dropdown_store ), & iter , if (( wastyping = ( status_box -> typing != 0 ))) purple_timeout_remove ( status_box -> typing ); if ( GTK_WIDGET_IS_SENSITIVE ( GTK_WIDGET ( status_box ))) if ( type == PIDGIN_STATUS_BOX_TYPE_POPULAR || type == PIDGIN_STATUS_BOX_TYPE_SAVED_POPULAR ) PurpleSavedStatus * saved ; saved = purple_savedstatus_find_by_creation_time ( GPOINTER_TO_INT ( data )); g_return_if_fail ( saved != NULL ); purple_savedstatus_activate ( saved ); if ( type == PIDGIN_STATUS_BOX_TYPE_CUSTOM ) PurpleSavedStatus * saved_status ; saved_status = purple_savedstatus_get_current (); if ( purple_savedstatus_get_type ( saved_status ) == PURPLE_STATUS_AVAILABLE ) saved_status = purple_savedstatus_new ( NULL , PURPLE_STATUS_AWAY ); pidgin_status_editor_show ( FALSE , purple_savedstatus_is_transient ( saved_status ) status_menu_refresh_iter ( status_box , wastyping ); pidgin_status_box_refresh ( status_box ); if ( type == PIDGIN_STATUS_BOX_TYPE_SAVED ) pidgin_status_window_show (); status_menu_refresh_iter ( status_box , wastyping ); pidgin_status_box_refresh ( status_box ); * Show the message box whenever the primitive allows for a * message attribute on any protocol that is enabled, * or our protocol, if we have account set accounts = g_list_prepend ( accounts , status_box -> account ); accounts = purple_accounts_get_all_active (); status_box -> imhtml_visible = FALSE ; for ( node = accounts ; node != NULL ; node = node -> next ) PurpleStatusType * status_type ; status_type = purple_account_get_status_type_with_primitive ( account , GPOINTER_TO_INT ( data )); if (( status_type != NULL ) && ( purple_status_type_get_attr ( status_type , "message" ) != NULL )) status_box -> imhtml_visible = TRUE ; if ( GTK_WIDGET_IS_SENSITIVE ( GTK_WIDGET ( status_box ))) if ( status_box -> imhtml_visible ) gtk_widget_show_all ( status_box -> vbox ); status_box -> typing = purple_timeout_add_seconds ( TYPING_TIMEOUT , ( GSourceFunc ) remove_typing_cb , status_box ); gtk_widget_grab_focus ( status_box -> imhtml ); buffer = gtk_text_view_get_buffer ( GTK_TEXT_VIEW ( status_box -> imhtml )); gtk_imhtml_set_populate_primary_clipboard ( GTK_IMHTML ( status_box -> imhtml ), FALSE ); gtk_text_buffer_get_bounds ( buffer , & start , & end ); gtk_text_buffer_move_mark ( buffer , gtk_text_buffer_get_mark ( buffer , "insert" ), & end ); gtk_text_buffer_move_mark ( buffer , gtk_text_buffer_get_mark ( buffer , "selection_bound" ), & start ); gtk_widget_hide_all ( status_box -> vbox ); activate_currently_selected_status ( status_box ); /* This is where we actually set the status */ pidgin_status_box_refresh ( status_box ); get_statusbox_index ( PidginStatusBox * box , PurpleSavedStatus * saved_status ) switch ( purple_savedstatus_get_type ( saved_status )) case PURPLE_STATUS_OFFLINE : case PURPLE_STATUS_INVISIBLE : case PURPLE_STATUS_UNAVAILABLE : case PURPLE_STATUS_AVAILABLE : static void imhtml_changed_cb ( GtkTextBuffer * buffer , void * data ) PidginStatusBox * status_box = ( PidginStatusBox * ) data ; if ( GTK_WIDGET_IS_SENSITIVE ( GTK_WIDGET ( status_box ))) if ( status_box -> typing != 0 ) { pidgin_status_box_pulse_typing ( status_box ); purple_timeout_remove ( status_box -> typing ); status_box -> typing = purple_timeout_add_seconds ( TYPING_TIMEOUT , ( GSourceFunc ) remove_typing_cb , status_box ); pidgin_status_box_refresh ( status_box ); static void imhtml_format_changed_cb ( GtkIMHtml * imhtml , GtkIMHtmlButtons buttons , void * data ) imhtml_changed_cb ( NULL , data ); char * pidgin_status_box_get_message ( PidginStatusBox * status_box ) if ( status_box -> imhtml_visible ) return gtk_imhtml_get_markup ( GTK_IMHTML ( status_box -> imhtml ));