source: trunk/gtk/msgwin.c @ 12090

Last change on this file since 12090 was 12090, checked in by jordan, 11 years ago

(trunk gtk) minor copyediting for clarity/readability

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