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

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

(1.5x gtk) #1886: faster main window renderering in gtk client

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