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

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

(trunk gtk) clean up yesterday's option menu code a bit

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