source: trunk/qt/mainwin.cc @ 10933

Last change on this file since 10933 was 10933, checked in by charles, 12 years ago

(trunk) rename the Qt client's "Units" class as "Formatter"

  • Property svn:keywords set to Date Rev Author Id
File size: 46.3 KB
Line 
1/*
2 * This file Copyright (C) 2009-2010 Mnemosyne LLC
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: mainwin.cc 10933 2010-07-03 01:10:36Z charles $
11 */
12
13#include <cassert>
14#include <iostream>
15
16#include <QCheckBox>
17#include <QCloseEvent>
18#include <QDesktopServices>
19#include <QFileDialog>
20#include <QHBoxLayout>
21#include <QInputDialog>
22#include <QLabel>
23#include <QMessageBox>
24#include <QSignalMapper>
25#include <QSize>
26#include <QStyle>
27#include <QSystemTrayIcon>
28#include <QUrl>
29
30#include <libtransmission/transmission.h>
31#include <libtransmission/utils.h>
32#include <libtransmission/version.h>
33
34#include "about.h"
35#include "details.h"
36#include "filters.h"
37#include "formatter.h"
38#include "hig.h"
39#include "mainwin.h"
40#include "make-dialog.h"
41#include "options.h"
42#include "prefs.h"
43#include "prefs-dialog.h"
44#include "relocate.h"
45#include "session.h"
46#include "session-dialog.h"
47#include "speed.h"
48#include "stats-dialog.h"
49#include "torrent-delegate.h"
50#include "torrent-delegate-min.h"
51#include "torrent-filter.h"
52#include "torrent-model.h"
53#include "triconpushbutton.h"
54#include "ui_mainwin.h"
55#include "qticonloader.h"
56
57#define PREFS_KEY "prefs-key";
58
59QIcon
60TrMainWindow :: getStockIcon( const QString& freedesktop_name, int fallback )
61{
62    QIcon fallbackIcon;
63
64    if( fallback > 0 )
65        fallbackIcon = style()->standardIcon( QStyle::StandardPixmap( fallback ), 0, this );
66
67    return QtIconLoader::icon( freedesktop_name, fallbackIcon );
68}
69
70namespace
71{
72    QSize calculateTextButtonSizeHint( QPushButton * button )
73    {
74        QStyleOptionButton opt;
75        opt.initFrom( button );
76        QString s( button->text( ) );
77        if( s.isEmpty( ) )
78            s = QString::fromLatin1( "XXXX" );
79        QFontMetrics fm = button->fontMetrics( );
80        QSize sz = fm.size( Qt::TextShowMnemonic, s );
81        return button->style()->sizeFromContents( QStyle::CT_PushButton, &opt, sz, button ).expandedTo( QApplication::globalStrut( ) );
82    }
83}
84
85
86TrMainWindow :: TrMainWindow( Session& session, Prefs& prefs, TorrentModel& model, bool minimized ):
87    myLastFullUpdateTime( 0 ),
88    mySessionDialog( new SessionDialog( session, prefs, this ) ),
89    myPrefsDialog( 0 ),
90    myAboutDialog( new AboutDialog( this ) ),
91    myStatsDialog( new StatsDialog( session, this ) ),
92    myDetailsDialog( 0 ),
93    myFilterModel( prefs ),
94    myTorrentDelegate( new TorrentDelegate( this ) ),
95    myTorrentDelegateMin( new TorrentDelegateMin( this ) ),
96    mySession( session ),
97    myPrefs( prefs ),
98    myModel( model ),
99    mySpeedModeOffIcon( ":/icons/alt-limit-off.png" ),
100    mySpeedModeOnIcon( ":/icons/alt-limit-on.png" ),
101    myLastSendTime( 0 ),
102    myLastReadTime( 0 ),
103    myNetworkTimer( this )
104{
105    QAction * sep = new QAction( this );
106    sep->setSeparator( true );
107
108    ui.setupUi( this );
109
110    QStyle * style = this->style();
111
112    int i = style->pixelMetric( QStyle::PM_SmallIconSize, 0, this );
113    const QSize smallIconSize( i, i );
114
115    // icons
116    ui.action_AddFile->setIcon( getStockIcon( "list-add", QStyle::SP_DialogOpenButton ) );
117    ui.action_New->setIcon( getStockIcon( "document-new", QStyle::SP_DesktopIcon ) );
118    ui.action_Properties->setIcon( getStockIcon( "document-properties", QStyle::SP_DesktopIcon ) );
119    ui.action_OpenFolder->setIcon( getStockIcon( "folder-open", QStyle::SP_DirOpenIcon ) );
120    ui.action_Start->setIcon( getStockIcon( "media-playback-start", QStyle::SP_MediaPlay ) );
121    ui.action_Announce->setIcon( getStockIcon( "network-transmit-receive" ) );
122    ui.action_Pause->setIcon( getStockIcon( "media-playback-pause", QStyle::SP_MediaPause ) );
123    ui.action_Remove->setIcon( getStockIcon( "list-remove", QStyle::SP_TrashIcon ) );
124    ui.action_Delete->setIcon( getStockIcon( "edit-delete", QStyle::SP_TrashIcon ) );
125    ui.action_StartAll->setIcon( getStockIcon( "media-playback-start", QStyle::SP_MediaPlay ) );
126    ui.action_PauseAll->setIcon( getStockIcon( "media-playback-pause", QStyle::SP_MediaPause ) );
127    ui.action_Quit->setIcon( getStockIcon( "application-exit" ) );
128    ui.action_SelectAll->setIcon( getStockIcon( "edit-select-all" ) );
129    ui.action_ReverseSortOrder->setIcon( getStockIcon( "view-sort-ascending", QStyle::SP_ArrowDown ) );
130    ui.action_Preferences->setIcon( getStockIcon( "preferences-system" ) );
131    ui.action_Contents->setIcon( getStockIcon( "help-contents", QStyle::SP_DialogHelpButton ) );
132    ui.action_About->setIcon( getStockIcon( "help-about" ) );
133
134    // ui signals
135    connect( ui.action_Toolbar, SIGNAL(toggled(bool)), this, SLOT(setToolbarVisible(bool)));
136    connect( ui.action_TrayIcon, SIGNAL(toggled(bool)), this, SLOT(setTrayIconVisible(bool)));
137    connect( ui.action_Filterbar, SIGNAL(toggled(bool)), this, SLOT(setFilterbarVisible(bool)));
138    connect( ui.action_Statusbar, SIGNAL(toggled(bool)), this, SLOT(setStatusbarVisible(bool)));
139    connect( ui.action_CompactView, SIGNAL(toggled(bool)), this, SLOT(setCompactView(bool)));
140    connect( ui.action_SortByActivity, SIGNAL(toggled(bool)), this, SLOT(onSortByActivityToggled(bool)));
141    connect( ui.action_SortByAge,      SIGNAL(toggled(bool)), this, SLOT(onSortByAgeToggled(bool)));
142    connect( ui.action_SortByETA,      SIGNAL(toggled(bool)), this, SLOT(onSortByETAToggled(bool)));
143    connect( ui.action_SortByName,     SIGNAL(toggled(bool)), this, SLOT(onSortByNameToggled(bool)));
144    connect( ui.action_SortByProgress, SIGNAL(toggled(bool)), this, SLOT(onSortByProgressToggled(bool)));
145    connect( ui.action_SortByRatio,    SIGNAL(toggled(bool)), this, SLOT(onSortByRatioToggled(bool)));
146    connect( ui.action_SortBySize,     SIGNAL(toggled(bool)), this, SLOT(onSortBySizeToggled(bool)));
147    connect( ui.action_SortByState,    SIGNAL(toggled(bool)), this, SLOT(onSortByStateToggled(bool)));
148    connect( ui.action_SortByTracker,  SIGNAL(toggled(bool)), this, SLOT(onSortByTrackerToggled(bool)));
149    connect( ui.action_ReverseSortOrder, SIGNAL(toggled(bool)), this, SLOT(setSortAscendingPref(bool)));
150    connect( ui.action_Start, SIGNAL(triggered()), this, SLOT(startSelected()));
151    connect( ui.action_Pause, SIGNAL(triggered()), this, SLOT(pauseSelected()));
152    connect( ui.action_Remove, SIGNAL(triggered()), this, SLOT(removeSelected()));
153    connect( ui.action_Delete, SIGNAL(triggered()), this, SLOT(deleteSelected()));
154    connect( ui.action_Verify, SIGNAL(triggered()), this, SLOT(verifySelected()) );
155    connect( ui.action_Announce, SIGNAL(triggered()), this, SLOT(reannounceSelected()) );
156    connect( ui.action_StartAll, SIGNAL(triggered()), this, SLOT(startAll()));
157    connect( ui.action_PauseAll, SIGNAL(triggered()), this, SLOT(pauseAll()));
158    connect( ui.action_AddFile, SIGNAL(triggered()), this, SLOT(openTorrent()));
159    connect( ui.action_AddURL, SIGNAL(triggered()), this, SLOT(openURL()));
160    connect( ui.action_New, SIGNAL(triggered()), this, SLOT(newTorrent()));
161    connect( ui.action_Preferences, SIGNAL(triggered()), this, SLOT(openPreferences()));
162    connect( ui.action_Statistics, SIGNAL(triggered()), myStatsDialog, SLOT(show()));
163    connect( ui.action_About, SIGNAL(triggered()), myAboutDialog, SLOT(show()));
164    connect( ui.action_Contents, SIGNAL(triggered()), this, SLOT(openHelp()));
165    connect( ui.action_OpenFolder, SIGNAL(triggered()), this, SLOT(openFolder()));
166    connect( ui.action_CopyMagnetToClipboard, SIGNAL(triggered()), this, SLOT(copyMagnetLinkToClipboard()));
167    connect( ui.action_SetLocation, SIGNAL(triggered()), this, SLOT(setLocation()));
168    connect( ui.action_Properties, SIGNAL(triggered()), this, SLOT(openProperties()));
169    connect( ui.action_SessionDialog, SIGNAL(triggered()), mySessionDialog, SLOT(show()));
170    connect( ui.listView, SIGNAL(activated(const QModelIndex&)), ui.action_Properties, SLOT(trigger()));
171
172    QAction * sep2 = new QAction( this );
173    sep2->setSeparator( true );
174    QAction * sep3 = new QAction( this );
175    sep3->setSeparator( true );
176
177    // context menu
178    QList<QAction*> actions;
179    actions << ui.action_Properties
180            << ui.action_OpenFolder
181            << sep2
182            << ui.action_Start
183            << ui.action_Announce
184            << ui.action_Pause
185            << ui.action_CopyMagnetToClipboard
186            << sep3
187            << ui.action_Verify
188            << ui.action_SetLocation
189            << sep
190            << ui.action_Remove
191            << ui.action_Delete;
192    addActions( actions );
193    setContextMenuPolicy( Qt::ActionsContextMenu );
194
195    // signals
196    connect( ui.action_SelectAll, SIGNAL(triggered()), ui.listView, SLOT(selectAll()));
197    connect( ui.action_DeselectAll, SIGNAL(triggered()), ui.listView, SLOT(clearSelection()));
198
199    connect( &myFilterModel, SIGNAL(rowsInserted(const QModelIndex&,int,int)), this, SLOT(refreshVisibleCount()));
200    connect( &myFilterModel, SIGNAL(rowsRemoved(const QModelIndex&,int,int)), this, SLOT(refreshVisibleCount()));
201    connect( &myFilterModel, SIGNAL(rowsInserted(const QModelIndex&,int,int)), this, SLOT(refreshActionSensitivity()));
202    connect( &myFilterModel, SIGNAL(rowsRemoved(const QModelIndex&,int,int)), this, SLOT(refreshActionSensitivity()));
203
204    connect( ui.action_Quit, SIGNAL(triggered()), QCoreApplication::instance(), SLOT(quit()) );
205
206    // torrent view
207    myFilterModel.setSourceModel( &myModel );
208    connect( &myModel, SIGNAL(modelReset()), this, SLOT(onModelReset()));
209    connect( &myModel, SIGNAL(rowsRemoved(const QModelIndex&,int,int)), this, SLOT(onModelReset()));
210    connect( &myModel, SIGNAL(rowsInserted(const QModelIndex&,int,int)), this, SLOT(onModelReset()));
211    ui.listView->setModel( &myFilterModel );
212    connect( ui.listView->selectionModel(), SIGNAL(selectionChanged(const QItemSelection&,const QItemSelection&)), this, SLOT(refreshActionSensitivity()));
213
214    QActionGroup * actionGroup = new QActionGroup( this );
215    actionGroup->addAction( ui.action_SortByActivity );
216    actionGroup->addAction( ui.action_SortByAge );
217    actionGroup->addAction( ui.action_SortByETA );
218    actionGroup->addAction( ui.action_SortByName );
219    actionGroup->addAction( ui.action_SortByProgress );
220    actionGroup->addAction( ui.action_SortByRatio );
221    actionGroup->addAction( ui.action_SortBySize );
222    actionGroup->addAction( ui.action_SortByState );
223    actionGroup->addAction( ui.action_SortByTracker );
224
225    QMenu * menu = new QMenu( );
226    menu->addAction( ui.action_AddFile );
227    menu->addAction( ui.action_AddURL );
228    menu->addSeparator( );
229    menu->addAction( ui.action_ShowMainWindow );
230    menu->addAction( ui.action_ShowMessageLog );
231    menu->addAction( ui.action_About );
232    menu->addSeparator( );
233    menu->addAction( ui.action_StartAll );
234    menu->addAction( ui.action_PauseAll );
235    menu->addSeparator( );
236    menu->addAction( ui.action_Quit );
237    myTrayIcon.setContextMenu( menu );
238    myTrayIcon.setIcon( QApplication::windowIcon( ) );
239
240    connect( &myPrefs, SIGNAL(changed(int)), this, SLOT(refreshPref(int)) );
241    connect( ui.action_ShowMainWindow, SIGNAL(toggled(bool)), this, SLOT(toggleWindows(bool)));
242    connect( &myTrayIcon, SIGNAL(activated(QSystemTrayIcon::ActivationReason)),
243             this, SLOT(trayActivated(QSystemTrayIcon::ActivationReason)));
244
245    ui.action_ShowMainWindow->setChecked( !minimized );
246    ui.action_TrayIcon->setChecked( minimized || prefs.getBool( Prefs::SHOW_TRAY_ICON ) );
247
248    ui.verticalLayout->addWidget( createStatusBar( ) );
249    ui.verticalLayout->insertWidget( 0, createFilterBar( ) );
250
251    QList<int> initKeys;
252    initKeys << Prefs :: MAIN_WINDOW_X
253             << Prefs :: SHOW_TRAY_ICON
254             << Prefs :: SORT_REVERSED
255             << Prefs :: SORT_MODE
256             << Prefs :: FILTER_MODE
257             << Prefs :: FILTERBAR
258             << Prefs :: STATUSBAR
259             << Prefs :: STATUSBAR_STATS
260             << Prefs :: TOOLBAR
261             << Prefs :: ALT_SPEED_LIMIT_ENABLED
262             << Prefs :: COMPACT_VIEW
263             << Prefs :: DSPEED
264             << Prefs :: DSPEED_ENABLED
265             << Prefs :: USPEED
266             << Prefs :: USPEED_ENABLED
267             << Prefs :: RATIO
268             << Prefs :: RATIO_ENABLED;
269    foreach( int key, initKeys )
270        refreshPref( key );
271
272    connect( &mySession, SIGNAL(sourceChanged()), this, SLOT(onSessionSourceChanged()) );
273    connect( &mySession, SIGNAL(statsUpdated()), this, SLOT(refreshStatusBar()) );
274    connect( &mySession, SIGNAL(dataReadProgress()), this, SLOT(dataReadProgress()) );
275    connect( &mySession, SIGNAL(dataSendProgress()), this, SLOT(dataSendProgress()) );
276    connect( &mySession, SIGNAL(httpAuthenticationRequired()), this, SLOT(wrongAuthentication()) );
277
278    if( mySession.isServer( ) )
279        myNetworkLabel->hide( );
280    else {
281        connect( &myNetworkTimer, SIGNAL(timeout()), this, SLOT(onNetworkTimer()));
282        myNetworkTimer.start( 1000 );
283    }
284
285    refreshActionSensitivity( );
286    refreshStatusBar( );
287    refreshTitle( );
288    refreshVisibleCount( );
289}
290
291TrMainWindow :: ~TrMainWindow( )
292{
293}
294
295/****
296*****
297****/
298
299void
300TrMainWindow :: closeEvent( QCloseEvent * event )
301{
302    // if they're using a tray icon, close to the tray
303    // instead of exiting
304    if( !myPrefs.getBool( Prefs :: SHOW_TRAY_ICON ) )
305        event->accept( );
306    else {
307        toggleWindows( false );
308        event->ignore( );
309    }
310}
311
312/****
313*****
314****/
315
316void
317TrMainWindow :: onSessionSourceChanged( )
318{
319    myModel.clear( );
320}
321
322void
323TrMainWindow :: onModelReset( )
324{
325    refreshTitle( );
326    refreshVisibleCount( );
327    refreshActionSensitivity( );
328    refreshStatusBar( );
329}
330
331/****
332*****
333****/
334
335#define PREF_VARIANTS_KEY "pref-variants-list"
336
337void
338TrMainWindow :: onSetPrefs( )
339{
340    const QVariantList p = sender()->property( PREF_VARIANTS_KEY ).toList( );
341    assert( ( p.size( ) % 2 ) == 0 );
342    for( int i=0, n=p.size(); i<n; i+=2 )
343        myPrefs.set( p[i].toInt(), p[i+1] );
344}
345
346void
347TrMainWindow :: onSetPrefs( bool isChecked )
348{
349    if( isChecked )
350        onSetPrefs( );
351}
352
353#define SHOW_KEY "show-mode"
354
355void
356TrMainWindow :: onShowModeClicked( )
357{
358    setShowMode( sender()->property(SHOW_KEY).toInt() );
359}
360
361QWidget *
362TrMainWindow :: createFilterBar( )
363{
364    int i;
365    QMenu * m;
366    QLineEdit * e;
367    QPushButton * p;
368    QHBoxLayout * h;
369    QActionGroup * a;
370    const int smallSize = style( )->pixelMetric( QStyle::PM_SmallIconSize, 0, this );
371    const QSize smallIconSize( smallSize, smallSize );
372
373    QWidget * top = myFilterBar = new QWidget;
374    h = new QHBoxLayout( top );
375    h->setContentsMargins( HIG::PAD_SMALL, HIG::PAD_SMALL, HIG::PAD_SMALL, HIG::PAD_SMALL );
376    h->setSpacing( HIG::PAD_SMALL );
377#ifdef Q_OS_MAC
378    top->setStyleSheet( "QPushButton{ "
379                        "  border-radius: 10px; "
380                        "  padding: 0 5px; "
381                        "  border: 1px none; "
382                        "} "
383                        "QPushButton:pressed, QPushButton:checked{ "
384                        "  border-width: 1px; "
385                        "  border-style: solid; "
386                        "  border-color: #5f5f5f #979797 #979797; "
387                        "  background-color: #979797; "
388                        "  color: white; "
389                        "} ");
390#endif
391
392        QList<QString> titles;
393        titles << tr( "A&ll" ) << tr( "&Active" ) << tr( "&Downloading" ) << tr( "&Seeding" ) << tr( "&Paused" );
394        for( i=0; i<titles.size(); ++i ) {
395            p = myFilterButtons[i] = new QPushButton( titles[i] );
396            p->setProperty( SHOW_KEY, i );
397            p->setFlat( true );
398            p->setCheckable( true );
399            p->setMaximumSize( calculateTextButtonSizeHint( p ) );
400            connect( p, SIGNAL(clicked()), this, SLOT(onShowModeClicked()));
401            h->addWidget( p );
402        }
403
404    h->addStretch( 1 );
405
406        a = new QActionGroup( this );
407        a->addAction( ui.action_FilterByName );
408        a->addAction( ui.action_FilterByFiles );
409        a->addAction( ui.action_FilterByTracker );
410        m = new QMenu( );
411        m->addAction( ui.action_FilterByName );
412        m->addAction( ui.action_FilterByFiles );
413        m->addAction( ui.action_FilterByTracker );
414        connect( ui.action_FilterByName, SIGNAL(triggered()), this, SLOT(filterByName()));
415        connect( ui.action_FilterByFiles, SIGNAL(triggered()), this, SLOT(filterByFiles()));
416        connect( ui.action_FilterByTracker, SIGNAL(triggered()), this, SLOT(filterByTracker()));
417        ui.action_FilterByName->setChecked( true );
418        p = myFilterTextButton = new TrIconPushButton;
419        p->setIcon( getStockIcon( "edit-find", QStyle::SP_ArrowForward ) );
420        p->setFlat( true );
421        p->setMenu( m );
422        h->addWidget( p );
423
424        e = myFilterTextLineEdit = new QLineEdit;
425        connect( e, SIGNAL(textChanged(QString)), &myFilterModel, SLOT(setText(QString)));
426        h->addWidget( e );
427
428        p = myFilterTextButton = new TrIconPushButton;
429        p->setIcon( getStockIcon( "edit-clear", QStyle::SP_DialogCloseButton ) );
430        p->setFlat( true );
431        connect( p, SIGNAL(clicked()), myFilterTextLineEdit, SLOT(clear()));
432        h->addWidget( p );
433
434    return top;
435}
436
437QWidget *
438TrMainWindow :: createStatusBar( )
439{
440    QMenu * m;
441    QLabel * l;
442    QWidget * w;
443    QHBoxLayout * h;
444    QPushButton * p;
445    QActionGroup * a;
446    const int i = style( )->pixelMetric( QStyle::PM_SmallIconSize, 0, this );
447    const QSize smallIconSize( i, i );
448
449    QWidget * top = myStatusBar = new QWidget;
450    h = new QHBoxLayout( top );
451    h->setContentsMargins( HIG::PAD_SMALL, HIG::PAD_SMALL, HIG::PAD_SMALL, HIG::PAD_SMALL );
452    h->setSpacing( HIG::PAD_SMALL );
453
454        p = myOptionsButton = new TrIconPushButton( this );
455        p->setIcon( QIcon( ":/icons/options.png" ) );
456        p->setIconSize( QPixmap( ":/icons/options.png" ).size() );
457        p->setFlat( true );
458        p->setMenu( createOptionsMenu( ) );
459        h->addWidget( p );
460
461        p = myAltSpeedButton = new QPushButton( this );
462        p->setIcon( myPrefs.get<bool>(Prefs::ALT_SPEED_LIMIT_ENABLED) ? mySpeedModeOnIcon : mySpeedModeOffIcon );
463        p->setIconSize( QPixmap( ":/icons/alt-limit-on.png" ).size() );
464        p->setCheckable( true );
465        p->setFixedWidth( p->height() );
466        p->setFlat( true );
467        h->addWidget( p );
468        connect( p, SIGNAL(clicked()), this, SLOT(toggleSpeedMode()));
469
470        l = myNetworkLabel = new QLabel;
471        h->addWidget( l );
472
473    h->addStretch( 1 );
474
475        l = myVisibleCountLabel = new QLabel( this );
476        h->addWidget( l );
477
478    h->addStretch( 1 );
479
480        a = new QActionGroup( this );
481        a->addAction( ui.action_TotalRatio );
482        a->addAction( ui.action_TotalTransfer );
483        a->addAction( ui.action_SessionRatio );
484        a->addAction( ui.action_SessionTransfer );
485        m = new QMenu( );
486        m->addAction( ui.action_TotalRatio );
487        m->addAction( ui.action_TotalTransfer );
488        m->addAction( ui.action_SessionRatio );
489        m->addAction( ui.action_SessionTransfer );
490        connect( ui.action_TotalRatio, SIGNAL(triggered()), this, SLOT(showTotalRatio()));
491        connect( ui.action_TotalTransfer, SIGNAL(triggered()), this, SLOT(showTotalTransfer()));
492        connect( ui.action_SessionRatio, SIGNAL(triggered()), this, SLOT(showSessionRatio()));
493        connect( ui.action_SessionTransfer, SIGNAL(triggered()), this, SLOT(showSessionTransfer()));
494        p = myStatsModeButton = new TrIconPushButton( this );
495        p->setIcon( QIcon( ":/icons/ratio.png" ) );
496        p->setIconSize( QPixmap( ":/icons/ratio.png" ).size() );
497        p->setFlat( true );
498        p->setMenu( m );
499        h->addWidget( p );
500        l = myStatsLabel = new QLabel( this );
501        h->addWidget( l );
502
503    h->addSpacing( HIG::PAD_BIG );
504
505        w = new QWidget( this );
506        w->setMinimumSize( HIG::PAD_BIG, 1 );
507        w->setMaximumSize( HIG::PAD_BIG, 1 );
508        h->addWidget( w );
509        l = myDownloadSpeedLabel = new QLabel( this );
510        h->addWidget( l );
511        l = new QLabel( this );
512        l->setPixmap( getStockIcon( "go-down", QStyle::SP_ArrowDown ).pixmap( smallIconSize ) );
513        h->addWidget( l );
514
515    h->addSpacing( HIG::PAD_BIG );
516
517        w = new QWidget( this );
518        w->setMinimumSize( HIG::PAD_BIG, 1 );
519        w->setMaximumSize( HIG::PAD_BIG, 1 );
520        h->addWidget( w );
521        l = myUploadSpeedLabel = new QLabel;
522        h->addWidget( l );
523        l = new QLabel;
524        l->setPixmap( getStockIcon( "go-up", QStyle::SP_ArrowUp ).pixmap( smallIconSize ) );
525        h->addWidget( l );
526
527    return top;
528}
529
530QMenu *
531TrMainWindow :: createOptionsMenu( )
532{
533    QMenu * menu;
534    QMenu * sub;
535    QAction * a;
536    QActionGroup * g;
537
538    QList<int> stockSpeeds;
539    stockSpeeds << 5 << 10 << 20 << 30 << 40 << 50 << 75 << 100 << 150 << 200 << 250 << 500 << 750;
540    QList<double> stockRatios;
541    stockRatios << 0.25 << 0.50 << 0.75 << 1 << 1.5 << 2 << 3;
542
543    menu = new QMenu;
544    sub = menu->addMenu( tr( "Limit Download Speed" ) );
545        int currentVal = myPrefs.get<int>( Prefs::DSPEED );
546        g = new QActionGroup( this );
547        a = myDlimitOffAction = sub->addAction( tr( "Unlimited" ) );
548        a->setCheckable( true );
549        a->setProperty( PREF_VARIANTS_KEY, QVariantList() << Prefs::DSPEED_ENABLED << false );
550        g->addAction( a );
551        connect( a, SIGNAL(triggered(bool)), this, SLOT(onSetPrefs(bool)) );
552        a = myDlimitOnAction = sub->addAction( tr( "Limited at %1" ).arg( Formatter::speedToString( Speed::fromBps( currentVal ) ) ) );
553        a->setCheckable( true );
554        a->setProperty( PREF_VARIANTS_KEY, QVariantList() << Prefs::DSPEED << currentVal << Prefs::DSPEED_ENABLED << true );
555        g->addAction( a );
556        connect( a, SIGNAL(triggered(bool)), this, SLOT(onSetPrefs(bool)) );
557        sub->addSeparator( );
558        foreach( int i, stockSpeeds ) {
559            const int Bps = i * Formatter::speed_K;
560            a = sub->addAction( Formatter::speedToString( Speed::fromBps( Bps ) ) );
561            a->setProperty( PREF_VARIANTS_KEY, QVariantList() << Prefs::DSPEED << Bps << Prefs::DSPEED_ENABLED << true );
562            connect( a, SIGNAL(triggered(bool)), this, SLOT(onSetPrefs()));
563        }
564
565    sub = menu->addMenu( tr( "Limit Upload Speed" ) );
566        currentVal = myPrefs.get<int>( Prefs::USPEED );
567        g = new QActionGroup( this );
568        a = myUlimitOffAction = sub->addAction( tr( "Unlimited" ) );
569        a->setCheckable( true );
570        a->setProperty( PREF_VARIANTS_KEY, QVariantList() << Prefs::USPEED_ENABLED << false );
571        g->addAction( a );
572        connect( a, SIGNAL(triggered(bool)), this, SLOT(onSetPrefs(bool)) );
573        a = myUlimitOnAction = sub->addAction( tr( "Limited at %1" ).arg( Formatter::speedToString( Speed::fromBps( currentVal ) ) ) );
574        a->setCheckable( true );
575        a->setProperty( PREF_VARIANTS_KEY, QVariantList() << Prefs::USPEED << currentVal << Prefs::USPEED_ENABLED << true );
576        g->addAction( a );
577        connect( a, SIGNAL(triggered(bool)), this, SLOT(onSetPrefs(bool)) );
578        sub->addSeparator( );
579        foreach( int i, stockSpeeds ) {
580            const int Bps = i * Formatter::speed_K;
581            a = sub->addAction( Formatter::speedToString( Speed::fromBps( Bps ) ) );
582            a->setProperty( PREF_VARIANTS_KEY, QVariantList() << Prefs::USPEED << Bps << Prefs::USPEED_ENABLED << true );
583            connect( a, SIGNAL(triggered(bool)), this, SLOT(onSetPrefs()));
584        }
585
586    menu->addSeparator( );
587    sub = menu->addMenu( tr( "Stop Seeding at Ratio" ) );
588
589        double d = myPrefs.get<double>( Prefs::RATIO );
590        g = new QActionGroup( this );
591        a = myRatioOffAction = sub->addAction( tr( "Seed Forever" ) );
592        a->setCheckable( true );
593        a->setProperty( PREF_VARIANTS_KEY, QVariantList() << Prefs::RATIO_ENABLED << false );
594        g->addAction( a );
595        connect( a, SIGNAL(triggered(bool)), this, SLOT(onSetPrefs(bool)) );
596        a = myRatioOnAction = sub->addAction( tr( "Stop at Ratio (%1)" ).arg( Formatter::ratioToString( d ) ) );
597        a->setCheckable( true );
598        a->setProperty( PREF_VARIANTS_KEY, QVariantList() << Prefs::RATIO << d << Prefs::RATIO_ENABLED << true );
599        g->addAction( a );
600        connect( a, SIGNAL(triggered(bool)), this, SLOT(onSetPrefs(bool)) );
601        sub->addSeparator( );
602        foreach( double i, stockRatios ) {
603            a = sub->addAction( Formatter::ratioToString( i ) );
604            a->setProperty( PREF_VARIANTS_KEY, QVariantList() << Prefs::RATIO << i << Prefs::RATIO_ENABLED << true );
605            connect( a, SIGNAL(triggered(bool)), this, SLOT(onSetPrefs()));
606        }
607
608    return menu;
609}
610
611/****
612*****
613****/
614
615void
616TrMainWindow :: setSortPref( int i )
617{
618    myPrefs.set( Prefs::SORT_MODE, SortMode( i ) );
619}
620void TrMainWindow :: onSortByActivityToggled ( bool b ) { if( b ) setSortPref( SortMode::SORT_BY_ACTIVITY ); }
621void TrMainWindow :: onSortByAgeToggled      ( bool b ) { if( b ) setSortPref( SortMode::SORT_BY_AGE );      }
622void TrMainWindow :: onSortByETAToggled      ( bool b ) { if( b ) setSortPref( SortMode::SORT_BY_ETA );      }
623void TrMainWindow :: onSortByNameToggled     ( bool b ) { if( b ) setSortPref( SortMode::SORT_BY_NAME );     }
624void TrMainWindow :: onSortByProgressToggled ( bool b ) { if( b ) setSortPref( SortMode::SORT_BY_PROGRESS ); }
625void TrMainWindow :: onSortByRatioToggled    ( bool b ) { if( b ) setSortPref( SortMode::SORT_BY_RATIO );    }
626void TrMainWindow :: onSortBySizeToggled     ( bool b ) { if( b ) setSortPref( SortMode::SORT_BY_SIZE );     }
627void TrMainWindow :: onSortByStateToggled    ( bool b ) { if( b ) setSortPref( SortMode::SORT_BY_STATE );    }
628void TrMainWindow :: onSortByTrackerToggled  ( bool b ) { if( b ) setSortPref( SortMode::SORT_BY_TRACKER );  }
629
630void
631TrMainWindow :: setSortAscendingPref( bool b )
632{
633    myPrefs.set( Prefs::SORT_REVERSED, b );
634}
635
636/****
637*****
638****/
639
640void
641TrMainWindow :: onPrefsDestroyed( )
642{
643    myPrefsDialog = 0;
644}
645
646void
647TrMainWindow :: openPreferences( )
648{
649    if( myPrefsDialog == 0 ) {
650        myPrefsDialog = new PrefsDialog( mySession, myPrefs, this );
651        connect( myPrefsDialog, SIGNAL(destroyed(QObject*)), this, SLOT(onPrefsDestroyed()));
652    }
653
654    myPrefsDialog->show( );
655}
656
657void
658TrMainWindow :: onDetailsDestroyed( )
659{
660    myDetailsDialog = 0;
661}
662
663void
664TrMainWindow :: openProperties( )
665{
666    if( myDetailsDialog == 0 ) {
667        myDetailsDialog = new Details( mySession, myPrefs, myModel, this );
668        connect( myDetailsDialog, SIGNAL(destroyed(QObject*)), this, SLOT(onDetailsDestroyed()));
669    }
670
671    myDetailsDialog->setIds( getSelectedTorrents( ) );
672    myDetailsDialog->show( );
673}
674
675void
676TrMainWindow :: setLocation( )
677{
678    QDialog * d = new RelocateDialog( mySession, myModel, getSelectedTorrents(), this );
679    d->show( );
680}
681
682void
683TrMainWindow :: openFolder( )
684{
685    const int torrentId( *getSelectedTorrents().begin() );
686    const Torrent * tor( myModel.getTorrentFromId( torrentId ) );
687    const QString path( tor->getPath( ) );
688    QDesktopServices :: openUrl( QUrl::fromLocalFile( path ) );
689}
690
691void
692TrMainWindow :: copyMagnetLinkToClipboard( )
693{
694    const int id( *getSelectedTorrents().begin() );
695    mySession.copyMagnetLinkToClipboard( id );
696}
697
698void
699TrMainWindow :: openHelp( )
700{
701    const char * fmt = "http://www.transmissionbt.com/help/gtk/%d.%dx";
702    int major, minor;
703    sscanf( SHORT_VERSION_STRING, "%d.%d", &major, &minor );
704    char url[128];
705    tr_snprintf( url, sizeof( url ), fmt, major, minor/10 );
706    QDesktopServices :: openUrl( QUrl( QString( url ) ) );
707}
708
709void
710TrMainWindow :: refreshTitle( )
711{
712    QString title( "Transmission" );
713    const QUrl url( mySession.getRemoteUrl( ) );
714    if( !url.isEmpty() )
715        title += tr( " - %1:%2" ).arg( url.host() ).arg( url.port() );
716    setWindowTitle( title );
717}
718
719void
720TrMainWindow :: refreshVisibleCount( )
721{
722    const int visibleCount( myFilterModel.rowCount( ) );
723    const int totalCount( visibleCount + myFilterModel.hiddenRowCount( ) );
724    QString str;
725    if( visibleCount == totalCount )
726        str = tr( "%Ln Torrent(s)", 0, totalCount );
727    else
728        str = tr( "%L1 of %Ln Torrent(s)", 0, totalCount ).arg( visibleCount );
729    myVisibleCountLabel->setText( str );
730    myVisibleCountLabel->setVisible( totalCount > 0 );
731}
732
733void
734TrMainWindow :: refreshStatusBar( )
735{
736    const Speed up( myModel.getUploadSpeed( ) );
737    const Speed down( myModel.getDownloadSpeed( ) );
738    myUploadSpeedLabel->setText( Formatter:: speedToString( up ) );
739    myDownloadSpeedLabel->setText( Formatter:: speedToString( down ) );
740
741    myNetworkLabel->setVisible( !mySession.isServer( ) );
742
743    const QString mode( myPrefs.getString( Prefs::STATUSBAR_STATS ) );
744    QString str;
745
746    if( mode == "session-ratio" )
747    {
748        str = tr( "Ratio: %1" ).arg( Formatter:: ratioToString( mySession.getStats().ratio ) );
749    }
750    else if( mode == "session-transfer" )
751    {
752        const tr_session_stats& stats( mySession.getStats( ) );
753        str = tr( "Down: %1, Up: %2" ).arg( Formatter:: sizeToString( stats.downloadedBytes ) )
754                                      .arg( Formatter:: sizeToString( stats.uploadedBytes ) );
755    }
756    else if( mode == "total-transfer" )
757    {
758        const tr_session_stats& stats( mySession.getCumulativeStats( ) );
759        str = tr( "Down: %1, Up: %2" ).arg( Formatter:: sizeToString( stats.downloadedBytes ) )
760                                      .arg( Formatter:: sizeToString( stats.uploadedBytes ) );
761    }
762    else // default is "total-ratio"
763    {
764        str = tr( "Ratio: %1" ).arg( Formatter:: ratioToString( mySession.getCumulativeStats().ratio ) );
765    }
766
767    myStatsLabel->setText( str );
768}
769
770void
771TrMainWindow :: refreshActionSensitivity( )
772{
773    int selected( 0 );
774    int paused( 0 );
775    int selectedAndPaused( 0 );
776    int canAnnounce( 0 );
777    const QAbstractItemModel * model( ui.listView->model( ) );
778    const QItemSelectionModel * selectionModel( ui.listView->selectionModel( ) );
779    const int rowCount( model->rowCount( ) );
780
781    // count how many torrents are selected, paused, etc
782    for( int row=0; row<rowCount; ++row ) {
783        const QModelIndex modelIndex( model->index( row, 0 ) );
784        assert( model == modelIndex.model( ) );
785        const Torrent * tor( model->data( modelIndex, TorrentModel::TorrentRole ).value<const Torrent*>( ) );
786        if( tor ) {
787            const bool isSelected( selectionModel->isSelected( modelIndex ) );
788            const bool isPaused( tor->isPaused( ) );
789            if( isSelected )
790                ++selected;
791            if( isPaused )
792                ++ paused;
793            if( isSelected && isPaused )
794                ++selectedAndPaused;
795            if( tor->canManualAnnounce( ) )
796                ++canAnnounce;
797        }
798    }
799
800    const bool haveSelection( selected > 0 );
801    ui.action_Verify->setEnabled( haveSelection );
802    ui.action_Remove->setEnabled( haveSelection );
803    ui.action_Delete->setEnabled( haveSelection );
804    ui.action_Properties->setEnabled( haveSelection );
805    ui.action_DeselectAll->setEnabled( haveSelection );
806    ui.action_SetLocation->setEnabled( haveSelection );
807
808    const bool oneSelection( selected == 1 );
809    ui.action_OpenFolder->setEnabled( oneSelection && mySession.isLocal( ) );
810    ui.action_CopyMagnetToClipboard->setEnabled( oneSelection );
811
812    ui.action_SelectAll->setEnabled( selected < rowCount );
813    ui.action_StartAll->setEnabled( paused > 0 );
814    ui.action_PauseAll->setEnabled( paused < rowCount );
815    ui.action_Start->setEnabled( selectedAndPaused > 0 );
816    ui.action_Pause->setEnabled( selectedAndPaused < selected );
817    ui.action_Announce->setEnabled( selected > 0 && ( canAnnounce == selected ) );
818
819    if( myDetailsDialog )
820        myDetailsDialog->setIds( getSelectedTorrents( ) );
821}
822
823/**
824***
825**/
826
827void
828TrMainWindow :: clearSelection( )
829{
830    ui.action_DeselectAll->trigger( );
831}
832
833QSet<int>
834TrMainWindow :: getSelectedTorrents( ) const
835{
836    QSet<int> ids;
837
838    foreach( QModelIndex index, ui.listView->selectionModel( )->selectedRows( ) )
839    {
840        const Torrent * tor( index.model()->data( index, TorrentModel::TorrentRole ).value<const Torrent*>( ) );
841        ids.insert( tor->id( ) );
842    }
843
844    return ids;
845}
846
847void
848TrMainWindow :: startSelected( )
849{
850    mySession.startTorrents( getSelectedTorrents( ) );
851}
852void
853TrMainWindow :: pauseSelected( )
854{
855    mySession.pauseTorrents( getSelectedTorrents( ) );
856}
857void
858TrMainWindow :: startAll( )
859{
860    mySession.startTorrents( );
861}
862void
863TrMainWindow :: pauseAll( )
864{
865    mySession.pauseTorrents( );
866}
867void
868TrMainWindow :: removeSelected( )
869{
870    removeTorrents( false );
871}
872void
873TrMainWindow :: deleteSelected( )
874{
875    removeTorrents( true );
876}
877void
878TrMainWindow :: verifySelected( )
879{
880    mySession.verifyTorrents( getSelectedTorrents( ) );
881}
882void
883TrMainWindow :: reannounceSelected( )
884{
885    mySession.reannounceTorrents( getSelectedTorrents( ) );
886}
887
888/**
889***
890**/
891
892void TrMainWindow :: setShowMode     ( int i ) { myPrefs.set( Prefs::FILTER_MODE, FilterMode( i ) ); }
893void TrMainWindow :: showAll         ( ) { setShowMode( FilterMode :: SHOW_ALL ); }
894void TrMainWindow :: showActive      ( ) { setShowMode( FilterMode :: SHOW_ACTIVE ); }
895void TrMainWindow :: showDownloading ( ) { setShowMode( FilterMode :: SHOW_DOWNLOADING ); }
896void TrMainWindow :: showSeeding     ( ) { setShowMode( FilterMode :: SHOW_SEEDING ); }
897void TrMainWindow :: showPaused      ( ) { setShowMode( FilterMode :: SHOW_PAUSED ); }
898
899void TrMainWindow :: filterByName    ( ) { myFilterModel.setTextMode( TorrentFilter :: FILTER_BY_NAME ); }
900void TrMainWindow :: filterByTracker ( ) { myFilterModel.setTextMode( TorrentFilter :: FILTER_BY_TRACKER ); }
901void TrMainWindow :: filterByFiles   ( ) { myFilterModel.setTextMode( TorrentFilter :: FILTER_BY_FILES ); }
902
903void TrMainWindow :: showTotalRatio      ( ) { myPrefs.set( Prefs::STATUSBAR_STATS, "total-ratio"); }
904void TrMainWindow :: showTotalTransfer   ( ) { myPrefs.set( Prefs::STATUSBAR_STATS, "total-transfer"); }
905void TrMainWindow :: showSessionRatio    ( ) { myPrefs.set( Prefs::STATUSBAR_STATS, "session-ratio"); }
906void TrMainWindow :: showSessionTransfer ( ) { myPrefs.set( Prefs::STATUSBAR_STATS, "session-transfer"); }
907
908/**
909***
910**/
911
912void
913TrMainWindow :: setCompactView( bool visible )
914{
915    myPrefs.set( Prefs :: COMPACT_VIEW, visible );
916}
917void
918TrMainWindow :: setTrayIconVisible( bool visible )
919{
920    myPrefs.set( Prefs :: SHOW_TRAY_ICON, visible );
921}
922void
923TrMainWindow :: toggleSpeedMode( )
924{
925    myPrefs.toggleBool( Prefs :: ALT_SPEED_LIMIT_ENABLED );
926}
927void
928TrMainWindow :: setToolbarVisible( bool visible )
929{
930    myPrefs.set( Prefs::TOOLBAR, visible );
931}
932void
933TrMainWindow :: setFilterbarVisible( bool visible )
934{
935    myPrefs.set( Prefs::FILTERBAR, visible );
936}
937void
938TrMainWindow :: setStatusbarVisible( bool visible )
939{
940    myPrefs.set( Prefs::STATUSBAR, visible );
941}
942
943/**
944***
945**/
946
947void
948TrMainWindow :: toggleWindows( bool doShow )
949{
950    if( !doShow )
951    {
952        hide( );
953    }
954    else
955    {
956        if ( !isVisible( ) ) show( );
957        if ( isMinimized( ) ) showNormal( );
958        activateWindow( );
959        raise( );
960    }
961}
962
963void
964TrMainWindow :: trayActivated( QSystemTrayIcon::ActivationReason reason )
965{
966    if( reason == QSystemTrayIcon::Trigger )
967    {
968        if( isMinimized ( ) )
969            toggleWindows( true );
970        else
971            ui.action_ShowMainWindow->toggle( );
972    }
973}
974
975
976void
977TrMainWindow :: refreshPref( int key )
978{
979    bool b;
980    int i;
981    QString str;
982
983    switch( key )
984    {
985        case Prefs::STATUSBAR_STATS:
986            str = myPrefs.getString( key );
987            ui.action_TotalRatio->setChecked     ( str == "total-ratio" );
988            ui.action_TotalTransfer->setChecked  ( str == "total-transfer" );
989            ui.action_SessionRatio->setChecked   ( str == "session-ratio" );
990            ui.action_SessionTransfer->setChecked( str == "session-transfer" );
991            refreshStatusBar( );
992            break;
993
994        case Prefs::SORT_REVERSED:
995            ui.action_ReverseSortOrder->setChecked( myPrefs.getBool( key ) );
996            break;
997
998        case Prefs::SORT_MODE:
999            i = myPrefs.get<SortMode>(key).mode( );
1000            ui.action_SortByActivity->setChecked ( i == SortMode::SORT_BY_ACTIVITY );
1001            ui.action_SortByAge->setChecked      ( i == SortMode::SORT_BY_AGE );
1002            ui.action_SortByETA->setChecked      ( i == SortMode::SORT_BY_ETA );
1003            ui.action_SortByName->setChecked     ( i == SortMode::SORT_BY_NAME );
1004            ui.action_SortByProgress->setChecked ( i == SortMode::SORT_BY_PROGRESS );
1005            ui.action_SortByRatio->setChecked    ( i == SortMode::SORT_BY_RATIO );
1006            ui.action_SortBySize->setChecked     ( i == SortMode::SORT_BY_SIZE );
1007            ui.action_SortByState->setChecked    ( i == SortMode::SORT_BY_STATE );
1008            ui.action_SortByTracker->setChecked  ( i == SortMode::SORT_BY_TRACKER );
1009            break;
1010
1011        case Prefs::DSPEED_ENABLED:
1012            (myPrefs.get<bool>(key) ? myDlimitOnAction : myDlimitOffAction)->setChecked( true );
1013            break;
1014
1015        case Prefs::DSPEED:
1016            myDlimitOnAction->setText( tr( "Limited at %1" ).arg( Formatter::speedToString( Speed::fromBps( myPrefs.get<int>(key) ) ) ) );
1017            break;
1018
1019        case Prefs::USPEED_ENABLED:
1020            (myPrefs.get<bool>(key) ? myUlimitOnAction : myUlimitOffAction)->setChecked( true );
1021            break;
1022
1023        case Prefs::USPEED:
1024            myUlimitOnAction->setText( tr( "Limited at %1" ).arg( Formatter::speedToString( Speed::fromBps( myPrefs.get<int>(key) ) ) ) );
1025            break;
1026
1027        case Prefs::RATIO_ENABLED:
1028            (myPrefs.get<bool>(key) ? myRatioOnAction : myRatioOffAction)->setChecked( true );
1029            break;
1030
1031        case Prefs::RATIO:
1032            myRatioOnAction->setText( tr( "Stop at Ratio (%1)" ).arg( Formatter::ratioToString( myPrefs.get<double>(key) ) ) );
1033            break;
1034
1035        case Prefs::FILTER_MODE:
1036            i = myPrefs.get<FilterMode>(key).mode( );
1037            for( int j=0; j<FilterMode::NUM_MODES; ++j )
1038                myFilterButtons[j]->setChecked( i==j );
1039            break;
1040
1041        case Prefs::FILTERBAR:
1042            b = myPrefs.getBool( key );
1043            myFilterBar->setVisible( b );
1044            ui.action_Filterbar->setChecked( b );
1045            break;
1046
1047        case Prefs::STATUSBAR:
1048            b = myPrefs.getBool( key );
1049            myStatusBar->setVisible( b );
1050            ui.action_Statusbar->setChecked( b );
1051            break;
1052
1053        case Prefs::TOOLBAR:
1054            b = myPrefs.getBool( key );
1055            ui.toolBar->setVisible( b );
1056            ui.action_Toolbar->setChecked( b );
1057            break;
1058
1059        case Prefs::SHOW_TRAY_ICON:
1060            b = myPrefs.getBool( key );
1061            ui.action_TrayIcon->setChecked( b );
1062            myTrayIcon.setVisible( b );
1063            break;
1064
1065        case Prefs::COMPACT_VIEW:
1066            b = myPrefs.getBool( key );
1067            ui.action_CompactView->setChecked( b );
1068            ui.listView->setItemDelegate( b ? myTorrentDelegateMin : myTorrentDelegate );
1069            ui.listView->reset( ); // force the rows to resize
1070            break;
1071
1072        case Prefs::MAIN_WINDOW_X:
1073        case Prefs::MAIN_WINDOW_Y:
1074        case Prefs::MAIN_WINDOW_WIDTH:
1075        case Prefs::MAIN_WINDOW_HEIGHT:
1076            setGeometry( myPrefs.getInt( Prefs::MAIN_WINDOW_X ),
1077                         myPrefs.getInt( Prefs::MAIN_WINDOW_Y ),
1078                         myPrefs.getInt( Prefs::MAIN_WINDOW_WIDTH ),
1079                         myPrefs.getInt( Prefs::MAIN_WINDOW_HEIGHT ) );
1080            break;
1081
1082        case Prefs :: ALT_SPEED_LIMIT_ENABLED:
1083        case Prefs :: ALT_SPEED_LIMIT_UP:
1084        case Prefs :: ALT_SPEED_LIMIT_DOWN: {
1085            b = myPrefs.getBool( Prefs :: ALT_SPEED_LIMIT_ENABLED );
1086            myAltSpeedButton->setChecked( b );
1087            myAltSpeedButton->setIcon( b ? mySpeedModeOnIcon : mySpeedModeOffIcon );
1088            const QString fmt = b ? tr( "Click to disable Temporary Speed Limits\n(%1 down, %2 up)" )
1089                                  : tr( "Click to enable Temporary Speed Limits\n(%1 down, %2 up)" );
1090            const Speed d = Speed::fromBps( myPrefs.getInt( Prefs::ALT_SPEED_LIMIT_DOWN ) );
1091            const Speed u = Speed::fromBps( myPrefs.getInt( Prefs::ALT_SPEED_LIMIT_UP ) );
1092            myAltSpeedButton->setToolTip( fmt.arg( Formatter::speedToString( d ) )
1093                                             .arg( Formatter::speedToString( u ) ) );
1094            break;
1095        }
1096
1097        default:
1098            break;
1099    }
1100}
1101
1102/***
1103****
1104***/
1105
1106void
1107TrMainWindow :: newTorrent( )
1108{
1109    MakeDialog * dialog = new MakeDialog( mySession, this );
1110    dialog->show( );
1111}
1112
1113void
1114TrMainWindow :: openTorrent( )
1115{
1116    QFileDialog * myFileDialog;
1117    myFileDialog = new QFileDialog( this,
1118                                    tr( "Add Torrent" ),
1119                                    myPrefs.getString( Prefs::OPEN_DIALOG_FOLDER ),
1120                                    tr( "Torrent Files (*.torrent);;All Files (*.*)" ) );
1121    myFileDialog->setFileMode( QFileDialog::ExistingFiles );
1122
1123    QCheckBox * button = new QCheckBox( tr( "Show &options dialog" ) );
1124    button->setChecked( myPrefs.getBool( Prefs::OPTIONS_PROMPT ) );
1125    QGridLayout * layout = dynamic_cast<QGridLayout*>(myFileDialog->layout());
1126    layout->addWidget( button, layout->rowCount( ), 0, 1, -1, Qt::AlignLeft );
1127    myFileDialogOptionsCheck = button;
1128
1129    connect( myFileDialog, SIGNAL(filesSelected(const QStringList&)),
1130             this, SLOT(addTorrents(const QStringList&)));
1131
1132    myFileDialog->show( );
1133}
1134
1135void
1136TrMainWindow :: openURL( )
1137{
1138    QString tmp;
1139    openURL( tmp );
1140}
1141
1142void
1143TrMainWindow :: openURL( QString url )
1144{
1145    bool ok;
1146    const QString key = QInputDialog::getText( this,
1147                                               tr( "Add URL or Magnet Link" ),
1148                                               tr( "Add URL or Magnet Link" ),
1149                                               QLineEdit::Normal,
1150                                               url,
1151                                               &ok );
1152    if( ok && !key.isEmpty( ) )
1153        mySession.addTorrent( key );
1154}
1155
1156void
1157TrMainWindow :: addTorrents( const QStringList& filenames )
1158{
1159    foreach( const QString& filename, filenames )
1160        addTorrent( filename );
1161}
1162
1163void
1164TrMainWindow :: addTorrent( const QString& filename )
1165{
1166    if( !myFileDialogOptionsCheck->isChecked( ) ) {
1167        mySession.addTorrent( filename );
1168        QApplication :: alert ( this );
1169    } else {
1170        Options * o = new Options( mySession, myPrefs, filename, this );
1171        o->show( );
1172        QApplication :: alert( o );
1173    }
1174}
1175
1176void
1177TrMainWindow :: removeTorrents( const bool deleteFiles )
1178{
1179    QSet<int> ids;
1180    QMessageBox msgBox( this );
1181    QString primary_text, secondary_text;
1182    int incomplete = 0;
1183    int connected  = 0;
1184    int count;
1185
1186    foreach( QModelIndex index, ui.listView->selectionModel( )->selectedRows( ) )
1187    {
1188        const Torrent * tor( index.model()->data( index, TorrentModel::TorrentRole ).value<const Torrent*>( ) );
1189        ids.insert( tor->id( ) );
1190        if( tor->connectedPeers( ) )
1191            ++connected;
1192        if( !tor->isDone( ) )
1193            ++incomplete;
1194    }
1195
1196    if( ids.isEmpty() )
1197        return;
1198    count = ids.size();
1199
1200    if( !deleteFiles )
1201    {
1202        primary_text = ( count == 1 )
1203            ? tr( "Remove torrent?" )
1204            : tr( "Remove %1 torrents?" ).arg( count );
1205    }
1206    else
1207    {
1208        primary_text = ( count == 1 )
1209            ? tr( "Delete this torrent's downloaded files?" )
1210            : tr( "Delete these %1 torrents' downloaded files?" ).arg( count );
1211    }
1212
1213    if( !incomplete && !connected )
1214    {
1215        secondary_text = ( count == 1 )
1216            ? tr( "Once removed, continuing the transfer will require the torrent file or magnet link." )
1217            : tr( "Once removed, continuing the transfers will require the torrent files or magnet links." );
1218    }
1219    else if( count == incomplete )
1220    {
1221        secondary_text = ( count == 1 )
1222            ? tr( "This torrent has not finished downloading." )
1223            : tr( "These torrents have not finished downloading." );
1224    }
1225    else if( count == connected )
1226    {
1227        secondary_text = ( count == 1 )
1228            ? tr( "This torrent is connected to peers." )
1229            : tr( "These torrents are connected to peers." );
1230    }
1231    else
1232    {
1233        if( connected )
1234        {
1235            secondary_text = ( connected == 1 )
1236                ? tr( "One of these torrents is connected to peers." )
1237                : tr( "Some of these torrents are connected to peers." );
1238        }
1239
1240        if( connected && incomplete )
1241        {
1242            secondary_text += "\n";
1243        }
1244
1245        if( incomplete )
1246        {
1247            secondary_text += ( incomplete == 1 )
1248                ? tr( "One of these torrents has not finished downloading." )
1249                : tr( "Some of these torrents have not finished downloading." );
1250        }
1251    }
1252
1253    msgBox.setWindowTitle( QString(" ") );
1254    msgBox.setText( QString( "<big><b>%1</big></b>" ).arg( primary_text ) );
1255    msgBox.setInformativeText( secondary_text );
1256    msgBox.setStandardButtons( QMessageBox::Ok | QMessageBox::Cancel );
1257    msgBox.setDefaultButton( QMessageBox::Cancel );
1258    msgBox.setIcon( QMessageBox::Question );
1259    /* hack needed to keep the dialog from being too narrow */
1260    QGridLayout* layout = (QGridLayout*)msgBox.layout();
1261    QSpacerItem* spacer = new QSpacerItem( 450, 0, QSizePolicy::Minimum, QSizePolicy::Expanding );
1262    layout->addItem( spacer, layout->rowCount(), 0, 1, layout->columnCount() );
1263
1264    if( msgBox.exec() == QMessageBox::Ok )
1265    {
1266        ui.listView->selectionModel()->clear();
1267        mySession.removeTorrents( ids, deleteFiles );
1268    }
1269}
1270
1271/***
1272****
1273***/
1274
1275void
1276TrMainWindow :: updateNetworkIcon( )
1277{
1278    const time_t now = time( NULL );
1279    const int period = 3;
1280    const bool isSending = now - myLastSendTime <= period;
1281    const bool isReading = now - myLastReadTime <= period;
1282    const char * key;
1283
1284    if( isSending && isReading )
1285        key = "network-transmit-receive";
1286    else if( isSending )
1287        key = "network-transmit";
1288    else if( isReading )
1289        key = "network-receive";
1290    else
1291        key = "network-idle";
1292
1293    QIcon icon = getStockIcon( key, QStyle::SP_DriveNetIcon );
1294    QPixmap pixmap = icon.pixmap ( 16, 16 );
1295    myNetworkLabel->setPixmap( pixmap );
1296    myNetworkLabel->setToolTip( isSending || isReading
1297        ? tr( "Transmission server is responding" )
1298        : tr( "Last response from server was %1 ago" ).arg( Formatter::timeToString( now-std::max(myLastReadTime,myLastSendTime))));
1299}
1300
1301void
1302TrMainWindow :: onNetworkTimer( )
1303{
1304    updateNetworkIcon( );
1305}
1306
1307void
1308TrMainWindow :: dataReadProgress( )
1309{
1310    myLastReadTime = time( NULL );
1311    updateNetworkIcon( );
1312}
1313
1314void
1315TrMainWindow :: dataSendProgress( )
1316{
1317    myLastSendTime = time( NULL );
1318    updateNetworkIcon( );
1319}
1320
1321void
1322TrMainWindow :: wrongAuthentication( )
1323{
1324    mySession.stop( );
1325    mySessionDialog->show( );
1326}
Note: See TracBrowser for help on using the repository browser.