/* * 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 */ #ifdef HAVE_CONFIG_H #include #endif #ifdef HAVE_TIME_H #include #endif #include #include #include "history.h" #define BORDER 8 #define MESSENGER_HISTORY_DEFAULT_LENGTH 200 #define MESSENGER_HISTORY_MAX_LENGTH 2000 #define HISTORY_GET_CURRENT_TIME( buf ) \ { \ time_t curtime = time( NULL ); \ const char *fmt = "[%c]"; \ strftime( buf, sizeof( buf ), fmt, localtime( &curtime ) ); \ } enum { HISTORY_COL_DATETIME, HISTORY_COL_MESSAGE, HISTORY_N_COLS }; enum { MENU_ACTION_COPY, MENU_ACTION_DELETE }; struct _MessengerHistory { GtkListStore *store; guint length; GtkWidget *tree_view; GtkWidget *dialog; GtkWidget *context_menu; }; static void messenger_history_trim( MessengerHistory *history ) { g_return_if_fail( history != NULL ); while ( gtk_tree_model_iter_n_children( GTK_TREE_MODEL( history->store ), NULL ) > history->length ) { GtkTreeIter iter; if ( gtk_tree_model_iter_nth_child( GTK_TREE_MODEL( history->store ), &iter, NULL, history->length ) ) { gtk_list_store_remove( history->store, &iter ); } } } MessengerHistory * messenger_history_new( void ) { MessengerHistory *history; history = g_new( MessengerHistory, 1 ); history->store = gtk_list_store_new( HISTORY_N_COLS, G_TYPE_STRING, G_TYPE_STRING ); history->length = MESSENGER_HISTORY_DEFAULT_LENGTH; history->dialog = NULL; return ( history ); } void messenger_history_message_prepend( MessengerHistory *history, const gchar *message ) { gchar buf[128]; GtkTreeIter iter; g_return_if_fail( history != NULL ); g_return_if_fail( message != NULL ); HISTORY_GET_CURRENT_TIME( buf ); gtk_list_store_prepend( history->store, &iter ); gtk_list_store_set( history->store, &iter, HISTORY_COL_DATETIME, buf, HISTORY_COL_MESSAGE, message, -1 ); messenger_history_trim( history ); } void messenger_history_message_append( MessengerHistory *history, const gchar *message ) { gchar buf[128]; GtkTreeIter iter; g_return_if_fail( history != NULL ); g_return_if_fail( message != NULL ); HISTORY_GET_CURRENT_TIME( buf ); gtk_list_store_append( history->store, &iter ); gtk_list_store_set( history->store, &iter, HISTORY_COL_DATETIME, buf, HISTORY_COL_MESSAGE, message, -1 ); messenger_history_trim( history ); } void messenger_history_message_prepend_raw( MessengerHistory *history, const gchar *message, const gchar *timestr ) { GtkTreeIter iter; g_return_if_fail( history != NULL ); g_return_if_fail( message != NULL ); g_return_if_fail( timestr != NULL ); gtk_list_store_prepend( history->store, &iter ); gtk_list_store_set( history->store, &iter, HISTORY_COL_DATETIME, timestr, HISTORY_COL_MESSAGE, message, -1 ); } void messenger_history_message_append_raw( MessengerHistory *history, const gchar *message, const gchar *timestr ) { GtkTreeIter iter; g_return_if_fail( history != NULL ); g_return_if_fail( message != NULL ); g_return_if_fail( timestr != NULL ); gtk_list_store_append( history->store, &iter ); gtk_list_store_set( history->store, &iter, HISTORY_COL_DATETIME, timestr, HISTORY_COL_MESSAGE, message, -1 ); } void messenger_history_set_length( MessengerHistory *history, guint length ) { g_return_if_fail( history != NULL ); history->length = length; messenger_history_trim( history ); } guint messenger_history_get_length( MessengerHistory *history ) { return ( history->length ); } static void history_dialog_histlen_changed_cb( GtkWidget *widget, MessengerHistory *history ) { history->length = gtk_spin_button_get_value_as_int( GTK_SPIN_BUTTON( widget ) ); } static void context_menu_item_activate_cb( GtkWidget *widget, gpointer user_data ) { MessengerHistory *history = user_data; GtkTreeIter iter; GtkTreePath *tree_path; tree_path = g_object_get_data( G_OBJECT( history->context_menu ), "tree-path" ); if ( gtk_tree_model_get_iter( GTK_TREE_MODEL( history->store ), &iter, tree_path ) ) { guint action_id; action_id = GPOINTER_TO_INT( g_object_get_data( G_OBJECT( widget ), "action-id" ) ); switch ( action_id ) { case MENU_ACTION_COPY: { GtkClipboard *sel_default, *sel_primary; gchar *time, *msg, *all; /* I wonder if abusing both clipboards is okay... */ sel_default = gtk_clipboard_get( GDK_SELECTION_CLIPBOARD ); sel_primary = gtk_clipboard_get( GDK_SELECTION_PRIMARY ); gtk_tree_model_get( GTK_TREE_MODEL( history->store ), &iter, HISTORY_COL_DATETIME, &time, HISTORY_COL_MESSAGE, &msg, -1 ); all = g_strdup_printf( "%s %s", time, msg ); gtk_clipboard_set_text( sel_default, all, -1 ); gtk_clipboard_set_text( sel_primary, all, -1 ); g_free( all ); g_free( msg ); g_free( time ); } break; case MENU_ACTION_DELETE: gtk_list_store_remove( history->store, &iter ); break; default: break; } } } static gboolean treeview_button_press_cb( GtkWidget *widget, GdkEventButton *event, gpointer user_data ) { MessengerHistory *history = user_data; if ( event->type == GDK_BUTTON_PRESS && event->button == 3 ) { if ( event->window == gtk_tree_view_get_bin_window( GTK_TREE_VIEW( history->tree_view ) ) ) { GtkTreePath *tree_path = NULL; tree_path = g_object_get_data( G_OBJECT( history->context_menu ), "tree-path" ); if ( tree_path ) { gtk_tree_path_free( tree_path ); tree_path = NULL; } if ( gtk_tree_view_get_path_at_pos( GTK_TREE_VIEW( history->tree_view ), event->x, event->y, &tree_path, NULL, NULL, NULL ) ) { gtk_menu_popup( GTK_MENU( history->context_menu ), NULL, NULL, NULL, NULL, event->button, event->time ); } g_object_set_data( G_OBJECT( history->context_menu ), "tree-path", tree_path ); } } return ( FALSE ); } static void history_dialog_destroy_cb( GtkObject *object, gpointer user_data ) { MessengerHistory *history = user_data; GtkTreePath *tree_path; tree_path = g_object_get_data( G_OBJECT( history->context_menu ), "tree-path" ); if ( tree_path ) { gtk_tree_path_free( tree_path ); } gtk_widget_destroy( history->context_menu ); history->dialog = NULL; } GtkWidget * messenger_history_create_dialog( MessengerHistory *history, GtkWindow *parent_window ) { GtkWidget *dialog; GtkWidget *vbox, *hbox; GtkWidget *treeview; GtkWidget *scrolledwindow; GtkWidget *spinner; GtkWidget *label; GtkWidget *menu_item; g_return_val_if_fail( history != NULL, NULL ); if ( history->dialog ) { return ( history->dialog ); } history->dialog = dialog = gtk_dialog_new_with_buttons( _( "Message history" ), parent_window, GTK_DIALOG_NO_SEPARATOR, GTK_STOCK_CLOSE, GTK_RESPONSE_CLOSE, NULL ); gtk_window_set_default_size( GTK_WINDOW( dialog ), 420, 300 ); gtk_container_set_border_width( GTK_CONTAINER( dialog ), BORDER / 2 ); vbox = GTK_DIALOG( dialog )->vbox; scrolledwindow = gtk_scrolled_window_new( NULL, NULL ); gtk_scrolled_window_set_shadow_type( GTK_SCROLLED_WINDOW( scrolledwindow ), GTK_SHADOW_ETCHED_IN ); gtk_scrolled_window_set_policy( GTK_SCROLLED_WINDOW( scrolledwindow ), GTK_POLICY_AUTOMATIC, GTK_POLICY_ALWAYS ); gtk_widget_show( scrolledwindow ); gtk_box_pack_start( GTK_BOX( vbox ), scrolledwindow, TRUE, TRUE, BORDER ); history->tree_view = treeview = gtk_tree_view_new_with_model( GTK_TREE_MODEL( history->store ) ); gtk_tree_view_set_headers_visible( GTK_TREE_VIEW( treeview ), FALSE ); gtk_tree_view_insert_column_with_attributes( GTK_TREE_VIEW( treeview ), -1, "", gtk_cell_renderer_text_new(), "text", HISTORY_COL_DATETIME, NULL ); gtk_tree_view_insert_column_with_attributes( GTK_TREE_VIEW( treeview ), -1, "", gtk_cell_renderer_text_new(), "text", HISTORY_COL_MESSAGE, NULL ); gtk_widget_show( treeview ); gtk_container_add( GTK_CONTAINER( scrolledwindow ), treeview ); g_signal_connect( G_OBJECT( treeview ), "button-press-event", G_CALLBACK( treeview_button_press_cb ), history ); hbox = gtk_hbox_new( FALSE, 0 ); gtk_widget_show( hbox ); gtk_box_pack_start( GTK_BOX( vbox ), hbox, FALSE, TRUE, BORDER ); label = gtk_label_new( _( "History length: " ) ); gtk_widget_show( label ); gtk_box_pack_start( GTK_BOX( hbox ), label, FALSE, FALSE, 0 ); spinner = gtk_spin_button_new_with_range( 1.0f, (gdouble) MESSENGER_HISTORY_MAX_LENGTH, 1.0f ); gtk_spin_button_set_value( GTK_SPIN_BUTTON( spinner ), (gdouble) history->length ); gtk_widget_show( spinner ); gtk_box_pack_start( GTK_BOX( hbox ), spinner, FALSE, TRUE, 0 ); g_signal_connect( G_OBJECT( spinner ), "value-changed", G_CALLBACK( history_dialog_histlen_changed_cb ), history ); g_signal_connect( G_OBJECT( dialog ), "destroy", G_CALLBACK( history_dialog_destroy_cb ), history ); g_signal_connect( G_OBJECT( dialog ), "response", G_CALLBACK( gtk_widget_destroy ), NULL ); /* Menu */ history->context_menu = gtk_menu_new(); menu_item = gtk_image_menu_item_new_from_stock( GTK_STOCK_COPY, NULL ); gtk_widget_show( menu_item ); g_object_set_data( G_OBJECT( menu_item ), "action-id", GINT_TO_POINTER( MENU_ACTION_COPY ) ); gtk_menu_shell_append( GTK_MENU_SHELL( history->context_menu ), menu_item ); g_signal_connect( G_OBJECT( menu_item ), "activate", G_CALLBACK( context_menu_item_activate_cb ), history ); menu_item = gtk_separator_menu_item_new(); gtk_widget_show( menu_item ); gtk_menu_shell_append( GTK_MENU_SHELL( history->context_menu ), menu_item ); menu_item = gtk_image_menu_item_new_from_stock( GTK_STOCK_DELETE, NULL ); gtk_widget_show( menu_item ); g_object_set_data( G_OBJECT( menu_item ), "action-id", GINT_TO_POINTER( MENU_ACTION_DELETE ) ); gtk_menu_shell_append( GTK_MENU_SHELL( history->context_menu ), menu_item ); g_signal_connect( G_OBJECT( menu_item ), "activate", G_CALLBACK( context_menu_item_activate_cb ), history ); return ( history->dialog ); } void messenger_history_free( MessengerHistory *history ) { g_return_if_fail( history != NULL ); if ( history->dialog ) { gtk_widget_destroy( history->dialog ); } if ( history->store ) { g_object_unref( G_OBJECT( history->store ) ); } g_free( history ); } void messenger_history_foreach( MessengerHistory *history, MessengerHistoryForeachFunc func, gpointer user_data ) { GtkTreeIter iter; gboolean iter_valid; guint index = 0; gchar *message, *timestr; g_return_if_fail( history != NULL ); g_return_if_fail( func != NULL ); iter_valid = gtk_tree_model_get_iter_first( GTK_TREE_MODEL( history->store ), &iter ); while ( iter_valid ) { gtk_tree_model_get( GTK_TREE_MODEL( history->store ), &iter, HISTORY_COL_DATETIME, ×tr, HISTORY_COL_MESSAGE, &message, -1 ); (*func)( index, message, timestr, user_data ); g_free( message ); g_free( timestr ); index++; iter_valid = gtk_tree_model_iter_next( GTK_TREE_MODEL( history->store ), &iter ); } }