source: trunk/gtk/msgwin.c @ 6402

Last change on this file since 6402 was 6402, checked in by charles, 14 years ago

(gtk) #1108: transmission includes several unlocalized strings

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