source: trunk/gtk/msgwin.c @ 11599

Last change on this file since 11599 was 11599, checked in by charles, 11 years ago

(trunk) Join the 21st century and use only 1 space at the end sentences. This commit is nearly as important as the semi-annual ones that remove trailing spaces from the ends of lines of code... :)

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