source: trunk/gtk/msgwin.c @ 8846

Last change on this file since 8846 was 8846, checked in by charles, 13 years ago

remove some unused utilities. better commenting on the utils that remain.

  • Property svn:keywords set to Date Rev Author Id
File size: 16.9 KB
Line 
1/*
2 * This file Copyright (C) 2008-2009 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 8846 2009-07-22 15:55:48Z 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 = "gray"; 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 );
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( 2, 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.