source: trunk/gtk/msgwin.c @ 5266

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

(gtk) set the default sort column & mode for the message window's list

  • Property svn:keywords set to Date Rev Author Id
File size: 14.5 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 5266 2008-03-16 21:41:27Z 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_CATEGORY,
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    guint refresh_tag;
47};
48
49static struct tr_msg_list * myTail = NULL;
50static struct tr_msg_list * myHead = NULL;
51
52/***
53****
54***/
55
56static void
57level_combo_changed_cb( GtkWidget * w, gpointer gdata )
58{
59    struct MsgData * data = gdata;
60    GtkTreeIter iter;
61    if( gtk_combo_box_get_active_iter( GTK_COMBO_BOX(w), &iter ) )
62    {
63        int level = 0;
64        GtkTreeModel * m = gtk_combo_box_get_model( GTK_COMBO_BOX( w ) );
65        gtk_tree_model_get( m, &iter, 1, &level, -1 );
66
67        tr_setMessageLevel( level );
68        tr_core_set_pref_int( data->core, PREF_KEY_MSGLEVEL, level );
69        data->maxLevel = level;
70        gtk_tree_model_filter_refilter( GTK_TREE_MODEL_FILTER( data->filter ) );
71    }
72}
73
74static void
75doSave( GtkWindow      * parent,
76        struct MsgData * data,
77        const char     * filename )
78{
79    FILE * fp = fopen( filename, "w+" );
80    if( !fp )
81    {
82        errmsg( parent,
83                _("Couldn't write file \"%s\": %s"),
84                filename, g_strerror( errno ) );
85    }
86    else
87    {
88        GtkTreeIter iter;
89        GtkTreeModel * model = GTK_TREE_MODEL( data->sort );
90        if( gtk_tree_model_iter_children( model, &iter, NULL ) ) do
91        {
92            char * date;
93            const char * levelStr;
94            const struct tr_msg_list * node;
95
96            gtk_tree_model_get( model, &iter,
97                                COL_TR_MSG, &node,
98                                -1 );
99            date = rfc822date( node->when*1000u );
100            switch( node->level ) {
101                case TR_MSG_DBG: levelStr = "debug"; break;
102                case TR_MSG_ERR: levelStr = "error"; break;
103                default:         levelStr = "     "; break;
104            }
105            fprintf( fp, "%s\t%s\t%s\t%s\n", date, levelStr, node->name, node->message );
106
107            g_free( date );
108        }
109        while( gtk_tree_model_iter_next( model, &iter ) );
110        fclose( fp );
111    }
112}
113
114static void
115onSaveDialogResponse( GtkWidget * d, int response, gpointer data )
116{
117  if( response == GTK_RESPONSE_ACCEPT )
118  {
119      char * filename = gtk_file_chooser_get_filename( GTK_FILE_CHOOSER( d ) );
120      doSave( GTK_WINDOW( d ), data, filename );
121      g_free( filename );
122  }
123
124  gtk_widget_destroy( d );
125}
126
127static void
128onSaveRequest( GtkWidget * w, gpointer data )
129{
130  GtkWindow * window = GTK_WINDOW( gtk_widget_get_toplevel( w ) );
131  GtkWidget * d = gtk_file_chooser_dialog_new( _("Save Log"), window,
132                                               GTK_FILE_CHOOSER_ACTION_SAVE,
133                                               GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
134                                               GTK_STOCK_SAVE, GTK_RESPONSE_ACCEPT,
135                                               NULL );
136  gtk_dialog_set_alternative_button_order( GTK_DIALOG( d ),
137                                           GTK_RESPONSE_ACCEPT,
138                                           GTK_RESPONSE_CANCEL,
139                                           -1 );
140  g_signal_connect( d, "response",
141                    G_CALLBACK( onSaveDialogResponse ), data );
142  gtk_widget_show( d );
143}
144
145static void
146onClearRequest( GtkWidget * w UNUSED, gpointer gdata )
147{
148    struct MsgData * data = gdata;
149    gtk_list_store_clear( data->store );
150    tr_freeMessageList( myHead );
151    myHead = myTail = NULL;
152}
153
154static struct {
155  const char * label;
156  const char * pref;
157  int id;
158} trLevels[] = {
159  { N_("Error"), "error", TR_MSG_ERR },
160  { N_("Information"), "info",  TR_MSG_INF },
161  { N_("Debug"), "debug", TR_MSG_DBG },
162};
163
164static const char*
165getForegroundColor( int msgLevel )
166{
167    const char * foreground;
168    switch( msgLevel ) {
169        case TR_MSG_DBG: foreground = "gray"; break;
170        case TR_MSG_INF: foreground = "black"; break;
171        case TR_MSG_ERR: foreground = "red"; break;
172        default: g_assert_not_reached( );
173    }
174    return foreground;
175}
176
177static void
178renderText( GtkTreeViewColumn  * column UNUSED,
179            GtkCellRenderer    * renderer,
180            GtkTreeModel       * tree_model,
181            GtkTreeIter        * iter,
182            gpointer             gcol )
183{
184    const int col = GPOINTER_TO_INT( gcol );
185    char * str = NULL;
186    const struct tr_msg_list * node;
187    gtk_tree_model_get( tree_model, iter, col, &str, COL_TR_MSG, &node, -1 );
188    g_object_set( renderer, "text", str,
189                            "foreground", getForegroundColor( node->level ),
190                            "ellipsize", PANGO_ELLIPSIZE_END,
191                            NULL );
192}
193
194static void
195renderTime( GtkTreeViewColumn  * column UNUSED,
196            GtkCellRenderer    * renderer,
197            GtkTreeModel       * tree_model,
198            GtkTreeIter        * iter,
199            gpointer             data UNUSED )
200{
201    struct tm tm;
202    char buf[16];
203    const struct tr_msg_list * node;
204
205    gtk_tree_model_get(tree_model, iter, COL_TR_MSG, &node, -1 );
206    tm = *localtime( &node->when );
207    g_snprintf( buf, sizeof( buf ), "%02d:%02d:%02d", tm.tm_hour, tm.tm_min, tm.tm_sec );
208    g_object_set (renderer, "text", buf,
209                            "foreground", getForegroundColor( node->level ),
210                            NULL );
211}
212
213static void
214appendColumn( GtkTreeView * view, int col )
215{
216    GtkCellRenderer * r;
217    GtkTreeViewColumn * c;
218    const char * title = NULL;
219
220    switch( col ) {
221        case COL_SEQUENCE: title = _( "Time" ); break;
222        case COL_CATEGORY: title = _( "Name"); break;
223        case COL_MESSAGE:  title = _( "Message" ); break;
224        default: g_assert_not_reached( );
225    }
226
227    switch( col )
228    {
229        case COL_CATEGORY:
230            r = gtk_cell_renderer_text_new( );
231            c = gtk_tree_view_column_new_with_attributes( title, r, NULL );
232            gtk_tree_view_column_set_cell_data_func( c, r, renderText, GINT_TO_POINTER(col), NULL );
233            gtk_tree_view_column_set_sizing( c, GTK_TREE_VIEW_COLUMN_FIXED );
234            gtk_tree_view_column_set_fixed_width( c, 200 );
235            gtk_tree_view_column_set_resizable( c, TRUE );
236            break;
237
238        case COL_MESSAGE:
239            r = gtk_cell_renderer_text_new( );
240            c = gtk_tree_view_column_new_with_attributes( title, r, NULL );
241            gtk_tree_view_column_set_cell_data_func( c, r, renderText, GINT_TO_POINTER(col), NULL );
242            gtk_tree_view_column_set_sizing( c, GTK_TREE_VIEW_COLUMN_FIXED );
243            gtk_tree_view_column_set_fixed_width( c, 500 );
244            gtk_tree_view_column_set_resizable( c, TRUE );
245            break;
246
247        case COL_SEQUENCE:
248            r = gtk_cell_renderer_text_new( );
249            c = gtk_tree_view_column_new_with_attributes( title, r, NULL );
250            gtk_tree_view_column_set_cell_data_func( c, r, renderTime, NULL, NULL );
251            gtk_tree_view_column_set_resizable( c, TRUE );
252            break;
253
254        default:
255            g_assert_not_reached( );
256            break;
257    }
258
259    gtk_tree_view_column_set_sort_column_id( c, col );
260    gtk_tree_view_append_column( view, c );
261}
262
263static gboolean
264isRowVisible( GtkTreeModel * model, GtkTreeIter * iter, gpointer gdata )
265{
266    const struct MsgData * data = gdata;
267    const struct tr_msg_list * node;
268    gtk_tree_model_get( model, iter, COL_TR_MSG, &node, -1 );
269    return node->level <= data->maxLevel;
270}
271
272static void
273onWindowDestroyed( gpointer gdata, GObject * deadWindow UNUSED )
274{
275    struct MsgData * data = gdata;
276    g_source_remove( data->refresh_tag );
277    g_free( data );
278}
279
280static tr_msg_list *
281addMessages( GtkListStore * store, struct tr_msg_list * head )
282{
283    const char * default_category = g_get_application_name( );
284    static int sequence = 1;
285    tr_msg_list * i;
286
287    for( i=head; i; i=i->next )
288    {
289        GtkTreeIter unused;
290
291        gtk_list_store_insert_with_values( store, &unused, 0,
292                                           COL_TR_MSG, i,
293                                           COL_CATEGORY, ( i->name ? i->name : default_category ),
294                                           COL_MESSAGE, i->message,
295                                           COL_SEQUENCE, sequence++,
296                                           -1 );
297
298         if( !i->next )
299             break;
300    }
301
302    return i; /* tail */
303}
304
305static gboolean
306onRefresh( gpointer gdata )
307{
308    tr_msg_list * msgs = tr_getQueuedMessages( );
309    if( msgs )
310    {
311        /* add the new messages and append them to the end of
312         * our persistent list */
313        struct MsgData * data = gdata;
314        tr_msg_list * tail = addMessages( data->store, msgs );
315        if( myTail )
316            myTail->next = msgs;
317        else
318            myHead = msgs;
319        myTail = tail;
320    }
321    return TRUE;
322}
323
324/**
325***  Public Functions
326**/
327
328GtkWidget *
329msgwin_new( TrCore * core )
330{
331    unsigned int i;
332    GtkListStore * store;
333    GtkWidget * win;
334    GtkWidget * vbox;
335    GtkWidget * levels;
336    GtkWidget * toolbar;
337    GtkWidget * w;
338    GtkWidget * view;
339    GtkWidget * l;
340    GtkCellRenderer * renderer;
341    int ii, curlevel;
342    struct MsgData * data;
343
344    data = g_new0( struct MsgData, 1 );
345    data->core = core;
346
347    win = gtk_window_new( GTK_WINDOW_TOPLEVEL );
348    gtk_window_set_title( GTK_WINDOW( win ), _( "Message Log" ) );
349    gtk_window_set_default_size( GTK_WINDOW( win ), 600, 400 );
350    gtk_window_set_role( GTK_WINDOW( win ), "message-log" );
351    vbox = gtk_vbox_new( FALSE, 0 );
352
353    /**
354    ***  toolbar
355    **/
356
357    toolbar = gtk_toolbar_new( );
358
359    gtk_toolbar_insert_stock( GTK_TOOLBAR(toolbar), GTK_STOCK_SAVE,
360                              NULL, NULL,
361                              G_CALLBACK(onSaveRequest), data, -1);
362
363    gtk_toolbar_insert_stock( GTK_TOOLBAR(toolbar), GTK_STOCK_CLEAR,
364                              NULL, NULL,
365                              G_CALLBACK(onClearRequest), data, -1);
366
367    gtk_toolbar_insert_space(GTK_TOOLBAR(toolbar), -1);
368
369
370    l = gtk_label_new( _( "Level" ) );
371    gtk_misc_set_padding( GTK_MISC( l ), GUI_PAD, 0 );
372    gtk_toolbar_append_element( GTK_TOOLBAR(toolbar),
373                                GTK_TOOLBAR_CHILD_WIDGET, l,
374                                NULL, _("Set the verbosity level"),
375                                NULL, NULL, NULL, NULL);
376
377    w = gtk_alignment_new( 0.0f, 0.0f, 0.0f, 0.0f );
378    gtk_widget_set_size_request( w, GUI_PAD_SMALL, GUI_PAD_SMALL );
379    gtk_toolbar_append_element( GTK_TOOLBAR(toolbar),
380                                GTK_TOOLBAR_CHILD_WIDGET, w,
381                                NULL, NULL, NULL, NULL, NULL, NULL);
382
383    store = gtk_list_store_new (2, G_TYPE_STRING, G_TYPE_INT);
384
385    curlevel = pref_int_get( PREF_KEY_MSGLEVEL );
386    for( i=ii=0; i<G_N_ELEMENTS(trLevels); ++i ) {
387        GtkTreeIter iter;
388        gtk_list_store_append (store, &iter);
389        gtk_list_store_set (store, &iter, 0, _(trLevels[i].label),
390                                          1, trLevels[i].id,
391                                         -1);
392        if( trLevels[i].id == curlevel )
393            ii = i;
394    }
395    levels = gtk_combo_box_new_with_model (GTK_TREE_MODEL(store));
396    g_object_unref( G_OBJECT( store ) );
397    store = NULL;
398
399    renderer = gtk_cell_renderer_text_new ();
400    gtk_cell_layout_pack_start( GTK_CELL_LAYOUT(levels), renderer, TRUE );
401    gtk_cell_layout_set_attributes( GTK_CELL_LAYOUT(levels), renderer,
402                                    "text", 0,
403                                    NULL );
404    gtk_combo_box_set_active( GTK_COMBO_BOX( levels ), ii );
405    g_signal_connect( levels, "changed",
406                      G_CALLBACK(level_combo_changed_cb), data );
407
408    gtk_toolbar_append_element( GTK_TOOLBAR( toolbar ),
409                                GTK_TOOLBAR_CHILD_WIDGET, levels,
410                                NULL, _("Set the verbosity level"),
411                                NULL, NULL, NULL, NULL);
412
413    gtk_box_pack_start( GTK_BOX( vbox ), toolbar, FALSE, FALSE, 0 );
414
415    /**
416    ***  messages
417    **/
418
419    data->store = gtk_list_store_new( N_COLUMNS,
420                                      G_TYPE_INT,        /* sequence */
421                                      G_TYPE_POINTER,    /* category */
422                                      G_TYPE_POINTER,    /* message */
423                                      G_TYPE_POINTER);   /* struct tr_msg_list */
424
425    addMessages( data->store, myHead );
426    onRefresh( data ); /* much faster to populate *before* it has listeners */
427
428    data->filter = gtk_tree_model_filter_new( GTK_TREE_MODEL( data->store ), NULL );
429    data->sort = gtk_tree_model_sort_new_with_model( data->filter );
430    gtk_tree_sortable_set_sort_column_id( GTK_TREE_SORTABLE( data->sort ),
431                                          COL_SEQUENCE,
432                                          GTK_SORT_ASCENDING );
433    data->maxLevel = pref_int_get( PREF_KEY_MSGLEVEL );
434    gtk_tree_model_filter_set_visible_func( GTK_TREE_MODEL_FILTER( data->filter ),
435                                            isRowVisible, data, NULL );
436
437
438    view = gtk_tree_view_new_with_model( data->sort );
439    data->view = GTK_TREE_VIEW( view );
440    gtk_tree_view_set_rules_hint( data->view, TRUE );
441    appendColumn( data->view, COL_SEQUENCE );
442    appendColumn( data->view, COL_CATEGORY );
443    appendColumn( data->view, COL_MESSAGE );
444    w = gtk_scrolled_window_new( NULL, NULL );
445    gtk_scrolled_window_set_policy( GTK_SCROLLED_WINDOW( w ),
446                                    GTK_POLICY_AUTOMATIC,
447                                    GTK_POLICY_AUTOMATIC );
448    gtk_scrolled_window_set_shadow_type( GTK_SCROLLED_WINDOW( w ),
449                                         GTK_SHADOW_IN);
450    gtk_container_add( GTK_CONTAINER( w ), view );
451    gtk_box_pack_start( GTK_BOX( vbox ), w, TRUE, TRUE, 0 );
452    gtk_container_add( GTK_CONTAINER( win ), vbox );
453
454    data->refresh_tag = g_timeout_add( 1000, onRefresh, data );
455    g_object_weak_ref( G_OBJECT( win ), onWindowDestroyed, data );
456
457    gtk_widget_show_all( win );
458    return win;
459}
Note: See TracBrowser for help on using the repository browser.