1
0
Fork 0
mirror of https://github.com/transmission/transmission synced 2024-12-27 01:57:52 +00:00
transmission/gtk/msgwin.c
Jordan Lee 879a2afcbd Update the copyright year in the source code comments.
The Berne Convention says that the copyright year is moot, so instead of adding another year to each file as in previous years, I've removed the year altogether from the source code comments in libtransmission, gtk, qt, utils, daemon, and cli.

Juliusz's copyright notice in tr-dht and Johannes' copyright notice in tr-lpd have been left alone; it didn't seem appropriate to modify them.
2011-01-19 13:48:47 +00:00

540 lines
17 KiB
C

/*
* This file Copyright (C) Mnemosyne LLC
*
* This file is licensed by the GPL version 2. Works owned by the
* Transmission project are granted a special exemption to clause 2(b)
* so that the bulk of its code can remain under the MIT license.
* This exemption does not extend to derived works not owned by
* the Transmission project.
*
* $Id$
*/
#include <errno.h>
#include <stdio.h>
#include <string.h>
#include <glib/gi18n.h>
#include <gtk/gtk.h>
#include <libtransmission/transmission.h>
#include "conf.h"
#include "hig.h"
#include "tr-core.h"
#include "msgwin.h"
#include "tr-prefs.h"
#include "util.h"
enum
{
COL_SEQUENCE,
COL_NAME,
COL_MESSAGE,
COL_TR_MSG,
N_COLUMNS
};
struct MsgData
{
TrCore * core;
GtkTreeView * view;
GtkListStore * store;
GtkTreeModel * filter;
GtkTreeModel * sort;
tr_msg_level maxLevel;
gboolean isPaused;
guint refresh_tag;
};
static struct tr_msg_list * myTail = NULL;
static struct tr_msg_list * myHead = NULL;
/****
*****
****/
/* is the user looking at the latest messages? */
static gboolean
is_pinned_to_new( struct MsgData * data )
{
gboolean pinned_to_new;
if( data->view == NULL )
{
pinned_to_new = TRUE;
}
else
{
GtkTreePath * last_visible;
if( gtk_tree_view_get_visible_range( data->view, NULL, &last_visible ) )
{
GtkTreeIter iter;
const int row_count = gtk_tree_model_iter_n_children( data->sort, NULL );
if( gtk_tree_model_iter_nth_child( data->sort, &iter, NULL, row_count-1 ) )
{
GtkTreePath * last_row = gtk_tree_model_get_path( data->sort, &iter );
pinned_to_new = !gtk_tree_path_compare( last_visible, last_row );
gtk_tree_path_free( last_row );
}
gtk_tree_path_free( last_visible );
}
}
return pinned_to_new;
}
static void
scroll_to_bottom( struct MsgData * data )
{
if( data->sort != NULL )
{
GtkTreeIter iter;
const int row_count = gtk_tree_model_iter_n_children( data->sort, NULL );
if( gtk_tree_model_iter_nth_child( data->sort, &iter, NULL, row_count-1 ) )
{
GtkTreePath * last_row = gtk_tree_model_get_path( data->sort, &iter );
gtk_tree_view_scroll_to_cell( data->view, last_row, NULL, TRUE, 1, 0 );
gtk_tree_path_free( last_row );
}
}
}
/****
*****
****/
static void
level_combo_changed_cb( GtkComboBox * combo_box, gpointer gdata )
{
struct MsgData * data = gdata;
const int level = gtr_combo_box_get_active_enum( combo_box );
const gboolean pinned_to_new = is_pinned_to_new( data );
tr_setMessageLevel( level );
tr_core_set_pref_int( data->core, TR_PREFS_KEY_MSGLEVEL, level );
data->maxLevel = level;
gtk_tree_model_filter_refilter( GTK_TREE_MODEL_FILTER( data->filter ) );
if( pinned_to_new )
scroll_to_bottom( data );
}
static void
doSave( GtkWindow * parent, struct MsgData * data, const char * filename )
{
FILE * fp = fopen( filename, "w+" );
if( !fp )
{
GtkWidget * w = gtk_message_dialog_new( parent, 0, GTK_MESSAGE_ERROR, GTK_BUTTONS_CLOSE, _( "Couldn't save \"%s\"" ), filename );
gtk_message_dialog_format_secondary_text( GTK_MESSAGE_DIALOG( w ), "%s", g_strerror( errno ) );
g_signal_connect_swapped( w, "response", G_CALLBACK( gtk_widget_destroy ), w );
gtk_widget_show( w );
}
else
{
GtkTreeIter iter;
GtkTreeModel * model = GTK_TREE_MODEL( data->sort );
if( gtk_tree_model_iter_children( model, &iter, NULL ) ) do
{
char * date;
const char * levelStr;
const struct tr_msg_list * node;
gtk_tree_model_get( model, &iter, COL_TR_MSG, &node, -1 );
date = gtr_localtime( node->when );
switch( node->level ) {
case TR_MSG_DBG: levelStr = "debug"; break;
case TR_MSG_ERR: levelStr = "error"; break;
default: levelStr = " "; break;
}
fprintf( fp, "%s\t%s\t%s\t%s\n", date, levelStr,
( node->name ? node->name : "" ),
( node->message ? node->message : "" ) );
g_free( date );
}
while( gtk_tree_model_iter_next( model, &iter ) );
fclose( fp );
}
}
static void
onSaveDialogResponse( GtkWidget * d, int response, gpointer data )
{
if( response == GTK_RESPONSE_ACCEPT )
{
char * file = gtk_file_chooser_get_filename( GTK_FILE_CHOOSER( d ) );
doSave( GTK_WINDOW( d ), data, file );
g_free( file );
}
gtk_widget_destroy( d );
}
static void
onSaveRequest( GtkWidget * w,
gpointer data )
{
GtkWindow * window = GTK_WINDOW( gtk_widget_get_toplevel( w ) );
GtkWidget * d = gtk_file_chooser_dialog_new( _( "Save Log" ), window,
GTK_FILE_CHOOSER_ACTION_SAVE,
GTK_STOCK_CANCEL,
GTK_RESPONSE_CANCEL,
GTK_STOCK_SAVE,
GTK_RESPONSE_ACCEPT,
NULL );
gtk_dialog_set_alternative_button_order( GTK_DIALOG( d ),
GTK_RESPONSE_ACCEPT,
GTK_RESPONSE_CANCEL,
-1 );
g_signal_connect( d, "response",
G_CALLBACK( onSaveDialogResponse ), data );
gtk_widget_show( d );
}
static void
onClearRequest( GtkWidget * w UNUSED, gpointer gdata )
{
struct MsgData * data = gdata;
gtk_list_store_clear( data->store );
tr_freeMessageList( myHead );
myHead = myTail = NULL;
}
static void
onPauseToggled( GtkToggleToolButton * w, gpointer gdata )
{
struct MsgData * data = gdata;
data->isPaused = gtk_toggle_tool_button_get_active( w );
}
static const char*
getForegroundColor( int msgLevel )
{
const char * foreground;
switch( msgLevel )
{
case TR_MSG_DBG:
foreground = "forestgreen"; break;
case TR_MSG_INF:
foreground = "black"; break;
case TR_MSG_ERR:
foreground = "red"; break;
default:
g_assert_not_reached( );
}
return foreground;
}
static void
renderText( GtkTreeViewColumn * column UNUSED,
GtkCellRenderer * renderer,
GtkTreeModel * tree_model,
GtkTreeIter * iter,
gpointer gcol )
{
const int col = GPOINTER_TO_INT( gcol );
char * str = NULL;
const struct tr_msg_list * node;
gtk_tree_model_get( tree_model, iter, col, &str, COL_TR_MSG, &node, -1 );
g_object_set( renderer, "text", str,
"foreground", getForegroundColor( node->level ),
"ellipsize", PANGO_ELLIPSIZE_END,
NULL );
}
static void
renderTime( GtkTreeViewColumn * column UNUSED,
GtkCellRenderer * renderer,
GtkTreeModel * tree_model,
GtkTreeIter * iter,
gpointer data UNUSED )
{
struct tm tm;
char buf[16];
const struct tr_msg_list * node;
gtk_tree_model_get( tree_model, iter, COL_TR_MSG, &node, -1 );
tm = *localtime( &node->when );
g_snprintf( buf, sizeof( buf ), "%02d:%02d:%02d", tm.tm_hour, tm.tm_min,
tm.tm_sec );
g_object_set ( renderer, "text", buf,
"foreground", getForegroundColor( node->level ),
NULL );
}
static void
appendColumn( GtkTreeView * view,
int col )
{
GtkCellRenderer * r;
GtkTreeViewColumn * c;
const char * title = NULL;
switch( col )
{
case COL_SEQUENCE:
title = _( "Time" ); break;
/* noun. column title for a list */
case COL_NAME:
title = _( "Name" ); break;
/* noun. column title for a list */
case COL_MESSAGE:
title = _( "Message" ); break;
default:
g_assert_not_reached( );
}
switch( col )
{
case COL_NAME:
r = gtk_cell_renderer_text_new( );
c = gtk_tree_view_column_new_with_attributes( title, r, NULL );
gtk_tree_view_column_set_cell_data_func( c, r, renderText,
GINT_TO_POINTER(
col ), NULL );
gtk_tree_view_column_set_sizing( c, GTK_TREE_VIEW_COLUMN_FIXED );
gtk_tree_view_column_set_fixed_width( c, 200 );
gtk_tree_view_column_set_resizable( c, TRUE );
break;
case COL_MESSAGE:
r = gtk_cell_renderer_text_new( );
c = gtk_tree_view_column_new_with_attributes( title, r, NULL );
gtk_tree_view_column_set_cell_data_func( c, r, renderText,
GINT_TO_POINTER(
col ), NULL );
gtk_tree_view_column_set_sizing( c, GTK_TREE_VIEW_COLUMN_FIXED );
gtk_tree_view_column_set_fixed_width( c, 500 );
gtk_tree_view_column_set_resizable( c, TRUE );
break;
case COL_SEQUENCE:
r = gtk_cell_renderer_text_new( );
c = gtk_tree_view_column_new_with_attributes( title, r, NULL );
gtk_tree_view_column_set_cell_data_func( c, r, renderTime, NULL,
NULL );
gtk_tree_view_column_set_resizable( c, TRUE );
break;
default:
g_assert_not_reached( );
break;
}
gtk_tree_view_column_set_sort_column_id( c, col );
gtk_tree_view_append_column( view, c );
}
static gboolean
isRowVisible( GtkTreeModel * model, GtkTreeIter * iter, gpointer gdata )
{
const struct MsgData * data = gdata;
const struct tr_msg_list * node;
gtk_tree_model_get( model, iter, COL_TR_MSG, &node, -1 );
return node->level <= data->maxLevel;
}
static void
onWindowDestroyed( gpointer gdata, GObject * deadWindow UNUSED )
{
struct MsgData * data = gdata;
g_source_remove( data->refresh_tag );
g_free( data );
}
static tr_msg_list *
addMessages( GtkListStore * store, struct tr_msg_list * head )
{
tr_msg_list * i;
static unsigned int sequence = 0;
const char * default_name = g_get_application_name( );
for( i=head; i && i->next; i=i->next )
{
const char * name = i->name ? i->name : default_name;
gtk_list_store_insert_with_values( store, NULL, 0,
COL_TR_MSG, i,
COL_NAME, name,
COL_MESSAGE, i->message,
COL_SEQUENCE, ++sequence,
-1 );
}
return i; /* tail */
}
static gboolean
onRefresh( gpointer gdata )
{
struct MsgData * data = gdata;
const gboolean pinned_to_new = is_pinned_to_new( data );
if( !data->isPaused )
{
tr_msg_list * msgs = tr_getQueuedMessages( );
if( msgs )
{
/* add the new messages and append them to the end of
* our persistent list */
tr_msg_list * tail = addMessages( data->store, msgs );
if( myTail )
myTail->next = msgs;
else
myHead = msgs;
myTail = tail;
}
if( pinned_to_new )
scroll_to_bottom( data );
}
return TRUE;
}
static GtkWidget*
debug_level_combo_new( void )
{
GtkWidget * w = gtr_combo_box_new_enum( _( "Error" ), TR_MSG_ERR,
_( "Information" ), TR_MSG_INF,
_( "Debug" ), TR_MSG_DBG,
NULL );
gtr_combo_box_set_active_enum( GTK_COMBO_BOX( w ), gtr_pref_int_get( TR_PREFS_KEY_MSGLEVEL ) );
return w;
}
/**
*** Public Functions
**/
GtkWidget *
gtr_message_log_window_new( GtkWindow * parent, TrCore * core )
{
GtkWidget * win;
GtkWidget * vbox;
GtkWidget * toolbar;
GtkWidget * w;
GtkWidget * view;
GtkToolItem * item;
struct MsgData * data;
data = g_new0( struct MsgData, 1 );
data->core = core;
win = gtk_window_new( GTK_WINDOW_TOPLEVEL );
gtk_window_set_transient_for( GTK_WINDOW( win ), parent );
gtk_window_set_title( GTK_WINDOW( win ), _( "Message Log" ) );
gtk_window_set_default_size( GTK_WINDOW( win ), 560, 350 );
gtk_window_set_role( GTK_WINDOW( win ), "message-log" );
vbox = gtk_vbox_new( FALSE, 0 );
/**
*** toolbar
**/
toolbar = gtk_toolbar_new( );
gtk_toolbar_set_style( GTK_TOOLBAR( toolbar ), GTK_TOOLBAR_BOTH_HORIZ );
item = gtk_tool_button_new_from_stock( GTK_STOCK_SAVE_AS );
g_object_set( G_OBJECT( item ), "is-important", TRUE, NULL );
g_signal_connect( item, "clicked", G_CALLBACK( onSaveRequest ), data );
gtk_toolbar_insert( GTK_TOOLBAR( toolbar ), item, -1 );
item = gtk_tool_button_new_from_stock( GTK_STOCK_CLEAR );
g_object_set( G_OBJECT( item ), "is-important", TRUE, NULL );
g_signal_connect( item, "clicked", G_CALLBACK( onClearRequest ), data );
gtk_toolbar_insert( GTK_TOOLBAR( toolbar ), item, -1 );
item = gtk_separator_tool_item_new( );
gtk_toolbar_insert( GTK_TOOLBAR( toolbar ), item, -1 );
item = gtk_toggle_tool_button_new_from_stock( GTK_STOCK_MEDIA_PAUSE );
g_object_set( G_OBJECT( item ), "is-important", TRUE, NULL );
g_signal_connect( item, "toggled", G_CALLBACK( onPauseToggled ), data );
gtk_toolbar_insert( GTK_TOOLBAR( toolbar ), item, -1 );
item = gtk_separator_tool_item_new( );
gtk_toolbar_insert( GTK_TOOLBAR( toolbar ), item, -1 );
w = gtk_label_new( _( "Level" ) );
gtk_misc_set_padding( GTK_MISC( w ), GUI_PAD, 0 );
item = gtk_tool_item_new( );
gtk_container_add( GTK_CONTAINER( item ), w );
gtk_toolbar_insert( GTK_TOOLBAR( toolbar ), item, -1 );
w = debug_level_combo_new( );
g_signal_connect( w, "changed", G_CALLBACK( level_combo_changed_cb ), data );
item = gtk_tool_item_new( );
gtk_container_add( GTK_CONTAINER( item ), w );
gtk_toolbar_insert( GTK_TOOLBAR( toolbar ), item, -1 );
gtk_box_pack_start( GTK_BOX( vbox ), toolbar, FALSE, FALSE, 0 );
/**
*** messages
**/
data->store = gtk_list_store_new( N_COLUMNS,
G_TYPE_UINT, /* sequence */
G_TYPE_POINTER, /* category */
G_TYPE_POINTER, /* message */
G_TYPE_POINTER ); /* struct tr_msg_list
*/
addMessages( data->store, myHead );
onRefresh( data ); /* much faster to populate *before* it has listeners */
data->filter = gtk_tree_model_filter_new( GTK_TREE_MODEL(
data->store ), NULL );
data->sort = gtk_tree_model_sort_new_with_model( data->filter );
gtk_tree_sortable_set_sort_column_id( GTK_TREE_SORTABLE( data->sort ),
COL_SEQUENCE,
GTK_SORT_ASCENDING );
data->maxLevel = gtr_pref_int_get( TR_PREFS_KEY_MSGLEVEL );
gtk_tree_model_filter_set_visible_func( GTK_TREE_MODEL_FILTER( data->
filter ),
isRowVisible, data, NULL );
view = gtk_tree_view_new_with_model( data->sort );
g_signal_connect( view, "button-release-event",
G_CALLBACK( on_tree_view_button_released ), NULL );
data->view = GTK_TREE_VIEW( view );
gtk_tree_view_set_rules_hint( data->view, TRUE );
appendColumn( data->view, COL_SEQUENCE );
appendColumn( data->view, COL_NAME );
appendColumn( data->view, COL_MESSAGE );
w = gtk_scrolled_window_new( NULL, NULL );
gtk_scrolled_window_set_policy( GTK_SCROLLED_WINDOW( w ),
GTK_POLICY_AUTOMATIC,
GTK_POLICY_AUTOMATIC );
gtk_scrolled_window_set_shadow_type( GTK_SCROLLED_WINDOW( w ),
GTK_SHADOW_IN );
gtk_container_add( GTK_CONTAINER( w ), view );
gtk_box_pack_start( GTK_BOX( vbox ), w, TRUE, TRUE, 0 );
gtk_container_add( GTK_CONTAINER( win ), vbox );
data->refresh_tag = gtr_timeout_add_seconds( SECONDARY_WINDOW_REFRESH_INTERVAL_SECONDS, onRefresh, data );
g_object_weak_ref( G_OBJECT( win ), onWindowDestroyed, data );
scroll_to_bottom( data );
gtk_widget_show_all( win );
return win;
}