source: trunk/gtk/msgwin.c @ 5922

Last change on this file since 5922 was 5922, checked in by charles, 14 years ago
  • change tr_stat.activityDate's and tr_stat.startedDate's type to time_t, since that's how everyone uses them anyway
  • add a little more documentation to tr_stat and tr_ctor in transmission.h
  • Property svn:keywords set to Date Rev Author Id
File size: 15.2 KB
Line 
1/*
2 * This file Copyright (C) 2008 Charles Kerr <charles@rebelbase.com>
3 *
4 * This file is licensed by the GPL version 2.  Works owned by the
5 * Transmission project are granted a special exemption to clause 2(b)
6 * so that the bulk of its code can remain under the MIT license.
7 * This exemption does not extend to derived works not owned by
8 * the Transmission project.
9 *
10 * $Id: msgwin.c 5922 2008-05-23 20:04:41Z charles $
11 */
12
13#include <errno.h>
14#include <stdio.h>
15#include <string.h>
16
17#include <glib/gi18n.h>
18#include <gtk/gtk.h>
19
20#include <libtransmission/transmission.h>
21
22#include "conf.h"
23#include "hig.h"
24#include "msgwin.h"
25#include "tr-core.h"
26#include "tr-prefs.h"
27#include "util.h"
28
29enum
30{
31    COL_SEQUENCE,
32    COL_NAME,
33    COL_MESSAGE,
34    COL_TR_MSG,
35    N_COLUMNS
36};
37
38struct MsgData
39{
40    TrCore * core;
41    GtkTreeView * view;
42    GtkListStore * store;
43    GtkTreeModel * filter;
44    GtkTreeModel * sort;
45    int maxLevel;
46    gboolean isPaused;
47    guint refresh_tag;
48};
49
50static struct tr_msg_list * myTail = NULL;
51static struct tr_msg_list * myHead = NULL;
52
53/***
54****
55***/
56
57static void
58level_combo_changed_cb( GtkWidget * w, gpointer gdata )
59{
60    struct MsgData * data = gdata;
61    GtkTreeIter iter;
62    if( gtk_combo_box_get_active_iter( GTK_COMBO_BOX(w), &iter ) )
63    {
64        int level = 0;
65        GtkTreeModel * m = gtk_combo_box_get_model( GTK_COMBO_BOX( w ) );
66        gtk_tree_model_get( m, &iter, 1, &level, -1 );
67
68        tr_setMessageLevel( level );
69        tr_core_set_pref_int( data->core, PREF_KEY_MSGLEVEL, level );
70        data->maxLevel = level;
71        gtk_tree_model_filter_refilter( GTK_TREE_MODEL_FILTER( data->filter ) );
72    }
73}
74
75static void
76doSave( GtkWindow      * parent,
77        struct MsgData * data,
78        const char     * filename )
79{
80    FILE * fp = fopen( filename, "w+" );
81    if( !fp )
82    {
83        errmsg( parent,
84                _("Couldn't save file \"%1$s\": %2$s"),
85                filename, g_strerror( errno ) );
86    }
87    else
88    {
89        GtkTreeIter iter;
90        GtkTreeModel * model = GTK_TREE_MODEL( data->sort );
91        if( gtk_tree_model_iter_children( model, &iter, NULL ) ) do
92        {
93            char * date;
94            const char * levelStr;
95            const struct tr_msg_list * node;
96
97            gtk_tree_model_get( model, &iter,
98                                COL_TR_MSG, &node,
99                                -1 );
100            date = rfc822date( node->when );
101            switch( node->level ) {
102                case TR_MSG_DBG: levelStr = "debug"; break;
103                case TR_MSG_ERR: levelStr = "error"; break;
104                default:         levelStr = "     "; break;
105            }
106            fprintf( fp, "%s\t%s\t%s\t%s\n", date, levelStr, node->name, node->message );
107
108            g_free( date );
109        }
110        while( gtk_tree_model_iter_next( model, &iter ) );
111        fclose( fp );
112    }
113}
114
115static void
116onSaveDialogResponse( GtkWidget * d, int response, gpointer data )
117{
118  if( response == GTK_RESPONSE_ACCEPT )
119  {
120      char * filename = gtk_file_chooser_get_filename( GTK_FILE_CHOOSER( d ) );
121      doSave( GTK_WINDOW( d ), data, filename );
122      g_free( filename );
123  }
124
125  gtk_widget_destroy( d );
126}
127
128static void
129onSaveRequest( GtkWidget * w, gpointer data )
130{
131  GtkWindow * window = GTK_WINDOW( gtk_widget_get_toplevel( w ) );
132  GtkWidget * d = gtk_file_chooser_dialog_new( _("Save Log"), window,
133                                               GTK_FILE_CHOOSER_ACTION_SAVE,
134                                               GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
135                                               GTK_STOCK_SAVE, GTK_RESPONSE_ACCEPT,
136                                               NULL );
137  gtk_dialog_set_alternative_button_order( GTK_DIALOG( d ),
138                                           GTK_RESPONSE_ACCEPT,
139                                           GTK_RESPONSE_CANCEL,
140                                           -1 );
141  g_signal_connect( d, "response",
142                    G_CALLBACK( onSaveDialogResponse ), data );
143  gtk_widget_show( d );
144}
145
146static void
147onClearRequest( GtkWidget * w UNUSED, gpointer gdata )
148{
149    struct MsgData * data = gdata;
150    gtk_list_store_clear( data->store );
151    tr_freeMessageList( myHead );
152    myHead = myTail = NULL;
153}
154
155static void
156onPauseToggled( GtkToggleToolButton * w, gpointer gdata )
157{
158    struct MsgData * data = gdata;
159    data->isPaused = gtk_toggle_tool_button_get_active( w );
160}
161
162static struct {
163  const char * label;
164  const char * pref;
165  int id;
166} trLevels[] = {
167  { N_("Error"), "error", TR_MSG_ERR },
168  { N_("Information"), "info",  TR_MSG_INF },
169  { N_("Debug"), "debug", TR_MSG_DBG },
170};
171
172static const char*
173getForegroundColor( int msgLevel )
174{
175    const char * foreground;
176    switch( msgLevel ) {
177        case TR_MSG_DBG: foreground = "gray"; break;
178        case TR_MSG_INF: foreground = "black"; break;
179        case TR_MSG_ERR: foreground = "red"; break;
180        default: g_assert_not_reached( );
181    }
182    return foreground;
183}
184
185static void
186renderText( GtkTreeViewColumn  * column UNUSED,
187            GtkCellRenderer    * renderer,
188            GtkTreeModel       * tree_model,
189            GtkTreeIter        * iter,
190            gpointer             gcol )
191{
192    const int col = GPOINTER_TO_INT( gcol );
193    char * str = NULL;
194    const struct tr_msg_list * node;
195    gtk_tree_model_get( tree_model, iter, col, &str, COL_TR_MSG, &node, -1 );
196    g_object_set( renderer, "text", str,
197                            "foreground", getForegroundColor( node->level ),
198                            "ellipsize", PANGO_ELLIPSIZE_END,
199                            NULL );
200}
201
202static void
203renderTime( GtkTreeViewColumn  * column UNUSED,
204            GtkCellRenderer    * renderer,
205            GtkTreeModel       * tree_model,
206            GtkTreeIter        * iter,
207            gpointer             data UNUSED )
208{
209    struct tm tm;
210    char buf[16];
211    const struct tr_msg_list * node;
212
213    gtk_tree_model_get(tree_model, iter, COL_TR_MSG, &node, -1 );
214    tm = *localtime( &node->when );
215    g_snprintf( buf, sizeof( buf ), "%02d:%02d:%02d", tm.tm_hour, tm.tm_min, tm.tm_sec );
216    g_object_set (renderer, "text", buf,
217                            "foreground", getForegroundColor( node->level ),
218                            NULL );
219}
220
221static void
222appendColumn( GtkTreeView * view, int col )
223{
224    GtkCellRenderer * r;
225    GtkTreeViewColumn * c;
226    const char * title = NULL;
227
228    switch( col ) {
229        case COL_SEQUENCE: title = _( "Time" ); break;
230        /* noun.  column title for a list */
231        case COL_NAME: title = _( "Name"); break;
232        /* noun.  column title for a list */
233        case COL_MESSAGE:  title = _( "Message" ); break;
234        default: g_assert_not_reached( );
235    }
236
237    switch( col )
238    {
239        case COL_NAME:
240            r = gtk_cell_renderer_text_new( );
241            c = gtk_tree_view_column_new_with_attributes( title, r, NULL );
242            gtk_tree_view_column_set_cell_data_func( c, r, renderText, GINT_TO_POINTER(col), NULL );
243            gtk_tree_view_column_set_sizing( c, GTK_TREE_VIEW_COLUMN_FIXED );
244            gtk_tree_view_column_set_fixed_width( c, 200 );
245            gtk_tree_view_column_set_resizable( c, TRUE );
246            break;
247
248        case COL_MESSAGE:
249            r = gtk_cell_renderer_text_new( );
250            c = gtk_tree_view_column_new_with_attributes( title, r, NULL );
251            gtk_tree_view_column_set_cell_data_func( c, r, renderText, GINT_TO_POINTER(col), NULL );
252            gtk_tree_view_column_set_sizing( c, GTK_TREE_VIEW_COLUMN_FIXED );
253            gtk_tree_view_column_set_fixed_width( c, 500 );
254            gtk_tree_view_column_set_resizable( c, TRUE );
255            break;
256
257        case COL_SEQUENCE:
258            r = gtk_cell_renderer_text_new( );
259            c = gtk_tree_view_column_new_with_attributes( title, r, NULL );
260            gtk_tree_view_column_set_cell_data_func( c, r, renderTime, NULL, NULL );
261            gtk_tree_view_column_set_resizable( c, TRUE );
262            break;
263
264        default:
265            g_assert_not_reached( );
266            break;
267    }
268
269    gtk_tree_view_column_set_sort_column_id( c, col );
270    gtk_tree_view_append_column( view, c );
271}
272
273static gboolean
274isRowVisible( GtkTreeModel * model, GtkTreeIter * iter, gpointer gdata )
275{
276    const struct MsgData * data = gdata;
277    const struct tr_msg_list * node;
278    gtk_tree_model_get( model, iter, COL_TR_MSG, &node, -1 );
279    return node->level <= data->maxLevel;
280}
281
282static void
283onWindowDestroyed( gpointer gdata, GObject * deadWindow UNUSED )
284{
285    struct MsgData * data = gdata;
286    g_source_remove( data->refresh_tag );
287    g_free( data );
288}
289
290static tr_msg_list *
291addMessages( GtkListStore * store, struct tr_msg_list * head )
292{
293    const char * default_name = g_get_application_name( );
294    static unsigned int sequence = 1;
295    tr_msg_list * i;
296
297    for( i=head; i; i=i->next )
298    {
299        GtkTreeIter unused;
300
301        gtk_list_store_insert_with_values( store, &unused, 0,
302                                           COL_TR_MSG, i,
303                                           COL_NAME, ( i->name ? i->name : default_name ),
304                                           COL_MESSAGE, i->message,
305                                           COL_SEQUENCE, sequence++,
306                                           -1 );
307
308         if( !i->next )
309             break;
310    }
311
312    return i; /* tail */
313}
314
315static gboolean
316onRefresh( gpointer gdata )
317{
318    struct MsgData * data = gdata;
319    if( !data->isPaused )
320    {
321        tr_msg_list * msgs = tr_getQueuedMessages( );
322        if( msgs )
323        {
324            /* add the new messages and append them to the end of
325             * our persistent list */
326            tr_msg_list * tail = addMessages( data->store, msgs );
327            if( myTail )
328                myTail->next = msgs;
329            else
330                myHead = msgs;
331            myTail = tail;
332        }
333    }
334    return TRUE;
335}
336
337static GtkWidget*
338debug_level_combo_new( void )
339{
340    unsigned int i;
341    int ii;
342    int curlevel;
343    GtkWidget * levels;
344    GtkListStore * store;
345    GtkCellRenderer * renderer;
346
347    store = gtk_list_store_new (2, G_TYPE_STRING, G_TYPE_INT);
348
349    curlevel = pref_int_get( PREF_KEY_MSGLEVEL );
350    for( i=ii=0; i<G_N_ELEMENTS(trLevels); ++i ) {
351        GtkTreeIter iter;
352        gtk_list_store_append (store, &iter);
353        gtk_list_store_set (store, &iter, 0, _(trLevels[i].label),
354                                          1, trLevels[i].id,
355                                         -1);
356        if( trLevels[i].id == curlevel )
357            ii = i;
358    }
359    levels = gtk_combo_box_new_with_model (GTK_TREE_MODEL(store));
360    g_object_unref( G_OBJECT( store ) );
361    store = NULL;
362
363    renderer = gtk_cell_renderer_text_new ();
364    gtk_cell_layout_pack_start( GTK_CELL_LAYOUT(levels), renderer, TRUE );
365    gtk_cell_layout_set_attributes( GTK_CELL_LAYOUT(levels), renderer,
366                                    "text", 0,
367                                    NULL );
368    gtk_combo_box_set_active( GTK_COMBO_BOX( levels ), ii );
369
370    return levels;
371}
372
373
374/**
375***  Public Functions
376**/
377
378GtkWidget *
379msgwin_new( TrCore * core )
380{
381    GtkWidget * win;
382    GtkWidget * vbox;
383    GtkWidget * toolbar;
384    GtkWidget * w;
385    GtkWidget * view;
386    GtkToolItem * item;
387    struct MsgData * data;
388
389    data = g_new0( struct MsgData, 1 );
390    data->core = core;
391
392    win = gtk_window_new( GTK_WINDOW_TOPLEVEL );
393    gtk_window_set_title( GTK_WINDOW( win ), _( "Message Log" ) );
394    gtk_window_set_default_size( GTK_WINDOW( win ), 560, 350 );
395    gtk_window_set_role( GTK_WINDOW( win ), "message-log" );
396    vbox = gtk_vbox_new( FALSE, 0 );
397
398    /**
399    ***  toolbar
400    **/
401
402    toolbar = gtk_toolbar_new( );
403    gtk_toolbar_set_orientation( GTK_TOOLBAR( toolbar ), GTK_ORIENTATION_HORIZONTAL );
404    gtk_toolbar_set_style( GTK_TOOLBAR( toolbar ), GTK_TOOLBAR_BOTH_HORIZ );
405
406      item = gtk_tool_button_new_from_stock( GTK_STOCK_SAVE );
407      g_object_set( G_OBJECT( item ), "is-important", TRUE, NULL );
408      g_signal_connect( item, "clicked", G_CALLBACK(onSaveRequest), data );
409      gtk_toolbar_insert( GTK_TOOLBAR( toolbar ), item, -1 );
410
411      item = gtk_tool_button_new_from_stock( GTK_STOCK_CLEAR );
412      g_object_set( G_OBJECT( item ), "is-important", TRUE, NULL );
413      g_signal_connect( item, "clicked", G_CALLBACK(onClearRequest), data );
414      gtk_toolbar_insert( GTK_TOOLBAR( toolbar ), item, -1 );
415
416    item = gtk_separator_tool_item_new( );
417    gtk_toolbar_insert( GTK_TOOLBAR( toolbar ), item, -1 );
418
419      item = gtk_toggle_tool_button_new_from_stock( GTK_STOCK_MEDIA_PAUSE );
420      g_object_set( G_OBJECT( item ), "is-important", TRUE, NULL );
421      g_signal_connect( item, "toggled", G_CALLBACK(onPauseToggled), data );
422      gtk_toolbar_insert( GTK_TOOLBAR( toolbar ), item, -1 );
423
424    item = gtk_separator_tool_item_new( );
425    gtk_toolbar_insert( GTK_TOOLBAR( toolbar ), item, -1 );
426
427      w = gtk_label_new( _( "Level" ) );
428      gtk_misc_set_padding( GTK_MISC( w ), GUI_PAD, 0 );
429      item = gtk_tool_item_new( );
430      gtk_container_add( GTK_CONTAINER( item ), w );
431      gtk_toolbar_insert( GTK_TOOLBAR( toolbar ), item, -1 );
432
433      w = debug_level_combo_new( );
434      g_signal_connect( w, "changed", G_CALLBACK(level_combo_changed_cb), data );
435      item = gtk_tool_item_new( );
436      gtk_container_add( GTK_CONTAINER( item ), w );
437      gtk_toolbar_insert( GTK_TOOLBAR( toolbar ), item, -1 );
438
439    gtk_box_pack_start( GTK_BOX( vbox ), toolbar, FALSE, FALSE, 0 );
440
441    /**
442    ***  messages
443    **/
444
445    data->store = gtk_list_store_new( N_COLUMNS,
446                                      G_TYPE_UINT,       /* sequence */
447                                      G_TYPE_POINTER,    /* category */
448                                      G_TYPE_POINTER,    /* message */
449                                      G_TYPE_POINTER);   /* struct tr_msg_list */
450
451    addMessages( data->store, myHead );
452    onRefresh( data ); /* much faster to populate *before* it has listeners */
453
454    data->filter = gtk_tree_model_filter_new( GTK_TREE_MODEL( data->store ), NULL );
455    data->sort = gtk_tree_model_sort_new_with_model( data->filter );
456    gtk_tree_sortable_set_sort_column_id( GTK_TREE_SORTABLE( data->sort ),
457                                          COL_SEQUENCE,
458                                          GTK_SORT_ASCENDING );
459    data->maxLevel = pref_int_get( PREF_KEY_MSGLEVEL );
460    gtk_tree_model_filter_set_visible_func( GTK_TREE_MODEL_FILTER( data->filter ),
461                                            isRowVisible, data, NULL );
462
463
464    view = gtk_tree_view_new_with_model( data->sort );
465    data->view = GTK_TREE_VIEW( view );
466    gtk_tree_view_set_rules_hint( data->view, TRUE );
467    appendColumn( data->view, COL_SEQUENCE );
468    appendColumn( data->view, COL_NAME );
469    appendColumn( data->view, COL_MESSAGE );
470    w = gtk_scrolled_window_new( NULL, NULL );
471    gtk_scrolled_window_set_policy( GTK_SCROLLED_WINDOW( w ),
472                                    GTK_POLICY_AUTOMATIC,
473                                    GTK_POLICY_AUTOMATIC );
474    gtk_scrolled_window_set_shadow_type( GTK_SCROLLED_WINDOW( w ),
475                                         GTK_SHADOW_IN);
476    gtk_container_add( GTK_CONTAINER( w ), view );
477    gtk_box_pack_start( GTK_BOX( vbox ), w, TRUE, TRUE, 0 );
478    gtk_container_add( GTK_CONTAINER( win ), vbox );
479
480    data->refresh_tag = g_timeout_add( 1666, onRefresh, data );
481    g_object_weak_ref( G_OBJECT( win ), onWindowDestroyed, data );
482
483    gtk_widget_show_all( win );
484    return win;
485}
Note: See TracBrowser for help on using the repository browser.