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

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

(trunk gtk) #2708 "missing tooltips" -- add previously-missing tooltips to two of the statusbar buttons

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