/* * Xfce4 Messenger Plugin - DBus message notification plugin for Xfce4 Panel * Copyright (C) 2005-2006 Pasi Orovuo * * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include #include #include #include "popup.h" #define BORDER 8 #define MESSENGER_POPUP_DEFAULT_TIMEOUT 5 #define MESSENGER_POPUP_DEFAULT_SPEED 5 #define MESSENGER_POPUP_DEFAULT_POSITION MESSENGER_POPUP_POSITION_SE typedef struct _PopupItem PopupItem; enum { PROP_0, PROP_TIMEOUT, PROP_POSITION }; enum { CLICKED, N_SIGNALS }; /* This won't happen any time soon I guess... typedef enum { POPUP_OUTSIDE_SCREEN, POPUP_ENTERING, POPUP_ON_SCREEN, POPUP_LEAVING } MessengerPopupState; */ static void messenger_popup_finalize ( GObject *object ); static void messenger_popup_set_property ( GObject *object, guint property_id, const GValue *value, GParamSpec *pspec ); static void messenger_popup_get_property ( GObject *object, guint property_id, GValue *value, GParamSpec *pspec ); static void messenger_popup_position_window ( MessengerPopup *popup ); static gboolean messenger_popup_timeout_cb ( gpointer user_data ); static void messenger_popup_item_prev_clicked_cb ( GtkWidget *widget, MessengerPopup *popup ); static void messenger_popup_item_next_clicked_cb ( GtkWidget *widget, MessengerPopup *popup ); static void messenger_popup_hide ( MessengerPopup *popup ); static void messenger_popup_set_item ( MessengerPopup *popup, PopupItem *item ); static gboolean messenger_popup_clicked_cb ( GtkWidget *widget, GdkEventButton *button, gpointer user_data ); static gboolean messenger_popup_label_clicked_cb ( GtkWidget *widget, GdkEventButton *button, gpointer user_data ); struct _MessengerPopup { GtkWindow parent; #if 0 GtkWidget *close_button; #endif GtkWidget *index_hbox; GtkWidget *index_label; GtkWidget *prev_button; GtkWidget *next_button; GtkWidget *icon; GtkWidget *text_label; guint timeout; guint elapsed; MessengerPopupPosition position; gint ref_x; gint ref_y; /* gint vvect; MessengerPopupState state; */ GPtrArray *item_list; }; struct _MessengerPopupClass { GtkWindowClass parent_class; void (*popup_text)( MessengerPopup *popup, const gchar *text, GdkPixbuf *pixbuf ); }; struct _PopupItem { gchar *text; GdkPixbuf *pixbuf; gint index; }; /* Inline 1x1 empty GdkPixbuf for clearing the icon */ #ifdef __SUNPRO_C #pragma align 4 (empty_pixbuf_data) #endif #ifdef __GNUC__ static const guint8 empty_pixbuf_data[] __attribute__ ((__aligned__ (4))) = #else static const guint8 empty_pixbuf_data[] = #endif { "GdkP\0\0\0\34\1\1\0\2\0\0\0\4\0\0\0\1\0\0\0\1\0\0\0\0" }; static guint messenger_popup_signals[N_SIGNALS] = { 0, }; static GdkPixbuf *empty_pixbuf = NULL; G_DEFINE_TYPE( MessengerPopup, messenger_popup, GTK_TYPE_WINDOW ); static void messenger_popup_class_init( MessengerPopupClass *klass ) { GObjectClass *gobject_class = G_OBJECT_CLASS( klass ); gobject_class->set_property = messenger_popup_set_property; gobject_class->get_property = messenger_popup_get_property; gobject_class->finalize = messenger_popup_finalize; klass->popup_text = NULL; g_object_class_install_property( gobject_class, PROP_TIMEOUT, g_param_spec_uint( "timeout", "Timeout", "Timeout", 0, 60, MESSENGER_POPUP_DEFAULT_TIMEOUT, G_PARAM_READWRITE ) ); g_object_class_install_property( gobject_class, PROP_POSITION, g_param_spec_uint( "position", "Position", "The corner of the screen in which " "the popup appears", MESSENGER_POPUP_POSITION_NW, MESSENGER_POPUP_POSITION_SE, MESSENGER_POPUP_POSITION_SE, G_PARAM_READWRITE ) ); messenger_popup_signals[CLICKED] = g_signal_new( "clicked", MESSENGER_TYPE_POPUP, G_SIGNAL_RUN_FIRST, G_STRUCT_OFFSET( MessengerPopupClass, popup_text ), NULL, NULL, g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0 ); } static void messenger_popup_init( MessengerPopup *self ) { GtkWidget *vbox, *hbox; GtkWidget *separator; GtkWidget *image; gint icon_w; GtkWidget *evbox, *evbox2; /* This is a popup window */ GTK_WINDOW( self )->type = GTK_WINDOW_POPUP; /* Initialize private fields */ self->position = MESSENGER_POPUP_DEFAULT_POSITION; self->timeout = MESSENGER_POPUP_DEFAULT_TIMEOUT; self->item_list = g_ptr_array_new(); gtk_window_stick( GTK_WINDOW( self ) ); /*gtk_window_set_keep_above( GTK_WINDOW( self ), TRUE );*/ gtk_window_set_decorated( GTK_WINDOW( self ), FALSE ); gtk_window_set_skip_taskbar_hint( GTK_WINDOW( self ), TRUE ); gtk_window_set_accept_focus( GTK_WINDOW( self ), FALSE ); evbox = gtk_event_box_new(); gtk_widget_show( evbox ); gtk_container_add( GTK_CONTAINER( self ), evbox ); vbox = gtk_vbox_new( FALSE, 0 ); gtk_widget_show( vbox ); gtk_container_add( GTK_CONTAINER( evbox ), vbox ); gtk_container_set_border_width( GTK_CONTAINER( vbox ), BORDER / 2 ); #if 0 hbox = gtk_hbox_new( FALSE, 0 ); gtk_widget_show( hbox ); gtk_box_pack_start( GTK_BOX( vbox ), hbox, FALSE, FALSE, 0 ); self->close_button = gtk_button_new(); gtk_button_set_relief( GTK_BUTTON( self->close_button ), GTK_RELIEF_NONE ); gtk_widget_show( self->close_button ); gtk_box_pack_end( GTK_BOX( hbox ), self->close_button, FALSE, FALSE, 0 ); image = gtk_image_new_from_stock( GTK_STOCK_CLOSE, GTK_ICON_SIZE_MENU ); gtk_widget_show( image ); gtk_container_add( GTK_CONTAINER( self->close_button ), image ); #endif self->index_hbox = hbox = gtk_hbox_new( FALSE, BORDER / 2 ); gtk_container_set_border_width( GTK_CONTAINER( hbox ), BORDER / 4 ); gtk_widget_show( hbox ); gtk_box_pack_start( GTK_BOX( vbox ), hbox, FALSE, FALSE, 0 ); self->index_label = gtk_label_new( NULL ); gtk_misc_set_alignment( GTK_MISC( self->index_label ), 0.0f, 0.5f ); gtk_misc_set_padding( GTK_MISC( self->index_label ), 10, 0 ); gtk_widget_show( self->index_label ); gtk_box_pack_start( GTK_BOX( hbox ), self->index_label, TRUE, TRUE, 0 ); self->prev_button = gtk_button_new(); gtk_widget_show( self->prev_button ); gtk_box_pack_start( GTK_BOX( hbox ), self->prev_button, FALSE, FALSE, 0 ); image = gtk_image_new_from_stock( GTK_STOCK_GO_BACK, GTK_ICON_SIZE_MENU ); gtk_widget_show( image ); gtk_container_add( GTK_CONTAINER( self->prev_button ), image ); self->next_button = gtk_button_new(); gtk_widget_show( self->next_button ); gtk_box_pack_start( GTK_BOX( hbox ), self->next_button, FALSE, FALSE, 0 ); image = gtk_image_new_from_stock( GTK_STOCK_GO_FORWARD, GTK_ICON_SIZE_MENU ); gtk_widget_show( image ); gtk_container_add( GTK_CONTAINER( self->next_button ), image ); separator = gtk_hseparator_new(); gtk_widget_show( separator ); gtk_box_pack_start( GTK_BOX( vbox ), separator, FALSE, FALSE, 0 ); hbox = gtk_hbox_new( FALSE, BORDER ); gtk_container_set_border_width( GTK_CONTAINER( hbox ), BORDER ); gtk_widget_show( hbox ); gtk_box_pack_start( GTK_BOX( vbox ), hbox, TRUE, TRUE, 0 ); gtk_icon_size_lookup( GTK_ICON_SIZE_DND, &icon_w, NULL ); self->icon = xfce_scaled_image_new(); gtk_widget_set_size_request( self->icon, icon_w, -1 ); gtk_widget_show( self->icon ); gtk_box_pack_start( GTK_BOX( hbox ), self->icon, FALSE, FALSE, 0 ); evbox2 = gtk_event_box_new(); gtk_widget_show( evbox2 ); gtk_box_pack_start( GTK_BOX( hbox ), evbox2, TRUE, TRUE, 0 ); self->text_label = gtk_label_new( NULL ); gtk_widget_set_size_request( self->text_label, 150, 75 ); gtk_label_set_use_markup( GTK_LABEL( self->text_label ), TRUE ); gtk_label_set_line_wrap( GTK_LABEL( self->text_label ), TRUE ); gtk_misc_set_alignment( GTK_MISC( self->text_label ), 0.0f, 0.5f ); gtk_misc_set_padding( GTK_MISC( self->text_label ), 0, 10 ); gtk_widget_show( self->text_label ); gtk_container_add( GTK_CONTAINER( evbox2 ), self->text_label ); if ( !empty_pixbuf ) { empty_pixbuf = gdk_pixbuf_new_from_inline( -1, empty_pixbuf_data, FALSE, NULL ); /* Wtf? Without cast to gpointer yields a warning! Why? Oh, why?! */ g_object_add_weak_pointer( G_OBJECT( empty_pixbuf ), (gpointer) &empty_pixbuf ); } else { g_object_ref( G_OBJECT( empty_pixbuf ) ); } g_signal_connect( G_OBJECT( self->prev_button ), "clicked", G_CALLBACK( messenger_popup_item_prev_clicked_cb ), self ); g_signal_connect( G_OBJECT( self->next_button ), "clicked", G_CALLBACK( messenger_popup_item_next_clicked_cb ), self ); g_signal_connect( G_OBJECT( evbox ), "button-press-event", G_CALLBACK( messenger_popup_clicked_cb ), self ); g_signal_connect( G_OBJECT( evbox2 ), "button-press-event", G_CALLBACK( messenger_popup_label_clicked_cb ), self ); } static void _popup_item_free( gpointer data, gpointer user_data ) { PopupItem *item = data; if ( item->pixbuf ) { g_object_unref( item->pixbuf ); } if ( item->text ) { g_free( item->text ); } g_free( item ); } static void messenger_popup_finalize( GObject *object ) { MessengerPopup *popup = MESSENGER_POPUP( object ); g_ptr_array_foreach( popup->item_list, _popup_item_free, NULL ); g_ptr_array_free( popup->item_list, TRUE ); g_object_unref( G_OBJECT( empty_pixbuf ) ); G_OBJECT_CLASS( messenger_popup_parent_class )->finalize( object ); } static void messenger_popup_set_property( GObject *object, guint property_id, const GValue *value, GParamSpec *pspec ) { MessengerPopup *popup = MESSENGER_POPUP( object ); switch ( property_id ) { case PROP_TIMEOUT: { guint timeout = g_value_get_uint( value ); popup->timeout = timeout != 0 ? timeout : MESSENGER_POPUP_DEFAULT_TIMEOUT; } break; case PROP_POSITION: popup->position = g_value_get_uint( value ); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID( object, property_id, pspec ); break; } } static void messenger_popup_get_property( GObject *object, guint property_id, GValue *value, GParamSpec *pspec ) { MessengerPopup *popup = MESSENGER_POPUP( object ); switch ( property_id ) { case PROP_TIMEOUT: g_value_set_uint( value, popup->timeout ); break; case PROP_POSITION: g_value_set_uint( value, popup->position ); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID( object, property_id, pspec ); break; } } static void messenger_popup_hide( MessengerPopup *popup ) { g_ptr_array_foreach( popup->item_list, _popup_item_free, NULL ); g_ptr_array_set_size( popup->item_list, 0 ); /* gtk_widget_set_sensitive( popup->next_button, FALSE ); gtk_widget_set_sensitive( popup->prev_button, FALSE ); */ gtk_widget_hide( GTK_WIDGET( popup ) ); } static gboolean messenger_popup_timeout_cb( gpointer user_data ) { MessengerPopup *popup = user_data; if ( ++popup->elapsed >= popup->timeout ) { messenger_popup_hide( popup ); return ( FALSE ); } return ( TRUE ); } static void messenger_popup_position_window( MessengerPopup *popup ) { GdkDisplay *display; GdkScreen *screen; GdkGravity gravity; gint width, height; display = gdk_display_get_default(); gdk_display_get_pointer( display, &screen, NULL, NULL, NULL ); gtk_window_set_screen( GTK_WINDOW( popup ), screen ); width = gdk_screen_get_width( screen ); height = gdk_screen_get_height( screen ); gravity = gtk_window_get_gravity( GTK_WINDOW( popup ) ); switch ( popup->position ) { case MESSENGER_POPUP_POSITION_NW: if ( gravity != GDK_GRAVITY_NORTH_WEST ) { gtk_window_set_gravity( GTK_WINDOW( popup ), GDK_GRAVITY_NORTH_WEST ); } popup->ref_x = 0; popup->ref_y = 0; break; case MESSENGER_POPUP_POSITION_NE: if ( gravity != GDK_GRAVITY_NORTH_EAST ) { gtk_window_set_gravity( GTK_WINDOW( popup ), GDK_GRAVITY_NORTH_EAST ); } popup->ref_x = width - 1; popup->ref_y = 0; break; case MESSENGER_POPUP_POSITION_SW: if ( gravity != GDK_GRAVITY_SOUTH_WEST ) { gtk_window_set_gravity( GTK_WINDOW( popup ), GDK_GRAVITY_SOUTH_WEST ); } popup->ref_x = 0; popup->ref_y = height -1; break; case MESSENGER_POPUP_POSITION_SE: if ( gravity != GDK_GRAVITY_SOUTH_EAST ) { gtk_window_set_gravity( GTK_WINDOW( popup ), GDK_GRAVITY_SOUTH_EAST ); } popup->ref_x = width - 1; popup->ref_y = height - 1; break; } gtk_window_move( GTK_WINDOW( popup ), popup->ref_x, popup->ref_y ); } static gboolean messenger_popup_clicked_cb( GtkWidget *widget, GdkEventButton *button, gpointer user_data ) { messenger_popup_hide( user_data ); return ( FALSE ); } static gboolean messenger_popup_label_clicked_cb( GtkWidget *widget, GdkEventButton *button, gpointer user_data ) { if ( button->button == 1 ) { g_signal_emit( MESSENGER_POPUP( user_data ), messenger_popup_signals[CLICKED], 0, NULL ); } return ( FALSE ); } static void messenger_popup_item_prev_clicked_cb( GtkWidget *widget, MessengerPopup *popup ) { gint index; index = GPOINTER_TO_INT( g_object_get_data( G_OBJECT( popup->index_label ), "popup-item-index" ) ) - 1; if ( index > 0 ) { PopupItem *item = g_ptr_array_index( popup->item_list, index - 1 ); messenger_popup_set_item( popup, item ); } popup->elapsed = 0; } static void messenger_popup_item_next_clicked_cb( GtkWidget *widget, MessengerPopup *popup ) { gint index; index = GPOINTER_TO_INT( g_object_get_data( G_OBJECT( popup->index_label ), "popup-item-index" ) ) - 1; if ( index < popup->item_list->len - 1 ) { PopupItem *item = g_ptr_array_index( popup->item_list, index + 1 ); messenger_popup_set_item( popup, item ); } popup->elapsed = 0; } static void messenger_popup_update_index_label( MessengerPopup *popup, gint cur ) { gchar index_text[32]; if ( cur == -1 ) { cur = GPOINTER_TO_INT( g_object_get_data( G_OBJECT( popup->index_label ), "popup-item-index" ) ); } else { g_object_set_data( G_OBJECT( popup->index_label ), "popup-item-index", GINT_TO_POINTER( cur ) ); } if ( cur == 1 ) { /* First */ if ( GTK_WIDGET_SENSITIVE( popup->prev_button ) ) { gtk_widget_set_sensitive( popup->prev_button, FALSE ); } if ( popup->item_list->len > 1 ) { if ( !GTK_WIDGET_SENSITIVE( popup->next_button ) ) { gtk_widget_set_sensitive( popup->next_button, TRUE ); } } else { if ( GTK_WIDGET_SENSITIVE( popup->next_button ) ) { gtk_widget_set_sensitive( popup->next_button, FALSE ); } } } else if ( cur == popup->item_list->len ) { /* Last * No need to check the list length, as if ( cur == 1 ) will * always catch single item lists */ if ( GTK_WIDGET_SENSITIVE( popup->next_button ) ) { gtk_widget_set_sensitive( popup->next_button, FALSE ); } if ( !GTK_WIDGET_SENSITIVE( popup->prev_button ) ) { gtk_widget_set_sensitive( popup->prev_button, TRUE ); } } else { if ( !GTK_WIDGET_SENSITIVE( popup->prev_button ) ) { gtk_widget_set_sensitive( popup->prev_button, TRUE ); } if ( !GTK_WIDGET_SENSITIVE( popup->next_button ) ) { gtk_widget_set_sensitive( popup->next_button, TRUE ); } } g_snprintf( index_text, sizeof( index_text ), "(%d/%d)", cur, popup->item_list->len ); gtk_label_set_text( GTK_LABEL( popup->index_label ), index_text ); } static void messenger_popup_set_item( MessengerPopup *popup, PopupItem *item ) { messenger_popup_update_index_label( popup, item->index + 1 ); gtk_label_set_text( GTK_LABEL( popup->text_label ), item->text ); xfce_scaled_image_set_from_pixbuf( XFCE_SCALED_IMAGE( popup->icon ), item->pixbuf ? item->pixbuf : empty_pixbuf ); } /* ======================= PUBLIC API ============================== */ GtkWidget * messenger_popup_new( void ) { return ( GTK_WIDGET( g_object_new( MESSENGER_TYPE_POPUP, NULL ) ) ); } void messenger_popup_set_timeout( MessengerPopup *popup, guint timeout ) { g_return_if_fail( MESSENGER_IS_POPUP( popup ) ); g_object_set( G_OBJECT( popup ), "timeout", timeout, NULL ); } guint messenger_popup_get_timeout( MessengerPopup *popup ) { g_return_val_if_fail( MESSENGER_IS_POPUP( popup ), 0 ); /* g_object_get? */ return ( popup->timeout ); } void messenger_popup_set_position( MessengerPopup *popup, MessengerPopupPosition position ) { g_return_if_fail( MESSENGER_IS_POPUP( popup ) ); g_return_if_fail( position >= MESSENGER_POPUP_POSITION_NW && position <= MESSENGER_POPUP_POSITION_SE ); g_object_set( G_OBJECT( popup ), "position", position, NULL ); messenger_popup_position_window( popup ); } MessengerPopupPosition messenger_popup_get_position( MessengerPopup *popup ) { g_return_val_if_fail( MESSENGER_IS_POPUP( popup ), -1 ); return ( popup->position ); } void messenger_popup_text( MessengerPopup *popup, const gchar *text, GdkPixbuf *pixbuf ) { PopupItem *item; g_return_if_fail( MESSENGER_IS_POPUP( popup ) ); item = g_new0( PopupItem, 1 ); item->index = popup->item_list->len; item->text = g_strdup( text ); if ( pixbuf && GDK_IS_PIXBUF( pixbuf ) ) { item->pixbuf = g_object_ref( G_OBJECT( pixbuf ) ); } g_ptr_array_add( popup->item_list, item ); if ( !item->index ) { popup->elapsed = 0; messenger_popup_set_item( popup, item ); messenger_popup_position_window( popup ); /* Tässä, sillä popup jää paneelin alle toisella * kerralla kun se näytetään, ellei juuri ennen * näyttämistä keep above */ gtk_window_set_keep_above( GTK_WINDOW( popup ), TRUE ); gtk_widget_show( GTK_WIDGET( popup ) ); g_timeout_add( 1000, messenger_popup_timeout_cb, popup ); } else { messenger_popup_update_index_label( popup, -1 ); } }