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

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

(trunk qt,gtk) include torrents-being-verified in the "active torrents" filter.

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