source: trunk/gtk/msgwin.c @ 10130

Last change on this file since 10130 was 10130, checked in by charles, 12 years ago

(trunk gtk) make the debug messages easier to read in the message window. grey-on-white was just stupid.

  • Property svn:keywords set to Date Rev Author Id
File size: 16.9 KB
Line 
1/*
2 * This file Copyright (C) 2008-2010 Mnemosyne LLC
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 10130 2010-02-07 17:15:02Z 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,
59                        gpointer    gdata )
60{
61    struct MsgData * data = gdata;
62    GtkTreeIter      iter;
63
64    if( gtk_combo_box_get_active_iter( GTK_COMBO_BOX( w ), &iter ) )
65    {
66        int            level = 0;
67        GtkTreeModel * m = gtk_combo_box_get_model( GTK_COMBO_BOX( w ) );
68        gtk_tree_model_get( m, &iter, 1, &level, -1 );
69
70        tr_setMessageLevel( level );
71        tr_core_set_pref_int( data->core, TR_PREFS_KEY_MSGLEVEL, level );
72        data->maxLevel = level;
73        gtk_tree_model_filter_refilter( GTK_TREE_MODEL_FILTER( data->filter ) );
74    }
75}
76
77static void
78doSave( GtkWindow * parent, struct MsgData * data, const char * filename )
79{
80    FILE * fp = fopen( filename, "w+" );
81
82    if( !fp )
83    {
84        GtkWidget * w = gtk_message_dialog_new( parent, 0, GTK_MESSAGE_ERROR, GTK_BUTTONS_CLOSE, _( "Couldn't save \"%s\"" ), filename );
85        gtk_message_dialog_format_secondary_text( GTK_MESSAGE_DIALOG( w ), "%s", g_strerror( errno ) );
86        g_signal_connect_swapped( w, "response", G_CALLBACK( gtk_widget_destroy ), w );
87        gtk_widget_show( w );
88    }
89    else
90    {
91        GtkTreeIter iter;
92        GtkTreeModel * model = GTK_TREE_MODEL( data->sort );
93        if( gtk_tree_model_iter_children( model, &iter, NULL ) ) do
94        {
95            char * date;
96            const char * levelStr;
97            const struct tr_msg_list * node;
98
99            gtk_tree_model_get( model, &iter, COL_TR_MSG, &node, -1 );
100            date = gtr_localtime( 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,
107                     ( node->name ? node->name : "" ),
108                     ( node->message ? node->message : "" ) );
109            g_free( date );
110        }
111        while( gtk_tree_model_iter_next( model, &iter ) );
112
113        fclose( fp );
114    }
115}
116
117static void
118onSaveDialogResponse( GtkWidget * d, int response, gpointer data )
119{
120    if( response == GTK_RESPONSE_ACCEPT )
121    {
122        char * file = gtk_file_chooser_get_filename( GTK_FILE_CHOOSER( d ) );
123        doSave( GTK_WINDOW( d ), data, file );
124        g_free( file );
125    }
126
127    gtk_widget_destroy( d );
128}
129
130static void
131onSaveRequest( GtkWidget * w,
132               gpointer    data )
133{
134    GtkWindow * window = GTK_WINDOW( gtk_widget_get_toplevel( w ) );
135    GtkWidget * d = gtk_file_chooser_dialog_new( _( "Save Log" ), window,
136                                                 GTK_FILE_CHOOSER_ACTION_SAVE,
137                                                 GTK_STOCK_CANCEL,
138                                                 GTK_RESPONSE_CANCEL,
139                                                 GTK_STOCK_SAVE,
140                                                 GTK_RESPONSE_ACCEPT,
141                                                 NULL );
142
143    gtk_dialog_set_alternative_button_order( GTK_DIALOG( d ),
144                                             GTK_RESPONSE_ACCEPT,
145                                             GTK_RESPONSE_CANCEL,
146                                             -1 );
147    g_signal_connect( d, "response",
148                      G_CALLBACK( onSaveDialogResponse ), data );
149    gtk_widget_show( d );
150}
151
152static void
153onClearRequest( GtkWidget * w UNUSED,
154                gpointer      gdata )
155{
156    struct MsgData * data = gdata;
157
158    gtk_list_store_clear( data->store );
159    tr_freeMessageList( myHead );
160    myHead = myTail = NULL;
161}
162
163static void
164onPauseToggled( GtkToggleToolButton * w,
165                gpointer              gdata )
166{
167    struct MsgData * data = gdata;
168
169    data->isPaused = gtk_toggle_tool_button_get_active( w );
170}
171
172static struct
173{
174    const char *  label;
175    const char *  pref;
176    int           id;
177} trLevels[] = {
178    { N_( "Error" ),       "error",       TR_MSG_ERR          },
179    { N_( "Information" ), "info",        TR_MSG_INF          },
180    { N_( "Debug" ),       "debug",       TR_MSG_DBG          },
181};
182
183static const char*
184getForegroundColor( int msgLevel )
185{
186    const char * foreground;
187
188    switch( msgLevel )
189    {
190        case TR_MSG_DBG:
191            foreground = "forestgreen"; break;
192
193        case TR_MSG_INF:
194            foreground = "black"; break;
195
196        case TR_MSG_ERR:
197            foreground = "red"; break;
198
199        default:
200            g_assert_not_reached( );
201    }
202    return foreground;
203}
204
205static void
206renderText( GtkTreeViewColumn  * column UNUSED,
207            GtkCellRenderer *           renderer,
208            GtkTreeModel *              tree_model,
209            GtkTreeIter *               iter,
210            gpointer                    gcol )
211{
212    const int                  col = GPOINTER_TO_INT( gcol );
213    char *                     str = NULL;
214    const struct tr_msg_list * node;
215
216    gtk_tree_model_get( tree_model, iter, col, &str, COL_TR_MSG, &node, -1 );
217    g_object_set( renderer, "text", str,
218                  "foreground", getForegroundColor( node->level ),
219                  "ellipsize", PANGO_ELLIPSIZE_END,
220                  NULL );
221}
222
223static void
224renderTime( GtkTreeViewColumn  * column UNUSED,
225            GtkCellRenderer *           renderer,
226            GtkTreeModel *              tree_model,
227            GtkTreeIter *               iter,
228            gpointer             data   UNUSED )
229{
230    struct tm                  tm;
231    char                       buf[16];
232    const struct tr_msg_list * node;
233
234    gtk_tree_model_get( tree_model, iter, COL_TR_MSG, &node, -1 );
235    tm = *localtime( &node->when );
236    g_snprintf( buf, sizeof( buf ), "%02d:%02d:%02d", tm.tm_hour, tm.tm_min,
237                tm.tm_sec );
238    g_object_set ( renderer, "text", buf,
239                   "foreground", getForegroundColor( node->level ),
240                   NULL );
241}
242
243static void
244appendColumn( GtkTreeView * view,
245              int           col )
246{
247    GtkCellRenderer *   r;
248    GtkTreeViewColumn * c;
249    const char *        title = NULL;
250
251    switch( col )
252    {
253        case COL_SEQUENCE:
254            title = _( "Time" ); break;
255
256        /* noun.  column title for a list */
257        case COL_NAME:
258            title = _( "Name" ); break;
259
260        /* noun.  column title for a list */
261        case COL_MESSAGE:
262            title = _( "Message" ); break;
263
264        default:
265            g_assert_not_reached( );
266    }
267
268    switch( col )
269    {
270        case COL_NAME:
271            r = gtk_cell_renderer_text_new( );
272            c = gtk_tree_view_column_new_with_attributes( title, r, NULL );
273            gtk_tree_view_column_set_cell_data_func( c, r, renderText,
274                                                     GINT_TO_POINTER(
275                                                         col ), NULL );
276            gtk_tree_view_column_set_sizing( c, GTK_TREE_VIEW_COLUMN_FIXED );
277            gtk_tree_view_column_set_fixed_width( c, 200 );
278            gtk_tree_view_column_set_resizable( c, TRUE );
279            break;
280
281        case COL_MESSAGE:
282            r = gtk_cell_renderer_text_new( );
283            c = gtk_tree_view_column_new_with_attributes( title, r, NULL );
284            gtk_tree_view_column_set_cell_data_func( c, r, renderText,
285                                                     GINT_TO_POINTER(
286                                                         col ), NULL );
287            gtk_tree_view_column_set_sizing( c, GTK_TREE_VIEW_COLUMN_FIXED );
288            gtk_tree_view_column_set_fixed_width( c, 500 );
289            gtk_tree_view_column_set_resizable( c, TRUE );
290            break;
291
292        case COL_SEQUENCE:
293            r = gtk_cell_renderer_text_new( );
294            c = gtk_tree_view_column_new_with_attributes( title, r, NULL );
295            gtk_tree_view_column_set_cell_data_func( c, r, renderTime, NULL,
296                                                     NULL );
297            gtk_tree_view_column_set_resizable( c, TRUE );
298            break;
299
300        default:
301            g_assert_not_reached( );
302            break;
303    }
304
305    gtk_tree_view_column_set_sort_column_id( c, col );
306    gtk_tree_view_append_column( view, c );
307}
308
309static gboolean
310isRowVisible( GtkTreeModel * model,
311              GtkTreeIter *  iter,
312              gpointer       gdata )
313{
314    const struct MsgData *     data = gdata;
315    const struct tr_msg_list * node;
316
317    gtk_tree_model_get( model, iter, COL_TR_MSG, &node, -1 );
318    return node->level <= data->maxLevel;
319}
320
321static void
322onWindowDestroyed( gpointer             gdata,
323                   GObject * deadWindow UNUSED )
324{
325    struct MsgData * data = gdata;
326
327    g_source_remove( data->refresh_tag );
328    g_free( data );
329}
330
331static tr_msg_list *
332addMessages( GtkListStore *       store,
333             struct tr_msg_list * head )
334{
335    const char *        default_name = g_get_application_name( );
336    static unsigned int sequence = 1;
337    tr_msg_list *       i;
338
339    for( i = head; i; i = i->next )
340    {
341        GtkTreeIter unused;
342
343        gtk_list_store_insert_with_values( store, &unused, 0,
344                                           COL_TR_MSG, i,
345                                           COL_NAME,
346                                           ( i->name ? i->name :
347                                             default_name ),
348                                           COL_MESSAGE, i->message,
349                                           COL_SEQUENCE, sequence++,
350                                           -1 );
351
352        if( !i->next )
353            break;
354    }
355
356    return i; /* tail */
357}
358
359static gboolean
360onRefresh( gpointer gdata )
361{
362    struct MsgData * data = gdata;
363
364    if( !data->isPaused )
365    {
366        tr_msg_list * msgs = tr_getQueuedMessages( );
367        if( msgs )
368        {
369            /* add the new messages and append them to the end of
370             * our persistent list */
371            tr_msg_list * tail = addMessages( data->store, msgs );
372            if( myTail )
373                myTail->next = msgs;
374            else
375                myHead = msgs;
376            myTail = tail;
377        }
378    }
379    return TRUE;
380}
381
382static GtkWidget*
383debug_level_combo_new( void )
384{
385    unsigned int      i;
386    int               ii;
387    int               curlevel;
388    GtkWidget *       levels;
389    GtkListStore *    store;
390    GtkCellRenderer * renderer;
391
392    store = gtk_list_store_new ( 2, G_TYPE_STRING, G_TYPE_INT );
393
394    curlevel = pref_int_get( TR_PREFS_KEY_MSGLEVEL );
395    for( i = ii = 0; i < G_N_ELEMENTS( trLevels ); ++i )
396    {
397        GtkTreeIter iter;
398        gtk_list_store_append ( store, &iter );
399        gtk_list_store_set ( store, &iter, 0, _( trLevels[i].label ),
400                             1, trLevels[i].id,
401                             -1 );
402        if( trLevels[i].id == curlevel )
403            ii = i;
404    }
405    levels = gtk_combo_box_new_with_model ( GTK_TREE_MODEL( store ) );
406    g_object_unref( G_OBJECT( store ) );
407    store = NULL;
408
409    renderer = gtk_cell_renderer_text_new ( );
410    gtk_cell_layout_pack_start( GTK_CELL_LAYOUT( levels ), renderer, TRUE );
411    gtk_cell_layout_set_attributes( GTK_CELL_LAYOUT( levels ), renderer,
412                                    "text", 0,
413                                    NULL );
414    gtk_combo_box_set_active( GTK_COMBO_BOX( levels ), ii );
415
416    return levels;
417}
418
419/**
420***  Public Functions
421**/
422
423GtkWidget *
424msgwin_new( TrCore * core )
425{
426    GtkWidget *      win;
427    GtkWidget *      vbox;
428    GtkWidget *      toolbar;
429    GtkWidget *      w;
430    GtkWidget *      view;
431    GtkToolItem *    item;
432    struct MsgData * data;
433
434    data = g_new0( struct MsgData, 1 );
435    data->core = core;
436
437    win = gtk_window_new( GTK_WINDOW_TOPLEVEL );
438    gtk_window_set_title( GTK_WINDOW( win ), _( "Message Log" ) );
439    gtk_window_set_default_size( GTK_WINDOW( win ), 560, 350 );
440    gtk_window_set_role( GTK_WINDOW( win ), "message-log" );
441    vbox = gtk_vbox_new( FALSE, 0 );
442
443    /**
444    ***  toolbar
445    **/
446
447    toolbar = gtk_toolbar_new( );
448    gtr_toolbar_set_orientation( GTK_TOOLBAR( toolbar ), GTK_ORIENTATION_HORIZONTAL );
449    gtk_toolbar_set_style( GTK_TOOLBAR( toolbar ), GTK_TOOLBAR_BOTH_HORIZ );
450
451    item = gtk_tool_button_new_from_stock( GTK_STOCK_SAVE_AS );
452    g_object_set( G_OBJECT( item ), "is-important", TRUE, NULL );
453    g_signal_connect( item, "clicked", G_CALLBACK( onSaveRequest ), data );
454    gtk_toolbar_insert( GTK_TOOLBAR( toolbar ), item, -1 );
455
456    item = gtk_tool_button_new_from_stock( GTK_STOCK_CLEAR );
457    g_object_set( G_OBJECT( item ), "is-important", TRUE, NULL );
458    g_signal_connect( item, "clicked", G_CALLBACK( onClearRequest ), data );
459    gtk_toolbar_insert( GTK_TOOLBAR( toolbar ), item, -1 );
460
461    item = gtk_separator_tool_item_new( );
462    gtk_toolbar_insert( GTK_TOOLBAR( toolbar ), item, -1 );
463
464    item = gtk_toggle_tool_button_new_from_stock( GTK_STOCK_MEDIA_PAUSE );
465    g_object_set( G_OBJECT( item ), "is-important", TRUE, NULL );
466    g_signal_connect( item, "toggled", G_CALLBACK( onPauseToggled ), data );
467    gtk_toolbar_insert( GTK_TOOLBAR( toolbar ), item, -1 );
468
469    item = gtk_separator_tool_item_new( );
470    gtk_toolbar_insert( GTK_TOOLBAR( toolbar ), item, -1 );
471
472    w = gtk_label_new( _( "Level" ) );
473    gtk_misc_set_padding( GTK_MISC( w ), GUI_PAD, 0 );
474    item = gtk_tool_item_new( );
475    gtk_container_add( GTK_CONTAINER( item ), w );
476    gtk_toolbar_insert( GTK_TOOLBAR( toolbar ), item, -1 );
477
478    w = debug_level_combo_new( );
479    g_signal_connect( w, "changed", G_CALLBACK(
480                          level_combo_changed_cb ), data );
481    item = gtk_tool_item_new( );
482    gtk_container_add( GTK_CONTAINER( item ), w );
483    gtk_toolbar_insert( GTK_TOOLBAR( toolbar ), item, -1 );
484
485    gtk_box_pack_start( GTK_BOX( vbox ), toolbar, FALSE, FALSE, 0 );
486
487    /**
488    ***  messages
489    **/
490
491    data->store = gtk_list_store_new( N_COLUMNS,
492                                      G_TYPE_UINT,       /* sequence */
493                                      G_TYPE_POINTER,    /* category */
494                                      G_TYPE_POINTER,    /* message */
495                                      G_TYPE_POINTER );   /* struct tr_msg_list
496                                                            */
497
498    addMessages( data->store, myHead );
499    onRefresh( data ); /* much faster to populate *before* it has listeners */
500
501    data->filter = gtk_tree_model_filter_new( GTK_TREE_MODEL(
502                                                  data->store ), NULL );
503    data->sort = gtk_tree_model_sort_new_with_model( data->filter );
504    gtk_tree_sortable_set_sort_column_id( GTK_TREE_SORTABLE( data->sort ),
505                                          COL_SEQUENCE,
506                                          GTK_SORT_ASCENDING );
507    data->maxLevel = pref_int_get( TR_PREFS_KEY_MSGLEVEL );
508    gtk_tree_model_filter_set_visible_func( GTK_TREE_MODEL_FILTER( data->
509                                                                   filter ),
510                                            isRowVisible, data, NULL );
511
512
513    view = gtk_tree_view_new_with_model( data->sort );
514    g_signal_connect( view, "button-release-event",
515                      G_CALLBACK( on_tree_view_button_released ), NULL );
516    data->view = GTK_TREE_VIEW( view );
517    gtk_tree_view_set_rules_hint( data->view, TRUE );
518    appendColumn( data->view, COL_SEQUENCE );
519    appendColumn( data->view, COL_NAME );
520    appendColumn( data->view, COL_MESSAGE );
521    w = gtk_scrolled_window_new( NULL, NULL );
522    gtk_scrolled_window_set_policy( GTK_SCROLLED_WINDOW( w ),
523                                    GTK_POLICY_AUTOMATIC,
524                                    GTK_POLICY_AUTOMATIC );
525    gtk_scrolled_window_set_shadow_type( GTK_SCROLLED_WINDOW( w ),
526                                         GTK_SHADOW_IN );
527    gtk_container_add( GTK_CONTAINER( w ), view );
528    gtk_box_pack_start( GTK_BOX( vbox ), w, TRUE, TRUE, 0 );
529    gtk_container_add( GTK_CONTAINER( win ), vbox );
530
531    data->refresh_tag = gtr_timeout_add_seconds( SECONDARY_WINDOW_REFRESH_INTERVAL_SECONDS, onRefresh, data );
532    g_object_weak_ref( G_OBJECT( win ), onWindowDestroyed, data );
533
534    gtk_widget_show_all( win );
535    return win;
536}
537
Note: See TracBrowser for help on using the repository browser.