2
* This file Copyright (C) 2009 Mnemosyne LLC
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.
17
#include <QCloseEvent>
18
#include <QDesktopServices>
19
#include <QFileDialog>
20
#include <QHBoxLayout>
21
#include <QInputDialog>
23
#include <QSignalMapper>
26
#include <QSystemTrayIcon>
29
#include <libtransmission/version.h>
36
#include "make-dialog.h"
39
#include "prefs-dialog.h"
42
#include "session-dialog.h"
44
#include "stats-dialog.h"
45
#include "torrent-delegate.h"
46
#include "torrent-delegate-min.h"
47
#include "torrent-filter.h"
48
#include "torrent-model.h"
49
#include "triconpushbutton.h"
50
#include "ui_mainwin.h"
52
#include "qticonloader.h"
54
#define PREFS_KEY "prefs-key";
57
TrMainWindow :: getStockIcon( const QString& freedesktop_name, int fallback )
62
fallbackIcon = style()->standardIcon( QStyle::StandardPixmap( fallback ), 0, this );
64
return QtIconLoader::icon( freedesktop_name, fallbackIcon );
69
QSize calculateTextButtonSizeHint( QPushButton * button )
71
QStyleOptionButton opt;
72
opt.initFrom( button );
73
QString s( button->text( ) );
75
s = QString::fromLatin1( "XXXX" );
76
QFontMetrics fm = button->fontMetrics( );
77
QSize sz = fm.size( Qt::TextShowMnemonic, s );
78
return button->style()->sizeFromContents( QStyle::CT_PushButton, &opt, sz, button ).expandedTo( QApplication::globalStrut( ) );
83
TrMainWindow :: TrMainWindow( Session& session, Prefs& prefs, TorrentModel& model, bool minimized ):
84
myLastFullUpdateTime( 0 ),
85
mySessionDialog( new SessionDialog( session, prefs, this ) ),
86
myPrefsDialog( new PrefsDialog( session, prefs, this ) ),
87
myAboutDialog( new AboutDialog( this ) ),
88
myStatsDialog( new StatsDialog( session, this ) ),
91
myFilterModel( prefs ),
92
myTorrentDelegate( new TorrentDelegate( this ) ),
93
myTorrentDelegateMin( new TorrentDelegateMin( this ) ),
97
mySpeedModeOffIcon( ":/icons/alt-limit-off.png" ),
98
mySpeedModeOnIcon( ":/icons/alt-limit-on.png" ),
101
myNetworkTimer( this )
103
QAction * sep = new QAction( this );
104
sep->setSeparator( true );
108
QStyle * style = this->style();
110
int i = style->pixelMetric( QStyle::PM_SmallIconSize, 0, this );
111
const QSize smallIconSize( i, i );
114
ui.action_AddFile->setIcon( getStockIcon( "list-add", QStyle::SP_DialogOpenButton ) );
115
ui.action_New->setIcon( getStockIcon( "document-new", QStyle::SP_DesktopIcon ) );
116
ui.action_Properties->setIcon( getStockIcon( "document-properties", QStyle::SP_DesktopIcon ) );
117
ui.action_OpenFolder->setIcon( getStockIcon( "folder-open", QStyle::SP_DirOpenIcon ) );
118
ui.action_Start->setIcon( getStockIcon( "media-playback-start", QStyle::SP_MediaPlay ) );
119
ui.action_Announce->setIcon( getStockIcon( "network-transmit-receive" ) );
120
ui.action_Pause->setIcon( getStockIcon( "media-playback-pause", QStyle::SP_MediaPause ) );
121
ui.action_Remove->setIcon( getStockIcon( "list-remove", QStyle::SP_TrashIcon ) );
122
ui.action_Delete->setIcon( getStockIcon( "edit-delete", QStyle::SP_TrashIcon ) );
123
ui.action_StartAll->setIcon( getStockIcon( "media-playback-start", QStyle::SP_MediaPlay ) );
124
ui.action_PauseAll->setIcon( getStockIcon( "media-playback-pause", QStyle::SP_MediaPause ) );
125
ui.action_Quit->setIcon( getStockIcon( "application-exit" ) );
126
ui.action_SelectAll->setIcon( getStockIcon( "edit-select-all" ) );
127
ui.action_ReverseSortOrder->setIcon( getStockIcon( "view-sort-ascending", QStyle::SP_ArrowDown ) );
128
ui.action_Preferences->setIcon( getStockIcon( "preferences-system" ) );
129
ui.action_Contents->setIcon( getStockIcon( "help-contents", QStyle::SP_DialogHelpButton ) );
130
ui.action_About->setIcon( getStockIcon( "help-about" ) );
133
connect( ui.action_Toolbar, SIGNAL(toggled(bool)), this, SLOT(setToolbarVisible(bool)));
134
connect( ui.action_TrayIcon, SIGNAL(toggled(bool)), this, SLOT(setTrayIconVisible(bool)));
135
connect( ui.action_Filterbar, SIGNAL(toggled(bool)), this, SLOT(setFilterbarVisible(bool)));
136
connect( ui.action_Statusbar, SIGNAL(toggled(bool)), this, SLOT(setStatusbarVisible(bool)));
137
connect( ui.action_MinimalView, SIGNAL(toggled(bool)), this, SLOT(setMinimalView(bool)));
138
connect( ui.action_SortByActivity, SIGNAL(toggled(bool)), this, SLOT(onSortByActivityToggled(bool)));
139
connect( ui.action_SortByAge, SIGNAL(toggled(bool)), this, SLOT(onSortByAgeToggled(bool)));
140
connect( ui.action_SortByETA, SIGNAL(toggled(bool)), this, SLOT(onSortByETAToggled(bool)));
141
connect( ui.action_SortByName, SIGNAL(toggled(bool)), this, SLOT(onSortByNameToggled(bool)));
142
connect( ui.action_SortByProgress, SIGNAL(toggled(bool)), this, SLOT(onSortByProgressToggled(bool)));
143
connect( ui.action_SortByRatio, SIGNAL(toggled(bool)), this, SLOT(onSortByRatioToggled(bool)));
144
connect( ui.action_SortBySize, SIGNAL(toggled(bool)), this, SLOT(onSortBySizeToggled(bool)));
145
connect( ui.action_SortByState, SIGNAL(toggled(bool)), this, SLOT(onSortByStateToggled(bool)));
146
connect( ui.action_SortByTracker, SIGNAL(toggled(bool)), this, SLOT(onSortByTrackerToggled(bool)));
147
connect( ui.action_ReverseSortOrder, SIGNAL(toggled(bool)), this, SLOT(setSortAscendingPref(bool)));
148
connect( ui.action_Start, SIGNAL(triggered()), this, SLOT(startSelected()));
149
connect( ui.action_Pause, SIGNAL(triggered()), this, SLOT(pauseSelected()));
150
connect( ui.action_Remove, SIGNAL(triggered()), this, SLOT(removeSelected()));
151
connect( ui.action_Delete, SIGNAL(triggered()), this, SLOT(deleteSelected()));
152
connect( ui.action_Verify, SIGNAL(triggered()), this, SLOT(verifySelected()) );
153
connect( ui.action_Announce, SIGNAL(triggered()), this, SLOT(reannounceSelected()) );
154
connect( ui.action_StartAll, SIGNAL(triggered()), this, SLOT(startAll()));
155
connect( ui.action_PauseAll, SIGNAL(triggered()), this, SLOT(pauseAll()));
156
connect( ui.action_AddFile, SIGNAL(triggered()), this, SLOT(openTorrent()));
157
connect( ui.action_AddURL, SIGNAL(triggered()), this, SLOT(openURL()));
158
connect( ui.action_New, SIGNAL(triggered()), this, SLOT(newTorrent()));
159
connect( ui.action_Preferences, SIGNAL(triggered()), myPrefsDialog, SLOT(show()));
160
connect( ui.action_Statistics, SIGNAL(triggered()), myStatsDialog, SLOT(show()));
161
connect( ui.action_About, SIGNAL(triggered()), myAboutDialog, SLOT(show()));
162
connect( ui.action_Contents, SIGNAL(triggered()), this, SLOT(openHelp()));
163
connect( ui.action_OpenFolder, SIGNAL(triggered()), this, SLOT(openFolder()));
164
connect( ui.action_CopyMagnetToClipboard, SIGNAL(triggered()), this, SLOT(copyMagnetLinkToClipboard()));
165
connect( ui.action_SetLocation, SIGNAL(triggered()), this, SLOT(setLocation()));
166
connect( ui.action_Properties, SIGNAL(triggered()), this, SLOT(openProperties()));
167
connect( ui.action_SessionDialog, SIGNAL(triggered()), mySessionDialog, SLOT(show()));
168
connect( ui.listView, SIGNAL(activated(const QModelIndex&)), ui.action_Properties, SLOT(trigger()));
170
QAction * sep2 = new QAction( this );
171
sep2->setSeparator( true );
172
QAction * sep3 = new QAction( this );
173
sep3->setSeparator( true );
176
QList<QAction*> actions;
177
actions << ui.action_Properties
178
<< ui.action_OpenFolder
181
<< ui.action_Announce
183
<< ui.action_CopyMagnetToClipboard
186
<< ui.action_SetLocation
190
addActions( actions );
191
setContextMenuPolicy( Qt::ActionsContextMenu );
194
connect( ui.action_SelectAll, SIGNAL(triggered()), ui.listView, SLOT(selectAll()));
195
connect( ui.action_DeselectAll, SIGNAL(triggered()), ui.listView, SLOT(clearSelection()));
197
connect( &myFilterModel, SIGNAL(rowsInserted(const QModelIndex&,int,int)), this, SLOT(refreshVisibleCount()));
198
connect( &myFilterModel, SIGNAL(rowsRemoved(const QModelIndex&,int,int)), this, SLOT(refreshVisibleCount()));
199
connect( &myFilterModel, SIGNAL(rowsInserted(const QModelIndex&,int,int)), this, SLOT(refreshActionSensitivity()));
200
connect( &myFilterModel, SIGNAL(rowsRemoved(const QModelIndex&,int,int)), this, SLOT(refreshActionSensitivity()));
202
connect( ui.action_Quit, SIGNAL(triggered()), QCoreApplication::instance(), SLOT(quit()) );
205
myFilterModel.setSourceModel( &myModel );
206
connect( &myModel, SIGNAL(modelReset()), this, SLOT(onModelReset()));
207
connect( &myModel, SIGNAL(rowsRemoved(const QModelIndex&,int,int)), this, SLOT(onModelReset()));
208
connect( &myModel, SIGNAL(rowsInserted(const QModelIndex&,int,int)), this, SLOT(onModelReset()));
209
ui.listView->setModel( &myFilterModel );
210
connect( ui.listView->selectionModel(), SIGNAL(selectionChanged(const QItemSelection&,const QItemSelection&)), this, SLOT(refreshActionSensitivity()));
212
QActionGroup * actionGroup = new QActionGroup( this );
213
actionGroup->addAction( ui.action_SortByActivity );
214
actionGroup->addAction( ui.action_SortByAge );
215
actionGroup->addAction( ui.action_SortByETA );
216
actionGroup->addAction( ui.action_SortByName );
217
actionGroup->addAction( ui.action_SortByProgress );
218
actionGroup->addAction( ui.action_SortByRatio );
219
actionGroup->addAction( ui.action_SortBySize );
220
actionGroup->addAction( ui.action_SortByState );
221
actionGroup->addAction( ui.action_SortByTracker );
223
QMenu * menu = new QMenu( );
224
menu->addAction( ui.action_AddFile );
225
menu->addAction( ui.action_AddURL );
226
menu->addSeparator( );
227
menu->addAction( ui.action_ShowMainWindow );
228
menu->addAction( ui.action_ShowMessageLog );
229
menu->addAction( ui.action_About );
230
menu->addSeparator( );
231
menu->addAction( ui.action_StartAll );
232
menu->addAction( ui.action_PauseAll );
233
menu->addSeparator( );
234
menu->addAction( ui.action_Quit );
235
myTrayIcon.setContextMenu( menu );
236
myTrayIcon.setIcon( QApplication::windowIcon( ) );
238
connect( &myPrefs, SIGNAL(changed(int)), this, SLOT(refreshPref(int)) );
239
connect( ui.action_ShowMainWindow, SIGNAL(toggled(bool)), this, SLOT(toggleWindows(bool)));
240
connect( &myTrayIcon, SIGNAL(activated(QSystemTrayIcon::ActivationReason)),
241
this, SLOT(trayActivated(QSystemTrayIcon::ActivationReason)));
243
ui.action_ShowMainWindow->setChecked( !minimized );
244
ui.action_TrayIcon->setChecked( minimized || prefs.getBool( Prefs::SHOW_TRAY_ICON ) );
246
ui.verticalLayout->addWidget( createStatusBar( ) );
247
ui.verticalLayout->insertWidget( 0, createFilterBar( ) );
250
initKeys << Prefs :: MAIN_WINDOW_X
251
<< Prefs :: SHOW_TRAY_ICON
252
<< Prefs :: SORT_REVERSED
253
<< Prefs :: SORT_MODE
254
<< Prefs :: FILTER_MODE
255
<< Prefs :: FILTERBAR
256
<< Prefs :: STATUSBAR
257
<< Prefs :: STATUSBAR_STATS
259
<< Prefs :: ALT_SPEED_LIMIT_ENABLED
260
<< Prefs :: MINIMAL_VIEW
262
<< Prefs :: DSPEED_ENABLED
264
<< Prefs :: USPEED_ENABLED
266
<< Prefs :: RATIO_ENABLED;
267
foreach( int key, initKeys )
270
connect( &mySession, SIGNAL(sourceChanged()), this, SLOT(onSessionSourceChanged()) );
271
connect( &mySession, SIGNAL(statsUpdated()), this, SLOT(refreshStatusBar()) );
272
connect( &mySession, SIGNAL(dataReadProgress()), this, SLOT(dataReadProgress()) );
273
connect( &mySession, SIGNAL(dataSendProgress()), this, SLOT(dataSendProgress()) );
274
connect( &mySession, SIGNAL(httpAuthenticationRequired()), this, SLOT(wrongAuthentication()) );
276
if( mySession.isServer( ) )
277
myNetworkLabel->hide( );
279
connect( &myNetworkTimer, SIGNAL(timeout()), this, SLOT(onNetworkTimer()));
280
myNetworkTimer.start( 1000 );
283
refreshActionSensitivity( );
286
refreshVisibleCount( );
289
TrMainWindow :: ~TrMainWindow( )
298
TrMainWindow :: closeEvent( QCloseEvent * event )
300
// if they're using a tray icon, close to the tray
301
// instead of exiting
302
if( !myPrefs.getBool( Prefs :: SHOW_TRAY_ICON ) )
305
toggleWindows( false );
315
TrMainWindow :: onSessionSourceChanged( )
321
TrMainWindow :: onModelReset( )
324
refreshVisibleCount( );
325
refreshActionSensitivity( );
333
#define PREF_VARIANTS_KEY "pref-variants-list"
336
TrMainWindow :: onSetPrefs( )
338
const QVariantList p = sender()->property( PREF_VARIANTS_KEY ).toList( );
339
assert( ( p.size( ) % 2 ) == 0 );
340
for( int i=0, n=p.size(); i<n; i+=2 )
341
myPrefs.set( p[i].toInt(), p[i+1] );
345
TrMainWindow :: onSetPrefs( bool isChecked )
351
#define SHOW_KEY "show-mode"
354
TrMainWindow :: onShowModeClicked( )
356
setShowMode( sender()->property(SHOW_KEY).toInt() );
360
TrMainWindow :: createFilterBar( )
368
const int smallSize = style( )->pixelMetric( QStyle::PM_SmallIconSize, 0, this );
369
const QSize smallIconSize( smallSize, smallSize );
371
QWidget * top = myFilterBar = new QWidget;
372
h = new QHBoxLayout( top );
373
h->setContentsMargins( HIG::PAD_SMALL, HIG::PAD_SMALL, HIG::PAD_SMALL, HIG::PAD_SMALL );
374
h->setSpacing( HIG::PAD_SMALL );
376
top->setStyleSheet( "QPushButton{ "
377
" border-radius: 10px; "
379
" border: 1px none; "
381
"QPushButton:pressed, QPushButton:checked{ "
382
" border-width: 1px; "
383
" border-style: solid; "
384
" border-color: #5f5f5f #979797 #979797; "
385
" background-color: #979797; "
390
QList<QString> titles;
391
titles << tr( "A&ll" ) << tr( "&Active" ) << tr( "&Downloading" ) << tr( "&Seeding" ) << tr( "&Paused" );
392
for( i=0; i<titles.size(); ++i ) {
393
p = myFilterButtons[i] = new QPushButton( titles[i] );
394
p->setProperty( SHOW_KEY, i );
396
p->setCheckable( true );
397
p->setMaximumSize( calculateTextButtonSizeHint( p ) );
398
connect( p, SIGNAL(clicked()), this, SLOT(onShowModeClicked()));
404
a = new QActionGroup( this );
405
a->addAction( ui.action_FilterByName );
406
a->addAction( ui.action_FilterByFiles );
407
a->addAction( ui.action_FilterByTracker );
409
m->addAction( ui.action_FilterByName );
410
m->addAction( ui.action_FilterByFiles );
411
m->addAction( ui.action_FilterByTracker );
412
connect( ui.action_FilterByName, SIGNAL(triggered()), this, SLOT(filterByName()));
413
connect( ui.action_FilterByFiles, SIGNAL(triggered()), this, SLOT(filterByFiles()));
414
connect( ui.action_FilterByTracker, SIGNAL(triggered()), this, SLOT(filterByTracker()));
415
ui.action_FilterByName->setChecked( true );
416
p = myFilterTextButton = new TrIconPushButton;
417
p->setIcon( getStockIcon( "edit-find", QStyle::SP_ArrowForward ) );
422
e = myFilterTextLineEdit = new QLineEdit;
423
connect( e, SIGNAL(textChanged(QString)), &myFilterModel, SLOT(setText(QString)));
426
p = myFilterTextButton = new TrIconPushButton;
427
p->setIcon( getStockIcon( "edit-clear", QStyle::SP_DialogCloseButton ) );
429
connect( p, SIGNAL(clicked()), myFilterTextLineEdit, SLOT(clear()));
436
TrMainWindow :: createStatusBar( )
444
const int i = style( )->pixelMetric( QStyle::PM_SmallIconSize, 0, this );
445
const QSize smallIconSize( i, i );
447
QWidget * top = myStatusBar = new QWidget;
448
h = new QHBoxLayout( top );
449
h->setContentsMargins( HIG::PAD_SMALL, HIG::PAD_SMALL, HIG::PAD_SMALL, HIG::PAD_SMALL );
450
h->setSpacing( HIG::PAD_SMALL );
452
p = myOptionsButton = new TrIconPushButton( this );
453
p->setIcon( QIcon( ":/icons/options.png" ) );
455
p->setMenu( createOptionsMenu( ) );
458
p = myAltSpeedButton = new TrIconPushButton( this );
459
p->setIcon( myPrefs.get<bool>(Prefs::ALT_SPEED_LIMIT_ENABLED) ? mySpeedModeOnIcon : mySpeedModeOffIcon );
462
connect( p, SIGNAL(clicked()), this, SLOT(toggleSpeedMode()));
464
l = myNetworkLabel = new QLabel;
469
l = myVisibleCountLabel = new QLabel( this );
474
a = new QActionGroup( this );
475
a->addAction( ui.action_TotalRatio );
476
a->addAction( ui.action_TotalTransfer );
477
a->addAction( ui.action_SessionRatio );
478
a->addAction( ui.action_SessionTransfer );
480
m->addAction( ui.action_TotalRatio );
481
m->addAction( ui.action_TotalTransfer );
482
m->addAction( ui.action_SessionRatio );
483
m->addAction( ui.action_SessionTransfer );
484
connect( ui.action_TotalRatio, SIGNAL(triggered()), this, SLOT(showTotalRatio()));
485
connect( ui.action_TotalTransfer, SIGNAL(triggered()), this, SLOT(showTotalTransfer()));
486
connect( ui.action_SessionRatio, SIGNAL(triggered()), this, SLOT(showSessionRatio()));
487
connect( ui.action_SessionTransfer, SIGNAL(triggered()), this, SLOT(showSessionTransfer()));
488
p = myStatsModeButton = new TrIconPushButton( this );
489
p->setIcon( QIcon( ":/icons/ratio.png" ) );
493
l = myStatsLabel = new QLabel( this );
496
w = new QWidget( this );
497
w->setMinimumSize( HIG::PAD_BIG, 1 );
498
w->setMaximumSize( HIG::PAD_BIG, 1 );
500
l = new QLabel( this );
501
l->setPixmap( getStockIcon( "go-down", QStyle::SP_ArrowDown ).pixmap( smallIconSize ) );
503
l2 = myDownloadSpeedLabel = new QLabel( this );
505
myDownStatusWidgets << w << l << l2;
507
w = new QWidget( this );
508
w->setMinimumSize( HIG::PAD_BIG, 1 );
509
w->setMaximumSize( HIG::PAD_BIG, 1 );
512
l->setPixmap( getStockIcon( "go-up", QStyle::SP_ArrowUp ).pixmap( smallIconSize ) );
514
l2 = myUploadSpeedLabel = new QLabel;
516
myUpStatusWidgets << w << l << l2;
522
TrMainWindow :: createOptionsMenu( )
529
QList<int> stockSpeeds;
530
stockSpeeds << 5 << 10 << 20 << 30 << 40 << 50 << 75 << 100 << 150 << 200 << 250 << 500 << 750;
531
QList<double> stockRatios;
532
stockRatios << 0.25 << 0.50 << 0.75 << 1 << 1.5 << 2 << 3;
535
sub = menu->addMenu( tr( "Limit Download Speed" ) );
536
int currentVal = myPrefs.get<int>( Prefs::DSPEED );
537
g = new QActionGroup( this );
538
a = myDlimitOffAction = sub->addAction( tr( "Unlimited" ) );
539
a->setCheckable( true );
540
a->setProperty( PREF_VARIANTS_KEY, QVariantList() << Prefs::DSPEED_ENABLED << false );
542
connect( a, SIGNAL(triggered(bool)), this, SLOT(onSetPrefs(bool)) );
543
a = myDlimitOnAction = sub->addAction( tr( "Limited at %1" ).arg( Utils::speedToString( Speed::fromKbps( currentVal ) ) ) );
544
a->setCheckable( true );
545
a->setProperty( PREF_VARIANTS_KEY, QVariantList() << Prefs::DSPEED << currentVal << Prefs::DSPEED_ENABLED << true );
547
connect( a, SIGNAL(triggered(bool)), this, SLOT(onSetPrefs(bool)) );
548
sub->addSeparator( );
549
foreach( int i, stockSpeeds ) {
550
a = sub->addAction( Utils::speedToString( Speed::fromKbps(i) ) );
551
a->setProperty( PREF_VARIANTS_KEY, QVariantList() << Prefs::DSPEED << i << Prefs::DSPEED_ENABLED << true );
552
connect( a, SIGNAL(triggered(bool)), this, SLOT(onSetPrefs()));
555
sub = menu->addMenu( tr( "Limit Upload Speed" ) );
556
currentVal = myPrefs.get<int>( Prefs::USPEED );
557
g = new QActionGroup( this );
558
a = myUlimitOffAction = sub->addAction( tr( "Unlimited" ) );
559
a->setCheckable( true );
560
a->setProperty( PREF_VARIANTS_KEY, QVariantList() << Prefs::USPEED_ENABLED << false );
562
connect( a, SIGNAL(triggered(bool)), this, SLOT(onSetPrefs(bool)) );
563
a = myUlimitOnAction = sub->addAction( tr( "Limited at %1" ).arg( Utils::speedToString( Speed::fromKbps( currentVal ) ) ) );
564
a->setCheckable( true );
565
a->setProperty( PREF_VARIANTS_KEY, QVariantList() << Prefs::USPEED << currentVal << Prefs::USPEED_ENABLED << true );
567
connect( a, SIGNAL(triggered(bool)), this, SLOT(onSetPrefs(bool)) );
568
sub->addSeparator( );
569
foreach( int i, stockSpeeds ) {
570
a = sub->addAction( Utils::speedToString( Speed::fromKbps(i) ) );
571
a->setProperty( PREF_VARIANTS_KEY, QVariantList() << Prefs::USPEED << i << Prefs::USPEED_ENABLED << true );
572
connect( a, SIGNAL(triggered(bool)), this, SLOT(onSetPrefs()));
575
menu->addSeparator( );
576
sub = menu->addMenu( tr( "Stop Seeding at Ratio" ) );
578
double d = myPrefs.get<double>( Prefs::RATIO );
579
g = new QActionGroup( this );
580
a = myRatioOffAction = sub->addAction( tr( "Seed Forever" ) );
581
a->setCheckable( true );
582
a->setProperty( PREF_VARIANTS_KEY, QVariantList() << Prefs::RATIO_ENABLED << false );
584
connect( a, SIGNAL(triggered(bool)), this, SLOT(onSetPrefs(bool)) );
585
a = myRatioOnAction = sub->addAction( tr( "Stop at Ratio (%1)" ).arg( Utils::ratioToString( d ) ) );
586
a->setCheckable( true );
587
a->setProperty( PREF_VARIANTS_KEY, QVariantList() << Prefs::RATIO << d << Prefs::RATIO_ENABLED << true );
589
connect( a, SIGNAL(triggered(bool)), this, SLOT(onSetPrefs(bool)) );
590
sub->addSeparator( );
591
foreach( double i, stockRatios ) {
592
a = sub->addAction( Utils::ratioToString( i ) );
593
a->setProperty( PREF_VARIANTS_KEY, QVariantList() << Prefs::RATIO << i << Prefs::RATIO_ENABLED << true );
594
connect( a, SIGNAL(triggered(bool)), this, SLOT(onSetPrefs()));
605
TrMainWindow :: setSortPref( int i )
607
myPrefs.set( Prefs::SORT_MODE, SortMode( i ) );
609
void TrMainWindow :: onSortByActivityToggled ( bool b ) { if( b ) setSortPref( SortMode::SORT_BY_ACTIVITY ); }
610
void TrMainWindow :: onSortByAgeToggled ( bool b ) { if( b ) setSortPref( SortMode::SORT_BY_AGE ); }
611
void TrMainWindow :: onSortByETAToggled ( bool b ) { if( b ) setSortPref( SortMode::SORT_BY_ETA ); }
612
void TrMainWindow :: onSortByNameToggled ( bool b ) { if( b ) setSortPref( SortMode::SORT_BY_NAME ); }
613
void TrMainWindow :: onSortByProgressToggled ( bool b ) { if( b ) setSortPref( SortMode::SORT_BY_PROGRESS ); }
614
void TrMainWindow :: onSortByRatioToggled ( bool b ) { if( b ) setSortPref( SortMode::SORT_BY_RATIO ); }
615
void TrMainWindow :: onSortBySizeToggled ( bool b ) { if( b ) setSortPref( SortMode::SORT_BY_SIZE ); }
616
void TrMainWindow :: onSortByStateToggled ( bool b ) { if( b ) setSortPref( SortMode::SORT_BY_STATE ); }
617
void TrMainWindow :: onSortByTrackerToggled ( bool b ) { if( b ) setSortPref( SortMode::SORT_BY_TRACKER ); }
620
TrMainWindow :: setSortAscendingPref( bool b )
622
myPrefs.set( Prefs::SORT_REVERSED, b );
630
TrMainWindow :: onDetailsDestroyed( )
636
TrMainWindow :: openProperties( )
638
if( myDetailsDialog == 0 ) {
639
myDetailsDialog = new Details( mySession, myModel, this );
640
connect( myDetailsDialog, SIGNAL(destroyed(QObject*)), this, SLOT(onDetailsDestroyed()));
643
myDetailsDialog->setIds( getSelectedTorrents( ) );
644
myDetailsDialog->show( );
648
TrMainWindow :: setLocation( )
650
QDialog * d = new RelocateDialog( mySession, getSelectedTorrents(), this );
655
TrMainWindow :: openFolder( )
657
const int torrentId( *getSelectedTorrents().begin() );
658
const Torrent * tor( myModel.getTorrentFromId( torrentId ) );
659
const QString path( tor->getPath( ) );
660
QDesktopServices :: openUrl( QUrl::fromLocalFile( path ) );
664
TrMainWindow :: copyMagnetLinkToClipboard( )
666
const int id( *getSelectedTorrents().begin() );
667
mySession.copyMagnetLinkToClipboard( id );
671
TrMainWindow :: openHelp( )
673
const char * fmt = "http://www.transmissionbt.com/help/gtk/%d.%dx";
675
sscanf( SHORT_VERSION_STRING, "%d.%d", &major, &minor );
677
snprintf( url, sizeof( url ), fmt, major, minor/10 );
678
QDesktopServices :: openUrl( QUrl( QString( url ) ) );
682
TrMainWindow :: refreshTitle( )
684
QString title( "Transmission" );
685
const QUrl url( mySession.getRemoteUrl( ) );
687
title += tr( " - %1" ).arg( url.toString(QUrl::RemoveUserInfo) );
688
setWindowTitle( title );
692
TrMainWindow :: refreshVisibleCount( )
694
const int visibleCount( myFilterModel.rowCount( ) );
695
const int totalCount( visibleCount + myFilterModel.hiddenRowCount( ) );
697
if( visibleCount == totalCount )
698
str = tr( "%Ln Torrent(s)", 0, totalCount );
700
str = tr( "%L1 of %Ln Torrent(s)", 0, totalCount ).arg( visibleCount );
701
myVisibleCountLabel->setText( str );
702
myVisibleCountLabel->setVisible( totalCount > 0 );
706
TrMainWindow :: refreshStatusBar( )
708
const Speed up( myModel.getUploadSpeed( ) );
709
const Speed down( myModel.getDownloadSpeed( ) );
710
myUploadSpeedLabel->setText( Utils :: speedToString( up ) );
711
myDownloadSpeedLabel->setText( Utils :: speedToString( down ) );
712
foreach( QWidget * w, myUpStatusWidgets ) w->setVisible( !up.isZero( ) );
713
foreach( QWidget * w, myDownStatusWidgets ) w->setVisible( !down.isZero( ) );
715
myNetworkLabel->setVisible( !mySession.isServer( ) );
717
const QString mode( myPrefs.getString( Prefs::STATUSBAR_STATS ) );
720
if( mode == "session-ratio" )
722
str = tr( "Ratio: %1" ).arg( Utils :: ratioToString( mySession.getStats().ratio ) );
724
else if( mode == "session-transfer" )
726
const tr_session_stats& stats( mySession.getStats( ) );
727
str = tr( "Down: %1, Up: %2" ).arg( Utils :: sizeToString( stats.downloadedBytes ) )
728
.arg( Utils :: sizeToString( stats.uploadedBytes ) );
730
else if( mode == "total-transfer" )
732
const tr_session_stats& stats( mySession.getCumulativeStats( ) );
733
str = tr( "Down: %1, Up: %2" ).arg( Utils :: sizeToString( stats.downloadedBytes ) )
734
.arg( Utils :: sizeToString( stats.uploadedBytes ) );
736
else // default is "total-ratio"
738
str = tr( "Ratio: %1" ).arg( Utils :: ratioToString( mySession.getCumulativeStats().ratio ) );
741
myStatsLabel->setText( str );
745
TrMainWindow :: refreshActionSensitivity( )
749
int selectedAndPaused( 0 );
750
int canAnnounce( 0 );
751
const QAbstractItemModel * model( ui.listView->model( ) );
752
const QItemSelectionModel * selectionModel( ui.listView->selectionModel( ) );
753
const int rowCount( model->rowCount( ) );
755
// count how many torrents are selected, paused, etc
756
for( int row=0; row<rowCount; ++row ) {
757
const QModelIndex modelIndex( model->index( row, 0 ) );
758
assert( model == modelIndex.model( ) );
759
const Torrent * tor( model->data( modelIndex, TorrentModel::TorrentRole ).value<const Torrent*>( ) );
761
const bool isSelected( selectionModel->isSelected( modelIndex ) );
762
const bool isPaused( tor->isPaused( ) );
767
if( isSelected && isPaused )
769
if( tor->canManualAnnounce( ) )
774
const bool haveSelection( selected > 0 );
775
ui.action_Verify->setEnabled( haveSelection );
776
ui.action_Remove->setEnabled( haveSelection );
777
ui.action_Delete->setEnabled( haveSelection );
778
ui.action_Properties->setEnabled( haveSelection );
779
ui.action_DeselectAll->setEnabled( haveSelection );
780
ui.action_SetLocation->setEnabled( haveSelection );
782
const bool oneSelection( selected == 1 );
783
ui.action_OpenFolder->setEnabled( oneSelection && mySession.isLocal( ) );
784
ui.action_CopyMagnetToClipboard->setEnabled( oneSelection );
786
ui.action_SelectAll->setEnabled( selected < rowCount );
787
ui.action_StartAll->setEnabled( paused > 0 );
788
ui.action_PauseAll->setEnabled( paused < rowCount );
789
ui.action_Start->setEnabled( selectedAndPaused > 0 );
790
ui.action_Pause->setEnabled( selectedAndPaused < selected );
791
ui.action_Announce->setEnabled( selected > 0 && ( canAnnounce == selected ) );
793
if( myDetailsDialog )
794
myDetailsDialog->setIds( getSelectedTorrents( ) );
802
TrMainWindow :: clearSelection( )
804
ui.action_DeselectAll->trigger( );
808
TrMainWindow :: getSelectedTorrents( ) const
812
foreach( QModelIndex index, ui.listView->selectionModel( )->selectedRows( ) )
814
const Torrent * tor( index.model()->data( index, TorrentModel::TorrentRole ).value<const Torrent*>( ) );
815
ids.insert( tor->id( ) );
822
TrMainWindow :: startSelected( )
824
mySession.startTorrents( getSelectedTorrents( ) );
827
TrMainWindow :: pauseSelected( )
829
mySession.pauseTorrents( getSelectedTorrents( ) );
832
TrMainWindow :: startAll( )
834
mySession.startTorrents( );
837
TrMainWindow :: pauseAll( )
839
mySession.pauseTorrents( );
842
TrMainWindow :: removeSelected( )
844
mySession.removeTorrents( getSelectedTorrents( ), false );
847
TrMainWindow :: deleteSelected( )
849
mySession.removeTorrents( getSelectedTorrents( ), true );
852
TrMainWindow :: verifySelected( )
854
mySession.verifyTorrents( getSelectedTorrents( ) );
857
TrMainWindow :: reannounceSelected( )
859
mySession.reannounceTorrents( getSelectedTorrents( ) );
866
void TrMainWindow :: setShowMode ( int i ) { myPrefs.set( Prefs::FILTER_MODE, FilterMode( i ) ); }
867
void TrMainWindow :: showAll ( ) { setShowMode( FilterMode :: SHOW_ALL ); }
868
void TrMainWindow :: showActive ( ) { setShowMode( FilterMode :: SHOW_ACTIVE ); }
869
void TrMainWindow :: showDownloading ( ) { setShowMode( FilterMode :: SHOW_DOWNLOADING ); }
870
void TrMainWindow :: showSeeding ( ) { setShowMode( FilterMode :: SHOW_SEEDING ); }
871
void TrMainWindow :: showPaused ( ) { setShowMode( FilterMode :: SHOW_PAUSED ); }
873
void TrMainWindow :: filterByName ( ) { myFilterModel.setTextMode( TorrentFilter :: FILTER_BY_NAME ); }
874
void TrMainWindow :: filterByTracker ( ) { myFilterModel.setTextMode( TorrentFilter :: FILTER_BY_TRACKER ); }
875
void TrMainWindow :: filterByFiles ( ) { myFilterModel.setTextMode( TorrentFilter :: FILTER_BY_FILES ); }
877
void TrMainWindow :: showTotalRatio ( ) { myPrefs.set( Prefs::STATUSBAR_STATS, "total-ratio"); }
878
void TrMainWindow :: showTotalTransfer ( ) { myPrefs.set( Prefs::STATUSBAR_STATS, "total-transfer"); }
879
void TrMainWindow :: showSessionRatio ( ) { myPrefs.set( Prefs::STATUSBAR_STATS, "session-ratio"); }
880
void TrMainWindow :: showSessionTransfer ( ) { myPrefs.set( Prefs::STATUSBAR_STATS, "session-transfer"); }
887
TrMainWindow :: setMinimalView( bool visible )
889
myPrefs.set( Prefs :: MINIMAL_VIEW, visible );
892
TrMainWindow :: setTrayIconVisible( bool visible )
894
myPrefs.set( Prefs :: SHOW_TRAY_ICON, visible );
897
TrMainWindow :: toggleSpeedMode( )
899
myPrefs.toggleBool( Prefs :: ALT_SPEED_LIMIT_ENABLED );
902
TrMainWindow :: setToolbarVisible( bool visible )
904
myPrefs.set( Prefs::TOOLBAR, visible );
907
TrMainWindow :: setFilterbarVisible( bool visible )
909
myPrefs.set( Prefs::FILTERBAR, visible );
912
TrMainWindow :: setStatusbarVisible( bool visible )
914
myPrefs.set( Prefs::STATUSBAR, visible );
922
TrMainWindow :: toggleWindows( bool doShow )
930
if ( !isVisible( ) ) show( );
931
if ( isMinimized( ) ) showNormal( );
938
TrMainWindow :: trayActivated( QSystemTrayIcon::ActivationReason reason )
940
if( reason == QSystemTrayIcon::Trigger )
942
if( isMinimized ( ) )
943
toggleWindows( true );
945
ui.action_ShowMainWindow->toggle( );
951
TrMainWindow :: refreshPref( int key )
959
case Prefs::STATUSBAR_STATS:
960
str = myPrefs.getString( key );
961
ui.action_TotalRatio->setChecked ( str == "total-ratio" );
962
ui.action_TotalTransfer->setChecked ( str == "total-transfer" );
963
ui.action_SessionRatio->setChecked ( str == "session-ratio" );
964
ui.action_SessionTransfer->setChecked( str == "session-transfer" );
968
case Prefs::SORT_REVERSED:
969
ui.action_ReverseSortOrder->setChecked( myPrefs.getBool( key ) );
972
case Prefs::SORT_MODE:
973
i = myPrefs.get<SortMode>(key).mode( );
974
ui.action_SortByActivity->setChecked ( i == SortMode::SORT_BY_ACTIVITY );
975
ui.action_SortByAge->setChecked ( i == SortMode::SORT_BY_AGE );
976
ui.action_SortByETA->setChecked ( i == SortMode::SORT_BY_ETA );
977
ui.action_SortByName->setChecked ( i == SortMode::SORT_BY_NAME );
978
ui.action_SortByProgress->setChecked ( i == SortMode::SORT_BY_PROGRESS );
979
ui.action_SortByRatio->setChecked ( i == SortMode::SORT_BY_RATIO );
980
ui.action_SortBySize->setChecked ( i == SortMode::SORT_BY_SIZE );
981
ui.action_SortByState->setChecked ( i == SortMode::SORT_BY_STATE );
982
ui.action_SortByTracker->setChecked ( i == SortMode::SORT_BY_TRACKER );
985
case Prefs::DSPEED_ENABLED:
986
(myPrefs.get<bool>(key) ? myDlimitOnAction : myDlimitOffAction)->setChecked( true );
990
myDlimitOnAction->setText( tr( "Limited at %1" ).arg( Utils::speedToString( Speed::fromKbps( myPrefs.get<int>(key) ) ) ) );
993
case Prefs::USPEED_ENABLED:
994
(myPrefs.get<bool>(key) ? myUlimitOnAction : myUlimitOffAction)->setChecked( true );
998
myUlimitOnAction->setText( tr( "Limited at %1" ).arg( Utils::speedToString( Speed::fromKbps( myPrefs.get<int>(key) ) ) ) );
1001
case Prefs::RATIO_ENABLED:
1002
(myPrefs.get<bool>(key) ? myRatioOnAction : myRatioOffAction)->setChecked( true );
1006
myRatioOnAction->setText( tr( "Stop at Ratio (%1)" ).arg( Utils::ratioToString( myPrefs.get<double>(key) ) ) );
1009
case Prefs::FILTER_MODE:
1010
i = myPrefs.get<FilterMode>(key).mode( );
1011
for( int j=0; j<FilterMode::NUM_MODES; ++j )
1012
myFilterButtons[j]->setChecked( i==j );
1015
case Prefs::FILTERBAR:
1016
b = myPrefs.getBool( key );
1017
myFilterBar->setVisible( b );
1018
ui.action_Filterbar->setChecked( b );
1021
case Prefs::STATUSBAR:
1022
b = myPrefs.getBool( key );
1023
myStatusBar->setVisible( b );
1024
ui.action_Statusbar->setChecked( b );
1027
case Prefs::TOOLBAR:
1028
b = myPrefs.getBool( key );
1029
ui.toolBar->setVisible( b );
1030
ui.action_Toolbar->setChecked( b );
1033
case Prefs::SHOW_TRAY_ICON:
1034
b = myPrefs.getBool( key );
1035
ui.action_TrayIcon->setChecked( b );
1036
myTrayIcon.setVisible( b );
1039
case Prefs::MINIMAL_VIEW:
1040
b = myPrefs.getBool( key );
1041
ui.action_MinimalView->setChecked( b );
1042
ui.listView->setItemDelegate( b ? myTorrentDelegateMin : myTorrentDelegate );
1043
ui.listView->reset( ); // force the rows to resize
1046
case Prefs::MAIN_WINDOW_X:
1047
case Prefs::MAIN_WINDOW_Y:
1048
case Prefs::MAIN_WINDOW_WIDTH:
1049
case Prefs::MAIN_WINDOW_HEIGHT:
1050
setGeometry( myPrefs.getInt( Prefs::MAIN_WINDOW_X ),
1051
myPrefs.getInt( Prefs::MAIN_WINDOW_Y ),
1052
myPrefs.getInt( Prefs::MAIN_WINDOW_WIDTH ),
1053
myPrefs.getInt( Prefs::MAIN_WINDOW_HEIGHT ) );
1056
case Prefs :: ALT_SPEED_LIMIT_ENABLED:
1057
case Prefs :: ALT_SPEED_LIMIT_UP:
1058
case Prefs :: ALT_SPEED_LIMIT_DOWN: {
1059
b = myPrefs.getBool( Prefs :: ALT_SPEED_LIMIT_ENABLED );
1060
myAltSpeedButton->setChecked( b );
1061
myAltSpeedButton->setIcon( b ? mySpeedModeOnIcon : mySpeedModeOffIcon );
1062
const QString fmt = b ? tr( "Click to disable Temporary Speed Limits\n(%1 down, %2 up)" )
1063
: tr( "Click to enable Temporary Speed Limits\n(%1 down, %2 up)" );
1064
const Speed d = Speed::fromKbps( myPrefs.getInt( Prefs::ALT_SPEED_LIMIT_DOWN ) );
1065
const Speed u = Speed::fromKbps( myPrefs.getInt( Prefs::ALT_SPEED_LIMIT_UP ) );
1066
myAltSpeedButton->setToolTip( fmt.arg( Utils::speedToString( d ) )
1067
.arg( Utils::speedToString( u ) ) );
1081
TrMainWindow :: newTorrent( )
1083
MakeDialog * dialog = new MakeDialog( mySession, this );
1088
TrMainWindow :: openTorrent( )
1090
if( myFileDialog == 0 )
1092
myFileDialog = new QFileDialog( this,
1093
tr( "Add Torrent" ),
1094
myPrefs.getString( Prefs::OPEN_DIALOG_FOLDER ),
1095
tr( "Torrent Files (*.torrent);;All Files (*.*)" ) );
1096
myFileDialog->setFileMode( QFileDialog::ExistingFiles );
1099
QCheckBox * button = new QCheckBox( tr( "Show &options dialog" ) );
1100
button->setChecked( myPrefs.getBool( Prefs::OPTIONS_PROMPT ) );
1101
QGridLayout * layout = dynamic_cast<QGridLayout*>(myFileDialog->layout());
1102
layout->addWidget( button, layout->rowCount( ), 0, 1, -1, Qt::AlignLeft );
1103
myFileDialogOptionsCheck = button;
1105
connect( myFileDialog, SIGNAL(filesSelected(const QStringList&)),
1106
this, SLOT(addTorrents(const QStringList&)));
1109
myFileDialog->show( );
1113
TrMainWindow :: openURL( )
1116
const QString key = QInputDialog::getText( this,
1117
tr( "Add URL or Magnet Link" ),
1118
tr( "Add URL or Magnet Link" ),
1122
if( ok && !key.isEmpty( ) )
1123
mySession.addTorrent( key );
1127
TrMainWindow :: addTorrents( const QStringList& filenames )
1129
foreach( const QString& filename, filenames )
1130
addTorrent( filename );
1134
TrMainWindow :: addTorrent( const QString& filename )
1136
if( !myFileDialogOptionsCheck->isChecked( ) ) {
1137
mySession.addTorrent( filename );
1138
QApplication :: alert ( this );
1140
Options * o = new Options( mySession, myPrefs, filename, this );
1142
QApplication :: alert( o );
1151
TrMainWindow :: updateNetworkIcon( )
1153
const time_t now = time( NULL );
1154
const int period = 3;
1155
const bool isSending = now - myLastSendTime <= period;
1156
const bool isReading = now - myLastReadTime <= period;
1159
if( isSending && isReading )
1160
key = "network-transmit-receive";
1161
else if( isSending )
1162
key = "network-transmit";
1163
else if( isReading )
1164
key = "network-receive";
1166
key = "network-idle";
1168
QIcon icon = getStockIcon( key, QStyle::SP_DriveNetIcon );
1169
QPixmap pixmap = icon.pixmap ( 16, 16 );
1170
myNetworkLabel->setPixmap( pixmap );
1171
myNetworkLabel->setToolTip( isSending || isReading
1172
? tr( "Transmission server is responding" )
1173
: tr( "Last response from server was %1 ago" ).arg( Utils::timeToString( now-std::max(myLastReadTime,myLastSendTime))));
1177
TrMainWindow :: onNetworkTimer( )
1179
updateNetworkIcon( );
1183
TrMainWindow :: dataReadProgress( )
1185
myLastReadTime = time( NULL );
1186
updateNetworkIcon( );
1190
TrMainWindow :: dataSendProgress( )
1192
myLastSendTime = time( NULL );
1193
updateNetworkIcon( );
1197
TrMainWindow :: wrongAuthentication( )
1200
mySessionDialog->show( );