source: branches/1.5x/gtk/tr-window.c @ 7664

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

(1.5x) updates from trunk: all of build system & libtransmission; bugfixes in daemon, cli, and gtk; sync tr_sessionInit() changes. NOTE: mac end needs to add request-list.[ch] to its libtransmissio rules

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