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

Last change on this file since 9916 was 9916, checked in by charles, 13 years ago

(trunk gtk) remove a few very minor memory leaks in the GTK+ client

  • Property svn:keywords set to Date Rev Author Id
File size: 41.5 KB
Line 
1/******************************************************************************
2 * $Id: tr-window.c 9916 2010-01-11 21:54:54Z 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            pch = g_utf8_casefold( inf->name, -1 );
393            ret = !text || ( strstr( pch, text ) != NULL );
394            g_free( pch );
395            break;
396    }
397
398    return ret;
399}
400
401static int
402checkFilterMode( filter_mode_t filter_mode,
403                 tr_torrent *  tor )
404{
405    int ret = 0;
406
407    switch( filter_mode )
408    {
409        case FILTER_MODE_DOWNLOADING:
410            ret = tr_torrentGetActivity( tor ) == TR_STATUS_DOWNLOAD;
411            break;
412
413        case FILTER_MODE_SEEDING:
414            ret = tr_torrentGetActivity( tor ) == TR_STATUS_SEED;
415            break;
416
417        case FILTER_MODE_PAUSED:
418            ret = tr_torrentGetActivity( tor ) == TR_STATUS_STOPPED;
419            break;
420
421        case FILTER_MODE_ACTIVE:
422        {
423            const tr_stat * s = tr_torrentStatCached( tor );
424            ret = s->peersSendingToUs > 0
425               || s->peersGettingFromUs > 0
426               || tr_torrentGetActivity( tor ) == TR_STATUS_CHECK;
427            break;
428        }
429
430        default: /* all */
431            ret = 1;
432    }
433
434    return ret;
435}
436
437static gboolean
438is_row_visible( GtkTreeModel * model,
439                GtkTreeIter *  iter,
440                gpointer       vprivate )
441{
442    PrivateData * p = vprivate;
443    tr_torrent *  tor;
444
445    gtk_tree_model_get( model, iter, MC_TORRENT_RAW, &tor, -1 );
446
447    return checkFilterMode( p->filter_mode, tor )
448           && checkFilterText( p->filter_text_mode, tr_torrentInfo( tor ), p->filter_text );
449}
450
451static void updateTorrentCount( PrivateData * p );
452
453static void
454refilter( PrivateData * p )
455{
456    gtk_tree_model_filter_refilter( GTK_TREE_MODEL_FILTER( p->filter_model ) );
457
458    updateTorrentCount( p );
459}
460
461static void
462filter_text_toggled_cb( GtkCheckMenuItem * menu_item,
463                        gpointer           vprivate )
464{
465    if( gtk_check_menu_item_get_active( menu_item ) )
466    {
467        PrivateData * p = vprivate;
468        p->filter_text_mode =
469            GPOINTER_TO_UINT( g_object_get_data( G_OBJECT( menu_item ),
470                                                 FILTER_TEXT_MODE_KEY ) );
471        refilter( p );
472    }
473}
474
475static void
476setFilter( PrivateData * p, int mode )
477{
478    if( mode != (int)p->filter_mode )
479    {
480        int i;
481
482        /* refilter */
483        p->filter_mode = mode;
484        refilter( p );
485
486        /* update the prefs */
487        tr_core_set_pref( p->core, PREF_KEY_FILTER_MODE, getFilterName( mode ) );
488
489        /* update the togglebuttons */
490        for( i=0; i<FILTER_MODE_QTY; ++i )
491            gtk_toggle_button_set_active( p->filter_toggles[i], i==mode );
492    }
493}
494
495
496static void
497filter_toggled_cb( GtkToggleButton * toggle, gpointer vprivate )
498{
499    if( gtk_toggle_button_get_active( toggle ) )
500    {
501        PrivateData * p = vprivate;
502        const int mode = GPOINTER_TO_UINT( g_object_get_data( G_OBJECT( toggle ), FILTER_MODE_KEY ) );
503        setFilter( p, mode );
504    }
505}
506
507static void
508filter_entry_changed( GtkEditable * e,
509                      gpointer      vprivate )
510{
511    char *        pch;
512    PrivateData * p = vprivate;
513
514    pch = gtk_editable_get_chars( e, 0, -1 );
515    g_free( p->filter_text );
516    p->filter_text = g_utf8_casefold( pch, -1 );
517    refilter( p );
518    g_free( pch );
519}
520
521
522#ifdef USE_SEXY
523static void
524entry_icon_released( SexyIconEntry           * entry  UNUSED,
525                     SexyIconEntryPosition     icon_pos,
526                     int                       button UNUSED,
527                     gpointer                  menu )
528{
529    if( icon_pos == SEXY_ICON_ENTRY_PRIMARY )
530        gtk_menu_popup( GTK_MENU( menu ), NULL, NULL, NULL, NULL, 0,
531                        gtk_get_current_event_time( ) );
532}
533#else
534static void
535entry_icon_release( GtkEntry              * entry  UNUSED,
536                    GtkEntryIconPosition    icon_pos,
537                    GdkEventButton        * event  UNUSED,
538                    gpointer                menu )
539{
540    if( icon_pos == GTK_ENTRY_ICON_SECONDARY )
541        gtk_entry_set_text( entry, "" );
542
543    if( icon_pos == GTK_ENTRY_ICON_PRIMARY )
544        gtk_menu_popup( GTK_MENU( menu ), NULL, NULL, NULL, NULL, 0,
545                        gtk_get_current_event_time( ) );
546}
547#endif
548
549#if GTK_CHECK_VERSION( 2, 12, 0 )
550
551static void
552findMaxAnnounceTime( GtkTreeModel *      model,
553                     GtkTreePath  * path UNUSED,
554                     GtkTreeIter *       iter,
555                     gpointer            gmaxTime )
556{
557    tr_torrent *    tor;
558    const tr_stat * torStat;
559    time_t *        maxTime = gmaxTime;
560
561    gtk_tree_model_get( model, iter, MC_TORRENT_RAW, &tor, -1 );
562    torStat = tr_torrentStatCached( tor );
563    *maxTime = MAX( *maxTime, torStat->manualAnnounceTime );
564}
565
566static gboolean
567onAskTrackerQueryTooltip( GtkWidget *            widget UNUSED,
568                          gint                   x UNUSED,
569                          gint                   y UNUSED,
570                          gboolean               keyboard_tip UNUSED,
571                          GtkTooltip *           tooltip,
572                          gpointer               gdata )
573{
574    const time_t now = time( NULL );
575    time_t       maxTime = 0;
576    PrivateData * p = gdata;
577
578    gtk_tree_selection_selected_foreach( p->selection,
579                                         findMaxAnnounceTime,
580                                         &maxTime );
581    if( maxTime <= now )
582    {
583        return FALSE;
584    }
585    else
586    {
587        char      buf[512];
588        char      timebuf[64];
589        const int seconds = maxTime - now;
590
591        tr_strltime( timebuf, seconds, sizeof( timebuf ) );
592        g_snprintf( buf, sizeof( buf ),
593                    _( "Tracker will allow requests in %s" ), timebuf );
594        gtk_tooltip_set_text( tooltip, buf );
595        return TRUE;
596    }
597}
598
599#endif
600
601static gboolean
602onAltSpeedToggledIdle( gpointer vp )
603{
604    PrivateData * p = vp;
605    gboolean b = tr_sessionUsesAltSpeed( tr_core_session( p->core ) );
606    tr_core_set_pref_bool( p->core, TR_PREFS_KEY_ALT_SPEED_ENABLED, b );
607
608    return FALSE;
609}
610
611static void
612onAltSpeedToggled( tr_session * s UNUSED, tr_bool isEnabled UNUSED, tr_bool byUser UNUSED, void * p )
613{
614    gtr_idle_add( onAltSpeedToggledIdle, p );
615}
616
617/***
618****  Speed limit menu
619***/
620
621#define DIRECTION_KEY "direction-key"
622#define ENABLED_KEY "enabled-key"
623#define SPEED_KEY "speed-key"
624
625static void
626onSpeedToggled( GtkCheckMenuItem * check, gpointer vp )
627{
628    PrivateData * p = vp;
629    GObject * o = G_OBJECT( check );
630    gboolean isEnabled = g_object_get_data( o, ENABLED_KEY ) != 0;
631    tr_direction dir = GPOINTER_TO_INT( g_object_get_data( o, DIRECTION_KEY ) );
632    const char * key = dir == TR_UP ? TR_PREFS_KEY_USPEED_ENABLED
633                                    : TR_PREFS_KEY_DSPEED_ENABLED;
634
635    if( gtk_check_menu_item_get_active( check ) )
636        tr_core_set_pref_bool( p->core, key, isEnabled );
637}
638
639static void
640onSpeedSet( GtkCheckMenuItem * check, gpointer vp )
641{
642    const char * key;
643    PrivateData * p = vp;
644    GObject * o = G_OBJECT( check );
645    const int speed = GPOINTER_TO_INT( g_object_get_data( o, SPEED_KEY ) );
646    tr_direction dir = GPOINTER_TO_INT( g_object_get_data( o, DIRECTION_KEY ) );
647
648    key = dir==TR_UP ? TR_PREFS_KEY_USPEED : TR_PREFS_KEY_DSPEED;
649    tr_core_set_pref_int( p->core, key, speed );
650
651    key = dir==TR_UP ? TR_PREFS_KEY_USPEED_ENABLED : TR_PREFS_KEY_DSPEED_ENABLED;
652    tr_core_set_pref_bool( p->core, key, TRUE );
653}
654
655static GtkWidget*
656createSpeedMenu( PrivateData * p, tr_direction dir )
657{
658    int i, n;
659    GtkWidget *w, *m;
660    const int speeds[] = { 5, 10, 20, 30, 40, 50, 75, 100, 150, 200, 250, 500, 750 };
661
662    m = gtk_menu_new( );
663
664    w = gtk_radio_menu_item_new_with_label( NULL, _( "Unlimited" ) );
665    p->speedlimit_off_item[dir] = w;
666    g_object_set_data( G_OBJECT( w ), DIRECTION_KEY, GINT_TO_POINTER( dir ) );
667    g_object_set_data( G_OBJECT( w ), ENABLED_KEY, GINT_TO_POINTER( FALSE ) );
668    g_signal_connect( w, "toggled", G_CALLBACK(onSpeedToggled), p );
669    gtk_menu_shell_append( GTK_MENU_SHELL( m ), w );
670
671    w = gtk_radio_menu_item_new_with_label_from_widget( GTK_RADIO_MENU_ITEM( w ), "" );
672    p->speedlimit_on_item[dir] = w;
673    g_object_set_data( G_OBJECT( w ), DIRECTION_KEY, GINT_TO_POINTER( dir ) );
674    g_object_set_data( G_OBJECT( w ), ENABLED_KEY, GINT_TO_POINTER( TRUE ) );
675    g_signal_connect( w, "toggled", G_CALLBACK(onSpeedToggled), p );
676    gtk_menu_shell_append( GTK_MENU_SHELL( m ), w );
677
678    w = gtk_separator_menu_item_new( );
679    gtk_menu_shell_append( GTK_MENU_SHELL( m ), w );
680
681    for( i=0, n=G_N_ELEMENTS(speeds); i<n; ++i )
682    {
683        char buf[128];
684        tr_strlspeed( buf, speeds[i], sizeof( buf ) );
685        w = gtk_menu_item_new_with_label( buf );
686        g_object_set_data( G_OBJECT( w ), DIRECTION_KEY, GINT_TO_POINTER( dir ) );
687        g_object_set_data( G_OBJECT( w ), SPEED_KEY, GINT_TO_POINTER( speeds[i] ) );
688        g_signal_connect( w, "activate", G_CALLBACK(onSpeedSet), p );
689        gtk_menu_shell_append( GTK_MENU_SHELL( m ), w );
690    }
691
692    return m;
693}
694
695/***
696****  Speed limit menu
697***/
698
699#define RATIO_KEY "stock-ratio-index"
700
701static const double stockRatios[] = { 0.25, 0.5, 0.75, 1, 1.5, 2, 3 };
702
703static void
704onRatioToggled( GtkCheckMenuItem * check, gpointer vp )
705{
706    PrivateData * p = vp;
707    if( gtk_check_menu_item_get_active( check ) )
708    {
709        gboolean f = g_object_get_data( G_OBJECT( check ), ENABLED_KEY ) != 0;
710        tr_core_set_pref_bool( p->core, TR_PREFS_KEY_RATIO_ENABLED, f );
711    }
712}
713static void
714onRatioSet( GtkCheckMenuItem * check, gpointer vp )
715{
716    PrivateData * p = vp;
717    int i = GPOINTER_TO_INT( g_object_get_data( G_OBJECT( check ), RATIO_KEY ) );
718    const double ratio = stockRatios[i];
719    tr_core_set_pref_double( p->core, TR_PREFS_KEY_RATIO, ratio );
720    tr_core_set_pref_bool  ( p->core, TR_PREFS_KEY_RATIO_ENABLED, TRUE );
721}
722
723static GtkWidget*
724createRatioMenu( PrivateData * p )
725{
726    int i, n;
727    GtkWidget *m, *w;
728
729    m = gtk_menu_new( );
730
731    w = gtk_radio_menu_item_new_with_label( NULL, _( "Seed Forever" ) );
732    p->ratio_off_item = w;
733    g_object_set_data( G_OBJECT( w ), ENABLED_KEY, GINT_TO_POINTER( FALSE ) );
734    g_signal_connect( w, "toggled", G_CALLBACK(onRatioToggled), p );
735    gtk_menu_shell_append( GTK_MENU_SHELL( m ), w );
736
737    w = gtk_radio_menu_item_new_with_label_from_widget( GTK_RADIO_MENU_ITEM( w ), "" );
738    p->ratio_on_item = w;
739    g_object_set_data( G_OBJECT( w ), ENABLED_KEY, GINT_TO_POINTER( TRUE ) );
740    g_signal_connect( w, "toggled", G_CALLBACK(onRatioToggled), p );
741    gtk_menu_shell_append( GTK_MENU_SHELL( m ), w );
742
743    w = gtk_separator_menu_item_new( );
744    gtk_menu_shell_append( GTK_MENU_SHELL( m ), w );
745
746    for( i=0, n=G_N_ELEMENTS(stockRatios); i<n; ++i )
747    {
748        char buf[128];
749        tr_strlratio( buf, stockRatios[i], sizeof( buf ) );
750        w = gtk_menu_item_new_with_label( buf );
751        g_object_set_data( G_OBJECT( w ), RATIO_KEY, GINT_TO_POINTER( i ) );
752        g_signal_connect( w, "activate", G_CALLBACK(onRatioSet), p );
753        gtk_menu_shell_append( GTK_MENU_SHELL( m ), w );
754    }
755
756    return m;
757}
758
759/***
760****  Option menu
761***/
762
763static GtkWidget*
764createOptionsMenu( PrivateData * p )
765{
766    GtkWidget * m;
767    GtkWidget * top = gtk_menu_new( );
768
769    m = gtk_menu_item_new_with_label( _( "Limit Download Speed" ) );
770    gtk_menu_item_set_submenu( GTK_MENU_ITEM( m ), createSpeedMenu( p, TR_DOWN ) );
771    gtk_menu_shell_append( GTK_MENU_SHELL( top ), m );
772
773    m = gtk_menu_item_new_with_label( _( "Limit Upload Speed" ) );
774    gtk_menu_item_set_submenu( GTK_MENU_ITEM( m ), createSpeedMenu( p, TR_UP ) );
775    gtk_menu_shell_append( GTK_MENU_SHELL( top ), m );
776
777    m = gtk_separator_menu_item_new( );
778    gtk_menu_shell_append( GTK_MENU_SHELL( top ), m );
779
780    m = gtk_menu_item_new_with_label( _( "Stop Seeding at Ratio" ) );
781    gtk_menu_item_set_submenu( GTK_MENU_ITEM( m ), createRatioMenu( p ) );
782    gtk_menu_shell_append( GTK_MENU_SHELL( top ), m );
783
784    gtk_widget_show_all( top );
785    return top;
786}
787
788static void
789onOptionsClicked( GtkButton * button UNUSED, gpointer vp )
790{
791    char buf1[512];
792    char buf2[512];
793    gboolean b;
794    GtkWidget * w;
795    PrivateData * p = vp;
796
797    w = p->speedlimit_on_item[TR_DOWN];
798    tr_strlspeed( buf1, pref_int_get( TR_PREFS_KEY_DSPEED ), sizeof( buf1 ) );
799    gtk_label_set_text( GTK_LABEL( gtk_bin_get_child( GTK_BIN( w ) ) ), buf1 );
800
801    b = pref_flag_get( TR_PREFS_KEY_DSPEED_ENABLED );
802    w = b ? p->speedlimit_on_item[TR_DOWN] : p->speedlimit_off_item[TR_DOWN];
803    gtk_check_menu_item_set_active( GTK_CHECK_MENU_ITEM( w ), TRUE );
804
805    w = p->speedlimit_on_item[TR_UP];
806    tr_strlspeed( buf1, pref_int_get( TR_PREFS_KEY_USPEED ), sizeof( buf1 ) );
807    gtk_label_set_text( GTK_LABEL( gtk_bin_get_child( GTK_BIN( w ) ) ), buf1 );
808
809    b = pref_flag_get( TR_PREFS_KEY_USPEED_ENABLED );
810    w = b ? p->speedlimit_on_item[TR_UP] : p->speedlimit_off_item[TR_UP];
811    gtk_check_menu_item_set_active( GTK_CHECK_MENU_ITEM( w ), TRUE );
812
813    tr_strlratio( buf1, pref_double_get( TR_PREFS_KEY_RATIO ), sizeof( buf1 ) );
814    g_snprintf( buf2, sizeof( buf2 ), _( "Stop at Ratio (%s)" ), buf1 );
815    gtk_label_set_text( GTK_LABEL( gtk_bin_get_child( GTK_BIN( p->ratio_on_item ) ) ), buf2 );
816
817    b = pref_flag_get( TR_PREFS_KEY_RATIO_ENABLED );
818    gtk_check_menu_item_set_active( GTK_CHECK_MENU_ITEM( b ? p->ratio_on_item : p->ratio_off_item ), TRUE );
819
820    gtk_menu_popup ( GTK_MENU( p->options_menu ), NULL, NULL, NULL, NULL, 0, gtk_get_current_event_time( ) );
821}
822
823/***
824****  PUBLIC
825***/
826
827GtkWidget *
828tr_window_new( GtkUIManager * ui_mgr, TrCore * core )
829{
830    int           i, n;
831    const char  * pch;
832    PrivateData * p;
833    GtkWidget   * mainmenu, *toolbar, *filter, *list, *status;
834    GtkWidget   * vbox, *w, *self, *h, *s, *hbox, *menu;
835    GtkWindow   * win;
836    GSList      * l;
837
838    const char *  filter_names[FILTER_MODE_QTY] = {
839        /* show all torrents */
840        N_( "A_ll" ),
841        /* show only torrents that have connected peers */
842        N_( "_Active" ),
843        /* show only torrents that are trying to download */
844        N_( "_Downloading" ),
845        /* show only torrents that are trying to upload */
846        N_( "_Seeding" ),
847        /* show only torrents that are paused */
848        N_( "_Paused" )
849    };
850    const char *  filter_text_names[FILTER_TEXT_MODE_QTY] = {
851        N_( "Name" ), N_( "Files" ), N_( "Tracker" )
852    };
853
854    p = g_new0( PrivateData, 1 );
855    p->filter_text_mode = FILTER_TEXT_MODE_NAME;
856    p->filter_text = NULL;
857
858    /* make the window */
859    self = gtk_window_new ( GTK_WINDOW_TOPLEVEL );
860    g_object_set_data_full( G_OBJECT(
861                                self ), PRIVATE_DATA_KEY, p, privateFree );
862    win = GTK_WINDOW( self );
863    gtk_window_set_title( win, g_get_application_name( ) );
864    gtk_window_set_role( win, "tr-main" );
865    gtk_window_set_default_size( win,
866                                 pref_int_get( PREF_KEY_MAIN_WINDOW_WIDTH ),
867                                 pref_int_get( PREF_KEY_MAIN_WINDOW_HEIGHT ) );
868    gtk_window_move( win, pref_int_get( PREF_KEY_MAIN_WINDOW_X ),
869                          pref_int_get( PREF_KEY_MAIN_WINDOW_Y ) );
870    if( pref_flag_get( PREF_KEY_MAIN_WINDOW_IS_MAXIMIZED ) )
871        gtk_window_maximize( win );
872    gtk_window_add_accel_group( win, gtk_ui_manager_get_accel_group( ui_mgr ) );
873
874    /* window's main container */
875    vbox = gtk_vbox_new ( FALSE, 0 );
876    gtk_container_add ( GTK_CONTAINER( self ), vbox );
877
878    /* main menu */
879    mainmenu = action_get_widget( "/main-window-menu" );
880    w = action_get_widget( "/main-window-menu/torrent-menu/update-tracker" );
881#if GTK_CHECK_VERSION( 2, 12, 0 )
882    g_signal_connect( w, "query-tooltip",
883                      G_CALLBACK( onAskTrackerQueryTooltip ), p );
884#endif
885
886    /* toolbar */
887    toolbar = p->toolbar = action_get_widget( "/main-window-toolbar" );
888    action_set_important( "add-torrent-toolbar", TRUE );
889    action_set_important( "show-torrent-properties", TRUE );
890
891    /* filter */
892    h = filter = p->filter = gtk_hbox_new( FALSE, 0 );
893    gtk_container_set_border_width( GTK_CONTAINER( h ), GUI_PAD_SMALL );
894    for( i = 0; i < FILTER_MODE_QTY; ++i )
895    {
896        const char * mnemonic = _( filter_names[i] );
897        w = gtk_toggle_button_new_with_mnemonic( mnemonic );
898        g_object_set_data( G_OBJECT( w ), FILTER_MODE_KEY, GINT_TO_POINTER( i ) );
899        gtk_button_set_relief( GTK_BUTTON( w ), GTK_RELIEF_NONE );
900        gtk_toggle_button_set_active( GTK_TOGGLE_BUTTON( w ), i == FILTER_MODE_ALL );
901        p->filter_toggles[i] = GTK_TOGGLE_BUTTON( w );
902        g_signal_connect( w, "toggled", G_CALLBACK( filter_toggled_cb ), p );
903        gtk_box_pack_start( GTK_BOX( h ), w, FALSE, FALSE, 0 );
904    }
905
906#ifdef USE_SEXY
907    s = sexy_icon_entry_new( );
908    sexy_icon_entry_add_clear_button( SEXY_ICON_ENTRY( s ) );
909    w = gtk_image_new_from_stock( GTK_STOCK_FIND, GTK_ICON_SIZE_MENU );
910    sexy_icon_entry_set_icon( SEXY_ICON_ENTRY( s ),
911                              SEXY_ICON_ENTRY_PRIMARY,
912                              GTK_IMAGE( w ) );
913    g_object_unref( w );
914    sexy_icon_entry_set_icon_highlight( SEXY_ICON_ENTRY( s ),
915                                        SEXY_ICON_ENTRY_PRIMARY, TRUE );
916#else
917    s = gtk_entry_new( );
918    gtk_entry_set_icon_from_stock( GTK_ENTRY( s ),
919                                   GTK_ENTRY_ICON_PRIMARY,
920                                   GTK_STOCK_FIND);
921   gtk_entry_set_icon_from_stock( GTK_ENTRY( s ),
922                                  GTK_ENTRY_ICON_SECONDARY,
923                                  GTK_STOCK_CLEAR );
924#endif
925    gtk_box_pack_end( GTK_BOX( h ), s, FALSE, FALSE, 0 );
926    g_signal_connect( s, "changed", G_CALLBACK( filter_entry_changed ), p );
927
928    /* status menu */
929    menu = p->status_menu = gtk_menu_new( );
930    l = NULL;
931    pch = pref_string_get( PREF_KEY_STATUSBAR_STATS );
932    for( i = 0, n = G_N_ELEMENTS( stats_modes ); i < n; ++i )
933    {
934        const char * val = stats_modes[i].val;
935        w = gtk_radio_menu_item_new_with_label( l, _( stats_modes[i].i18n ) );
936        l = gtk_radio_menu_item_get_group( GTK_RADIO_MENU_ITEM( w ) );
937        gtk_check_menu_item_set_active( GTK_CHECK_MENU_ITEM( w ),
938                                       !strcmp( val, pch ) );
939        g_object_set_data( G_OBJECT(
940                               w ), STATS_MODE,
941                           (gpointer)stats_modes[i].val );
942        g_signal_connect( w, "toggled", G_CALLBACK(
943                              status_menu_toggled_cb ), p );
944        gtk_menu_shell_append( GTK_MENU_SHELL( menu ), w );
945        gtk_widget_show( w );
946    }
947
948    /* status */
949    h = status = p->status = gtk_hbox_new( FALSE, GUI_PAD );
950    gtk_container_set_border_width( GTK_CONTAINER( h ), GUI_PAD_SMALL );
951
952        w = gtk_button_new( );
953        gtk_container_add( GTK_CONTAINER( w ), gtk_image_new_from_stock( "options", GTK_ICON_SIZE_SMALL_TOOLBAR ) );
954        gtr_widget_set_tooltip_text( w, _( "Options" ) );
955        gtk_box_pack_start( GTK_BOX( h ), w, 0, 0, 0 );
956        gtk_button_set_relief( GTK_BUTTON( w ), GTK_RELIEF_NONE );
957        p->options_menu = createOptionsMenu( p );
958        g_signal_connect( w, "clicked", G_CALLBACK(onOptionsClicked), p );
959
960        p->alt_speed_image = gtk_image_new( );
961        w = p->alt_speed_button = gtk_toggle_button_new( );
962        gtk_button_set_image( GTK_BUTTON( w ), p->alt_speed_image );
963        gtk_button_set_relief( GTK_BUTTON( w ), GTK_RELIEF_NONE );
964        g_signal_connect( w, "toggled", G_CALLBACK(alt_speed_toggled_cb ), p );
965        gtk_box_pack_start( GTK_BOX( h ), w, 0, 0, 0 );
966
967        w = p->gutter_lb = gtk_label_new( "N Torrents" );
968        gtk_box_pack_start( GTK_BOX( h ), w, 1, 1, GUI_PAD_BIG );
969
970        hbox = gtk_hbox_new( FALSE, GUI_PAD_SMALL );
971            w = gtk_alignment_new( 0.0f, 0.0f, 0.0f, 0.0f );
972            gtk_widget_set_size_request( w, GUI_PAD, 0u );
973            gtk_box_pack_start( GTK_BOX( hbox ), w, FALSE, FALSE, 0 );
974            w = gtk_image_new_from_stock( GTK_STOCK_GO_UP, GTK_ICON_SIZE_MENU );
975            gtk_box_pack_start( GTK_BOX( hbox ), w, FALSE, FALSE, 0 );
976            w = p->ul_lb = gtk_label_new( NULL );
977            gtk_box_pack_start( GTK_BOX( hbox ), w, FALSE, FALSE, 0 );
978        gtk_box_pack_end( GTK_BOX( h ), hbox, FALSE, FALSE, 0 );
979
980        hbox = gtk_hbox_new( FALSE, GUI_PAD_SMALL );
981            w = gtk_alignment_new( 0.0f, 0.0f, 0.0f, 0.0f );
982            gtk_widget_set_size_request( w, GUI_PAD, 0u );
983            gtk_box_pack_start( GTK_BOX( hbox ), w, FALSE, FALSE, 0 );
984            w = gtk_image_new_from_stock( GTK_STOCK_GO_DOWN, GTK_ICON_SIZE_MENU );
985            gtk_box_pack_start( GTK_BOX( hbox ), w, FALSE, FALSE, 0 );
986            w = p->dl_lb = gtk_label_new( NULL );
987            gtk_box_pack_start( GTK_BOX( hbox ), w, FALSE, FALSE, 0 );
988        gtk_box_pack_end( GTK_BOX( h ), hbox, FALSE, FALSE, 0 );
989
990        hbox = gtk_hbox_new( FALSE, GUI_PAD_SMALL );
991            w = gtk_alignment_new( 0.0f, 0.0f, 0.0f, 0.0f );
992            gtk_widget_set_size_request( w, GUI_PAD, 0u );
993            gtk_box_pack_start( GTK_BOX( hbox ), w, FALSE, FALSE, 0 );
994            w = gtk_button_new( );
995            gtr_widget_set_tooltip_text( w, _( "Statistics" ) );
996            gtk_container_add( GTK_CONTAINER( w ), gtk_image_new_from_stock( "ratio", GTK_ICON_SIZE_SMALL_TOOLBAR ) );
997            gtk_button_set_relief( GTK_BUTTON( w ), GTK_RELIEF_NONE );
998            g_signal_connect( w, "clicked", G_CALLBACK( onYinYangReleased ), p );
999            gtk_box_pack_start( GTK_BOX( hbox ), w, FALSE, FALSE, 0 );
1000            w = p->stats_lb = gtk_label_new( NULL );
1001            gtk_box_pack_end( GTK_BOX( h ), w, FALSE, FALSE, 0 );
1002        gtk_box_pack_end( GTK_BOX( h ), hbox, FALSE, FALSE, 0 );
1003
1004
1005    menu = gtk_menu_new( );
1006    l = NULL;
1007    for( i=0; i<FILTER_TEXT_MODE_QTY; ++i )
1008    {
1009        const char * name = _( filter_text_names[i] );
1010        GtkWidget *  w = gtk_radio_menu_item_new_with_label ( l, name );
1011        l = gtk_radio_menu_item_get_group( GTK_RADIO_MENU_ITEM( w ) );
1012        g_object_set_data( G_OBJECT( w ), FILTER_TEXT_MODE_KEY,
1013                           GINT_TO_POINTER( i ) );
1014        g_signal_connect( w, "toggled",
1015                          G_CALLBACK( filter_text_toggled_cb ), p );
1016        gtk_menu_shell_append( GTK_MENU_SHELL( menu ), w );
1017        gtk_widget_show( w );
1018    }
1019
1020#ifdef USE_SEXY
1021    g_signal_connect( s, "icon-released", G_CALLBACK( entry_icon_released ), menu );
1022#else
1023    g_signal_connect( s, "icon-release", G_CALLBACK( entry_icon_release ), menu );
1024
1025#endif
1026
1027    /* workarea */
1028    p->view = makeview( p, core );
1029    w = list = p->scroll = gtk_scrolled_window_new( NULL, NULL );
1030    gtk_scrolled_window_set_policy( GTK_SCROLLED_WINDOW( w ),
1031                                    GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC );
1032    gtk_scrolled_window_set_shadow_type( GTK_SCROLLED_WINDOW( w ),
1033                                         GTK_SHADOW_IN );
1034    gtk_container_add( GTK_CONTAINER( w ), p->view );
1035
1036    /* layout the widgets */
1037    {
1038        const char * str = pref_string_get( PREF_KEY_MAIN_WINDOW_LAYOUT_ORDER );
1039        char ** tokens = g_strsplit( str, ",", -1 );
1040        for( i=0; tokens && tokens[i]; ++i )
1041        {
1042            const char * key = tokens[i];
1043
1044            if( !strcmp( key, "menu" ) )
1045                gtk_box_pack_start( GTK_BOX( vbox ), mainmenu, FALSE, FALSE, 0 );
1046            else if( !strcmp( key, "toolbar" ) )
1047                gtk_box_pack_start( GTK_BOX( vbox ), toolbar, FALSE, FALSE, 0 );
1048            else if( !strcmp( key, "filter" ) )
1049                gtk_box_pack_start( GTK_BOX( vbox ), filter, FALSE, FALSE, 0 );
1050            else if( !strcmp( key, "list" ) )
1051                gtk_box_pack_start( GTK_BOX( vbox ), list, TRUE, TRUE, 0 );
1052            else if( !strcmp( key, "statusbar" ) )
1053                gtk_box_pack_start( GTK_BOX( vbox ), status, FALSE, FALSE, 0 );
1054        }
1055        g_strfreev( tokens );
1056    }
1057
1058    {
1059        int w=0, h=0;
1060        /* this is to determine the maximum width/height for the label */
1061        PangoLayout * pango_layout =
1062            gtk_widget_create_pango_layout( p->ul_lb, _( "999.9 KB/s" ) );
1063        pango_layout_get_pixel_size( pango_layout, &w, &h );
1064        gtk_widget_set_size_request( p->ul_lb, w, h );
1065        gtk_widget_set_size_request( p->dl_lb, w, h );
1066        gtk_misc_set_alignment( GTK_MISC( p->ul_lb ), 1.0, 0.5 );
1067        gtk_misc_set_alignment( GTK_MISC( p->dl_lb ), 1.0, 0.5 );
1068        g_object_unref( G_OBJECT( pango_layout ) );
1069    }
1070
1071    /* show all but the window */
1072    gtk_widget_show_all( vbox );
1073
1074    /* listen for prefs changes that affect the window */
1075    p->core = core;
1076    prefsChanged( core, PREF_KEY_MINIMAL_VIEW, self );
1077    prefsChanged( core, PREF_KEY_FILTERBAR, self );
1078    prefsChanged( core, PREF_KEY_STATUSBAR, self );
1079    prefsChanged( core, PREF_KEY_STATUSBAR_STATS, self );
1080    prefsChanged( core, PREF_KEY_TOOLBAR, self );
1081    prefsChanged( core, PREF_KEY_FILTER_MODE, self );
1082    prefsChanged( core, TR_PREFS_KEY_ALT_SPEED_ENABLED, self );
1083    p->pref_handler_id = g_signal_connect( core, "prefs-changed",
1084                                           G_CALLBACK( prefsChanged ), self );
1085
1086    tr_sessionSetAltSpeedFunc( tr_core_session( core ), onAltSpeedToggled, p );
1087
1088    filter_entry_changed( GTK_EDITABLE( s ), p );
1089    return self;
1090}
1091
1092static void
1093updateTorrentCount( PrivateData * p )
1094{
1095    if( p && p->core )
1096    {
1097        char      buf[512];
1098        const int torrentCount = gtk_tree_model_iter_n_children(
1099            tr_core_model( p->core ), NULL );
1100        const int visibleCount = gtk_tree_model_iter_n_children(
1101            p->filter_model, NULL );
1102
1103        if( !torrentCount )
1104            *buf = '\0';
1105        else if( torrentCount != visibleCount )
1106            g_snprintf( buf, sizeof( buf ),
1107                        ngettext( "%1$'d of %2$'d Torrent",
1108                                  "%1$'d of %2$'d Torrents",
1109                                  torrentCount ),
1110                        visibleCount, torrentCount );
1111        else
1112            g_snprintf( buf, sizeof( buf ),
1113                        ngettext( "%'d Torrent", "%'d Torrents", torrentCount ),
1114                        torrentCount );
1115        gtk_label_set_text( GTK_LABEL( p->gutter_lb ), buf );
1116    }
1117}
1118
1119static void
1120updateStats( PrivateData * p )
1121{
1122    const char *            pch;
1123    char                    up[32], down[32], ratio[32], buf[512];
1124    struct tr_session_stats stats;
1125    tr_session *            session = tr_core_session( p->core );
1126
1127    /* update the stats */
1128    pch = pref_string_get( PREF_KEY_STATUSBAR_STATS );
1129    if( !strcmp( pch, "session-ratio" ) )
1130    {
1131        tr_sessionGetStats( session, &stats );
1132        tr_strlratio( ratio, stats.ratio, sizeof( ratio ) );
1133        g_snprintf( buf, sizeof( buf ), _( "Ratio: %s" ), ratio );
1134    }
1135    else if( !strcmp( pch, "session-transfer" ) )
1136    {
1137        tr_sessionGetStats( session, &stats );
1138        tr_strlsize( up, stats.uploadedBytes, sizeof( up ) );
1139        tr_strlsize( down, stats.downloadedBytes, sizeof( down ) );
1140        /* Translators: "size|" is here for disambiguation.  Please remove it from your translation.
1141           %1$s is the size of the data we've downloaded
1142           %2$s is the size of the data we've uploaded */
1143        g_snprintf( buf, sizeof( buf ), Q_(
1144                        "size|Down: %1$s, Up: %2$s" ), down, up );
1145    }
1146    else if( !strcmp( pch, "total-transfer" ) )
1147    {
1148        tr_sessionGetCumulativeStats( session, &stats );
1149        tr_strlsize( up, stats.uploadedBytes, sizeof( up ) );
1150        tr_strlsize( down, stats.downloadedBytes, sizeof( down ) );
1151        /* Translators: "size|" is here for disambiguation.  Please remove it from your translation.
1152           %1$s is the size of the data we've downloaded
1153           %2$s is the size of the data we've uploaded */
1154        g_snprintf( buf, sizeof( buf ), Q_(
1155                        "size|Down: %1$s, Up: %2$s" ), down, up );
1156    }
1157    else     /* default is total-ratio */
1158    {
1159        tr_sessionGetCumulativeStats( session, &stats );
1160        tr_strlratio( ratio, stats.ratio, sizeof( ratio ) );
1161        g_snprintf( buf, sizeof( buf ), _( "Ratio: %s" ), ratio );
1162    }
1163    gtk_label_set_text( GTK_LABEL( p->stats_lb ), buf );
1164}
1165
1166static void
1167updateSpeeds( PrivateData * p )
1168{
1169    tr_session * session = tr_core_session( p->core );
1170
1171    if( session != NULL )
1172    {
1173        char buf[128];
1174        double up=0, down=0;
1175        GtkTreeIter iter;
1176        GtkTreeModel * model = tr_core_model( p->core );
1177
1178        if( gtk_tree_model_get_iter_first( model, &iter ) ) do
1179        {
1180            double u, d;
1181            gtk_tree_model_get( model, &iter, MC_SPEED_UP, &u,
1182                                              MC_SPEED_DOWN, &d,
1183                                              -1 );
1184            up += u;
1185            down += d;
1186        }
1187        while( gtk_tree_model_iter_next( model, &iter ) );
1188
1189        tr_strlspeed( buf, down, sizeof( buf ) );
1190        gtk_label_set_text( GTK_LABEL( p->dl_lb ), buf );
1191
1192        tr_strlspeed( buf, up, sizeof( buf ) );
1193        gtk_label_set_text( GTK_LABEL( p->ul_lb ), buf );
1194    }
1195}
1196
1197void
1198tr_window_update( TrWindow * self )
1199{
1200    PrivateData * p = get_private_data( self );
1201
1202    if( p && p->core && tr_core_session( p->core ) )
1203    {
1204        updateSpeeds( p );
1205        updateTorrentCount( p );
1206        updateStats( p );
1207        refilter( p );
1208    }
1209}
1210
1211GtkTreeSelection*
1212tr_window_get_selection( TrWindow * w )
1213{
1214    return get_private_data( w )->selection;
1215}
1216
Note: See TracBrowser for help on using the repository browser.