source: trunk/gtk/tr-window.c @ 10108

Last change on this file since 10108 was 10108, checked in by charles, 12 years ago

(trunk gtk) don't dereference a NULL pointer if a torrent has no "name"

  • Property svn:keywords set to Date Rev Author Id
File size: 41.6 KB
Line 
1/******************************************************************************
2 * $Id: tr-window.c 10108 2010-02-06 05:21:25Z charles $
3 *
4 * Copyright (c) 2005-2008 Transmission authors and contributors
5 *
6 * Permission is hereby granted, free of charge, to any person obtaining a
7 * copy of this software and associated documentation files (the "Software"),
8 * to deal in the Software without restriction, including without limitation
9 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
10 * and/or sell copies of the Software, and to permit persons to whom the
11 * Software is furnished to do so, subject to the following conditions:
12 *
13 * The above copyright notice and this permission notice shall be included in
14 * all copies or substantial portions of the Software.
15 *
16 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
21 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
22 * DEALINGS IN THE SOFTWARE.
23 *****************************************************************************/
24
25#include <string.h>
26
27#include <gtk/gtk.h>
28#include <glib/gi18n.h>
29
30#if !GTK_CHECK_VERSION( 2,16,0 )
31 /* FIXME: when 2.16 has been out long enough, it would be really nice to
32  * get rid of this libsexy usage because of its makefile strangeness */
33 #define USE_SEXY
34 #include "sexy-icon-entry.h"
35#endif
36
37#include <libtransmission/transmission.h>
38
39#include "actions.h"
40#include "conf.h"
41#include "hig.h"
42#include "torrent-cell-renderer.h"
43#include "tr-prefs.h"
44#include "tr-torrent.h"
45#include "tr-window.h"
46#include "util.h"
47
48#if !GTK_CHECK_VERSION( 2, 8, 0 )
49static void
50gtk_tree_view_column_queue_resize( GtkTreeViewColumn * column ) /* yuck */
51{
52    const int spacing = gtk_tree_view_column_get_spacing( column );
53
54    gtk_tree_view_column_set_spacing( column, spacing + 1 );
55    gtk_tree_view_column_set_spacing( column, spacing );
56}
57
58#endif
59
60typedef enum
61{
62    FILTER_TEXT_MODE_NAME,
63    FILTER_TEXT_MODE_FILES,
64    FILTER_TEXT_MODE_TRACKER,
65    FILTER_TEXT_MODE_QTY
66}
67filter_text_mode_t;
68
69typedef enum
70{
71    FILTER_MODE_ALL,
72    FILTER_MODE_ACTIVE,
73    FILTER_MODE_DOWNLOADING,
74    FILTER_MODE_SEEDING,
75    FILTER_MODE_PAUSED,
76    FILTER_MODE_QTY
77}
78filter_mode_t;
79
80typedef struct
81{
82    GtkWidget *           speedlimit_on_item[2];
83    GtkWidget *           speedlimit_off_item[2];
84    GtkWidget *           ratio_on_item;
85    GtkWidget *           ratio_off_item;
86    GtkWidget *           scroll;
87    GtkWidget *           view;
88    GtkWidget *           toolbar;
89    GtkWidget *           filter;
90    GtkWidget *           status;
91    GtkWidget *           status_menu;
92    GtkWidget *           ul_lb;
93    GtkWidget *           dl_lb;
94    GtkWidget *           stats_lb;
95    GtkWidget *           gutter_lb;
96    GtkWidget *           alt_speed_image;
97    GtkWidget *           alt_speed_button;
98    GtkWidget *           options_menu;
99    GtkTreeSelection *    selection;
100    GtkCellRenderer *     renderer;
101    GtkTreeViewColumn *   column;
102    GtkTreeModel *        filter_model;
103    TrCore *              core;
104    gulong                pref_handler_id;
105    filter_mode_t         filter_mode;
106    filter_text_mode_t    filter_text_mode;
107    char *                filter_text;
108    GtkToggleButton     * filter_toggles[FILTER_MODE_QTY];
109}
110PrivateData;
111
112static const char*
113getFilterName( int mode )
114{
115    switch( mode )
116    {
117        case FILTER_MODE_ACTIVE:      return "show-active";
118        case FILTER_MODE_DOWNLOADING: return "show-downloading";
119        case FILTER_MODE_SEEDING:     return "show-seeding";
120        case FILTER_MODE_PAUSED:      return "show-paused";
121        default:                      return "show-all"; /* the fallback */
122    }
123}
124static int
125getFilterModeFromName( const char * name )
126{
127    if( !strcmp( name, "show-active"      ) ) return FILTER_MODE_ACTIVE;
128    if( !strcmp( name, "show-downloading" ) ) return FILTER_MODE_DOWNLOADING;
129    if( !strcmp( name, "show-seeding"     ) ) return FILTER_MODE_SEEDING;
130    if( !strcmp( name, "show-paused"      ) ) return FILTER_MODE_PAUSED;
131    return FILTER_MODE_ALL; /* the fallback */
132}
133
134#define PRIVATE_DATA_KEY "private-data"
135#define FILTER_MODE_KEY "tr-filter-mode"
136#define FILTER_TEXT_MODE_KEY "tr-filter-text-mode"
137
138static PrivateData*
139get_private_data( TrWindow * w )
140{
141    return g_object_get_data ( G_OBJECT( w ), PRIVATE_DATA_KEY );
142}
143
144/***
145****
146***/
147
148static void
149on_popup_menu( GtkWidget * self UNUSED,
150               GdkEventButton * event )
151{
152    GtkWidget * menu = action_get_widget ( "/main-window-popup" );
153
154    gtk_menu_popup ( GTK_MENU( menu ), NULL, NULL, NULL, NULL,
155                    ( event ? event->button : 0 ),
156                    ( event ? event->time : 0 ) );
157}
158
159static void
160view_row_activated( GtkTreeView       * tree_view UNUSED,
161                    GtkTreePath       * path      UNUSED,
162                    GtkTreeViewColumn * column    UNUSED,
163                    gpointer            user_data UNUSED )
164{
165    action_activate( "show-torrent-properties" );
166}
167
168static gboolean is_row_visible( GtkTreeModel *,
169                                GtkTreeIter  *,
170                                gpointer );
171
172static GtkWidget*
173makeview( PrivateData * p,
174          TrCore *      core )
175{
176    GtkWidget *         view;
177    GtkTreeViewColumn * col;
178    GtkTreeSelection *  sel;
179    GtkCellRenderer *   r;
180    GtkTreeModel *      filter_model;
181
182    view = gtk_tree_view_new( );
183    gtk_tree_view_set_headers_visible( GTK_TREE_VIEW( view ), FALSE );
184    gtk_tree_view_set_fixed_height_mode( GTK_TREE_VIEW( view ), TRUE );
185
186    p->selection = gtk_tree_view_get_selection( GTK_TREE_VIEW( view ) );
187
188    p->column = col = GTK_TREE_VIEW_COLUMN (g_object_new (GTK_TYPE_TREE_VIEW_COLUMN,
189        "title", _("Torrent"),
190        "resizable", TRUE,
191        "sizing", GTK_TREE_VIEW_COLUMN_FIXED,
192        NULL));
193
194    p->renderer = r = torrent_cell_renderer_new( );
195    gtk_tree_view_column_pack_start( col, r, FALSE );
196    gtk_tree_view_column_add_attribute( col, r, "torrent", MC_TORRENT_RAW );
197    gtk_tree_view_column_add_attribute( col, r, "piece-upload-speed", MC_SPEED_UP );
198    gtk_tree_view_column_add_attribute( col, r, "piece-download-speed", MC_SPEED_DOWN );
199
200    gtk_tree_view_append_column( GTK_TREE_VIEW( view ), col );
201    g_object_set( r, "xpad", GUI_PAD_SMALL, "ypad", GUI_PAD_SMALL, NULL );
202
203    gtk_tree_view_set_rules_hint( GTK_TREE_VIEW( view ), TRUE );
204    sel = gtk_tree_view_get_selection( GTK_TREE_VIEW( view ) );
205    gtk_tree_selection_set_mode( GTK_TREE_SELECTION( sel ),
206                                 GTK_SELECTION_MULTIPLE );
207
208    g_signal_connect( view, "popup-menu",
209                      G_CALLBACK( on_popup_menu ), NULL );
210    g_signal_connect( view, "button-press-event",
211                      G_CALLBACK( on_tree_view_button_pressed ),
212                      (void *) on_popup_menu );
213    g_signal_connect( view, "button-release-event",
214                      G_CALLBACK( on_tree_view_button_released ), NULL );
215    g_signal_connect( view, "row-activated",
216                      G_CALLBACK( view_row_activated ), NULL );
217
218
219    filter_model = p->filter_model = gtk_tree_model_filter_new(
220                       tr_core_model( core ), NULL );
221
222    gtk_tree_model_filter_set_visible_func( GTK_TREE_MODEL_FILTER(
223                                                filter_model ),
224                                            is_row_visible,
225                                            p, NULL );
226
227    gtk_tree_view_set_model( GTK_TREE_VIEW( view ), filter_model );
228
229    return view;
230}
231
232static void syncAltSpeedButton( PrivateData * p );
233static void setFilter( PrivateData * p, int mode );
234
235static void
236prefsChanged( TrCore * core UNUSED,
237              const char *  key,
238              gpointer      wind )
239{
240    PrivateData * p = get_private_data( GTK_WINDOW( wind ) );
241
242    if( !strcmp( key, PREF_KEY_MINIMAL_VIEW ) )
243    {
244        g_object_set( p->renderer, "minimal", pref_flag_get( key ), NULL );
245        /* since the cell size has changed, we need gtktreeview to revalidate
246         * its fixed-height mode values.  Unfortunately there's not an API call
247         * for that, but it *does* revalidate when it thinks the style's been tweaked */
248        g_signal_emit_by_name( p->view, "style-set", NULL, NULL );
249    }
250    else if( !strcmp( key, PREF_KEY_FILTER_MODE ) )
251    {
252        setFilter( p, getFilterModeFromName( pref_string_get( key ) ) );
253    }
254    else if( !strcmp( key, PREF_KEY_STATUSBAR ) )
255    {
256        const gboolean isEnabled = pref_flag_get( key );
257        g_object_set( p->status, "visible", isEnabled, NULL );
258    }
259    else if( !strcmp( key, PREF_KEY_FILTERBAR ) )
260    {
261        const gboolean isEnabled = pref_flag_get( key );
262        g_object_set( p->filter, "visible", isEnabled, NULL );
263    }
264    else if( !strcmp( key, PREF_KEY_TOOLBAR ) )
265    {
266        const gboolean isEnabled = pref_flag_get( key );
267        g_object_set( p->toolbar, "visible", isEnabled, NULL );
268    }
269    else if( !strcmp( key, PREF_KEY_STATUSBAR_STATS ) )
270    {
271        tr_window_update( (TrWindow*)wind );
272    }
273    else if( !strcmp( key, TR_PREFS_KEY_ALT_SPEED_ENABLED ) ||
274             !strcmp( key, TR_PREFS_KEY_ALT_SPEED_UP ) ||
275             !strcmp( key, TR_PREFS_KEY_ALT_SPEED_DOWN ) )
276    {
277        syncAltSpeedButton( p );
278    }
279}
280
281static void
282privateFree( gpointer vprivate )
283{
284    PrivateData * p = vprivate;
285    g_signal_handler_disconnect( p->core, p->pref_handler_id );
286    g_free( p->filter_text );
287    g_free( p );
288}
289
290static void
291onYinYangReleased( GtkWidget * w UNUSED, gpointer vprivate )
292{
293    PrivateData * p = vprivate;
294
295    gtk_menu_popup( GTK_MENU( p->status_menu ),
296                    NULL, NULL, NULL, NULL, 0,
297                    gtk_get_current_event_time( ) );
298}
299
300#define STATS_MODE "stats-mode"
301
302static struct
303{
304    const char *  val, *i18n;
305} stats_modes[] = {
306    { "total-ratio",      N_( "Total Ratio" )                },
307    { "session-ratio",    N_( "Session Ratio" )              },
308    { "total-transfer",   N_( "Total Transfer" )             },
309    { "session-transfer", N_( "Session Transfer" )           }
310};
311
312static void
313status_menu_toggled_cb( GtkCheckMenuItem * menu_item,
314                        gpointer           vprivate )
315{
316    if( gtk_check_menu_item_get_active( menu_item ) )
317    {
318        PrivateData * p = vprivate;
319        const char *  val = g_object_get_data( G_OBJECT(
320                                                   menu_item ), STATS_MODE );
321        tr_core_set_pref( p->core, PREF_KEY_STATUSBAR_STATS, val );
322    }
323}
324
325static void
326syncAltSpeedButton( PrivateData * p )
327{
328    char u[32];
329    char d[32];
330    char * str;
331    const char * fmt;
332    const gboolean b = pref_flag_get( TR_PREFS_KEY_ALT_SPEED_ENABLED );
333    const char * stock = b ? "alt-speed-on" : "alt-speed-off";
334    GtkWidget * w = p->alt_speed_button;
335
336    tr_strlspeed( u, pref_int_get( TR_PREFS_KEY_ALT_SPEED_UP ), sizeof( u ) );
337    tr_strlspeed( d, pref_int_get( TR_PREFS_KEY_ALT_SPEED_DOWN ), sizeof( d ) );
338    fmt = b ? _( "Click to disable Temporary Speed Limits\n(%1$s down, %2$s up)" )
339            : _( "Click to enable Temporary Speed Limits\n(%1$s down, %2$s up)" );
340    str = g_strdup_printf( fmt, d, u );
341
342    gtk_toggle_button_set_active( GTK_TOGGLE_BUTTON( w ), b );
343    gtk_image_set_from_stock( GTK_IMAGE( p->alt_speed_image ), stock, -1 );
344    gtk_button_set_alignment( GTK_BUTTON( w ), 0.5, 0.5 );
345    gtr_widget_set_tooltip_text( w, str );
346
347    g_free( str );
348}
349
350static void
351alt_speed_toggled_cb( GtkToggleButton * button, gpointer vprivate )
352{
353    PrivateData * p = vprivate;
354    const gboolean b = gtk_toggle_button_get_active( button );
355    tr_core_set_pref_bool( p->core, TR_PREFS_KEY_ALT_SPEED_ENABLED,  b );
356}
357
358/***
359****  FILTER
360***/
361
362static int
363checkFilterText( filter_text_mode_t    filter_text_mode,
364                 const tr_info       * inf,
365                 const char          * text )
366{
367    tr_file_index_t i;
368    int             ret = 0;
369    char *          pch;
370
371    switch( filter_text_mode )
372    {
373        case FILTER_TEXT_MODE_FILES:
374            for( i = 0; i < inf->fileCount && !ret; ++i )
375            {
376                pch = g_utf8_casefold( inf->files[i].name, -1 );
377                ret = !text || strstr( pch, text ) != NULL;
378                g_free( pch );
379            }
380            break;
381
382        case FILTER_TEXT_MODE_TRACKER:
383            if( inf->trackerCount > 0 )
384            {
385                pch = g_utf8_casefold( inf->trackers[0].announce, -1 );
386                ret = !text || ( strstr( pch, text ) != NULL );
387                g_free( pch );
388            }
389            break;
390
391        default: /* NAME */
392            if( !inf->name )
393                ret = TRUE;
394            else {
395                pch = g_utf8_casefold( inf->name, -1 );
396                ret = !text || ( strstr( pch, text ) != NULL );
397                g_free( pch );
398            }
399            break;
400    }
401
402    return ret;
403}
404
405static int
406checkFilterMode( filter_mode_t filter_mode,
407                 tr_torrent *  tor )
408{
409    int ret = 0;
410
411    switch( filter_mode )
412    {
413        case FILTER_MODE_DOWNLOADING:
414            ret = tr_torrentGetActivity( tor ) == TR_STATUS_DOWNLOAD;
415            break;
416
417        case FILTER_MODE_SEEDING:
418            ret = tr_torrentGetActivity( tor ) == TR_STATUS_SEED;
419            break;
420
421        case FILTER_MODE_PAUSED:
422            ret = tr_torrentGetActivity( tor ) == TR_STATUS_STOPPED;
423            break;
424
425        case FILTER_MODE_ACTIVE:
426        {
427            const tr_stat * s = tr_torrentStatCached( tor );
428            ret = s->peersSendingToUs > 0
429               || s->peersGettingFromUs > 0
430               || tr_torrentGetActivity( tor ) == TR_STATUS_CHECK;
431            break;
432        }
433
434        default: /* all */
435            ret = 1;
436    }
437
438    return ret;
439}
440
441static gboolean
442is_row_visible( GtkTreeModel * model,
443                GtkTreeIter *  iter,
444                gpointer       vprivate )
445{
446    PrivateData * p = vprivate;
447    tr_torrent *  tor;
448
449    gtk_tree_model_get( model, iter, MC_TORRENT_RAW, &tor, -1 );
450
451    return checkFilterMode( p->filter_mode, tor )
452           && checkFilterText( p->filter_text_mode, tr_torrentInfo( tor ), p->filter_text );
453}
454
455static void updateTorrentCount( PrivateData * p );
456
457static void
458refilter( PrivateData * p )
459{
460    gtk_tree_model_filter_refilter( GTK_TREE_MODEL_FILTER( p->filter_model ) );
461
462    updateTorrentCount( p );
463}
464
465static void
466filter_text_toggled_cb( GtkCheckMenuItem * menu_item,
467                        gpointer           vprivate )
468{
469    if( gtk_check_menu_item_get_active( menu_item ) )
470    {
471        PrivateData * p = vprivate;
472        p->filter_text_mode =
473            GPOINTER_TO_UINT( g_object_get_data( G_OBJECT( menu_item ),
474                                                 FILTER_TEXT_MODE_KEY ) );
475        refilter( p );
476    }
477}
478
479static void
480setFilter( PrivateData * p, int mode )
481{
482    if( mode != (int)p->filter_mode )
483    {
484        int i;
485
486        /* refilter */
487        p->filter_mode = mode;
488        refilter( p );
489
490        /* update the prefs */
491        tr_core_set_pref( p->core, PREF_KEY_FILTER_MODE, getFilterName( mode ) );
492
493        /* update the togglebuttons */
494        for( i=0; i<FILTER_MODE_QTY; ++i )
495            gtk_toggle_button_set_active( p->filter_toggles[i], i==mode );
496    }
497}
498
499
500static void
501filter_toggled_cb( GtkToggleButton * toggle, gpointer vprivate )
502{
503    if( gtk_toggle_button_get_active( toggle ) )
504    {
505        PrivateData * p = vprivate;
506        const int mode = GPOINTER_TO_UINT( g_object_get_data( G_OBJECT( toggle ), FILTER_MODE_KEY ) );
507        setFilter( p, mode );
508    }
509}
510
511static void
512filter_entry_changed( GtkEditable * e,
513                      gpointer      vprivate )
514{
515    char *        pch;
516    PrivateData * p = vprivate;
517
518    pch = gtk_editable_get_chars( e, 0, -1 );
519    g_free( p->filter_text );
520    p->filter_text = g_utf8_casefold( pch, -1 );
521    refilter( p );
522    g_free( pch );
523}
524
525
526#ifdef USE_SEXY
527static void
528entry_icon_released( SexyIconEntry           * entry  UNUSED,
529                     SexyIconEntryPosition     icon_pos,
530                     int                       button UNUSED,
531                     gpointer                  menu )
532{
533    if( icon_pos == SEXY_ICON_ENTRY_PRIMARY )
534        gtk_menu_popup( GTK_MENU( menu ), NULL, NULL, NULL, NULL, 0,
535                        gtk_get_current_event_time( ) );
536}
537#else
538static void
539entry_icon_release( GtkEntry              * entry  UNUSED,
540                    GtkEntryIconPosition    icon_pos,
541                    GdkEventButton        * event  UNUSED,
542                    gpointer                menu )
543{
544    if( icon_pos == GTK_ENTRY_ICON_SECONDARY )
545        gtk_entry_set_text( entry, "" );
546
547    if( icon_pos == GTK_ENTRY_ICON_PRIMARY )
548        gtk_menu_popup( GTK_MENU( menu ), NULL, NULL, NULL, NULL, 0,
549                        gtk_get_current_event_time( ) );
550}
551#endif
552
553#if GTK_CHECK_VERSION( 2, 12, 0 )
554
555static void
556findMaxAnnounceTime( GtkTreeModel *      model,
557                     GtkTreePath  * path UNUSED,
558                     GtkTreeIter *       iter,
559                     gpointer            gmaxTime )
560{
561    tr_torrent *    tor;
562    const tr_stat * torStat;
563    time_t *        maxTime = gmaxTime;
564
565    gtk_tree_model_get( model, iter, MC_TORRENT_RAW, &tor, -1 );
566    torStat = tr_torrentStatCached( tor );
567    *maxTime = MAX( *maxTime, torStat->manualAnnounceTime );
568}
569
570static gboolean
571onAskTrackerQueryTooltip( GtkWidget *            widget UNUSED,
572                          gint                   x UNUSED,
573                          gint                   y UNUSED,
574                          gboolean               keyboard_tip UNUSED,
575                          GtkTooltip *           tooltip,
576                          gpointer               gdata )
577{
578    const time_t now = time( NULL );
579    time_t       maxTime = 0;
580    PrivateData * p = gdata;
581
582    gtk_tree_selection_selected_foreach( p->selection,
583                                         findMaxAnnounceTime,
584                                         &maxTime );
585    if( maxTime <= now )
586    {
587        return FALSE;
588    }
589    else
590    {
591        char      buf[512];
592        char      timebuf[64];
593        const int seconds = maxTime - now;
594
595        tr_strltime( timebuf, seconds, sizeof( timebuf ) );
596        g_snprintf( buf, sizeof( buf ),
597                    _( "Tracker will allow requests in %s" ), timebuf );
598        gtk_tooltip_set_text( tooltip, buf );
599        return TRUE;
600    }
601}
602
603#endif
604
605static gboolean
606onAltSpeedToggledIdle( gpointer vp )
607{
608    PrivateData * p = vp;
609    gboolean b = tr_sessionUsesAltSpeed( tr_core_session( p->core ) );
610    tr_core_set_pref_bool( p->core, TR_PREFS_KEY_ALT_SPEED_ENABLED, b );
611
612    return FALSE;
613}
614
615static void
616onAltSpeedToggled( tr_session * s UNUSED, tr_bool isEnabled UNUSED, tr_bool byUser UNUSED, void * p )
617{
618    gtr_idle_add( onAltSpeedToggledIdle, p );
619}
620
621/***
622****  Speed limit menu
623***/
624
625#define DIRECTION_KEY "direction-key"
626#define ENABLED_KEY "enabled-key"
627#define SPEED_KEY "speed-key"
628
629static void
630onSpeedToggled( GtkCheckMenuItem * check, gpointer vp )
631{
632    PrivateData * p = vp;
633    GObject * o = G_OBJECT( check );
634    gboolean isEnabled = g_object_get_data( o, ENABLED_KEY ) != 0;
635    tr_direction dir = GPOINTER_TO_INT( g_object_get_data( o, DIRECTION_KEY ) );
636    const char * key = dir == TR_UP ? TR_PREFS_KEY_USPEED_ENABLED
637                                    : TR_PREFS_KEY_DSPEED_ENABLED;
638
639    if( gtk_check_menu_item_get_active( check ) )
640        tr_core_set_pref_bool( p->core, key, isEnabled );
641}
642
643static void
644onSpeedSet( GtkCheckMenuItem * check, gpointer vp )
645{
646    const char * key;
647    PrivateData * p = vp;
648    GObject * o = G_OBJECT( check );
649    const int speed = GPOINTER_TO_INT( g_object_get_data( o, SPEED_KEY ) );
650    tr_direction dir = GPOINTER_TO_INT( g_object_get_data( o, DIRECTION_KEY ) );
651
652    key = dir==TR_UP ? TR_PREFS_KEY_USPEED : TR_PREFS_KEY_DSPEED;
653    tr_core_set_pref_int( p->core, key, speed );
654
655    key = dir==TR_UP ? TR_PREFS_KEY_USPEED_ENABLED : TR_PREFS_KEY_DSPEED_ENABLED;
656    tr_core_set_pref_bool( p->core, key, TRUE );
657}
658
659static GtkWidget*
660createSpeedMenu( PrivateData * p, tr_direction dir )
661{
662    int i, n;
663    GtkWidget *w, *m;
664    const int speeds[] = { 5, 10, 20, 30, 40, 50, 75, 100, 150, 200, 250, 500, 750 };
665
666    m = gtk_menu_new( );
667
668    w = gtk_radio_menu_item_new_with_label( NULL, _( "Unlimited" ) );
669    p->speedlimit_off_item[dir] = w;
670    g_object_set_data( G_OBJECT( w ), DIRECTION_KEY, GINT_TO_POINTER( dir ) );
671    g_object_set_data( G_OBJECT( w ), ENABLED_KEY, GINT_TO_POINTER( FALSE ) );
672    g_signal_connect( w, "toggled", G_CALLBACK(onSpeedToggled), p );
673    gtk_menu_shell_append( GTK_MENU_SHELL( m ), w );
674
675    w = gtk_radio_menu_item_new_with_label_from_widget( GTK_RADIO_MENU_ITEM( w ), "" );
676    p->speedlimit_on_item[dir] = w;
677    g_object_set_data( G_OBJECT( w ), DIRECTION_KEY, GINT_TO_POINTER( dir ) );
678    g_object_set_data( G_OBJECT( w ), ENABLED_KEY, GINT_TO_POINTER( TRUE ) );
679    g_signal_connect( w, "toggled", G_CALLBACK(onSpeedToggled), p );
680    gtk_menu_shell_append( GTK_MENU_SHELL( m ), w );
681
682    w = gtk_separator_menu_item_new( );
683    gtk_menu_shell_append( GTK_MENU_SHELL( m ), w );
684
685    for( i=0, n=G_N_ELEMENTS(speeds); i<n; ++i )
686    {
687        char buf[128];
688        tr_strlspeed( buf, speeds[i], sizeof( buf ) );
689        w = gtk_menu_item_new_with_label( buf );
690        g_object_set_data( G_OBJECT( w ), DIRECTION_KEY, GINT_TO_POINTER( dir ) );
691        g_object_set_data( G_OBJECT( w ), SPEED_KEY, GINT_TO_POINTER( speeds[i] ) );
692        g_signal_connect( w, "activate", G_CALLBACK(onSpeedSet), p );
693        gtk_menu_shell_append( GTK_MENU_SHELL( m ), w );
694    }
695
696    return m;
697}
698
699/***
700****  Speed limit menu
701***/
702
703#define RATIO_KEY "stock-ratio-index"
704
705static const double stockRatios[] = { 0.25, 0.5, 0.75, 1, 1.5, 2, 3 };
706
707static void
708onRatioToggled( GtkCheckMenuItem * check, gpointer vp )
709{
710    PrivateData * p = vp;
711    if( gtk_check_menu_item_get_active( check ) )
712    {
713        gboolean f = g_object_get_data( G_OBJECT( check ), ENABLED_KEY ) != 0;
714        tr_core_set_pref_bool( p->core, TR_PREFS_KEY_RATIO_ENABLED, f );
715    }
716}
717static void
718onRatioSet( GtkCheckMenuItem * check, gpointer vp )
719{
720    PrivateData * p = vp;
721    int i = GPOINTER_TO_INT( g_object_get_data( G_OBJECT( check ), RATIO_KEY ) );
722    const double ratio = stockRatios[i];
723    tr_core_set_pref_double( p->core, TR_PREFS_KEY_RATIO, ratio );
724    tr_core_set_pref_bool  ( p->core, TR_PREFS_KEY_RATIO_ENABLED, TRUE );
725}
726
727static GtkWidget*
728createRatioMenu( PrivateData * p )
729{
730    int i, n;
731    GtkWidget *m, *w;
732
733    m = gtk_menu_new( );
734
735    w = gtk_radio_menu_item_new_with_label( NULL, _( "Seed Forever" ) );
736    p->ratio_off_item = w;
737    g_object_set_data( G_OBJECT( w ), ENABLED_KEY, GINT_TO_POINTER( FALSE ) );
738    g_signal_connect( w, "toggled", G_CALLBACK(onRatioToggled), p );
739    gtk_menu_shell_append( GTK_MENU_SHELL( m ), w );
740
741    w = gtk_radio_menu_item_new_with_label_from_widget( GTK_RADIO_MENU_ITEM( w ), "" );
742    p->ratio_on_item = w;
743    g_object_set_data( G_OBJECT( w ), ENABLED_KEY, GINT_TO_POINTER( TRUE ) );
744    g_signal_connect( w, "toggled", G_CALLBACK(onRatioToggled), p );
745    gtk_menu_shell_append( GTK_MENU_SHELL( m ), w );
746
747    w = gtk_separator_menu_item_new( );
748    gtk_menu_shell_append( GTK_MENU_SHELL( m ), w );
749
750    for( i=0, n=G_N_ELEMENTS(stockRatios); i<n; ++i )
751    {
752        char buf[128];
753        tr_strlratio( buf, stockRatios[i], sizeof( buf ) );
754        w = gtk_menu_item_new_with_label( buf );
755        g_object_set_data( G_OBJECT( w ), RATIO_KEY, GINT_TO_POINTER( i ) );
756        g_signal_connect( w, "activate", G_CALLBACK(onRatioSet), p );
757        gtk_menu_shell_append( GTK_MENU_SHELL( m ), w );
758    }
759
760    return m;
761}
762
763/***
764****  Option menu
765***/
766
767static GtkWidget*
768createOptionsMenu( PrivateData * p )
769{
770    GtkWidget * m;
771    GtkWidget * top = gtk_menu_new( );
772
773    m = gtk_menu_item_new_with_label( _( "Limit Download Speed" ) );
774    gtk_menu_item_set_submenu( GTK_MENU_ITEM( m ), createSpeedMenu( p, TR_DOWN ) );
775    gtk_menu_shell_append( GTK_MENU_SHELL( top ), m );
776
777    m = gtk_menu_item_new_with_label( _( "Limit Upload Speed" ) );
778    gtk_menu_item_set_submenu( GTK_MENU_ITEM( m ), createSpeedMenu( p, TR_UP ) );
779    gtk_menu_shell_append( GTK_MENU_SHELL( top ), m );
780
781    m = gtk_separator_menu_item_new( );
782    gtk_menu_shell_append( GTK_MENU_SHELL( top ), m );
783
784    m = gtk_menu_item_new_with_label( _( "Stop Seeding at Ratio" ) );
785    gtk_menu_item_set_submenu( GTK_MENU_ITEM( m ), createRatioMenu( p ) );
786    gtk_menu_shell_append( GTK_MENU_SHELL( top ), m );
787
788    gtk_widget_show_all( top );
789    return top;
790}
791
792static void
793onOptionsClicked( GtkButton * button UNUSED, gpointer vp )
794{
795    char buf1[512];
796    char buf2[512];
797    gboolean b;
798    GtkWidget * w;
799    PrivateData * p = vp;
800
801    w = p->speedlimit_on_item[TR_DOWN];
802    tr_strlspeed( buf1, pref_int_get( TR_PREFS_KEY_DSPEED ), sizeof( buf1 ) );
803    gtk_label_set_text( GTK_LABEL( gtk_bin_get_child( GTK_BIN( w ) ) ), buf1 );
804
805    b = pref_flag_get( TR_PREFS_KEY_DSPEED_ENABLED );
806    w = b ? p->speedlimit_on_item[TR_DOWN] : p->speedlimit_off_item[TR_DOWN];
807    gtk_check_menu_item_set_active( GTK_CHECK_MENU_ITEM( w ), TRUE );
808
809    w = p->speedlimit_on_item[TR_UP];
810    tr_strlspeed( buf1, pref_int_get( TR_PREFS_KEY_USPEED ), sizeof( buf1 ) );
811    gtk_label_set_text( GTK_LABEL( gtk_bin_get_child( GTK_BIN( w ) ) ), buf1 );
812
813    b = pref_flag_get( TR_PREFS_KEY_USPEED_ENABLED );
814    w = b ? p->speedlimit_on_item[TR_UP] : p->speedlimit_off_item[TR_UP];
815    gtk_check_menu_item_set_active( GTK_CHECK_MENU_ITEM( w ), TRUE );
816
817    tr_strlratio( buf1, pref_double_get( TR_PREFS_KEY_RATIO ), sizeof( buf1 ) );
818    g_snprintf( buf2, sizeof( buf2 ), _( "Stop at Ratio (%s)" ), buf1 );
819    gtk_label_set_text( GTK_LABEL( gtk_bin_get_child( GTK_BIN( p->ratio_on_item ) ) ), buf2 );
820
821    b = pref_flag_get( TR_PREFS_KEY_RATIO_ENABLED );
822    gtk_check_menu_item_set_active( GTK_CHECK_MENU_ITEM( b ? p->ratio_on_item : p->ratio_off_item ), TRUE );
823
824    gtk_menu_popup ( GTK_MENU( p->options_menu ), NULL, NULL, NULL, NULL, 0, gtk_get_current_event_time( ) );
825}
826
827/***
828****  PUBLIC
829***/
830
831GtkWidget *
832tr_window_new( GtkUIManager * ui_mgr, TrCore * core )
833{
834    int           i, n;
835    const char  * pch;
836    PrivateData * p;
837    GtkWidget   * mainmenu, *toolbar, *filter, *list, *status;
838    GtkWidget   * vbox, *w, *self, *h, *s, *hbox, *menu;
839    GtkWindow   * win;
840    GSList      * l;
841
842    const char *  filter_names[FILTER_MODE_QTY] = {
843        /* show all torrents */
844        N_( "A_ll" ),
845        /* show only torrents that have connected peers */
846        N_( "_Active" ),
847        /* show only torrents that are trying to download */
848        N_( "_Downloading" ),
849        /* show only torrents that are trying to upload */
850        N_( "_Seeding" ),
851        /* show only torrents that are paused */
852        N_( "_Paused" )
853    };
854    const char *  filter_text_names[FILTER_TEXT_MODE_QTY] = {
855        N_( "Name" ), N_( "Files" ), N_( "Tracker" )
856    };
857
858    p = g_new0( PrivateData, 1 );
859    p->filter_text_mode = FILTER_TEXT_MODE_NAME;
860    p->filter_text = NULL;
861
862    /* make the window */
863    self = gtk_window_new ( GTK_WINDOW_TOPLEVEL );
864    g_object_set_data_full( G_OBJECT(
865                                self ), PRIVATE_DATA_KEY, p, privateFree );
866    win = GTK_WINDOW( self );
867    gtk_window_set_title( win, g_get_application_name( ) );
868    gtk_window_set_role( win, "tr-main" );
869    gtk_window_set_default_size( win,
870                                 pref_int_get( PREF_KEY_MAIN_WINDOW_WIDTH ),
871                                 pref_int_get( PREF_KEY_MAIN_WINDOW_HEIGHT ) );
872    gtk_window_move( win, pref_int_get( PREF_KEY_MAIN_WINDOW_X ),
873                          pref_int_get( PREF_KEY_MAIN_WINDOW_Y ) );
874    if( pref_flag_get( PREF_KEY_MAIN_WINDOW_IS_MAXIMIZED ) )
875        gtk_window_maximize( win );
876    gtk_window_add_accel_group( win, gtk_ui_manager_get_accel_group( ui_mgr ) );
877
878    /* window's main container */
879    vbox = gtk_vbox_new ( FALSE, 0 );
880    gtk_container_add ( GTK_CONTAINER( self ), vbox );
881
882    /* main menu */
883    mainmenu = action_get_widget( "/main-window-menu" );
884    w = action_get_widget( "/main-window-menu/torrent-menu/update-tracker" );
885#if GTK_CHECK_VERSION( 2, 12, 0 )
886    g_signal_connect( w, "query-tooltip",
887                      G_CALLBACK( onAskTrackerQueryTooltip ), p );
888#endif
889
890    /* toolbar */
891    toolbar = p->toolbar = action_get_widget( "/main-window-toolbar" );
892    action_set_important( "add-torrent-toolbar", TRUE );
893    action_set_important( "show-torrent-properties", TRUE );
894
895    /* filter */
896    h = filter = p->filter = gtk_hbox_new( FALSE, 0 );
897    gtk_container_set_border_width( GTK_CONTAINER( h ), GUI_PAD_SMALL );
898    for( i = 0; i < FILTER_MODE_QTY; ++i )
899    {
900        const char * mnemonic = _( filter_names[i] );
901        w = gtk_toggle_button_new_with_mnemonic( mnemonic );
902        g_object_set_data( G_OBJECT( w ), FILTER_MODE_KEY, GINT_TO_POINTER( i ) );
903        gtk_button_set_relief( GTK_BUTTON( w ), GTK_RELIEF_NONE );
904        gtk_toggle_button_set_active( GTK_TOGGLE_BUTTON( w ), i == FILTER_MODE_ALL );
905        p->filter_toggles[i] = GTK_TOGGLE_BUTTON( w );
906        g_signal_connect( w, "toggled", G_CALLBACK( filter_toggled_cb ), p );
907        gtk_box_pack_start( GTK_BOX( h ), w, FALSE, FALSE, 0 );
908    }
909
910#ifdef USE_SEXY
911    s = sexy_icon_entry_new( );
912    sexy_icon_entry_add_clear_button( SEXY_ICON_ENTRY( s ) );
913    w = gtk_image_new_from_stock( GTK_STOCK_FIND, GTK_ICON_SIZE_MENU );
914    sexy_icon_entry_set_icon( SEXY_ICON_ENTRY( s ),
915                              SEXY_ICON_ENTRY_PRIMARY,
916                              GTK_IMAGE( w ) );
917    g_object_unref( w );
918    sexy_icon_entry_set_icon_highlight( SEXY_ICON_ENTRY( s ),
919                                        SEXY_ICON_ENTRY_PRIMARY, TRUE );
920#else
921    s = gtk_entry_new( );
922    gtk_entry_set_icon_from_stock( GTK_ENTRY( s ),
923                                   GTK_ENTRY_ICON_PRIMARY,
924                                   GTK_STOCK_FIND);
925   gtk_entry_set_icon_from_stock( GTK_ENTRY( s ),
926                                  GTK_ENTRY_ICON_SECONDARY,
927                                  GTK_STOCK_CLEAR );
928#endif
929    gtk_box_pack_end( GTK_BOX( h ), s, FALSE, FALSE, 0 );
930    g_signal_connect( s, "changed", G_CALLBACK( filter_entry_changed ), p );
931
932    /* status menu */
933    menu = p->status_menu = gtk_menu_new( );
934    l = NULL;
935    pch = pref_string_get( PREF_KEY_STATUSBAR_STATS );
936    for( i = 0, n = G_N_ELEMENTS( stats_modes ); i < n; ++i )
937    {
938        const char * val = stats_modes[i].val;
939        w = gtk_radio_menu_item_new_with_label( l, _( stats_modes[i].i18n ) );
940        l = gtk_radio_menu_item_get_group( GTK_RADIO_MENU_ITEM( w ) );
941        gtk_check_menu_item_set_active( GTK_CHECK_MENU_ITEM( w ),
942                                       !strcmp( val, pch ) );
943        g_object_set_data( G_OBJECT(
944                               w ), STATS_MODE,
945                           (gpointer)stats_modes[i].val );
946        g_signal_connect( w, "toggled", G_CALLBACK(
947                              status_menu_toggled_cb ), p );
948        gtk_menu_shell_append( GTK_MENU_SHELL( menu ), w );
949        gtk_widget_show( w );
950    }
951
952    /* status */
953    h = status = p->status = gtk_hbox_new( FALSE, GUI_PAD );
954    gtk_container_set_border_width( GTK_CONTAINER( h ), GUI_PAD_SMALL );
955
956        w = gtk_button_new( );
957        gtk_container_add( GTK_CONTAINER( w ), gtk_image_new_from_stock( "options", GTK_ICON_SIZE_SMALL_TOOLBAR ) );
958        gtr_widget_set_tooltip_text( w, _( "Options" ) );
959        gtk_box_pack_start( GTK_BOX( h ), w, 0, 0, 0 );
960        gtk_button_set_relief( GTK_BUTTON( w ), GTK_RELIEF_NONE );
961        p->options_menu = createOptionsMenu( p );
962        g_signal_connect( w, "clicked", G_CALLBACK(onOptionsClicked), p );
963
964        p->alt_speed_image = gtk_image_new( );
965        w = p->alt_speed_button = gtk_toggle_button_new( );
966        gtk_button_set_image( GTK_BUTTON( w ), p->alt_speed_image );
967        gtk_button_set_relief( GTK_BUTTON( w ), GTK_RELIEF_NONE );
968        g_signal_connect( w, "toggled", G_CALLBACK(alt_speed_toggled_cb ), p );
969        gtk_box_pack_start( GTK_BOX( h ), w, 0, 0, 0 );
970
971        w = p->gutter_lb = gtk_label_new( "N Torrents" );
972        gtk_box_pack_start( GTK_BOX( h ), w, 1, 1, GUI_PAD_BIG );
973
974        hbox = gtk_hbox_new( FALSE, GUI_PAD_SMALL );
975            w = gtk_alignment_new( 0.0f, 0.0f, 0.0f, 0.0f );
976            gtk_widget_set_size_request( w, GUI_PAD, 0u );
977            gtk_box_pack_start( GTK_BOX( hbox ), w, FALSE, FALSE, 0 );
978            w = gtk_image_new_from_stock( GTK_STOCK_GO_UP, GTK_ICON_SIZE_MENU );
979            gtk_box_pack_start( GTK_BOX( hbox ), w, FALSE, FALSE, 0 );
980            w = p->ul_lb = gtk_label_new( NULL );
981            gtk_box_pack_start( GTK_BOX( hbox ), w, FALSE, FALSE, 0 );
982        gtk_box_pack_end( GTK_BOX( h ), hbox, FALSE, FALSE, 0 );
983
984        hbox = gtk_hbox_new( FALSE, GUI_PAD_SMALL );
985            w = gtk_alignment_new( 0.0f, 0.0f, 0.0f, 0.0f );
986            gtk_widget_set_size_request( w, GUI_PAD, 0u );
987            gtk_box_pack_start( GTK_BOX( hbox ), w, FALSE, FALSE, 0 );
988            w = gtk_image_new_from_stock( GTK_STOCK_GO_DOWN, GTK_ICON_SIZE_MENU );
989            gtk_box_pack_start( GTK_BOX( hbox ), w, FALSE, FALSE, 0 );
990            w = p->dl_lb = gtk_label_new( NULL );
991            gtk_box_pack_start( GTK_BOX( hbox ), w, FALSE, FALSE, 0 );
992        gtk_box_pack_end( GTK_BOX( h ), hbox, FALSE, FALSE, 0 );
993
994        hbox = gtk_hbox_new( FALSE, GUI_PAD_SMALL );
995            w = gtk_alignment_new( 0.0f, 0.0f, 0.0f, 0.0f );
996            gtk_widget_set_size_request( w, GUI_PAD, 0u );
997            gtk_box_pack_start( GTK_BOX( hbox ), w, FALSE, FALSE, 0 );
998            w = gtk_button_new( );
999            gtr_widget_set_tooltip_text( w, _( "Statistics" ) );
1000            gtk_container_add( GTK_CONTAINER( w ), gtk_image_new_from_stock( "ratio", GTK_ICON_SIZE_SMALL_TOOLBAR ) );
1001            gtk_button_set_relief( GTK_BUTTON( w ), GTK_RELIEF_NONE );
1002            g_signal_connect( w, "clicked", G_CALLBACK( onYinYangReleased ), p );
1003            gtk_box_pack_start( GTK_BOX( hbox ), w, FALSE, FALSE, 0 );
1004            w = p->stats_lb = gtk_label_new( NULL );
1005            gtk_box_pack_end( GTK_BOX( h ), w, FALSE, FALSE, 0 );
1006        gtk_box_pack_end( GTK_BOX( h ), hbox, FALSE, FALSE, 0 );
1007
1008
1009    menu = gtk_menu_new( );
1010    l = NULL;
1011    for( i=0; i<FILTER_TEXT_MODE_QTY; ++i )
1012    {
1013        const char * name = _( filter_text_names[i] );
1014        GtkWidget *  w = gtk_radio_menu_item_new_with_label ( l, name );
1015        l = gtk_radio_menu_item_get_group( GTK_RADIO_MENU_ITEM( w ) );
1016        g_object_set_data( G_OBJECT( w ), FILTER_TEXT_MODE_KEY,
1017                           GINT_TO_POINTER( i ) );
1018        g_signal_connect( w, "toggled",
1019                          G_CALLBACK( filter_text_toggled_cb ), p );
1020        gtk_menu_shell_append( GTK_MENU_SHELL( menu ), w );
1021        gtk_widget_show( w );
1022    }
1023
1024#ifdef USE_SEXY
1025    g_signal_connect( s, "icon-released", G_CALLBACK( entry_icon_released ), menu );
1026#else
1027    g_signal_connect( s, "icon-release", G_CALLBACK( entry_icon_release ), menu );
1028
1029#endif
1030
1031    /* workarea */
1032    p->view = makeview( p, core );
1033    w = list = p->scroll = gtk_scrolled_window_new( NULL, NULL );
1034    gtk_scrolled_window_set_policy( GTK_SCROLLED_WINDOW( w ),
1035                                    GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC );
1036    gtk_scrolled_window_set_shadow_type( GTK_SCROLLED_WINDOW( w ),
1037                                         GTK_SHADOW_IN );
1038    gtk_container_add( GTK_CONTAINER( w ), p->view );
1039
1040    /* layout the widgets */
1041    {
1042        const char * str = pref_string_get( PREF_KEY_MAIN_WINDOW_LAYOUT_ORDER );
1043        char ** tokens = g_strsplit( str, ",", -1 );
1044        for( i=0; tokens && tokens[i]; ++i )
1045        {
1046            const char * key = tokens[i];
1047
1048            if( !strcmp( key, "menu" ) )
1049                gtk_box_pack_start( GTK_BOX( vbox ), mainmenu, FALSE, FALSE, 0 );
1050            else if( !strcmp( key, "toolbar" ) )
1051                gtk_box_pack_start( GTK_BOX( vbox ), toolbar, FALSE, FALSE, 0 );
1052            else if( !strcmp( key, "filter" ) )
1053                gtk_box_pack_start( GTK_BOX( vbox ), filter, FALSE, FALSE, 0 );
1054            else if( !strcmp( key, "list" ) )
1055                gtk_box_pack_start( GTK_BOX( vbox ), list, TRUE, TRUE, 0 );
1056            else if( !strcmp( key, "statusbar" ) )
1057                gtk_box_pack_start( GTK_BOX( vbox ), status, FALSE, FALSE, 0 );
1058        }
1059        g_strfreev( tokens );
1060    }
1061
1062    {
1063        int w=0, h=0;
1064        /* this is to determine the maximum width/height for the label */
1065        PangoLayout * pango_layout =
1066            gtk_widget_create_pango_layout( p->ul_lb, _( "999.9 KB/s" ) );
1067        pango_layout_get_pixel_size( pango_layout, &w, &h );
1068        gtk_widget_set_size_request( p->ul_lb, w, h );
1069        gtk_widget_set_size_request( p->dl_lb, w, h );
1070        gtk_misc_set_alignment( GTK_MISC( p->ul_lb ), 1.0, 0.5 );
1071        gtk_misc_set_alignment( GTK_MISC( p->dl_lb ), 1.0, 0.5 );
1072        g_object_unref( G_OBJECT( pango_layout ) );
1073    }
1074
1075    /* show all but the window */
1076    gtk_widget_show_all( vbox );
1077
1078    /* listen for prefs changes that affect the window */
1079    p->core = core;
1080    prefsChanged( core, PREF_KEY_MINIMAL_VIEW, self );
1081    prefsChanged( core, PREF_KEY_FILTERBAR, self );
1082    prefsChanged( core, PREF_KEY_STATUSBAR, self );
1083    prefsChanged( core, PREF_KEY_STATUSBAR_STATS, self );
1084    prefsChanged( core, PREF_KEY_TOOLBAR, self );
1085    prefsChanged( core, PREF_KEY_FILTER_MODE, self );
1086    prefsChanged( core, TR_PREFS_KEY_ALT_SPEED_ENABLED, self );
1087    p->pref_handler_id = g_signal_connect( core, "prefs-changed",
1088                                           G_CALLBACK( prefsChanged ), self );
1089
1090    tr_sessionSetAltSpeedFunc( tr_core_session( core ), onAltSpeedToggled, p );
1091
1092    filter_entry_changed( GTK_EDITABLE( s ), p );
1093    return self;
1094}
1095
1096static void
1097updateTorrentCount( PrivateData * p )
1098{
1099    if( p && p->core )
1100    {
1101        char      buf[512];
1102        const int torrentCount = gtk_tree_model_iter_n_children(
1103            tr_core_model( p->core ), NULL );
1104        const int visibleCount = gtk_tree_model_iter_n_children(
1105            p->filter_model, NULL );
1106
1107        if( !torrentCount )
1108            *buf = '\0';
1109        else if( torrentCount != visibleCount )
1110            g_snprintf( buf, sizeof( buf ),
1111                        ngettext( "%1$'d of %2$'d Torrent",
1112                                  "%1$'d of %2$'d Torrents",
1113                                  torrentCount ),
1114                        visibleCount, torrentCount );
1115        else
1116            g_snprintf( buf, sizeof( buf ),
1117                        ngettext( "%'d Torrent", "%'d Torrents", torrentCount ),
1118                        torrentCount );
1119        gtk_label_set_text( GTK_LABEL( p->gutter_lb ), buf );
1120    }
1121}
1122
1123static void
1124updateStats( PrivateData * p )
1125{
1126    const char *            pch;
1127    char                    up[32], down[32], ratio[32], buf[512];
1128    struct tr_session_stats stats;
1129    tr_session *            session = tr_core_session( p->core );
1130
1131    /* update the stats */
1132    pch = pref_string_get( PREF_KEY_STATUSBAR_STATS );
1133    if( !strcmp( pch, "session-ratio" ) )
1134    {
1135        tr_sessionGetStats( session, &stats );
1136        tr_strlratio( ratio, stats.ratio, sizeof( ratio ) );
1137        g_snprintf( buf, sizeof( buf ), _( "Ratio: %s" ), ratio );
1138    }
1139    else if( !strcmp( pch, "session-transfer" ) )
1140    {
1141        tr_sessionGetStats( session, &stats );
1142        tr_strlsize( up, stats.uploadedBytes, sizeof( up ) );
1143        tr_strlsize( down, stats.downloadedBytes, sizeof( down ) );
1144        /* Translators: "size|" is here for disambiguation.  Please remove it from your translation.
1145           %1$s is the size of the data we've downloaded
1146           %2$s is the size of the data we've uploaded */
1147        g_snprintf( buf, sizeof( buf ), Q_(
1148                        "size|Down: %1$s, Up: %2$s" ), down, up );
1149    }
1150    else if( !strcmp( pch, "total-transfer" ) )
1151    {
1152        tr_sessionGetCumulativeStats( session, &stats );
1153        tr_strlsize( up, stats.uploadedBytes, sizeof( up ) );
1154        tr_strlsize( down, stats.downloadedBytes, sizeof( down ) );
1155        /* Translators: "size|" is here for disambiguation.  Please remove it from your translation.
1156           %1$s is the size of the data we've downloaded
1157           %2$s is the size of the data we've uploaded */
1158        g_snprintf( buf, sizeof( buf ), Q_(
1159                        "size|Down: %1$s, Up: %2$s" ), down, up );
1160    }
1161    else     /* default is total-ratio */
1162    {
1163        tr_sessionGetCumulativeStats( session, &stats );
1164        tr_strlratio( ratio, stats.ratio, sizeof( ratio ) );
1165        g_snprintf( buf, sizeof( buf ), _( "Ratio: %s" ), ratio );
1166    }
1167    gtk_label_set_text( GTK_LABEL( p->stats_lb ), buf );
1168}
1169
1170static void
1171updateSpeeds( PrivateData * p )
1172{
1173    tr_session * session = tr_core_session( p->core );
1174
1175    if( session != NULL )
1176    {
1177        char buf[128];
1178        double up=0, down=0;
1179        GtkTreeIter iter;
1180        GtkTreeModel * model = tr_core_model( p->core );
1181
1182        if( gtk_tree_model_get_iter_first( model, &iter ) ) do
1183        {
1184            double u, d;
1185            gtk_tree_model_get( model, &iter, MC_SPEED_UP, &u,
1186                                              MC_SPEED_DOWN, &d,
1187                                              -1 );
1188            up += u;
1189            down += d;
1190        }
1191        while( gtk_tree_model_iter_next( model, &iter ) );
1192
1193        tr_strlspeed( buf, down, sizeof( buf ) );
1194        gtk_label_set_text( GTK_LABEL( p->dl_lb ), buf );
1195
1196        tr_strlspeed( buf, up, sizeof( buf ) );
1197        gtk_label_set_text( GTK_LABEL( p->ul_lb ), buf );
1198    }
1199}
1200
1201void
1202tr_window_update( TrWindow * self )
1203{
1204    PrivateData * p = get_private_data( self );
1205
1206    if( p && p->core && tr_core_session( p->core ) )
1207    {
1208        updateSpeeds( p );
1209        updateTorrentCount( p );
1210        updateStats( p );
1211        refilter( p );
1212    }
1213}
1214
1215GtkTreeSelection*
1216tr_window_get_selection( TrWindow * w )
1217{
1218    return get_private_data( w )->selection;
1219}
1220
Note: See TracBrowser for help on using the repository browser.