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

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

(trunk gtk) remember filtering mode between sessions

  • Property svn:keywords set to Date Rev Author Id
File size: 30.2 KB
Line 
1/******************************************************************************
2 * $Id: tr-window.c 8195 2009-04-10 04:22:57Z 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#include <libtransmission/transmission.h>
31
32#include "actions.h"
33#include "conf.h"
34#include "hig.h"
35#include "sexy-icon-entry.h"
36#include "torrent-cell-renderer.h"
37#include "tr-prefs.h"
38#include "tr-torrent.h"
39#include "tr-window.h"
40#include "util.h"
41
42#if !GTK_CHECK_VERSION( 2, 8, 0 )
43static void
44gtk_tree_view_column_queue_resize( GtkTreeViewColumn * column ) /* yuck */
45{
46    const int spacing = gtk_tree_view_column_get_spacing( column );
47
48    gtk_tree_view_column_set_spacing( column, spacing + 1 );
49    gtk_tree_view_column_set_spacing( column, spacing );
50}
51
52#endif
53
54typedef enum
55{
56    FILTER_TEXT_MODE_NAME,
57    FILTER_TEXT_MODE_FILES,
58    FILTER_TEXT_MODE_TRACKER,
59    FILTER_TEXT_MODE_QTY
60}
61filter_text_mode_t;
62
63typedef enum
64{
65    FILTER_MODE_ALL,
66    FILTER_MODE_ACTIVE,
67    FILTER_MODE_DOWNLOADING,
68    FILTER_MODE_SEEDING,
69    FILTER_MODE_PAUSED,
70    FILTER_MODE_QTY
71}
72filter_mode_t;
73
74typedef struct
75{
76    GtkWidget *           scroll;
77    GtkWidget *           view;
78    GtkWidget *           toolbar;
79    GtkWidget *           filter;
80    GtkWidget *           status;
81    GtkWidget *           status_menu;
82    GtkWidget *           ul_lb;
83    GtkWidget *           dl_lb;
84    GtkWidget *           stats_lb;
85    GtkWidget *           gutter_lb;
86    GtkWidget *           alt_speed_image[2]; /* 0==off, 1==on */
87    GtkWidget *           alt_speed_button;
88    GtkTreeSelection *    selection;
89    GtkCellRenderer *     renderer;
90    GtkTreeViewColumn *   column;
91    GtkTreeModel *        filter_model;
92    TrCore *              core;
93    gulong                pref_handler_id;
94    filter_mode_t         filter_mode;
95    filter_text_mode_t    filter_text_mode;
96    char *                filter_text;
97    GtkToggleButton     * filter_toggles[FILTER_MODE_QTY];
98}
99PrivateData;
100
101static const char*
102getFilterName( int mode )
103{
104    switch( mode )
105    {
106        case FILTER_MODE_ACTIVE:      return "show-active";
107        case FILTER_MODE_DOWNLOADING: return "show-downloading";
108        case FILTER_MODE_SEEDING:     return "show-seeding";
109        case FILTER_MODE_PAUSED:      return "show-paused";
110        default:                      return "show-active"; /* the fallback */
111    }
112}
113static int
114getFilterModeFromName( const char * name )
115{
116    if( !strcmp( name, "show-active"      ) ) return FILTER_MODE_ACTIVE;
117    if( !strcmp( name, "show-downloading" ) ) return FILTER_MODE_DOWNLOADING;
118    if( !strcmp( name, "show-seeding"     ) ) return FILTER_MODE_SEEDING;
119    if( !strcmp( name, "show-paused"      ) ) return FILTER_MODE_PAUSED;
120    return FILTER_MODE_ALL; /* the fallback */
121}
122
123#define PRIVATE_DATA_KEY "private-data"
124#define FILTER_MODE_KEY "tr-filter-mode"
125#define FILTER_TEXT_MODE_KEY "tr-filter-text-mode"
126
127static PrivateData*
128get_private_data( TrWindow * w )
129{
130    return g_object_get_data ( G_OBJECT( w ), PRIVATE_DATA_KEY );
131}
132
133/***
134****
135***/
136
137static void
138on_popup_menu( GtkWidget * self UNUSED,
139               GdkEventButton * event )
140{
141    GtkWidget * menu = action_get_widget ( "/main-window-popup" );
142
143    gtk_menu_popup ( GTK_MENU( menu ), NULL, NULL, NULL, NULL,
144                    ( event ? event->button : 0 ),
145                    ( event ? event->time : 0 ) );
146}
147
148static void
149view_row_activated( GtkTreeView       * tree_view UNUSED,
150                    GtkTreePath       * path      UNUSED,
151                    GtkTreeViewColumn * column    UNUSED,
152                    gpointer            user_data UNUSED )
153{
154    action_activate( "show-torrent-properties" );
155}
156
157static gboolean is_row_visible( GtkTreeModel *,
158                                GtkTreeIter  *,
159                                gpointer );
160
161static GtkWidget*
162makeview( PrivateData * p,
163          TrCore *      core )
164{
165    GtkWidget *         view;
166    GtkTreeViewColumn * col;
167    GtkTreeSelection *  sel;
168    GtkCellRenderer *   r;
169    GtkTreeModel *      filter_model;
170
171    view = gtk_tree_view_new( );
172    gtk_tree_view_set_headers_visible( GTK_TREE_VIEW( view ), FALSE );
173    gtk_tree_view_set_fixed_height_mode( GTK_TREE_VIEW( view ), TRUE );
174
175    p->selection = gtk_tree_view_get_selection( GTK_TREE_VIEW( view ) );
176
177    p->column = col = GTK_TREE_VIEW_COLUMN (g_object_new (GTK_TYPE_TREE_VIEW_COLUMN,                 
178        "title", _("Torrent"),
179        "resizable", TRUE,
180        "sizing", GTK_TREE_VIEW_COLUMN_FIXED,
181        NULL));
182
183    p->renderer = r = torrent_cell_renderer_new( );
184    gtk_tree_view_column_pack_start( col, r, FALSE );
185    gtk_tree_view_column_add_attribute( col, r, "torrent", MC_TORRENT_RAW );
186   
187    gtk_tree_view_append_column( GTK_TREE_VIEW( view ), col );
188    g_object_set( r, "xpad", GUI_PAD_SMALL, "ypad", GUI_PAD_SMALL, NULL );
189
190    gtk_tree_view_set_rules_hint( GTK_TREE_VIEW( view ), TRUE );
191    sel = gtk_tree_view_get_selection( GTK_TREE_VIEW( view ) );
192    gtk_tree_selection_set_mode( GTK_TREE_SELECTION( sel ),
193                                 GTK_SELECTION_MULTIPLE );
194
195    g_signal_connect( view, "popup-menu",
196                      G_CALLBACK( on_popup_menu ), NULL );
197    g_signal_connect( view, "button-press-event",
198                      G_CALLBACK( on_tree_view_button_pressed ),
199                      (void *) on_popup_menu );
200    g_signal_connect( view, "button-release-event",
201                      G_CALLBACK( on_tree_view_button_released ), NULL );
202    g_signal_connect( view, "row-activated",
203                      G_CALLBACK( view_row_activated ), NULL );
204
205
206    filter_model = p->filter_model = gtk_tree_model_filter_new(
207                       tr_core_model( core ), NULL );
208
209    gtk_tree_model_filter_set_visible_func( GTK_TREE_MODEL_FILTER(
210                                                filter_model ),
211                                            is_row_visible,
212                                            p, NULL );
213
214    gtk_tree_view_set_model( GTK_TREE_VIEW( view ), filter_model );
215
216    return view;
217}
218
219static void syncAltSpeedButton( PrivateData * p );
220static void setFilter( PrivateData * p, int mode );
221
222static void
223prefsChanged( TrCore * core UNUSED,
224              const char *  key,
225              gpointer      wind )
226{
227    PrivateData * p = get_private_data( GTK_WINDOW( wind ) );
228
229    if( !strcmp( key, PREF_KEY_MINIMAL_VIEW ) )
230    {
231        g_object_set( p->renderer, "minimal", pref_flag_get( key ), NULL );
232        /* since the cell size has changed, we need gtktreeview to revalidate
233         * its fixed-height mode values.  Unfortunately there's not an API call
234         * for that, but it *does* revalidate when it thinks the style's been tweaked */
235        g_signal_emit_by_name( p->view, "style-set", NULL, NULL );
236    }
237    else if( !strcmp( key, PREF_KEY_FILTER_MODE ) )
238    {
239        setFilter( p, getFilterModeFromName( pref_string_get( key ) ) );
240    }
241    else if( !strcmp( key, PREF_KEY_STATUSBAR ) )
242    {
243        const gboolean isEnabled = pref_flag_get( key );
244        g_object_set( p->status, "visible", isEnabled, NULL );
245    }
246    else if( !strcmp( key, PREF_KEY_FILTERBAR ) )
247    {
248        const gboolean isEnabled = pref_flag_get( key );
249        g_object_set( p->filter, "visible", isEnabled, NULL );
250    }
251    else if( !strcmp( key, PREF_KEY_TOOLBAR ) )
252    {
253        const gboolean isEnabled = pref_flag_get( key );
254        g_object_set( p->toolbar, "visible", isEnabled, NULL );
255    }
256    else if( !strcmp( key, PREF_KEY_STATUSBAR_STATS ) )
257    {
258        tr_window_update( (TrWindow*)wind );
259    }
260    else if( !strcmp( key, TR_PREFS_KEY_ALT_SPEED_ENABLED ) )
261    {
262        syncAltSpeedButton( p );
263    }
264}
265
266static void
267privateFree( gpointer vprivate )
268{
269    PrivateData * p = vprivate;
270
271    g_signal_handler_disconnect( p->core, p->pref_handler_id );
272    g_free( p->filter_text );
273    g_free( p );
274}
275
276static void
277onYinYangReleased( GtkWidget * w           UNUSED,
278                   GdkEventButton * button UNUSED,
279                   gpointer                vprivate )
280{
281    PrivateData * p = vprivate;
282
283    gtk_menu_popup( GTK_MENU(
284                       p->status_menu ), NULL, NULL, NULL, NULL, 0,
285                   gtk_get_current_event_time( ) );
286}
287
288#define STATS_MODE "stats-mode"
289
290static struct
291{
292    const char *  val, *i18n;
293} stats_modes[] = {
294    { "total-ratio",      N_( "Total Ratio" )                },
295    { "session-ratio",    N_( "Session Ratio" )              },
296    { "total-transfer",   N_( "Total Transfer" )             },
297    { "session-transfer", N_( "Session Transfer" )           }
298};
299
300static void
301status_menu_toggled_cb( GtkCheckMenuItem * menu_item,
302                        gpointer           vprivate )
303{
304    if( gtk_check_menu_item_get_active( menu_item ) )
305    {
306        PrivateData * p = vprivate;
307        const char *  val = g_object_get_data( G_OBJECT(
308                                                   menu_item ), STATS_MODE );
309        tr_core_set_pref( p->core, PREF_KEY_STATUSBAR_STATS, val );
310    }
311}
312
313static void
314syncAltSpeedButton( PrivateData * p )
315{
316    const char * tip;
317    const gboolean b = pref_flag_get( TR_PREFS_KEY_ALT_SPEED_ENABLED );
318    GtkWidget * w = p->alt_speed_button;
319
320    gtk_toggle_button_set_active( GTK_TOGGLE_BUTTON( w ), b );
321
322    gtk_button_set_image( GTK_BUTTON( w ), p->alt_speed_image[b?1:0] );
323    gtk_button_set_alignment( GTK_BUTTON( w ), 0.5, 0.5 );
324
325    tip = b ? _( "Click to disable Speed Limit Mode" )
326            : _( "Click to enable Speed Limit Mode" );
327    gtr_widget_set_tooltip_text( w, tip );
328}
329
330static void
331alt_speed_toggled_cb( GtkToggleButton * button, gpointer vprivate )
332{
333    PrivateData * p = vprivate;
334    const gboolean b = gtk_toggle_button_get_active( button );
335    tr_core_set_pref_bool( p->core, TR_PREFS_KEY_ALT_SPEED_ENABLED,  b );
336}
337   
338/***
339****  FILTER
340***/
341
342static int
343checkFilterText( filter_text_mode_t    filter_text_mode,
344                 const tr_info       * torInfo,
345                 const char          * text )
346{
347    tr_file_index_t i;
348    int             ret = 0;
349    char *          pch;
350
351    switch( filter_text_mode )
352    {
353        case FILTER_TEXT_MODE_FILES:
354            for( i = 0; i < torInfo->fileCount && !ret; ++i )
355            {
356                pch = g_utf8_casefold( torInfo->files[i].name, -1 );
357                ret = !text || strstr( pch, text ) != NULL;
358                g_free( pch );
359            }
360            break;
361
362        case FILTER_TEXT_MODE_TRACKER:
363            pch = g_utf8_casefold( torInfo->trackers[0].announce, -1 );
364            ret = !text || ( strstr( pch, text ) != NULL );
365            g_free( pch );
366            break;
367
368        default: /* NAME */
369            pch = g_utf8_casefold( torInfo->name, -1 );
370            ret = !text || ( strstr( pch, text ) != NULL );
371            g_free( pch );
372            break;
373    }
374
375    return ret;
376}
377
378static int
379checkFilterMode( filter_mode_t filter_mode,
380                 tr_torrent *  tor )
381{
382    int ret = 0;
383
384    switch( filter_mode )
385    {
386        case FILTER_MODE_DOWNLOADING:
387            ret = tr_torrentGetActivity( tor ) == TR_STATUS_DOWNLOAD;
388            break;
389
390        case FILTER_MODE_SEEDING:
391            ret = tr_torrentGetActivity( tor ) == TR_STATUS_SEED;
392            break;
393
394        case FILTER_MODE_PAUSED:
395            ret = tr_torrentGetActivity( tor ) == TR_STATUS_STOPPED;
396            break;
397
398        case FILTER_MODE_ACTIVE:
399        {
400            const tr_stat * s = tr_torrentStatCached( tor );
401            ret = s->peersSendingToUs > 0
402               || s->peersGettingFromUs > 0
403               || tr_torrentGetActivity( tor ) == TR_STATUS_CHECK;
404            break;
405        }
406
407        default: /* all */
408            ret = 1;
409    }
410
411    return ret;
412}
413
414static gboolean
415is_row_visible( GtkTreeModel * model,
416                GtkTreeIter *  iter,
417                gpointer       vprivate )
418{
419    PrivateData * p = vprivate;
420    tr_torrent *  tor;
421
422    gtk_tree_model_get( model, iter, MC_TORRENT_RAW, &tor, -1 );
423
424    return checkFilterMode( p->filter_mode, tor )
425           && checkFilterText( p->filter_text_mode, tr_torrentInfo( tor ), p->filter_text );
426}
427
428static void updateTorrentCount( PrivateData * p );
429
430static void
431refilter( PrivateData * p )
432{
433    gtk_tree_model_filter_refilter( GTK_TREE_MODEL_FILTER( p->filter_model ) );
434
435    updateTorrentCount( p );
436}
437
438static void
439filter_text_toggled_cb( GtkCheckMenuItem * menu_item,
440                        gpointer           vprivate )
441{
442    if( gtk_check_menu_item_get_active( menu_item ) )
443    {
444        PrivateData * p = vprivate;
445        p->filter_text_mode =
446            GPOINTER_TO_UINT( g_object_get_data( G_OBJECT( menu_item ),
447                                                 FILTER_TEXT_MODE_KEY ) );
448        refilter( p );
449    }
450}
451
452static void
453setFilter( PrivateData * p, int mode )
454{
455    if( mode != (int)p->filter_mode )
456    {
457        int i;
458
459        /* refilter */
460        p->filter_mode = mode;
461        refilter( p );
462
463        /* update the prefs */
464        tr_core_set_pref( p->core, PREF_KEY_FILTER_MODE, getFilterName( mode ) );
465
466        /* update the togglebuttons */
467        for( i=0; i<FILTER_MODE_QTY; ++i )
468            gtk_toggle_button_set_active( p->filter_toggles[i], i==mode );
469    }
470}
471 
472
473static void
474filter_toggled_cb( GtkToggleButton * toggle, gpointer vprivate )
475{
476    if( gtk_toggle_button_get_active( toggle ) )
477    {
478        PrivateData * p = vprivate;
479        const int mode = GPOINTER_TO_UINT( g_object_get_data( G_OBJECT( toggle ), FILTER_MODE_KEY ) );
480        setFilter( p, mode );
481    }
482}
483
484static void
485filter_entry_changed( GtkEditable * e,
486                      gpointer      vprivate )
487{
488    char *        pch;
489    PrivateData * p = vprivate;
490
491    pch = gtk_editable_get_chars( e, 0, -1 );
492    g_free( p->filter_text );
493    p->filter_text = g_utf8_casefold( pch, -1 );
494    refilter( p );
495    g_free( pch );
496}
497
498static void
499entry_icon_released( SexyIconEntry         * entry  UNUSED,
500                     SexyIconEntryPosition          icon_pos,
501                     int                     button UNUSED,
502                     gpointer                       menu )
503{
504    if( icon_pos == SEXY_ICON_ENTRY_PRIMARY )
505        gtk_menu_popup ( GTK_MENU(
506                            menu ), NULL, NULL, NULL, NULL, 0,
507                        gtk_get_current_event_time( ) );
508}
509
510#if GTK_CHECK_VERSION( 2, 12, 0 )
511
512static void
513findMaxAnnounceTime( GtkTreeModel *      model,
514                     GtkTreePath  * path UNUSED,
515                     GtkTreeIter *       iter,
516                     gpointer            gmaxTime )
517{
518    tr_torrent *    tor;
519    const tr_stat * torStat;
520    time_t *        maxTime = gmaxTime;
521
522    gtk_tree_model_get( model, iter, MC_TORRENT_RAW, &tor, -1 );
523    torStat = tr_torrentStatCached( tor );
524    *maxTime = MAX( *maxTime, torStat->manualAnnounceTime );
525}
526
527static gboolean
528onAskTrackerQueryTooltip( GtkWidget *            widget UNUSED,
529                          gint                   x UNUSED,
530                          gint                   y UNUSED,
531                          gboolean               keyboard_tip UNUSED,
532                          GtkTooltip *           tooltip,
533                          gpointer               gdata )
534{
535    const time_t now = time( NULL );
536    time_t       maxTime = 0;
537    PrivateData * p = gdata;
538
539    gtk_tree_selection_selected_foreach( p->selection,
540                                         findMaxAnnounceTime,
541                                         &maxTime );
542    if( maxTime <= now )
543    {
544        return FALSE;
545    }
546    else
547    {
548        char      buf[128];
549        char      timebuf[64];
550        const int seconds = maxTime - now;
551
552        tr_strltime( timebuf, seconds, sizeof( timebuf ) );
553        g_snprintf( buf, sizeof( buf ),
554                    _( "Tracker will allow requests in %s" ), timebuf );
555        gtk_tooltip_set_text( tooltip, buf );
556        return TRUE;
557    }
558}
559
560#endif
561
562static gboolean
563onAltSpeedToggledIdle( gpointer vp )
564{
565    PrivateData * p = vp;
566    gboolean b = tr_sessionUsesAltSpeed( tr_core_session( p->core ) );
567    tr_core_set_pref_bool( p->core, TR_PREFS_KEY_ALT_SPEED_ENABLED, b );
568
569    return FALSE;
570}
571
572static void
573onAltSpeedToggled( tr_session * s UNUSED, tr_bool isEnabled UNUSED, tr_bool byUser UNUSED, void * p )
574{
575    g_idle_add( onAltSpeedToggledIdle, p );
576}
577
578/***
579****  PUBLIC
580***/
581
582GtkWidget *
583tr_window_new( GtkUIManager * ui_mgr, TrCore * core )
584{
585    int           i, n;
586    const char *  pch;
587    PrivateData * p;
588    GtkWidget   *mainmenu, *toolbar, *filter, *list, *status;
589    GtkWidget *   vbox, *w, *self, *h, *c, *s, *image, *menu;
590    GtkWindow *   win;
591    GSList *      l;
592
593    const char *  filter_names[FILTER_MODE_QTY] = {
594        /* show all torrents */
595        N_( "A_ll" ),
596        /* show only torrents that have connected peers */
597        N_( "_Active" ),
598        /* show only torrents that are trying to download */
599        N_( "_Downloading" ),
600        /* show only torrents that are trying to upload */
601        N_( "_Seeding" ),
602        /* show only torrents that are paused */
603        N_( "_Paused" )
604    };
605    const char *  filter_text_names[FILTER_TEXT_MODE_QTY] = {
606        N_( "Name" ), N_( "Files" ), N_( "Tracker" )
607    };
608
609    p = g_new0( PrivateData, 1 );
610    p->filter_text_mode = FILTER_TEXT_MODE_NAME;
611    p->filter_text = NULL;
612
613    /* make the window */
614    self = gtk_window_new ( GTK_WINDOW_TOPLEVEL );
615    g_object_set_data_full( G_OBJECT(
616                                self ), PRIVATE_DATA_KEY, p, privateFree );
617    win = GTK_WINDOW( self );
618    gtk_window_set_title( win, g_get_application_name( ) );
619    gtk_window_set_role( win, "tr-main" );
620    gtk_window_set_default_size( win,
621                                 pref_int_get( PREF_KEY_MAIN_WINDOW_WIDTH ),
622                                 pref_int_get( PREF_KEY_MAIN_WINDOW_HEIGHT ) );
623    gtk_window_move( win, pref_int_get( PREF_KEY_MAIN_WINDOW_X ),
624                     pref_int_get( PREF_KEY_MAIN_WINDOW_Y ) );
625    gtk_window_add_accel_group( win, gtk_ui_manager_get_accel_group( ui_mgr ) );
626
627    /* window's main container */
628    vbox = gtk_vbox_new ( FALSE, 0 );
629    gtk_container_add ( GTK_CONTAINER( self ), vbox );
630
631    /* main menu */
632    w = mainmenu = action_get_widget( "/main-window-menu" );
633    w = action_get_widget( "/main-window-menu/torrent-menu/update-tracker" );
634#if GTK_CHECK_VERSION( 2, 12, 0 )
635    g_signal_connect( w, "query-tooltip",
636                      G_CALLBACK( onAskTrackerQueryTooltip ), p );
637#endif
638
639    /* toolbar */
640    w = toolbar = p->toolbar = action_get_widget( "/main-window-toolbar" );
641
642    /* filter */
643    h = filter = p->filter = gtk_hbox_new( FALSE, 0 );
644    gtk_container_set_border_width( GTK_CONTAINER( h ), GUI_PAD_SMALL );
645    for( i = 0; i < FILTER_MODE_QTY; ++i )
646    {
647        const char * mnemonic = _( filter_names[i] );
648        w = gtk_toggle_button_new_with_mnemonic( mnemonic );
649        g_object_set_data( G_OBJECT( w ), FILTER_MODE_KEY, GINT_TO_POINTER( i ) );
650        gtk_button_set_relief( GTK_BUTTON( w ), GTK_RELIEF_NONE );
651        gtk_toggle_button_set_active( GTK_TOGGLE_BUTTON( w ), i == FILTER_MODE_ALL );
652        p->filter_toggles[i] = GTK_TOGGLE_BUTTON( w );
653        g_signal_connect( w, "toggled", G_CALLBACK( filter_toggled_cb ), p );
654        gtk_box_pack_start( GTK_BOX( h ), w, FALSE, FALSE, 0 );
655    }
656
657    s = sexy_icon_entry_new( );
658    sexy_icon_entry_add_clear_button( SEXY_ICON_ENTRY( s ) );
659    image = gtk_image_new_from_stock( GTK_STOCK_FIND, GTK_ICON_SIZE_MENU );
660    sexy_icon_entry_set_icon( SEXY_ICON_ENTRY(
661                                 s ), SEXY_ICON_ENTRY_PRIMARY,
662                             GTK_IMAGE( image ) );
663    sexy_icon_entry_set_icon_highlight( SEXY_ICON_ENTRY(
664                                            s ), SEXY_ICON_ENTRY_PRIMARY,
665                                        TRUE );
666    gtk_box_pack_end( GTK_BOX( h ), s, FALSE, FALSE, 0 );
667    g_signal_connect( s, "changed", G_CALLBACK( filter_entry_changed ), p );
668
669    /* status menu */
670    menu = p->status_menu = gtk_menu_new( );
671    l = NULL;
672    pch = pref_string_get( PREF_KEY_STATUSBAR_STATS );
673    for( i = 0, n = G_N_ELEMENTS( stats_modes ); i < n; ++i )
674    {
675        const char * val = stats_modes[i].val;
676        w = gtk_radio_menu_item_new_with_label( l, _( stats_modes[i].i18n ) );
677        l = gtk_radio_menu_item_get_group( GTK_RADIO_MENU_ITEM( w ) );
678        gtk_check_menu_item_set_active( GTK_CHECK_MENU_ITEM( w ),
679                                       !strcmp( val, pch ) );
680        g_object_set_data( G_OBJECT(
681                               w ), STATS_MODE,
682                           (gpointer)stats_modes[i].val );
683        g_signal_connect( w, "toggled", G_CALLBACK(
684                              status_menu_toggled_cb ), p );
685        gtk_menu_shell_append( GTK_MENU_SHELL( menu ), w );
686        gtk_widget_show( w );
687    }
688
689    /* status */
690    h = status = p->status = gtk_hbox_new( FALSE, GUI_PAD );
691    gtk_container_set_border_width( GTK_CONTAINER( h ), GUI_PAD_SMALL );
692    p->alt_speed_image[0] = gtk_image_new_from_stock( "alt-speed-off", -1 );
693    p->alt_speed_image[1]  = gtk_image_new_from_stock( "alt-speed-on", -1 );
694    w = p->alt_speed_button = gtk_toggle_button_new( );
695    /*gtk_button_set_relief( GTK_BUTTON( w ), GTK_RELIEF_NONE );*/
696    g_object_ref( G_OBJECT( p->alt_speed_image[0] ) );
697    g_object_ref( G_OBJECT( p->alt_speed_image[1] ) );
698    g_signal_connect( w, "toggled", G_CALLBACK(alt_speed_toggled_cb ), p );
699    gtk_box_pack_start( GTK_BOX( h ), w, 0, 0, 0 );
700    w = p->gutter_lb = gtk_label_new( "N Torrents" );
701    gtk_box_pack_start( GTK_BOX( h ), w, 1, 1, GUI_PAD_BIG );
702    w = p->ul_lb = gtk_label_new( NULL );
703    gtk_box_pack_end( GTK_BOX( h ), w, FALSE, FALSE, 0 );
704    w = gtk_image_new_from_stock( GTK_STOCK_GO_UP, GTK_ICON_SIZE_MENU );
705    gtk_box_pack_end( GTK_BOX( h ), w, FALSE, FALSE, 0 );
706    w = gtk_alignment_new( 0.0f, 0.0f, 0.0f, 0.0f );
707    gtk_widget_set_size_request( w, GUI_PAD, 0u );
708    gtk_box_pack_end( GTK_BOX( h ), w, FALSE, FALSE, 0 );
709    w = p->dl_lb = gtk_label_new( NULL );
710    gtk_box_pack_end( GTK_BOX( h ), w, FALSE, FALSE, 0 );
711    w = gtk_image_new_from_stock( GTK_STOCK_GO_DOWN, GTK_ICON_SIZE_MENU );
712    gtk_box_pack_end( GTK_BOX( h ), w, FALSE, FALSE, 0 );
713    w = gtk_alignment_new( 0.0f, 0.0f, 0.0f, 0.0f );
714    gtk_widget_set_size_request( w, GUI_PAD, 0u );
715    gtk_box_pack_end( GTK_BOX( h ), w, FALSE, FALSE, 0 );
716    w = p->stats_lb = gtk_label_new( NULL );
717    gtk_box_pack_end( GTK_BOX( h ), w, FALSE, FALSE, 0 );
718    w = gtk_image_new_from_stock( GTK_STOCK_REFRESH, GTK_ICON_SIZE_MENU );
719    c = gtk_event_box_new( );
720    gtk_container_add( GTK_CONTAINER( c ), w );
721    w = c;
722    gtk_box_pack_end( GTK_BOX( h ), w, FALSE, FALSE, 0 );
723    g_signal_connect( w, "button-release-event",
724                      G_CALLBACK( onYinYangReleased ), p );
725
726    menu = gtk_menu_new( );
727    l = NULL;
728    for( i = 0; i < FILTER_TEXT_MODE_QTY; ++i )
729    {
730        const char * name = _( filter_text_names[i] );
731        GtkWidget *  w = gtk_radio_menu_item_new_with_label ( l, name );
732        l = gtk_radio_menu_item_get_group( GTK_RADIO_MENU_ITEM( w ) );
733        g_object_set_data( G_OBJECT( w ), FILTER_TEXT_MODE_KEY,
734                           GINT_TO_POINTER( i ) );
735        g_signal_connect( w, "toggled",
736                          G_CALLBACK( filter_text_toggled_cb ), p );
737        gtk_menu_shell_append( GTK_MENU_SHELL( menu ), w );
738        gtk_widget_show( w );
739    }
740    g_signal_connect( s, "icon-released",
741                      G_CALLBACK( entry_icon_released ), menu );
742
743    /* workarea */
744    p->view = makeview( p, core );
745    w = list = p->scroll = gtk_scrolled_window_new( NULL, NULL );
746    gtk_scrolled_window_set_policy( GTK_SCROLLED_WINDOW( w ),
747                                    GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC );
748    gtk_scrolled_window_set_shadow_type( GTK_SCROLLED_WINDOW( w ),
749                                         GTK_SHADOW_IN );
750    gtk_container_add( GTK_CONTAINER( w ), p->view );
751
752    /* layout the widgets */
753    {
754        const char * str = pref_string_get( PREF_KEY_MAIN_WINDOW_LAYOUT_ORDER );
755        char ** tokens = g_strsplit( str, ",", -1 );
756        for( i=0; tokens && tokens[i]; ++i )
757        {
758            const char * key = tokens[i];
759
760            if( !strcmp( key, "menu" ) )
761                gtk_box_pack_start( GTK_BOX( vbox ), mainmenu, FALSE, FALSE, 0 );
762            else if( !strcmp( key, "toolbar" ) )
763                gtk_box_pack_start( GTK_BOX( vbox ), toolbar, FALSE, FALSE, 0 );
764            else if( !strcmp( key, "filter" ) )
765                gtk_box_pack_start( GTK_BOX( vbox ), filter, FALSE, FALSE, 0 );
766            else if( !strcmp( key, "list" ) )
767                gtk_box_pack_start( GTK_BOX( vbox ), list, TRUE, TRUE, 0 );
768            else if( !strcmp( key, "statusbar" ) )
769                gtk_box_pack_start( GTK_BOX( vbox ), status, FALSE, FALSE, 0 );
770        }
771        g_strfreev( tokens );
772    }
773
774    /* show all but the window */
775    gtk_widget_show_all( vbox );
776
777    /* listen for prefs changes that affect the window */
778    p->core = core;
779    prefsChanged( core, PREF_KEY_MINIMAL_VIEW, self );
780    prefsChanged( core, PREF_KEY_FILTERBAR, self );
781    prefsChanged( core, PREF_KEY_STATUSBAR, self );
782    prefsChanged( core, PREF_KEY_STATUSBAR_STATS, self );
783    prefsChanged( core, PREF_KEY_TOOLBAR, self );
784    prefsChanged( core, PREF_KEY_FILTER_MODE, self );
785    prefsChanged( core, TR_PREFS_KEY_ALT_SPEED_ENABLED, self );
786    p->pref_handler_id = g_signal_connect( core, "prefs-changed",
787                                           G_CALLBACK( prefsChanged ), self );
788
789    tr_sessionSetAltSpeedFunc( tr_core_session( core ), onAltSpeedToggled, p );
790
791    filter_entry_changed( GTK_EDITABLE( s ), p );
792    return self;
793}
794
795static void
796updateTorrentCount( PrivateData * p )
797{
798    if( p && p->core )
799    {
800        char      buf[128];
801        const int torrentCount = gtk_tree_model_iter_n_children(
802            tr_core_model( p->core ), NULL );
803        const int visibleCount = gtk_tree_model_iter_n_children(
804            p->filter_model, NULL );
805
806        if( torrentCount != visibleCount )
807            g_snprintf( buf, sizeof( buf ),
808                        ngettext( "%1$'d of %2$'d Torrent",
809                                  "%1$'d of %2$'d Torrents",
810                                  torrentCount ),
811                        visibleCount, torrentCount );
812        else
813            g_snprintf( buf, sizeof( buf ), ngettext( "%'d Torrent",
814                                                      "%'d Torrents",
815                                                      torrentCount ),
816                        torrentCount );
817        gtk_label_set_text( GTK_LABEL( p->gutter_lb ), buf );
818    }
819}
820
821static void
822updateStats( PrivateData * p )
823{
824    const char *            pch;
825    char                    up[32], down[32], ratio[32], buf[128];
826    struct tr_session_stats stats;
827    tr_session *            session = tr_core_session( p->core );
828
829    /* update the stats */
830    pch = pref_string_get( PREF_KEY_STATUSBAR_STATS );
831    if( !strcmp( pch, "session-ratio" ) )
832    {
833        tr_sessionGetStats( session, &stats );
834        tr_strlratio( ratio, stats.ratio, sizeof( ratio ) );
835        g_snprintf( buf, sizeof( buf ), _( "Ratio: %s" ), ratio );
836    }
837    else if( !strcmp( pch, "session-transfer" ) )
838    {
839        tr_sessionGetStats( session, &stats );
840        tr_strlsize( up, stats.uploadedBytes, sizeof( up ) );
841        tr_strlsize( down, stats.downloadedBytes, sizeof( down ) );
842        /* Translators: "size|" is here for disambiguation.  Please remove it from your translation.
843           %1$s is the size of the data we've downloaded
844           %2$s is the size of the data we've uploaded */
845        g_snprintf( buf, sizeof( buf ), Q_(
846                        "size|Down: %1$s, Up: %2$s" ), down, up );
847    }
848    else if( !strcmp( pch, "total-transfer" ) )
849    {
850        tr_sessionGetCumulativeStats( session, &stats );
851        tr_strlsize( up, stats.uploadedBytes, sizeof( up ) );
852        tr_strlsize( down, stats.downloadedBytes, sizeof( down ) );
853        /* Translators: "size|" is here for disambiguation.  Please remove it from your translation.
854           %1$s is the size of the data we've downloaded
855           %2$s is the size of the data we've uploaded */
856        g_snprintf( buf, sizeof( buf ), Q_(
857                        "size|Down: %1$s, Up: %2$s" ), down, up );
858    }
859    else     /* default is total-ratio */
860    {
861        tr_sessionGetCumulativeStats( session, &stats );
862        tr_strlratio( ratio, stats.ratio, sizeof( ratio ) );
863        g_snprintf( buf, sizeof( buf ), _( "Ratio: %s" ), ratio );
864    }
865    gtk_label_set_text( GTK_LABEL( p->stats_lb ), buf );
866}
867
868static void
869updateSpeeds( PrivateData * p )
870{
871    tr_session * session = tr_core_session( p->core );
872
873    if( session != NULL )
874    {
875        char buf[128];
876        double d;
877
878        d = tr_sessionGetPieceSpeed( session, TR_DOWN );
879        tr_strlspeed( buf, d, sizeof( buf ) );
880        gtk_label_set_text( GTK_LABEL( p->dl_lb ), buf );
881
882        d = tr_sessionGetPieceSpeed( session, TR_UP );
883        tr_strlspeed( buf, d, sizeof( buf ) );
884        gtk_label_set_text( GTK_LABEL( p->ul_lb ), buf );
885    }
886}
887
888void
889tr_window_update( TrWindow * self )
890{
891    PrivateData * p = get_private_data( self );
892
893    if( p && p->core && tr_core_session( p->core ) )
894    {
895        updateSpeeds( p );
896        updateTorrentCount( p );
897        updateStats( p );
898        refilter( p );
899    }
900}
901
902GtkTreeSelection*
903tr_window_get_selection( TrWindow * w )
904{
905    return get_private_data( w )->selection;
906}
907
Note: See TracBrowser for help on using the repository browser.