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

Last change on this file since 6807 was 6807, checked in by charles, 14 years ago

(gtk) #1306: clicking in a blank area in a list clears the selection.

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