source: trunk/gtk/msgwin.c @ 7404

Last change on this file since 7404 was 7404, checked in by charles, 7 years ago

updated email address

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