source: trunk/qt/mainwin.cc @ 8323

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

(trunk qt) Remember between runs whether we were running a local or remote session. Add a gui dialog for switching sessions during runtime.

File size: 38.9 KB
Line 
1/*
2 * This file Copyright (C) 2009 Charles Kerr <charles@transmissionbt.com>
3 *
4 * This file is licensed by the GPL version 2.  Works owned by the
5 * Transmission project are granted a special exemption to clause 2(b)
6 * so that the bulk of its code can remain under the MIT license.
7 * This exemption does not extend to derived works not owned by
8 * the Transmission project.
9 *
10 * $Id:$
11 */
12
13#include <cassert>
14#include <iostream>
15
16#include <QCheckBox>
17#include <QDesktopServices>
18#include <QFileDialog>
19#include <QLabel>
20#include <QSize>
21#include <QStyle>
22#include <QHBoxLayout>
23#include <QSystemTrayIcon>
24#include <QUrl>
25#include <QSignalMapper>
26
27#include <libtransmission/version.h>
28
29#include "about.h"
30#include "details.h"
31#include "filters.h"
32#include "hig.h"
33#include "mainwin.h"
34#include "make-dialog.h"
35#include "options.h"
36#include "prefs.h"
37#include "prefs-dialog.h"
38#include "session.h"
39#include "session-dialog.h"
40#include "speed.h"
41#include "stats-dialog.h"
42#include "torrent-delegate.h"
43#include "torrent-delegate-min.h"
44#include "torrent-filter.h"
45#include "torrent-model.h"
46#include "triconpushbutton.h"
47#include "ui_mainwin.h"
48#include "utils.h"
49#include "qticonloader.h"
50
51#define PREFS_KEY "prefs-key";
52
53QIcon
54TrMainWindow :: getStockIcon( const QString& freedesktop_name, int fallback )
55{
56    QIcon fallbackIcon;
57
58    if( fallback > 0 )
59        fallbackIcon = style()->standardIcon( QStyle::StandardPixmap( fallback ), 0, this );
60
61    return QtIconLoader::icon( freedesktop_name, fallbackIcon );
62}
63
64namespace
65{
66    QSize calculateTextButtonSizeHint( QPushButton * button )
67    {
68        QStyleOptionButton opt;
69        opt.initFrom( button );
70        QString s( button->text( ) );
71        if( s.isEmpty( ) )
72            s = QString::fromLatin1( "XXXX" );
73        QFontMetrics fm = button->fontMetrics( );
74        QSize sz = fm.size( Qt::TextShowMnemonic, s );
75        return button->style()->sizeFromContents( QStyle::CT_PushButton, &opt, sz, button ).expandedTo( QApplication::globalStrut( ) );
76    }
77}
78
79
80TrMainWindow :: TrMainWindow( Session& session, Prefs& prefs, TorrentModel& model, bool minimized ):
81    myLastFullUpdateTime( 0 ),
82    myPrefsDialog( new PrefsDialog( session, prefs, this ) ),
83    myAboutDialog( new AboutDialog( this ) ),
84    myStatsDialog( new StatsDialog( session, this ) ),
85    myDetailsDialog( 0 ),
86    myFileDialog( 0 ),
87    myFilterModel( prefs ),
88    myTorrentDelegate( new TorrentDelegate( this ) ),
89    myTorrentDelegateMin( new TorrentDelegateMin( this ) ),
90    mySession( session ),
91    myPrefs( prefs ),
92    myModel( model ),
93    mySpeedModeOffIcon( ":/icons/alt-limit-off.png" ),
94    mySpeedModeOnIcon( ":/icons/alt-limit-on.png" ),
95    myLastSendTime( 0 ),
96    myLastReadTime( 0 ),
97    myNetworkTimer( this )
98{
99    QAction * sep = new QAction( this );
100    sep->setSeparator( true );
101
102    ui.setupUi( this );
103
104    QStyle * style = this->style();
105
106    int i = style->pixelMetric( QStyle::PM_SmallIconSize, 0, this );
107    const QSize smallIconSize( i, i );
108
109    // icons
110    ui.action_Add->setIcon( getStockIcon( "list-add", QStyle::SP_DialogOpenButton ) );
111    ui.action_New->setIcon( getStockIcon( "document-new", QStyle::SP_DesktopIcon ) );
112    ui.action_Properties->setIcon( getStockIcon( "document-properties", QStyle::SP_DesktopIcon ) );
113    ui.action_OpenFolder->setIcon( getStockIcon( "folder-open", QStyle::SP_DirOpenIcon ) );
114    ui.action_Start->setIcon( getStockIcon( "media-playback-start", QStyle::SP_MediaPlay ) );
115    ui.action_Announce->setIcon( getStockIcon( "network-transmit-receive" ) );
116    ui.action_Pause->setIcon( getStockIcon( "media-playback-pause", QStyle::SP_MediaPause ) );
117    ui.action_Remove->setIcon( getStockIcon( "list-remove", QStyle::SP_TrashIcon ) );
118    ui.action_Delete->setIcon( getStockIcon( "edit-delete", QStyle::SP_TrashIcon ) );
119    ui.action_StartAll->setIcon( getStockIcon( "media-playback-start", QStyle::SP_MediaPlay ) );
120    ui.action_PauseAll->setIcon( getStockIcon( "media-playback-pause", QStyle::SP_MediaPause ) );
121    ui.action_Quit->setIcon( getStockIcon( "application-exit" ) );
122    ui.action_SelectAll->setIcon( getStockIcon( "edit-select-all" ) );
123    ui.action_ReverseSortOrder->setIcon( getStockIcon( "view-sort-ascending", QStyle::SP_ArrowDown ) );
124    ui.action_Preferences->setIcon( getStockIcon( "preferences-system" ) );
125    ui.action_Contents->setIcon( getStockIcon( "help-contents", QStyle::SP_DialogHelpButton ) );
126    ui.action_About->setIcon( getStockIcon( "help-about" ) );
127
128    // ui signals
129    connect( ui.action_Toolbar, SIGNAL(toggled(bool)), this, SLOT(setToolbarVisible(bool)));
130    connect( ui.action_TrayIcon, SIGNAL(toggled(bool)), this, SLOT(setTrayIconVisible(bool)));
131    connect( ui.action_Filterbar, SIGNAL(toggled(bool)), this, SLOT(setFilterbarVisible(bool)));
132    connect( ui.action_Statusbar, SIGNAL(toggled(bool)), this, SLOT(setStatusbarVisible(bool)));
133    connect( ui.action_MinimalView, SIGNAL(toggled(bool)), this, SLOT(setMinimalView(bool)));
134    connect( ui.action_SortByActivity, SIGNAL(toggled(bool)), this, SLOT(onSortByActivityToggled(bool)));
135    connect( ui.action_SortByAge,      SIGNAL(toggled(bool)), this, SLOT(onSortByAgeToggled(bool)));
136    connect( ui.action_SortByETA,      SIGNAL(toggled(bool)), this, SLOT(onSortByETAToggled(bool)));
137    connect( ui.action_SortByName,     SIGNAL(toggled(bool)), this, SLOT(onSortByNameToggled(bool)));
138    connect( ui.action_SortByProgress, SIGNAL(toggled(bool)), this, SLOT(onSortByProgressToggled(bool)));
139    connect( ui.action_SortByRatio,    SIGNAL(toggled(bool)), this, SLOT(onSortByRatioToggled(bool)));
140    connect( ui.action_SortBySize,     SIGNAL(toggled(bool)), this, SLOT(onSortBySizeToggled(bool)));
141    connect( ui.action_SortByState,    SIGNAL(toggled(bool)), this, SLOT(onSortByStateToggled(bool)));
142    connect( ui.action_SortByTracker,  SIGNAL(toggled(bool)), this, SLOT(onSortByTrackerToggled(bool)));
143    connect( ui.action_ReverseSortOrder, SIGNAL(toggled(bool)), this, SLOT(setSortAscendingPref(bool)));
144    connect( ui.action_Start, SIGNAL(triggered()), this, SLOT(startSelected()));
145    connect( ui.action_Pause, SIGNAL(triggered()), this, SLOT(pauseSelected()));
146    connect( ui.action_Remove, SIGNAL(triggered()), this, SLOT(removeSelected()));
147    connect( ui.action_Delete, SIGNAL(triggered()), this, SLOT(deleteSelected()));
148    connect( ui.action_Verify, SIGNAL(triggered()), this, SLOT(verifySelected()) );
149    connect( ui.action_Announce, SIGNAL(triggered()), this, SLOT(reannounceSelected()) );
150    connect( ui.action_StartAll, SIGNAL(triggered()), this, SLOT(startAll()));
151    connect( ui.action_PauseAll, SIGNAL(triggered()), this, SLOT(pauseAll()));
152    connect( ui.action_Add, SIGNAL(triggered()), this, SLOT(openTorrent()));
153    connect( ui.action_New, SIGNAL(triggered()), this, SLOT(newTorrent()));
154    connect( ui.action_Preferences, SIGNAL(triggered()), myPrefsDialog, SLOT(show()));
155    connect( ui.action_Statistics, SIGNAL(triggered()), myStatsDialog, SLOT(show()));
156    connect( ui.action_About, SIGNAL(triggered()), myAboutDialog, SLOT(show()));
157    connect( ui.action_Contents, SIGNAL(triggered()), this, SLOT(openHelp()));
158    connect( ui.action_OpenFolder, SIGNAL(triggered()), this, SLOT(openFolder()));
159    connect( ui.action_Properties, SIGNAL(triggered()), this, SLOT(openProperties()));
160    connect( ui.action_SessionDialog, SIGNAL(triggered()), this, SLOT(openSessionDialog()));
161    connect( ui.listView, SIGNAL(activated(const QModelIndex&)), ui.action_Properties, SLOT(trigger()));
162
163    // context menu
164    QList<QAction*> actions;
165    actions << ui.action_Properties
166            << ui.action_OpenFolder
167            << sep
168            << ui.action_Start
169            << ui.action_Pause
170            << ui.action_Verify
171            << ui.action_Announce
172            << sep
173            << ui.action_Remove
174            << ui.action_Delete;
175    addActions( actions );
176    setContextMenuPolicy( Qt::ActionsContextMenu );
177
178    // signals
179    connect( ui.action_SelectAll, SIGNAL(triggered()), ui.listView, SLOT(selectAll()));
180    connect( ui.action_DeselectAll, SIGNAL(triggered()), ui.listView, SLOT(clearSelection()));
181
182    connect( &myFilterModel, SIGNAL(rowsInserted(const QModelIndex&,int,int)), this, SLOT(refreshVisibleCount()));
183    connect( &myFilterModel, SIGNAL(rowsRemoved(const QModelIndex&,int,int)), this, SLOT(refreshVisibleCount()));
184    connect( &myFilterModel, SIGNAL(rowsInserted(const QModelIndex&,int,int)), this, SLOT(refreshActionSensitivity()));
185    connect( &myFilterModel, SIGNAL(rowsRemoved(const QModelIndex&,int,int)), this, SLOT(refreshActionSensitivity()));
186
187    connect( ui.action_Quit, SIGNAL(triggered()), QCoreApplication::instance(), SLOT(quit()) );
188
189    // torrent view
190    myFilterModel.setSourceModel( &myModel );
191    ui.listView->setModel( &myFilterModel );
192    connect( ui.listView->selectionModel(), SIGNAL(selectionChanged(const QItemSelection&,const QItemSelection&)), this, SLOT(refreshActionSensitivity()));
193
194    QActionGroup * actionGroup = new QActionGroup( this );
195    actionGroup->addAction( ui.action_SortByActivity );
196    actionGroup->addAction( ui.action_SortByAge );
197    actionGroup->addAction( ui.action_SortByETA );
198    actionGroup->addAction( ui.action_SortByName );
199    actionGroup->addAction( ui.action_SortByProgress );
200    actionGroup->addAction( ui.action_SortByRatio );
201    actionGroup->addAction( ui.action_SortBySize );
202    actionGroup->addAction( ui.action_SortByState );
203    actionGroup->addAction( ui.action_SortByTracker );
204
205    QMenu * menu = new QMenu( );
206    menu->addAction( ui.action_Add );
207    menu->addSeparator( );
208    menu->addAction( ui.action_ShowMainWindow );
209    menu->addAction( ui.action_ShowMessageLog );
210    menu->addAction( ui.action_About );
211    menu->addSeparator( );
212    menu->addAction( ui.action_StartAll );
213    menu->addAction( ui.action_PauseAll );
214    menu->addSeparator( );
215    menu->addAction( ui.action_Quit );
216    myTrayIcon.setContextMenu( menu );
217    myTrayIcon.setIcon( QApplication::windowIcon( ) );
218
219    connect( &myPrefs, SIGNAL(changed(int)), this, SLOT(refreshPref(int)) );
220    connect( ui.action_ShowMainWindow, SIGNAL(toggled(bool)), this, SLOT(toggleWindows()));
221    connect( &myTrayIcon, SIGNAL(activated(QSystemTrayIcon::ActivationReason)),
222             this, SLOT(trayActivated(QSystemTrayIcon::ActivationReason)));
223
224    ui.action_ShowMainWindow->setChecked( !minimized );
225    ui.action_TrayIcon->setChecked( minimized || prefs.getBool( Prefs::SHOW_TRAY_ICON ) );
226
227    ui.verticalLayout->addWidget( createStatusBar( ) );
228    ui.verticalLayout->insertWidget( 0, createFilterBar( ) );
229
230    QList<int> initKeys;
231    initKeys << Prefs :: MAIN_WINDOW_X
232             << Prefs :: SHOW_TRAY_ICON
233             << Prefs :: SORT_REVERSED
234             << Prefs :: SORT_MODE
235             << Prefs :: FILTER_MODE
236             << Prefs :: FILTERBAR
237             << Prefs :: STATUSBAR
238             << Prefs :: STATUSBAR_STATS
239             << Prefs :: TOOLBAR
240             << Prefs :: ALT_SPEED_LIMIT_ENABLED
241             << Prefs :: MINIMAL_VIEW
242             << Prefs :: DSPEED
243             << Prefs :: DSPEED_ENABLED
244             << Prefs :: USPEED
245             << Prefs :: USPEED_ENABLED
246             << Prefs :: RATIO
247             << Prefs :: RATIO_ENABLED;
248    foreach( int key, initKeys )
249        refreshPref( key );
250
251    connect( &mySession, SIGNAL(sourceChanged()), this, SLOT(onSessionSourceChanged()) );
252    connect( &mySession, SIGNAL(statsUpdated()), this, SLOT(refreshStatusBar()) );
253    connect( &mySession, SIGNAL(dataReadProgress()), this, SLOT(dataReadProgress()) );
254    connect( &mySession, SIGNAL(dataSendProgress()), this, SLOT(dataSendProgress()) );
255
256    if( mySession.isServer( ) )
257        myNetworkLabel->hide( );
258    else {
259        connect( &myNetworkTimer, SIGNAL(timeout()), this, SLOT(onNetworkTimer()));
260        myNetworkTimer.start( 1000 );
261    }
262
263    refreshActionSensitivity( );
264    refreshStatusBar( );
265    refreshTitle( );
266    refreshVisibleCount( );
267}
268
269TrMainWindow :: ~TrMainWindow( )
270{
271}
272
273/****
274*****
275****/
276
277void
278TrMainWindow :: onSessionSourceChanged( )
279{
280    myModel.clear( );
281    refreshTitle( );
282    refreshVisibleCount( );
283    refreshActionSensitivity( );
284    refreshStatusBar( );
285}
286
287/****
288*****
289****/
290
291#define PREF_VARIANTS_KEY "pref-variants-list"
292
293void
294TrMainWindow :: onSetPrefs( )
295{
296    const QVariantList p = sender()->property( PREF_VARIANTS_KEY ).toList( );
297    assert( ( p.size( ) % 2 ) == 0 );
298    for( int i=0, n=p.size(); i<n; i+=2 )
299        myPrefs.set( p[i].toInt(), p[i+1] );
300}
301
302void
303TrMainWindow :: onSetPrefs( bool isChecked )
304{
305    if( isChecked )
306        onSetPrefs( );
307}
308
309#define SHOW_KEY "show-mode"
310
311void
312TrMainWindow :: onShowModeClicked( )
313{
314    setShowMode( sender()->property(SHOW_KEY).toInt() );
315}
316
317QWidget *
318TrMainWindow :: createFilterBar( )
319{
320    int i;
321    QMenu * m;
322    QLineEdit * e;
323    QPushButton * p;
324    QHBoxLayout * h;
325    QActionGroup * a;
326    const int smallSize = style( )->pixelMetric( QStyle::PM_SmallIconSize, 0, this );
327    const QSize smallIconSize( smallSize, smallSize );
328
329    QWidget * top = myFilterBar = new QWidget;
330    h = new QHBoxLayout( top );
331    h->setContentsMargins( HIG::PAD_SMALL, HIG::PAD_SMALL, HIG::PAD_SMALL, HIG::PAD_SMALL );
332    h->setSpacing( HIG::PAD_SMALL );
333
334        QList<QString> titles;
335        titles << tr( "A&ll" ) << tr( "&Active" ) << tr( "&Downloading" ) << tr( "&Seeding" ) << tr( "&Paused" );
336        for( i=0; i<titles.size(); ++i ) {
337            p = myFilterButtons[i] = new QPushButton( titles[i] );
338            p->setProperty( SHOW_KEY, i );
339            p->setFlat( true );
340            p->setCheckable( true );
341            p->setMaximumSize( calculateTextButtonSizeHint( p ) );
342            connect( p, SIGNAL(clicked()), this, SLOT(onShowModeClicked()));
343            h->addWidget( p );
344        }
345
346    h->addStretch( 1 );
347
348        a = new QActionGroup( this );
349        a->addAction( ui.action_FilterByName );
350        a->addAction( ui.action_FilterByFiles );
351        a->addAction( ui.action_FilterByTracker );
352        m = new QMenu( );
353        m->addAction( ui.action_FilterByName );
354        m->addAction( ui.action_FilterByFiles );
355        m->addAction( ui.action_FilterByTracker );
356        connect( ui.action_FilterByName, SIGNAL(triggered()), this, SLOT(filterByName()));
357        connect( ui.action_FilterByFiles, SIGNAL(triggered()), this, SLOT(filterByFiles()));
358        connect( ui.action_FilterByTracker, SIGNAL(triggered()), this, SLOT(filterByTracker()));
359        ui.action_FilterByName->setChecked( true );
360        p = myFilterTextButton = new TrIconPushButton;
361        p->setIcon( getStockIcon( "edit-find", QStyle::SP_ArrowForward ) );
362        p->setFlat( true );
363        p->setMenu( m );
364        h->addWidget( p );
365
366        e = myFilterTextLineEdit = new QLineEdit;
367        connect( e, SIGNAL(textChanged(QString)), &myFilterModel, SLOT(setText(QString)));
368        h->addWidget( e );
369
370        p = myFilterTextButton = new TrIconPushButton;
371        p->setIcon( getStockIcon( "edit-clear", QStyle::SP_DialogCloseButton ) );
372        p->setFlat( true );
373        connect( p, SIGNAL(clicked()), myFilterTextLineEdit, SLOT(clear()));
374        h->addWidget( p );
375
376    return top;
377}
378
379QWidget *
380TrMainWindow :: createStatusBar( )
381{
382    QMenu * m;
383    QLabel *l, *l2;
384    QWidget *w;
385    QHBoxLayout * h;
386    QPushButton * p;
387    QActionGroup * a;
388    const int i = style( )->pixelMetric( QStyle::PM_SmallIconSize, 0, this );
389    const QSize smallIconSize( i, i );
390
391    QWidget * top = myStatusBar = new QWidget;
392    h = new QHBoxLayout( top );
393    h->setContentsMargins( HIG::PAD_SMALL, HIG::PAD_SMALL, HIG::PAD_SMALL, HIG::PAD_SMALL );
394    h->setSpacing( HIG::PAD_SMALL );
395
396        p = myOptionsButton = new TrIconPushButton( this );
397        p->setIcon( QIcon( ":/icons/options.png" ) );
398        p->setFlat( true );
399        p->setMenu( createOptionsMenu( ) );
400        h->addWidget( p );
401
402        p = myAltSpeedButton = new TrIconPushButton( this );
403        p->setIcon( myPrefs.get<bool>(Prefs::ALT_SPEED_LIMIT_ENABLED) ? mySpeedModeOnIcon : mySpeedModeOffIcon );
404        p->setFlat( true );
405        h->addWidget( p );
406        connect( p, SIGNAL(clicked()), this, SLOT(toggleSpeedMode()));
407
408        l = myNetworkLabel = new QLabel;
409        h->addWidget( l );
410
411    h->addStretch( 1 );
412
413        l = myVisibleCountLabel = new QLabel( this );
414        h->addWidget( l );
415
416    h->addStretch( 1 );
417 
418        a = new QActionGroup( this );
419        a->addAction( ui.action_TotalRatio );
420        a->addAction( ui.action_TotalTransfer );
421        a->addAction( ui.action_SessionRatio );
422        a->addAction( ui.action_SessionTransfer );
423        m = new QMenu( );
424        m->addAction( ui.action_TotalRatio );
425        m->addAction( ui.action_TotalTransfer );
426        m->addAction( ui.action_SessionRatio );
427        m->addAction( ui.action_SessionTransfer );
428        connect( ui.action_TotalRatio, SIGNAL(triggered()), this, SLOT(showTotalRatio()));
429        connect( ui.action_TotalTransfer, SIGNAL(triggered()), this, SLOT(showTotalTransfer()));
430        connect( ui.action_SessionRatio, SIGNAL(triggered()), this, SLOT(showSessionRatio()));
431        connect( ui.action_SessionTransfer, SIGNAL(triggered()), this, SLOT(showSessionTransfer()));
432        p = myStatsModeButton = new TrIconPushButton( this );
433        p->setIcon( getStockIcon( "view-refresh", QStyle::SP_BrowserReload ) );
434        p->setFlat( true );
435        p->setMenu( m );
436        h->addWidget( p ); 
437        l = myStatsLabel = new QLabel( this );
438        h->addWidget( l ); 
439   
440        w = new QWidget( this );
441        w->setMinimumSize( HIG::PAD_BIG, 1 );
442        w->setMaximumSize( HIG::PAD_BIG, 1 );
443        h->addWidget( w );
444        l = new QLabel( this );
445        l->setPixmap( getStockIcon( "go-down", QStyle::SP_ArrowDown ).pixmap( smallIconSize ) );
446        h->addWidget( l );
447        l2 = myDownloadSpeedLabel = new QLabel( this );
448        h->addWidget( l2 );
449        myDownStatusWidgets << w << l << l2;
450
451        w = new QWidget( this );
452        w->setMinimumSize( HIG::PAD_BIG, 1 );
453        w->setMaximumSize( HIG::PAD_BIG, 1 );
454        h->addWidget( w );
455        l = new QLabel;
456        l->setPixmap( getStockIcon( "go-up", QStyle::SP_ArrowUp ).pixmap( smallIconSize ) );
457        h->addWidget( l );
458        l2 = myUploadSpeedLabel = new QLabel;
459        h->addWidget( l2 );
460        myUpStatusWidgets << w << l << l2;
461
462    return top;
463}
464
465QMenu *
466TrMainWindow :: createOptionsMenu( )
467{
468    QMenu * menu;
469    QMenu * sub;
470    QAction * a;
471    QActionGroup * g;
472
473    QList<int> stockSpeeds;
474    stockSpeeds << 5 << 10 << 20 << 30 << 40 << 50 << 75 << 100 << 150 << 200 << 250 << 500 << 750;
475    QList<double> stockRatios;
476    stockRatios << 0.25 << 0.50 << 0.75 << 1 << 1.5 << 2 << 3;
477
478    menu = new QMenu;
479    sub = menu->addMenu( tr( "Limit Download Speed" ) );
480        int currentVal = myPrefs.get<int>( Prefs::DSPEED );
481        g = new QActionGroup( this );
482        a = myDlimitOffAction = sub->addAction( tr( "Unlimited" ) );
483        a->setCheckable( true );
484        a->setProperty( PREF_VARIANTS_KEY, QVariantList() << Prefs::DSPEED_ENABLED << false );
485        g->addAction( a );
486        connect( a, SIGNAL(triggered(bool)), this, SLOT(onSetPrefs(bool)) );
487        a = myDlimitOnAction = sub->addAction( tr( "Limited at %1" ).arg( Utils::speedToString( Speed::fromKbps( currentVal ) ) ) );
488        a->setCheckable( true );
489        a->setProperty( PREF_VARIANTS_KEY, QVariantList() << Prefs::DSPEED << currentVal << Prefs::DSPEED_ENABLED << true );
490        g->addAction( a );
491        connect( a, SIGNAL(triggered(bool)), this, SLOT(onSetPrefs(bool)) );
492        sub->addSeparator( );
493        foreach( int i, stockSpeeds ) {
494            a = sub->addAction( Utils::speedToString( Speed::fromKbps(i) ) );
495            a->setProperty( PREF_VARIANTS_KEY, QVariantList() << Prefs::DSPEED << i << Prefs::DSPEED_ENABLED << true );
496            connect( a, SIGNAL(triggered(bool)), this, SLOT(onSetPrefs()));
497        }
498
499    sub = menu->addMenu( tr( "Limit Upload Speed" ) );
500        currentVal = myPrefs.get<int>( Prefs::USPEED );
501        g = new QActionGroup( this );
502        a = myUlimitOffAction = sub->addAction( tr( "Unlimited" ) );
503        a->setCheckable( true );
504        a->setProperty( PREF_VARIANTS_KEY, QVariantList() << Prefs::USPEED_ENABLED << false );
505        g->addAction( a );
506        connect( a, SIGNAL(triggered(bool)), this, SLOT(onSetPrefs(bool)) );
507        a = myUlimitOnAction = sub->addAction( tr( "Limited at %1" ).arg( Utils::speedToString( Speed::fromKbps( currentVal ) ) ) );
508        a->setCheckable( true );
509        a->setProperty( PREF_VARIANTS_KEY, QVariantList() << Prefs::USPEED << currentVal << Prefs::USPEED_ENABLED << true );
510        g->addAction( a );
511        connect( a, SIGNAL(triggered(bool)), this, SLOT(onSetPrefs(bool)) );
512        sub->addSeparator( );
513        foreach( int i, stockSpeeds ) {
514            a = sub->addAction( Utils::speedToString( Speed::fromKbps(i) ) );
515            a->setProperty( PREF_VARIANTS_KEY, QVariantList() << Prefs::USPEED << i << Prefs::USPEED_ENABLED << true );
516            connect( a, SIGNAL(triggered(bool)), this, SLOT(onSetPrefs()));
517        }
518
519    menu->addSeparator( );
520    sub = menu->addMenu( tr( "Stop Seeding at Ratio" ) );
521
522        double d = myPrefs.get<double>( Prefs::RATIO );
523        g = new QActionGroup( this );
524        a = myRatioOffAction = sub->addAction( tr( "Seed Forever" ) );
525        a->setCheckable( true );
526        a->setProperty( PREF_VARIANTS_KEY, QVariantList() << Prefs::RATIO_ENABLED << false );
527        g->addAction( a );
528        connect( a, SIGNAL(triggered(bool)), this, SLOT(onSetPrefs(bool)) );
529        a = myRatioOnAction = sub->addAction( tr( "Stop at Ratio (%1)" ).arg( Utils::ratioToString( d ) ) );
530        a->setCheckable( true );
531        a->setProperty( PREF_VARIANTS_KEY, QVariantList() << Prefs::RATIO << d << Prefs::RATIO_ENABLED << true );
532        g->addAction( a );
533        connect( a, SIGNAL(triggered(bool)), this, SLOT(onSetPrefs(bool)) );
534        sub->addSeparator( );
535        foreach( double i, stockRatios ) {
536            a = sub->addAction( Utils::ratioToString( i ) );
537            a->setProperty( PREF_VARIANTS_KEY, QVariantList() << Prefs::RATIO << i << Prefs::RATIO_ENABLED << true );
538            connect( a, SIGNAL(triggered(bool)), this, SLOT(onSetPrefs()));
539        }
540
541    return menu;
542}
543
544/****
545*****
546****/
547
548void
549TrMainWindow :: setSortPref( int i )
550{
551    myPrefs.set( Prefs::SORT_MODE, SortMode( i ) );
552}
553void TrMainWindow :: onSortByActivityToggled ( bool b ) { if( b ) setSortPref( SortMode::SORT_BY_ACTIVITY ); }
554void TrMainWindow :: onSortByAgeToggled      ( bool b ) { if( b ) setSortPref( SortMode::SORT_BY_AGE );      }
555void TrMainWindow :: onSortByETAToggled      ( bool b ) { if( b ) setSortPref( SortMode::SORT_BY_ETA );      }
556void TrMainWindow :: onSortByNameToggled     ( bool b ) { if( b ) setSortPref( SortMode::SORT_BY_NAME );     }
557void TrMainWindow :: onSortByProgressToggled ( bool b ) { if( b ) setSortPref( SortMode::SORT_BY_PROGRESS ); }
558void TrMainWindow :: onSortByRatioToggled    ( bool b ) { if( b ) setSortPref( SortMode::SORT_BY_RATIO );    }
559void TrMainWindow :: onSortBySizeToggled     ( bool b ) { if( b ) setSortPref( SortMode::SORT_BY_SIZE );     }
560void TrMainWindow :: onSortByStateToggled    ( bool b ) { if( b ) setSortPref( SortMode::SORT_BY_STATE );    }
561void TrMainWindow :: onSortByTrackerToggled  ( bool b ) { if( b ) setSortPref( SortMode::SORT_BY_TRACKER );  }
562
563void
564TrMainWindow :: setSortAscendingPref( bool b )
565{
566    myPrefs.set( Prefs::SORT_REVERSED, b );
567}
568
569/****
570*****
571****/
572
573void
574TrMainWindow :: onDetailsDestroyed( )
575{
576    myDetailsDialog = 0;
577}
578
579void
580TrMainWindow :: openProperties( )
581{
582    if( myDetailsDialog == 0 ) {
583        myDetailsDialog = new Details( mySession, myModel, this );
584        connect( myDetailsDialog, SIGNAL(destroyed(QObject*)), this, SLOT(onDetailsDestroyed()));
585    }
586
587    myDetailsDialog->setIds( getSelectedTorrents( ) );
588    myDetailsDialog->show( );
589}
590
591void
592TrMainWindow :: openSessionDialog( )
593{
594    SessionDialog * d = new SessionDialog( mySession, myPrefs, this );
595    d->show( );
596}
597
598void
599TrMainWindow :: openFolder( )
600{
601    const int torrentId( *getSelectedTorrents().begin() );
602    const Torrent * tor( myModel.getTorrentFromId( torrentId ) );
603    const QString path( tor->getPath( ) );
604    QDesktopServices :: openUrl( QUrl::fromLocalFile( path ) );
605}
606
607void
608TrMainWindow :: openHelp( )
609{
610    const char * fmt = "http://www.transmissionbt.com/help/gtk/%d.%dx";
611    int major, minor;
612    sscanf( SHORT_VERSION_STRING, "%d.%d", &major, &minor );
613    char url[128];
614    snprintf( url, sizeof( url ), fmt, major, minor/10 );
615    QDesktopServices :: openUrl( QUrl( QString( url ) ) );
616}
617
618void
619TrMainWindow :: refreshTitle( )
620{
621    QString title( "Transmission" );
622    const QUrl url( mySession.getRemoteUrl( ) );
623    if( !url.isEmpty() )
624        title += tr( " - %1" ).arg( url.toString(QUrl::RemoveUserInfo) );
625    setWindowTitle( title );
626}
627
628void
629TrMainWindow :: refreshVisibleCount( )
630{
631    const int visibleCount( myFilterModel.rowCount( ) );
632    const int totalCount( visibleCount + myFilterModel.hiddenRowCount( ) );
633    QString str;
634    if( visibleCount == totalCount )
635        str = tr( "%Ln Torrent(s)", 0, totalCount );
636    else
637        str = tr( "%L1 of %Ln Torrent(s)", 0, totalCount ).arg( visibleCount );
638    myVisibleCountLabel->setText( str );
639    myVisibleCountLabel->setVisible( totalCount > 0 );
640}
641
642void
643TrMainWindow :: refreshStatusBar( )
644{
645    const Speed up( myModel.getUploadSpeed( ) );
646    const Speed down( myModel.getDownloadSpeed( ) );
647    myUploadSpeedLabel->setText( Utils :: speedToString( up ) );
648    myDownloadSpeedLabel->setText( Utils :: speedToString( down ) );
649    foreach( QWidget * w, myUpStatusWidgets ) w->setVisible( !up.isZero( ) );
650    foreach( QWidget * w, myDownStatusWidgets ) w->setVisible( !down.isZero( ) );
651
652    const QString mode( myPrefs.getString( Prefs::STATUSBAR_STATS ) );
653    QString str;
654
655    if( mode == "session-ratio" )
656    {
657        str = tr( "Ratio: %1" ).arg( Utils :: ratioToString( mySession.getStats().ratio ) );
658    }
659    else if( mode == "session-transfer" )
660    {
661        const tr_session_stats& stats( mySession.getStats( ) );
662        str = tr( "Down: %1, Up: %2" ).arg( Utils :: sizeToString( stats.downloadedBytes ) )
663                                      .arg( Utils :: sizeToString( stats.uploadedBytes ) );
664    }
665    else if( mode == "total-transfer" )
666    {
667        const tr_session_stats& stats( mySession.getCumulativeStats( ) );
668        str = tr( "Down: %1, Up: %2" ).arg( Utils :: sizeToString( stats.downloadedBytes ) )
669                                      .arg( Utils :: sizeToString( stats.uploadedBytes ) );
670    }
671    else /* default is "total-ratio" */
672    {
673        str = tr( "Ratio: %1" ).arg( Utils :: ratioToString( mySession.getCumulativeStats().ratio ) );
674    }
675
676    myStatsLabel->setText( str );
677}
678
679void
680TrMainWindow :: refreshActionSensitivity( )
681{
682    int selected( 0 );
683    int paused( 0 );
684    int selectedAndPaused( 0 );
685    int canAnnounce( 0 );
686    const QAbstractItemModel * model( ui.listView->model( ) );
687    const QItemSelectionModel * selectionModel( ui.listView->selectionModel( ) );
688    const int rowCount( model->rowCount( ) );
689
690    /* count how many torrents are selected, paused, etc */
691    for( int row=0; row<rowCount; ++row ) {
692        const QModelIndex modelIndex( model->index( row, 0 ) );
693        assert( model == modelIndex.model( ) );
694        const Torrent * tor( model->data( modelIndex, TorrentModel::TorrentRole ).value<const Torrent*>( ) );
695        const bool isSelected( selectionModel->isSelected( modelIndex ) );
696        const bool isPaused( tor->isPaused( ) );
697        if( isSelected )
698            ++selected;
699        if( isPaused )
700            ++ paused;
701        if( isSelected && isPaused )
702            ++selectedAndPaused;
703        if( tor->canManualAnnounce( ) )
704            ++canAnnounce;
705    }
706
707    const bool haveSelection( selected > 0 );
708    ui.action_Verify->setEnabled( haveSelection );
709    ui.action_Remove->setEnabled( haveSelection );
710    ui.action_Delete->setEnabled( haveSelection );
711    ui.action_Properties->setEnabled( haveSelection );
712    ui.action_DeselectAll->setEnabled( haveSelection );
713
714    const bool oneSelection( selected == 1 );
715    ui.action_OpenFolder->setEnabled( oneSelection );
716
717    ui.action_SelectAll->setEnabled( selected < rowCount );
718    ui.action_StartAll->setEnabled( paused > 0 );
719    ui.action_PauseAll->setEnabled( paused < rowCount );
720    ui.action_Start->setEnabled( selectedAndPaused > 0 );
721    ui.action_Pause->setEnabled( selectedAndPaused < selected );
722    ui.action_Announce->setEnabled( selected > 0 && ( canAnnounce == selected ) );
723
724    if( myDetailsDialog )
725        myDetailsDialog->setIds( getSelectedTorrents( ) );
726}
727
728/**
729***
730**/
731
732void
733TrMainWindow :: clearSelection( )
734{
735    ui.action_DeselectAll->trigger( );
736}
737
738QSet<int>
739TrMainWindow :: getSelectedTorrents( ) const
740{
741    QSet<int> ids;
742
743    foreach( QModelIndex index, ui.listView->selectionModel( )->selectedRows( ) )
744    {
745        const Torrent * tor( index.model()->data( index, TorrentModel::TorrentRole ).value<const Torrent*>( ) );
746        ids.insert( tor->id( ) );
747    }
748
749    return ids;
750}
751
752void
753TrMainWindow :: startSelected( )
754{
755    mySession.startTorrents( getSelectedTorrents( ) );
756}
757void
758TrMainWindow :: pauseSelected( )
759{
760    mySession.pauseTorrents( getSelectedTorrents( ) );
761}
762void
763TrMainWindow :: startAll( )
764{
765    mySession.startTorrents( );
766}
767void
768TrMainWindow :: pauseAll( )
769{
770    mySession.pauseTorrents( );
771}
772void
773TrMainWindow :: removeSelected( )
774{
775    mySession.removeTorrents( getSelectedTorrents( ), false );
776}
777void
778TrMainWindow :: deleteSelected( )
779{
780    mySession.removeTorrents( getSelectedTorrents( ), true );
781}
782void
783TrMainWindow :: verifySelected( )
784{
785    mySession.verifyTorrents( getSelectedTorrents( ) );
786}
787void
788TrMainWindow :: reannounceSelected( )
789{
790    mySession.reannounceTorrents( getSelectedTorrents( ) );
791}
792
793/**
794***
795**/
796
797void TrMainWindow :: setShowMode     ( int i ) { myPrefs.set( Prefs::FILTER_MODE, FilterMode( i ) ); }
798void TrMainWindow :: showAll         ( ) { setShowMode( FilterMode :: SHOW_ALL ); }
799void TrMainWindow :: showActive      ( ) { setShowMode( FilterMode :: SHOW_ACTIVE ); }
800void TrMainWindow :: showDownloading ( ) { setShowMode( FilterMode :: SHOW_DOWNLOADING ); }
801void TrMainWindow :: showSeeding     ( ) { setShowMode( FilterMode :: SHOW_SEEDING ); }
802void TrMainWindow :: showPaused      ( ) { setShowMode( FilterMode :: SHOW_PAUSED ); }
803
804void TrMainWindow :: filterByName    ( ) { myFilterModel.setTextMode( TorrentFilter :: FILTER_BY_NAME ); }
805void TrMainWindow :: filterByTracker ( ) { myFilterModel.setTextMode( TorrentFilter :: FILTER_BY_TRACKER ); }
806void TrMainWindow :: filterByFiles   ( ) { myFilterModel.setTextMode( TorrentFilter :: FILTER_BY_FILES ); }
807
808void TrMainWindow :: showTotalRatio      ( ) { myPrefs.set( Prefs::STATUSBAR_STATS, "total-ratio"); }
809void TrMainWindow :: showTotalTransfer   ( ) { myPrefs.set( Prefs::STATUSBAR_STATS, "total-transfer"); }
810void TrMainWindow :: showSessionRatio    ( ) { myPrefs.set( Prefs::STATUSBAR_STATS, "session-ratio"); }
811void TrMainWindow :: showSessionTransfer ( ) { myPrefs.set( Prefs::STATUSBAR_STATS, "session-transfer"); }
812
813/**
814***
815**/
816
817void
818TrMainWindow :: setMinimalView( bool visible )
819{
820    myPrefs.set( Prefs :: MINIMAL_VIEW, visible );
821}
822void
823TrMainWindow :: setTrayIconVisible( bool visible )
824{
825    myPrefs.set( Prefs :: SHOW_TRAY_ICON, visible );
826}
827void
828TrMainWindow :: toggleSpeedMode( )
829{
830    myPrefs.toggleBool( Prefs :: ALT_SPEED_LIMIT_ENABLED );
831}
832void
833TrMainWindow :: setToolbarVisible( bool visible )
834{
835    myPrefs.set( Prefs::TOOLBAR, visible );
836}
837void
838TrMainWindow :: setFilterbarVisible( bool visible )
839{
840    myPrefs.set( Prefs::FILTERBAR, visible );
841}
842void
843TrMainWindow :: setStatusbarVisible( bool visible )
844{
845    myPrefs.set( Prefs::STATUSBAR, visible );
846}
847
848/**
849***
850**/
851
852void
853TrMainWindow :: toggleWindows( )
854{
855    setVisible( !isVisible( ) );
856}
857
858void
859TrMainWindow :: trayActivated( QSystemTrayIcon::ActivationReason reason )
860{
861    if( reason == QSystemTrayIcon::Trigger )
862        ui.action_ShowMainWindow->toggle( );
863}
864
865
866void
867TrMainWindow :: refreshPref( int key )
868{
869    bool b;
870    int i;
871    QString str;
872
873    switch( key )
874    {
875        case Prefs::STATUSBAR_STATS:
876            str = myPrefs.getString( key );
877            ui.action_TotalRatio->setChecked     ( str == "total-ratio" );
878            ui.action_TotalTransfer->setChecked  ( str == "total-transfer" );
879            ui.action_SessionRatio->setChecked   ( str == "session-ratio" );
880            ui.action_SessionTransfer->setChecked( str == "session-transfer" );
881            refreshStatusBar( );
882            break;
883
884        case Prefs::SORT_REVERSED:
885            ui.action_ReverseSortOrder->setChecked( myPrefs.getBool( key ) );
886            break;
887
888        case Prefs::SORT_MODE:
889            i = myPrefs.get<SortMode>(key).mode( );
890            ui.action_SortByActivity->setChecked ( i == SortMode::SORT_BY_ACTIVITY );
891            ui.action_SortByAge->setChecked      ( i == SortMode::SORT_BY_AGE );
892            ui.action_SortByETA->setChecked      ( i == SortMode::SORT_BY_ETA );
893            ui.action_SortByName->setChecked     ( i == SortMode::SORT_BY_NAME );
894            ui.action_SortByProgress->setChecked ( i == SortMode::SORT_BY_PROGRESS );
895            ui.action_SortByRatio->setChecked    ( i == SortMode::SORT_BY_RATIO );
896            ui.action_SortBySize->setChecked     ( i == SortMode::SORT_BY_SIZE );
897            ui.action_SortByState->setChecked    ( i == SortMode::SORT_BY_STATE );
898            ui.action_SortByTracker->setChecked  ( i == SortMode::SORT_BY_TRACKER );
899            break;
900
901        case Prefs::DSPEED_ENABLED:
902            (myPrefs.get<bool>(key) ? myDlimitOnAction : myDlimitOffAction)->setChecked( true );
903            break;
904     
905        case Prefs::DSPEED:
906            myDlimitOnAction->setText( tr( "Limited at %1" ).arg( Utils::speedToString( Speed::fromKbps( myPrefs.get<int>(key) ) ) ) );
907            break;
908
909        case Prefs::USPEED_ENABLED:
910            (myPrefs.get<bool>(key) ? myUlimitOnAction : myUlimitOffAction)->setChecked( true );
911            break;
912     
913        case Prefs::USPEED:
914            myUlimitOnAction->setText( tr( "Limited at %1" ).arg( Utils::speedToString( Speed::fromKbps( myPrefs.get<int>(key) ) ) ) );
915            break;
916
917        case Prefs::RATIO_ENABLED:
918            (myPrefs.get<bool>(key) ? myRatioOnAction : myRatioOffAction)->setChecked( true );
919            break;
920
921        case Prefs::RATIO:
922            myRatioOnAction->setText( tr( "Stop at Ratio (%1)" ).arg( Utils::ratioToString( myPrefs.get<double>(key) ) ) );
923            break;
924
925        case Prefs::FILTER_MODE:
926            i = myPrefs.get<FilterMode>(key).mode( );
927            for( int j=0; j<FilterMode::NUM_MODES; ++j )
928                myFilterButtons[j]->setChecked( i==j );
929            break;
930
931        case Prefs::FILTERBAR:
932            b = myPrefs.getBool( key );
933            myFilterBar->setVisible( b );
934            ui.action_Filterbar->setChecked( b );
935            break;
936
937        case Prefs::STATUSBAR:
938            b = myPrefs.getBool( key );
939            myStatusBar->setVisible( b );
940            ui.action_Statusbar->setChecked( b );
941            break;
942
943        case Prefs::TOOLBAR:
944            b = myPrefs.getBool( key );
945            ui.toolBar->setVisible( b );
946            ui.action_Toolbar->setChecked( b );
947            break;
948
949        case Prefs::SHOW_TRAY_ICON:
950            b = myPrefs.getBool( key );
951            ui.action_TrayIcon->setChecked( b );
952            myTrayIcon.setVisible( b );
953            break;
954
955        case Prefs::MINIMAL_VIEW:
956            b = myPrefs.getBool( key );
957            ui.action_MinimalView->setChecked( b );
958            ui.listView->setItemDelegate( b ? myTorrentDelegateMin : myTorrentDelegate );
959            ui.listView->reset( ); // force the rows to resize
960            break;
961
962        case Prefs::MAIN_WINDOW_X:
963        case Prefs::MAIN_WINDOW_Y:
964        case Prefs::MAIN_WINDOW_WIDTH:
965        case Prefs::MAIN_WINDOW_HEIGHT:
966            setGeometry( myPrefs.getInt( Prefs::MAIN_WINDOW_X ),
967                         myPrefs.getInt( Prefs::MAIN_WINDOW_Y ),
968                         myPrefs.getInt( Prefs::MAIN_WINDOW_WIDTH ),
969                         myPrefs.getInt( Prefs::MAIN_WINDOW_HEIGHT ) );
970            break;
971
972        case Prefs :: ALT_SPEED_LIMIT_ENABLED:
973            b = myPrefs.getBool( key );
974            myAltSpeedButton->setChecked( b );
975            myAltSpeedButton->setIcon( b ? mySpeedModeOnIcon : mySpeedModeOffIcon );
976            myAltSpeedButton->setToolTip( b ? tr( "Click to disable Speed Limit Mode" )
977                                            : tr( "Click to enable Speed Limit Mode" ) );
978            break;
979
980        default:
981            break;
982    }
983}
984
985/***
986****
987***/
988
989void
990TrMainWindow :: newTorrent( )
991{
992    MakeDialog * d = new MakeDialog( mySession, this );
993    d->show( );
994}
995
996void
997TrMainWindow :: openTorrent( )
998{
999    if( myFileDialog == 0 )
1000    {
1001        myFileDialog = new QFileDialog( this,
1002                                        tr( "Add Torrent" ),
1003                                        myPrefs.getString( Prefs::OPEN_DIALOG_FOLDER ),
1004                                        tr( "Torrent Files (*.torrent);;All Files (*.*)" ) );
1005        myFileDialog->setFileMode( QFileDialog::ExistingFiles );
1006
1007
1008        QCheckBox * button = new QCheckBox( tr( "Display &options dialog" ) );
1009        button->setChecked( myPrefs.getBool( Prefs::OPTIONS_PROMPT ) );
1010        QGridLayout * layout = dynamic_cast<QGridLayout*>(myFileDialog->layout());
1011        layout->addWidget( button, layout->rowCount( ), 0, 1, -1, Qt::AlignLeft );
1012        myFileDialogOptionsCheck = button;
1013
1014        connect( myFileDialog, SIGNAL(filesSelected(const QStringList&)), this, SLOT(addTorrents(const QStringList&)));
1015    }
1016
1017    myFileDialog->show( );
1018}
1019
1020void
1021TrMainWindow :: addTorrents( const QStringList& filenames )
1022{
1023    foreach( const QString& filename, filenames )
1024        addTorrent( filename );
1025}
1026
1027void
1028TrMainWindow :: addTorrent( const QString& filename )
1029{
1030    if( !myFileDialogOptionsCheck->isChecked( ) ) {
1031        mySession.addTorrent( filename );
1032        QApplication :: alert ( this );
1033    } else {
1034        Options * o = new Options( mySession, myPrefs, filename, this );
1035        o->show( );
1036        QApplication :: alert( o );
1037    }
1038}
1039
1040/***
1041****
1042***/
1043
1044void
1045TrMainWindow :: updateNetworkIcon( )
1046{
1047    const time_t now = time( NULL );
1048    const int period = 3;
1049    const bool isSending = now - myLastSendTime <= period;
1050    const bool isReading = now - myLastReadTime <= period;
1051    const char * key;
1052
1053    if( isSending && isReading )
1054        key = "network-transmit-receive";
1055    else if( isSending )
1056        key = "network-transmit";
1057    else if( isReading )
1058        key = "network-receive";
1059    else
1060        key = "network-idle";
1061
1062    QIcon icon = getStockIcon( key, QStyle::SP_DriveNetIcon );
1063    QPixmap pixmap = icon.pixmap ( 16, 16 );
1064    myNetworkLabel->setPixmap( pixmap );
1065    myNetworkLabel->setToolTip( isSending || isReading
1066        ? tr( "Transmission server is responding" )
1067        : tr( "Last response from server was %1 ago" ).arg( Utils::timeToString( now-std::max(myLastReadTime,myLastSendTime))));
1068}
1069
1070void
1071TrMainWindow :: onNetworkTimer( )
1072{
1073    updateNetworkIcon( );
1074}
1075
1076void
1077TrMainWindow :: dataReadProgress( )
1078{
1079    myLastReadTime = time( NULL );
1080    updateNetworkIcon( );
1081}
1082
1083void
1084TrMainWindow :: dataSendProgress( )
1085{
1086    myLastSendTime = time( NULL );
1087    updateNetworkIcon( );
1088}
Note: See TracBrowser for help on using the repository browser.