mirror of
https://github.com/transmission/transmission
synced 2024-12-27 01:57:52 +00:00
1456 lines
50 KiB
C++
1456 lines
50 KiB
C++
/*
|
|
* This file Copyright (C) Mnemosyne LLC
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License version 2
|
|
* as published by the Free Software Foundation.
|
|
*
|
|
* http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
|
|
*
|
|
* $Id$
|
|
*/
|
|
|
|
#include <cassert>
|
|
#include <iostream>
|
|
|
|
#include <QtGui>
|
|
|
|
#include <libtransmission/transmission.h>
|
|
#include <libtransmission/utils.h>
|
|
#include <libtransmission/version.h>
|
|
|
|
#include "about.h"
|
|
#include "add-data.h"
|
|
#include "app.h"
|
|
#include "details.h"
|
|
#include "filterbar.h"
|
|
#include "filters.h"
|
|
#include "formatter.h"
|
|
#include "hig.h"
|
|
#include "mainwin.h"
|
|
#include "make-dialog.h"
|
|
#include "options.h"
|
|
#include "prefs.h"
|
|
#include "prefs-dialog.h"
|
|
#include "relocate.h"
|
|
#include "session.h"
|
|
#include "session-dialog.h"
|
|
#include "speed.h"
|
|
#include "stats-dialog.h"
|
|
#include "torrent-delegate.h"
|
|
#include "torrent-delegate-min.h"
|
|
#include "torrent-filter.h"
|
|
#include "torrent-model.h"
|
|
#include "triconpushbutton.h"
|
|
#include "ui_mainwin.h"
|
|
|
|
#define PREFS_KEY "prefs-key";
|
|
|
|
|
|
/**
|
|
* This is a proxy-style for that forces it to be always disabled.
|
|
* We use this to make our torrent list view behave consistently on
|
|
* both GTK and Qt implementations.
|
|
*/
|
|
class ListViewProxyStyle: public QProxyStyle
|
|
{
|
|
public:
|
|
int styleHint(StyleHint hint,
|
|
const QStyleOption *option = 0,
|
|
const QWidget *widget = 0,
|
|
QStyleHintReturn *returnData = 0) const
|
|
{
|
|
if (hint == QStyle::SH_ItemView_ActivateItemOnSingleClick)
|
|
return 0;
|
|
return QProxyStyle::styleHint(hint, option, widget, returnData);
|
|
}
|
|
};
|
|
|
|
|
|
QIcon
|
|
TrMainWindow :: getStockIcon( const QString& name, int fallback )
|
|
{
|
|
QIcon icon = QIcon::fromTheme( name );
|
|
|
|
if( icon.isNull( ) && ( fallback >= 0 ) )
|
|
icon = style()->standardIcon( QStyle::StandardPixmap( fallback ), 0, this );
|
|
|
|
return icon;
|
|
}
|
|
|
|
namespace
|
|
{
|
|
QSize calculateTextButtonSizeHint( QPushButton * button )
|
|
{
|
|
QStyleOptionButton opt;
|
|
opt.initFrom( button );
|
|
QString s( button->text( ) );
|
|
if( s.isEmpty( ) )
|
|
s = QString::fromLatin1( "XXXX" );
|
|
QFontMetrics fm = button->fontMetrics( );
|
|
QSize sz = fm.size( Qt::TextShowMnemonic, s );
|
|
return button->style()->sizeFromContents( QStyle::CT_PushButton, &opt, sz, button ).expandedTo( QApplication::globalStrut( ) );
|
|
}
|
|
}
|
|
|
|
|
|
TrMainWindow :: TrMainWindow( Session& session, Prefs& prefs, TorrentModel& model, bool minimized ):
|
|
myLastFullUpdateTime( 0 ),
|
|
mySessionDialog( new SessionDialog( session, prefs, this ) ),
|
|
myPrefsDialog( 0 ),
|
|
myAboutDialog( new AboutDialog( this ) ),
|
|
myStatsDialog( new StatsDialog( session, this ) ),
|
|
myDetailsDialog( 0 ),
|
|
myFilterModel( prefs ),
|
|
myTorrentDelegate( new TorrentDelegate( this ) ),
|
|
myTorrentDelegateMin( new TorrentDelegateMin( this ) ),
|
|
mySession( session ),
|
|
myPrefs( prefs ),
|
|
myModel( model ),
|
|
mySpeedModeOffIcon( ":/icons/alt-limit-off.png" ),
|
|
mySpeedModeOnIcon( ":/icons/alt-limit-on.png" ),
|
|
myLastSendTime( 0 ),
|
|
myLastReadTime( 0 ),
|
|
myNetworkTimer( this ),
|
|
myRefreshTrayIconTimer( this ),
|
|
myRefreshActionSensitivityTimer( this )
|
|
{
|
|
setAcceptDrops( true );
|
|
|
|
QAction * sep = new QAction( this );
|
|
sep->setSeparator( true );
|
|
|
|
ui.setupUi( this );
|
|
|
|
QStyle * style = this->style();
|
|
|
|
int i = style->pixelMetric( QStyle::PM_SmallIconSize, 0, this );
|
|
const QSize smallIconSize( i, i );
|
|
|
|
ui.listView->setStyle(new ListViewProxyStyle);
|
|
|
|
// icons
|
|
ui.action_OpenFile->setIcon( getStockIcon( "folder-open", QStyle::SP_DialogOpenButton ) );
|
|
ui.action_New->setIcon( getStockIcon( "document-new", QStyle::SP_DesktopIcon ) );
|
|
ui.action_Properties->setIcon( getStockIcon( "document-properties", QStyle::SP_DesktopIcon ) );
|
|
ui.action_OpenFolder->setIcon( getStockIcon( "folder-open", QStyle::SP_DirOpenIcon ) );
|
|
ui.action_Start->setIcon( getStockIcon( "media-playback-start", QStyle::SP_MediaPlay ) );
|
|
ui.action_StartNow->setIcon( getStockIcon( "media-playback-start", QStyle::SP_MediaPlay ) );
|
|
ui.action_Announce->setIcon( getStockIcon( "network-transmit-receive" ) );
|
|
ui.action_Pause->setIcon( getStockIcon( "media-playback-pause", QStyle::SP_MediaPause ) );
|
|
ui.action_Remove->setIcon( getStockIcon( "list-remove", QStyle::SP_TrashIcon ) );
|
|
ui.action_Delete->setIcon( getStockIcon( "edit-delete", QStyle::SP_TrashIcon ) );
|
|
ui.action_StartAll->setIcon( getStockIcon( "media-playback-start", QStyle::SP_MediaPlay ) );
|
|
ui.action_PauseAll->setIcon( getStockIcon( "media-playback-pause", QStyle::SP_MediaPause ) );
|
|
ui.action_Quit->setIcon( getStockIcon( "application-exit" ) );
|
|
ui.action_SelectAll->setIcon( getStockIcon( "edit-select-all" ) );
|
|
ui.action_ReverseSortOrder->setIcon( getStockIcon( "view-sort-ascending", QStyle::SP_ArrowDown ) );
|
|
ui.action_Preferences->setIcon( getStockIcon( "preferences-system" ) );
|
|
ui.action_Contents->setIcon( getStockIcon( "help-contents", QStyle::SP_DialogHelpButton ) );
|
|
ui.action_About->setIcon( getStockIcon( "help-about" ) );
|
|
ui.action_QueueMoveTop->setIcon( getStockIcon( "go-top" ) );
|
|
ui.action_QueueMoveUp->setIcon( getStockIcon( "go-up", QStyle::SP_ArrowUp ) );
|
|
ui.action_QueueMoveDown->setIcon( getStockIcon( "go-down", QStyle::SP_ArrowDown ) );
|
|
ui.action_QueueMoveBottom->setIcon( getStockIcon( "go-bottom" ) );
|
|
|
|
// ui signals
|
|
connect( ui.action_Toolbar, SIGNAL(toggled(bool)), this, SLOT(setToolbarVisible(bool)));
|
|
connect( ui.action_Filterbar, SIGNAL(toggled(bool)), this, SLOT(setFilterbarVisible(bool)));
|
|
connect( ui.action_Statusbar, SIGNAL(toggled(bool)), this, SLOT(setStatusbarVisible(bool)));
|
|
connect( ui.action_CompactView, SIGNAL(toggled(bool)), this, SLOT(setCompactView(bool)));
|
|
connect( ui.action_SortByActivity, SIGNAL(toggled(bool)), this, SLOT(onSortByActivityToggled(bool)));
|
|
connect( ui.action_SortByAge, SIGNAL(toggled(bool)), this, SLOT(onSortByAgeToggled(bool)));
|
|
connect( ui.action_SortByETA, SIGNAL(toggled(bool)), this, SLOT(onSortByETAToggled(bool)));
|
|
connect( ui.action_SortByName, SIGNAL(toggled(bool)), this, SLOT(onSortByNameToggled(bool)));
|
|
connect( ui.action_SortByProgress, SIGNAL(toggled(bool)), this, SLOT(onSortByProgressToggled(bool)));
|
|
connect( ui.action_SortByQueue, SIGNAL(toggled(bool)), this, SLOT(onSortByQueueToggled(bool)));
|
|
connect( ui.action_SortByRatio, SIGNAL(toggled(bool)), this, SLOT(onSortByRatioToggled(bool)));
|
|
connect( ui.action_SortBySize, SIGNAL(toggled(bool)), this, SLOT(onSortBySizeToggled(bool)));
|
|
connect( ui.action_SortByState, SIGNAL(toggled(bool)), this, SLOT(onSortByStateToggled(bool)));
|
|
connect( ui.action_ReverseSortOrder, SIGNAL(toggled(bool)), this, SLOT(setSortAscendingPref(bool)));
|
|
connect( ui.action_Start, SIGNAL(triggered()), this, SLOT(startSelected()));
|
|
connect( ui.action_QueueMoveTop, SIGNAL(triggered()), this, SLOT(queueMoveTop()));
|
|
connect( ui.action_QueueMoveUp, SIGNAL(triggered()), this, SLOT(queueMoveUp()));
|
|
connect( ui.action_QueueMoveDown, SIGNAL(triggered()), this, SLOT(queueMoveDown()));
|
|
connect( ui.action_QueueMoveBottom, SIGNAL(triggered()), this, SLOT(queueMoveBottom()));
|
|
connect( ui.action_StartNow, SIGNAL(triggered()), this, SLOT(startSelectedNow()));
|
|
connect( ui.action_Pause, SIGNAL(triggered()), this, SLOT(pauseSelected()));
|
|
connect( ui.action_Remove, SIGNAL(triggered()), this, SLOT(removeSelected()));
|
|
connect( ui.action_Delete, SIGNAL(triggered()), this, SLOT(deleteSelected()));
|
|
connect( ui.action_Verify, SIGNAL(triggered()), this, SLOT(verifySelected()) );
|
|
connect( ui.action_Announce, SIGNAL(triggered()), this, SLOT(reannounceSelected()) );
|
|
connect( ui.action_StartAll, SIGNAL(triggered()), this, SLOT(startAll()));
|
|
connect( ui.action_PauseAll, SIGNAL(triggered()), this, SLOT(pauseAll()));
|
|
connect( ui.action_OpenFile, SIGNAL(triggered()), this, SLOT(openTorrent()));
|
|
connect( ui.action_AddURL, SIGNAL(triggered()), this, SLOT(openURL()));
|
|
connect( ui.action_New, SIGNAL(triggered()), this, SLOT(newTorrent()));
|
|
connect( ui.action_Preferences, SIGNAL(triggered()), this, SLOT(openPreferences()));
|
|
connect( ui.action_Statistics, SIGNAL(triggered()), myStatsDialog, SLOT(show()));
|
|
connect( ui.action_Donate, SIGNAL(triggered()), this, SLOT(openDonate()));
|
|
connect( ui.action_About, SIGNAL(triggered()), myAboutDialog, SLOT(show()));
|
|
connect( ui.action_Contents, SIGNAL(triggered()), this, SLOT(openHelp()));
|
|
connect( ui.action_OpenFolder, SIGNAL(triggered()), this, SLOT(openFolder()));
|
|
connect( ui.action_CopyMagnetToClipboard, SIGNAL(triggered()), this, SLOT(copyMagnetLinkToClipboard()));
|
|
connect( ui.action_SetLocation, SIGNAL(triggered()), this, SLOT(setLocation()));
|
|
connect( ui.action_Properties, SIGNAL(triggered()), this, SLOT(openProperties()));
|
|
connect( ui.action_SessionDialog, SIGNAL(triggered()), mySessionDialog, SLOT(show()));
|
|
|
|
connect( ui.listView, SIGNAL(activated(const QModelIndex&)), ui.action_Properties, SLOT(trigger()));
|
|
|
|
// signals
|
|
connect( ui.action_SelectAll, SIGNAL(triggered()), ui.listView, SLOT(selectAll()));
|
|
connect( ui.action_DeselectAll, SIGNAL(triggered()), ui.listView, SLOT(clearSelection()));
|
|
|
|
connect( &myFilterModel, SIGNAL(rowsInserted(const QModelIndex&,int,int)), this, SLOT(refreshVisibleCount()));
|
|
connect( &myFilterModel, SIGNAL(rowsRemoved(const QModelIndex&,int,int)), this, SLOT(refreshVisibleCount()));
|
|
connect( &myFilterModel, SIGNAL(rowsInserted(const QModelIndex&,int,int)), this, SLOT(refreshActionSensitivitySoon()));
|
|
connect( &myFilterModel, SIGNAL(rowsRemoved(const QModelIndex&,int,int)), this, SLOT(refreshActionSensitivitySoon()));
|
|
|
|
connect( ui.action_Quit, SIGNAL(triggered()), QCoreApplication::instance(), SLOT(quit()) );
|
|
|
|
// torrent view
|
|
myFilterModel.setSourceModel( &myModel );
|
|
connect( &myModel, SIGNAL(modelReset()), this, SLOT(onModelReset()));
|
|
connect( &myModel, SIGNAL(rowsRemoved(const QModelIndex&,int,int)), this, SLOT(onModelReset()));
|
|
connect( &myModel, SIGNAL(rowsInserted(const QModelIndex&,int,int)), this, SLOT(onModelReset()));
|
|
connect( &myModel, SIGNAL(dataChanged(const QModelIndex&,const QModelIndex&)), this, SLOT(refreshTrayIconSoon()));
|
|
|
|
ui.listView->setModel( &myFilterModel );
|
|
connect( ui.listView->selectionModel(), SIGNAL(selectionChanged(const QItemSelection&,const QItemSelection&)), this, SLOT(refreshActionSensitivitySoon()));
|
|
|
|
QActionGroup * actionGroup = new QActionGroup( this );
|
|
actionGroup->addAction( ui.action_SortByActivity );
|
|
actionGroup->addAction( ui.action_SortByAge );
|
|
actionGroup->addAction( ui.action_SortByETA );
|
|
actionGroup->addAction( ui.action_SortByName );
|
|
actionGroup->addAction( ui.action_SortByProgress );
|
|
actionGroup->addAction( ui.action_SortByQueue );
|
|
actionGroup->addAction( ui.action_SortByRatio );
|
|
actionGroup->addAction( ui.action_SortBySize );
|
|
actionGroup->addAction( ui.action_SortByState );
|
|
|
|
myAltSpeedAction = new QAction( tr( "Speed Limits" ), this );
|
|
myAltSpeedAction->setIcon( myPrefs.get<bool>(Prefs::ALT_SPEED_LIMIT_ENABLED) ? mySpeedModeOnIcon : mySpeedModeOffIcon );
|
|
connect( myAltSpeedAction, SIGNAL(triggered()), this, SLOT(toggleSpeedMode()) );
|
|
|
|
QMenu * menu = new QMenu( );
|
|
menu->addAction( ui.action_OpenFile );
|
|
menu->addAction( ui.action_AddURL );
|
|
menu->addSeparator( );
|
|
menu->addAction( ui.action_ShowMainWindow );
|
|
menu->addAction( ui.action_ShowMessageLog );
|
|
menu->addAction( ui.action_About );
|
|
menu->addSeparator( );
|
|
menu->addAction( ui.action_StartAll );
|
|
menu->addAction( ui.action_PauseAll );
|
|
menu->addAction( myAltSpeedAction );
|
|
menu->addSeparator( );
|
|
menu->addAction( ui.action_Quit );
|
|
myTrayIcon.setContextMenu( menu );
|
|
myTrayIcon.setIcon( QApplication::windowIcon( ) );
|
|
|
|
connect( &myPrefs, SIGNAL(changed(int)), this, SLOT(refreshPref(int)) );
|
|
connect( ui.action_ShowMainWindow, SIGNAL(triggered(bool)), this, SLOT(toggleWindows(bool)));
|
|
connect( &myTrayIcon, SIGNAL(activated(QSystemTrayIcon::ActivationReason)),
|
|
this, SLOT(trayActivated(QSystemTrayIcon::ActivationReason)));
|
|
|
|
toggleWindows( !minimized );
|
|
ui.action_TrayIcon->setChecked( minimized || prefs.getBool( Prefs::SHOW_TRAY_ICON ) );
|
|
|
|
ui.verticalLayout->addWidget( createStatusBar( ) );
|
|
ui.verticalLayout->insertWidget( 0, myFilterBar = new FilterBar( myPrefs, myModel, myFilterModel ) );
|
|
|
|
QList<int> initKeys;
|
|
initKeys << Prefs :: MAIN_WINDOW_X
|
|
<< Prefs :: SHOW_TRAY_ICON
|
|
<< Prefs :: SORT_REVERSED
|
|
<< Prefs :: SORT_MODE
|
|
<< Prefs :: FILTERBAR
|
|
<< Prefs :: STATUSBAR
|
|
<< Prefs :: STATUSBAR_STATS
|
|
<< Prefs :: TOOLBAR
|
|
<< Prefs :: ALT_SPEED_LIMIT_ENABLED
|
|
<< Prefs :: COMPACT_VIEW
|
|
<< Prefs :: DSPEED
|
|
<< Prefs :: DSPEED_ENABLED
|
|
<< Prefs :: USPEED
|
|
<< Prefs :: USPEED_ENABLED
|
|
<< Prefs :: RATIO
|
|
<< Prefs :: RATIO_ENABLED;
|
|
foreach( int key, initKeys )
|
|
refreshPref( key );
|
|
|
|
connect( &mySession, SIGNAL(sourceChanged()), this, SLOT(onSessionSourceChanged()) );
|
|
connect( &mySession, SIGNAL(statsUpdated()), this, SLOT(refreshStatusBar()) );
|
|
connect( &mySession, SIGNAL(dataReadProgress()), this, SLOT(dataReadProgress()) );
|
|
connect( &mySession, SIGNAL(dataSendProgress()), this, SLOT(dataSendProgress()) );
|
|
connect( &mySession, SIGNAL(httpAuthenticationRequired()), this, SLOT(wrongAuthentication()) );
|
|
|
|
if( mySession.isServer( ) )
|
|
myNetworkLabel->hide( );
|
|
else {
|
|
connect( &myNetworkTimer, SIGNAL(timeout()), this, SLOT(onNetworkTimer()));
|
|
myNetworkTimer.start( 1000 );
|
|
}
|
|
|
|
connect( &myRefreshTrayIconTimer, SIGNAL(timeout()), this, SLOT(refreshTrayIcon()) );
|
|
connect( &myRefreshActionSensitivityTimer, SIGNAL(timeout()), this, SLOT(refreshActionSensitivity()) );
|
|
|
|
|
|
refreshActionSensitivitySoon( );
|
|
refreshTrayIconSoon( );
|
|
refreshStatusBar( );
|
|
refreshTitle( );
|
|
refreshVisibleCount( );
|
|
}
|
|
|
|
TrMainWindow :: ~TrMainWindow( )
|
|
{
|
|
}
|
|
|
|
/****
|
|
*****
|
|
****/
|
|
|
|
void
|
|
TrMainWindow :: onSessionSourceChanged( )
|
|
{
|
|
myModel.clear( );
|
|
}
|
|
|
|
void
|
|
TrMainWindow :: onModelReset( )
|
|
{
|
|
refreshTitle( );
|
|
refreshVisibleCount( );
|
|
refreshActionSensitivitySoon( );
|
|
refreshStatusBar( );
|
|
refreshTrayIconSoon( );
|
|
}
|
|
|
|
/****
|
|
*****
|
|
****/
|
|
|
|
#define PREF_VARIANTS_KEY "pref-variants-list"
|
|
|
|
void
|
|
TrMainWindow :: onSetPrefs( )
|
|
{
|
|
const QVariantList p = sender()->property( PREF_VARIANTS_KEY ).toList( );
|
|
assert( ( p.size( ) % 2 ) == 0 );
|
|
for( int i=0, n=p.size(); i<n; i+=2 )
|
|
myPrefs.set( p[i].toInt(), p[i+1] );
|
|
}
|
|
|
|
void
|
|
TrMainWindow :: onSetPrefs( bool isChecked )
|
|
{
|
|
if( isChecked )
|
|
onSetPrefs( );
|
|
}
|
|
|
|
#define SHOW_KEY "show-mode"
|
|
|
|
QWidget *
|
|
TrMainWindow :: createStatusBar( )
|
|
{
|
|
QMenu * m;
|
|
QLabel * l;
|
|
QHBoxLayout * h;
|
|
QPushButton * p;
|
|
QActionGroup * a;
|
|
const int i = style( )->pixelMetric( QStyle::PM_SmallIconSize, 0, this );
|
|
const QSize smallIconSize( i, i );
|
|
|
|
QWidget * top = myStatusBar = new QWidget;
|
|
h = new QHBoxLayout( top );
|
|
h->setContentsMargins( HIG::PAD_SMALL, HIG::PAD_SMALL, HIG::PAD_SMALL, HIG::PAD_SMALL );
|
|
h->setSpacing( HIG::PAD_SMALL );
|
|
|
|
p = myOptionsButton = new TrIconPushButton( this );
|
|
p->setIcon( QIcon( ":/icons/utilities.png" ) );
|
|
p->setIconSize( QPixmap( ":/icons/utilities.png" ).size() );
|
|
p->setFlat( true );
|
|
p->setMenu( createOptionsMenu( ) );
|
|
h->addWidget( p );
|
|
|
|
p = myAltSpeedButton = new QPushButton( this );
|
|
p->setIcon( myPrefs.get<bool>(Prefs::ALT_SPEED_LIMIT_ENABLED) ? mySpeedModeOnIcon : mySpeedModeOffIcon );
|
|
p->setIconSize( QPixmap( ":/icons/alt-limit-on.png" ).size() );
|
|
p->setCheckable( true );
|
|
p->setFixedWidth( p->height() );
|
|
p->setFlat( true );
|
|
h->addWidget( p );
|
|
connect( p, SIGNAL(clicked()), this, SLOT(toggleSpeedMode()));
|
|
|
|
l = myNetworkLabel = new QLabel;
|
|
h->addWidget( l );
|
|
|
|
h->addStretch( 1 );
|
|
|
|
l = myVisibleCountLabel = new QLabel( this );
|
|
h->addWidget( l );
|
|
|
|
h->addStretch( 1 );
|
|
|
|
a = new QActionGroup( this );
|
|
a->addAction( ui.action_TotalRatio );
|
|
a->addAction( ui.action_TotalTransfer );
|
|
a->addAction( ui.action_SessionRatio );
|
|
a->addAction( ui.action_SessionTransfer );
|
|
m = new QMenu( );
|
|
m->addAction( ui.action_TotalRatio );
|
|
m->addAction( ui.action_TotalTransfer );
|
|
m->addAction( ui.action_SessionRatio );
|
|
m->addAction( ui.action_SessionTransfer );
|
|
connect( ui.action_TotalRatio, SIGNAL(triggered()), this, SLOT(showTotalRatio()));
|
|
connect( ui.action_TotalTransfer, SIGNAL(triggered()), this, SLOT(showTotalTransfer()));
|
|
connect( ui.action_SessionRatio, SIGNAL(triggered()), this, SLOT(showSessionRatio()));
|
|
connect( ui.action_SessionTransfer, SIGNAL(triggered()), this, SLOT(showSessionTransfer()));
|
|
p = myStatsModeButton = new TrIconPushButton( this );
|
|
p->setIcon( QIcon( ":/icons/ratio.png" ) );
|
|
p->setIconSize( QPixmap( ":/icons/ratio.png" ).size() );
|
|
p->setFlat( true );
|
|
p->setMenu( m );
|
|
h->addWidget( p );
|
|
l = myStatsLabel = new QLabel( this );
|
|
h->addWidget( l );
|
|
|
|
h->addStretch( 1 );
|
|
|
|
l = myDownloadSpeedLabel = new QLabel( this );
|
|
const int minimumSpeedWidth = l->fontMetrics().width( Formatter::speedToString(Speed::fromKBps(999.99)));
|
|
l->setMinimumWidth( minimumSpeedWidth );
|
|
l->setAlignment( Qt::AlignRight|Qt::AlignVCenter );
|
|
h->addWidget( l );
|
|
l = new QLabel( this );
|
|
l->setPixmap( getStockIcon( "go-down", QStyle::SP_ArrowDown ).pixmap( smallIconSize ) );
|
|
h->addWidget( l );
|
|
|
|
h->addStretch( 1 );
|
|
|
|
l = myUploadSpeedLabel = new QLabel;
|
|
l->setMinimumWidth( minimumSpeedWidth );
|
|
l->setAlignment( Qt::AlignRight|Qt::AlignVCenter );
|
|
h->addWidget( l );
|
|
l = new QLabel;
|
|
l->setPixmap( getStockIcon( "go-up", QStyle::SP_ArrowUp ).pixmap( smallIconSize ) );
|
|
h->addWidget( l );
|
|
|
|
return top;
|
|
}
|
|
|
|
QMenu *
|
|
TrMainWindow :: createOptionsMenu( )
|
|
{
|
|
QMenu * menu;
|
|
QMenu * sub;
|
|
QAction * a;
|
|
QActionGroup * g;
|
|
|
|
QList<int> stockSpeeds;
|
|
stockSpeeds << 5 << 10 << 20 << 30 << 40 << 50 << 75 << 100 << 150 << 200 << 250 << 500 << 750;
|
|
QList<double> stockRatios;
|
|
stockRatios << 0.25 << 0.50 << 0.75 << 1 << 1.5 << 2 << 3;
|
|
|
|
menu = new QMenu;
|
|
sub = menu->addMenu( tr( "Limit Download Speed" ) );
|
|
int currentVal = myPrefs.get<int>( Prefs::DSPEED );
|
|
g = new QActionGroup( this );
|
|
a = myDlimitOffAction = sub->addAction( tr( "Unlimited" ) );
|
|
a->setCheckable( true );
|
|
a->setProperty( PREF_VARIANTS_KEY, QVariantList() << Prefs::DSPEED_ENABLED << false );
|
|
g->addAction( a );
|
|
connect( a, SIGNAL(triggered(bool)), this, SLOT(onSetPrefs(bool)) );
|
|
a = myDlimitOnAction = sub->addAction( tr( "Limited at %1" ).arg( Formatter::speedToString( Speed::fromKBps( currentVal ) ) ) );
|
|
a->setCheckable( true );
|
|
a->setProperty( PREF_VARIANTS_KEY, QVariantList() << Prefs::DSPEED << currentVal << Prefs::DSPEED_ENABLED << true );
|
|
g->addAction( a );
|
|
connect( a, SIGNAL(triggered(bool)), this, SLOT(onSetPrefs(bool)) );
|
|
sub->addSeparator( );
|
|
foreach( int i, stockSpeeds ) {
|
|
a = sub->addAction( Formatter::speedToString( Speed::fromKBps( i ) ) );
|
|
a->setProperty( PREF_VARIANTS_KEY, QVariantList() << Prefs::DSPEED << i << Prefs::DSPEED_ENABLED << true );
|
|
connect( a, SIGNAL(triggered(bool)), this, SLOT(onSetPrefs()));
|
|
}
|
|
|
|
sub = menu->addMenu( tr( "Limit Upload Speed" ) );
|
|
currentVal = myPrefs.get<int>( Prefs::USPEED );
|
|
g = new QActionGroup( this );
|
|
a = myUlimitOffAction = sub->addAction( tr( "Unlimited" ) );
|
|
a->setCheckable( true );
|
|
a->setProperty( PREF_VARIANTS_KEY, QVariantList() << Prefs::USPEED_ENABLED << false );
|
|
g->addAction( a );
|
|
connect( a, SIGNAL(triggered(bool)), this, SLOT(onSetPrefs(bool)) );
|
|
a = myUlimitOnAction = sub->addAction( tr( "Limited at %1" ).arg( Formatter::speedToString( Speed::fromKBps( currentVal ) ) ) );
|
|
a->setCheckable( true );
|
|
a->setProperty( PREF_VARIANTS_KEY, QVariantList() << Prefs::USPEED << currentVal << Prefs::USPEED_ENABLED << true );
|
|
g->addAction( a );
|
|
connect( a, SIGNAL(triggered(bool)), this, SLOT(onSetPrefs(bool)) );
|
|
sub->addSeparator( );
|
|
foreach( int i, stockSpeeds ) {
|
|
a = sub->addAction( Formatter::speedToString( Speed::fromKBps( i ) ) );
|
|
a->setProperty( PREF_VARIANTS_KEY, QVariantList() << Prefs::USPEED << i << Prefs::USPEED_ENABLED << true );
|
|
connect( a, SIGNAL(triggered(bool)), this, SLOT(onSetPrefs()));
|
|
}
|
|
|
|
menu->addSeparator( );
|
|
sub = menu->addMenu( tr( "Stop Seeding at Ratio" ) );
|
|
|
|
double d = myPrefs.get<double>( Prefs::RATIO );
|
|
g = new QActionGroup( this );
|
|
a = myRatioOffAction = sub->addAction( tr( "Seed Forever" ) );
|
|
a->setCheckable( true );
|
|
a->setProperty( PREF_VARIANTS_KEY, QVariantList() << Prefs::RATIO_ENABLED << false );
|
|
g->addAction( a );
|
|
connect( a, SIGNAL(triggered(bool)), this, SLOT(onSetPrefs(bool)) );
|
|
a = myRatioOnAction = sub->addAction( tr( "Stop at Ratio (%1)" ).arg( Formatter::ratioToString( d ) ) );
|
|
a->setCheckable( true );
|
|
a->setProperty( PREF_VARIANTS_KEY, QVariantList() << Prefs::RATIO << d << Prefs::RATIO_ENABLED << true );
|
|
g->addAction( a );
|
|
connect( a, SIGNAL(triggered(bool)), this, SLOT(onSetPrefs(bool)) );
|
|
sub->addSeparator( );
|
|
foreach( double i, stockRatios ) {
|
|
a = sub->addAction( Formatter::ratioToString( i ) );
|
|
a->setProperty( PREF_VARIANTS_KEY, QVariantList() << Prefs::RATIO << i << Prefs::RATIO_ENABLED << true );
|
|
connect( a, SIGNAL(triggered(bool)), this, SLOT(onSetPrefs()));
|
|
}
|
|
|
|
return menu;
|
|
}
|
|
|
|
/****
|
|
*****
|
|
****/
|
|
|
|
void
|
|
TrMainWindow :: setSortPref( int i )
|
|
{
|
|
myPrefs.set( Prefs::SORT_MODE, SortMode( i ) );
|
|
}
|
|
void TrMainWindow :: onSortByActivityToggled ( bool b ) { if( b ) setSortPref( SortMode::SORT_BY_ACTIVITY ); }
|
|
void TrMainWindow :: onSortByAgeToggled ( bool b ) { if( b ) setSortPref( SortMode::SORT_BY_AGE ); }
|
|
void TrMainWindow :: onSortByETAToggled ( bool b ) { if( b ) setSortPref( SortMode::SORT_BY_ETA ); }
|
|
void TrMainWindow :: onSortByNameToggled ( bool b ) { if( b ) setSortPref( SortMode::SORT_BY_NAME ); }
|
|
void TrMainWindow :: onSortByProgressToggled ( bool b ) { if( b ) setSortPref( SortMode::SORT_BY_PROGRESS ); }
|
|
void TrMainWindow :: onSortByQueueToggled ( bool b ) { if( b ) setSortPref( SortMode::SORT_BY_QUEUE ); }
|
|
void TrMainWindow :: onSortByRatioToggled ( bool b ) { if( b ) setSortPref( SortMode::SORT_BY_RATIO ); }
|
|
void TrMainWindow :: onSortBySizeToggled ( bool b ) { if( b ) setSortPref( SortMode::SORT_BY_SIZE ); }
|
|
void TrMainWindow :: onSortByStateToggled ( bool b ) { if( b ) setSortPref( SortMode::SORT_BY_STATE ); }
|
|
|
|
void
|
|
TrMainWindow :: setSortAscendingPref( bool b )
|
|
{
|
|
myPrefs.set( Prefs::SORT_REVERSED, b );
|
|
}
|
|
|
|
/****
|
|
*****
|
|
****/
|
|
|
|
void
|
|
TrMainWindow :: showEvent( QShowEvent * event )
|
|
{
|
|
Q_UNUSED (event);
|
|
|
|
ui.action_ShowMainWindow->setChecked(true);
|
|
}
|
|
|
|
/****
|
|
*****
|
|
****/
|
|
|
|
void
|
|
TrMainWindow :: hideEvent( QHideEvent * event )
|
|
{
|
|
Q_UNUSED (event);
|
|
|
|
if (!isVisible())
|
|
ui.action_ShowMainWindow->setChecked(false);
|
|
}
|
|
|
|
/****
|
|
*****
|
|
****/
|
|
|
|
void
|
|
TrMainWindow :: onPrefsDestroyed( )
|
|
{
|
|
myPrefsDialog = 0;
|
|
}
|
|
|
|
void
|
|
TrMainWindow :: openPreferences( )
|
|
{
|
|
if( myPrefsDialog == 0 ) {
|
|
myPrefsDialog = new PrefsDialog( mySession, myPrefs, this );
|
|
connect( myPrefsDialog, SIGNAL(destroyed(QObject*)), this, SLOT(onPrefsDestroyed()));
|
|
}
|
|
|
|
myPrefsDialog->show( );
|
|
}
|
|
|
|
void
|
|
TrMainWindow :: onDetailsDestroyed( )
|
|
{
|
|
myDetailsDialog = 0;
|
|
}
|
|
|
|
void
|
|
TrMainWindow :: openProperties( )
|
|
{
|
|
if( myDetailsDialog == 0 ) {
|
|
myDetailsDialog = new Details( mySession, myPrefs, myModel, this );
|
|
connect( myDetailsDialog, SIGNAL(destroyed(QObject*)), this, SLOT(onDetailsDestroyed()));
|
|
}
|
|
|
|
myDetailsDialog->setIds( getSelectedTorrents( ) );
|
|
myDetailsDialog->show( );
|
|
}
|
|
|
|
void
|
|
TrMainWindow :: setLocation( )
|
|
{
|
|
QDialog * d = new RelocateDialog( mySession, myModel, getSelectedTorrents(), this );
|
|
d->show( );
|
|
}
|
|
|
|
// Open Folder & select torrent's file or top folder
|
|
#undef HAVE_OPEN_SELECT
|
|
#if defined(Q_OS_WIN)
|
|
# define HAVE_OPEN_SELECT
|
|
static
|
|
void openSelect(const QString& path)
|
|
{
|
|
const QString explorer = "explorer";
|
|
QString param;
|
|
if (!QFileInfo(path).isDir())
|
|
param = QLatin1String("/select,");
|
|
param += QDir::toNativeSeparators(path);
|
|
QProcess::startDetached(explorer, QStringList(param));
|
|
}
|
|
#elif defined(Q_OS_MAC)
|
|
# define HAVE_OPEN_SELECT
|
|
static
|
|
void openSelect(const QString& path)
|
|
{
|
|
QStringList scriptArgs;
|
|
scriptArgs << QLatin1String("-e")
|
|
<< QString::fromLatin1("tell application \"Finder\" to reveal POSIX file \"%1\"")
|
|
.arg(path);
|
|
QProcess::execute(QLatin1String("/usr/bin/osascript"), scriptArgs);
|
|
scriptArgs.clear();
|
|
scriptArgs << QLatin1String("-e")
|
|
<< QLatin1String("tell application \"Finder\" to activate");
|
|
QProcess::execute("/usr/bin/osascript", scriptArgs);
|
|
}
|
|
#endif
|
|
|
|
void
|
|
TrMainWindow :: openFolder( )
|
|
{
|
|
const int torrentId( *getSelectedTorrents().begin() );
|
|
const Torrent * tor( myModel.getTorrentFromId( torrentId ) );
|
|
QString path( tor->getPath( ) );
|
|
const FileList files = tor->files();
|
|
const QString firstfile = files.at(0).filename;
|
|
int slashIndex = firstfile.indexOf('/');
|
|
if (slashIndex > -1) {
|
|
path = path + "/" + firstfile.left(slashIndex);
|
|
}
|
|
#ifdef HAVE_OPEN_SELECT
|
|
else {
|
|
openSelect( path + "/" + firstfile );
|
|
return;
|
|
}
|
|
#endif
|
|
QDesktopServices :: openUrl( QUrl::fromLocalFile( path ) );
|
|
}
|
|
|
|
void
|
|
TrMainWindow :: copyMagnetLinkToClipboard( )
|
|
{
|
|
const int id( *getSelectedTorrents().begin() );
|
|
mySession.copyMagnetLinkToClipboard( id );
|
|
}
|
|
|
|
void
|
|
TrMainWindow :: openDonate( )
|
|
{
|
|
QDesktopServices :: openUrl( QUrl( "http://www.transmissionbt.com/donate.php" ) );
|
|
}
|
|
|
|
void
|
|
TrMainWindow :: openHelp( )
|
|
{
|
|
const char * fmt = "http://www.transmissionbt.com/help/gtk/%d.%dx";
|
|
int major, minor;
|
|
sscanf( SHORT_VERSION_STRING, "%d.%d", &major, &minor );
|
|
char url[128];
|
|
tr_snprintf( url, sizeof( url ), fmt, major, minor/10 );
|
|
QDesktopServices :: openUrl( QUrl( url ) );
|
|
}
|
|
|
|
void
|
|
TrMainWindow :: refreshTitle( )
|
|
{
|
|
QString title( "Transmission" );
|
|
const QUrl url( mySession.getRemoteUrl( ) );
|
|
if( !url.isEmpty() )
|
|
title += tr( " - %1:%2" ).arg( url.host() ).arg( url.port() );
|
|
setWindowTitle( title );
|
|
}
|
|
|
|
void
|
|
TrMainWindow :: refreshVisibleCount( )
|
|
{
|
|
const int visibleCount( myFilterModel.rowCount( ) );
|
|
const int totalCount( visibleCount + myFilterModel.hiddenRowCount( ) );
|
|
QString str;
|
|
if( visibleCount == totalCount )
|
|
str = tr( "%Ln Torrent(s)", 0, totalCount );
|
|
else
|
|
str = tr( "%L1 of %Ln Torrent(s)", 0, totalCount ).arg( visibleCount );
|
|
myVisibleCountLabel->setText( str );
|
|
myVisibleCountLabel->setVisible( totalCount > 0 );
|
|
}
|
|
|
|
void
|
|
TrMainWindow :: refreshTrayIconSoon( )
|
|
{
|
|
if( !myRefreshTrayIconTimer.isActive( ) )
|
|
{
|
|
myRefreshTrayIconTimer.setSingleShot( true );
|
|
myRefreshTrayIconTimer.start( 100 );
|
|
}
|
|
}
|
|
void
|
|
TrMainWindow :: refreshTrayIcon( )
|
|
{
|
|
const QString idle = tr( "Idle" );
|
|
const Speed u (myModel.getUploadSpeed());
|
|
const Speed d (myModel.getDownloadSpeed());
|
|
|
|
myTrayIcon.setToolTip( tr( "Transmission\nUp: %1\nDown: %2" )
|
|
.arg( u.isZero() ? idle : Formatter::speedToString( u ) )
|
|
.arg( d.isZero() ? idle : Formatter::speedToString( d ) ) );
|
|
}
|
|
|
|
void
|
|
TrMainWindow :: refreshStatusBar( )
|
|
{
|
|
const Speed up( myModel.getUploadSpeed( ) );
|
|
const Speed down( myModel.getDownloadSpeed( ) );
|
|
myUploadSpeedLabel->setText( Formatter:: speedToString( up ) );
|
|
myDownloadSpeedLabel->setText( Formatter:: speedToString( down ) );
|
|
|
|
myNetworkLabel->setVisible( !mySession.isServer( ) );
|
|
|
|
const QString mode( myPrefs.getString( Prefs::STATUSBAR_STATS ) );
|
|
QString str;
|
|
|
|
if( mode == "session-ratio" )
|
|
{
|
|
str = tr( "Ratio: %1" ).arg( Formatter:: ratioToString( mySession.getStats().ratio ) );
|
|
}
|
|
else if( mode == "session-transfer" )
|
|
{
|
|
const tr_session_stats& stats( mySession.getStats( ) );
|
|
str = tr( "Down: %1, Up: %2" ).arg( Formatter:: sizeToString( stats.downloadedBytes ) )
|
|
.arg( Formatter:: sizeToString( stats.uploadedBytes ) );
|
|
}
|
|
else if( mode == "total-transfer" )
|
|
{
|
|
const tr_session_stats& stats( mySession.getCumulativeStats( ) );
|
|
str = tr( "Down: %1, Up: %2" ).arg( Formatter:: sizeToString( stats.downloadedBytes ) )
|
|
.arg( Formatter:: sizeToString( stats.uploadedBytes ) );
|
|
}
|
|
else // default is "total-ratio"
|
|
{
|
|
str = tr( "Ratio: %1" ).arg( Formatter:: ratioToString( mySession.getCumulativeStats().ratio ) );
|
|
}
|
|
|
|
myStatsLabel->setText( str );
|
|
}
|
|
|
|
|
|
|
|
void
|
|
TrMainWindow :: refreshActionSensitivitySoon( )
|
|
{
|
|
if( !myRefreshActionSensitivityTimer.isActive( ) )
|
|
{
|
|
myRefreshActionSensitivityTimer.setSingleShot( true );
|
|
myRefreshActionSensitivityTimer.start( 100 );
|
|
}
|
|
}
|
|
void
|
|
TrMainWindow :: refreshActionSensitivity( )
|
|
{
|
|
int selected( 0 );
|
|
int paused( 0 );
|
|
int queued( 0 );
|
|
int selectedAndPaused( 0 );
|
|
int selectedAndQueued( 0 );
|
|
int canAnnounce( 0 );
|
|
const QAbstractItemModel * model( ui.listView->model( ) );
|
|
const QItemSelectionModel * selectionModel( ui.listView->selectionModel( ) );
|
|
const int rowCount( model->rowCount( ) );
|
|
|
|
// count how many torrents are selected, paused, etc
|
|
for( int row=0; row<rowCount; ++row ) {
|
|
const QModelIndex modelIndex( model->index( row, 0 ) );
|
|
assert( model == modelIndex.model( ) );
|
|
const Torrent * tor( model->data( modelIndex, TorrentModel::TorrentRole ).value<const Torrent*>( ) );
|
|
if( tor ) {
|
|
const bool isSelected( selectionModel->isSelected( modelIndex ) );
|
|
const bool isPaused( tor->isPaused( ) );
|
|
const bool isQueued( tor->isQueued( ) );
|
|
if( isSelected ) ++selected;
|
|
if( isQueued ) ++queued;
|
|
if( isPaused ) ++ paused;
|
|
if( isSelected && isPaused ) ++selectedAndPaused;
|
|
if( isSelected && isQueued ) ++selectedAndQueued;
|
|
if( tor->canManualAnnounce( ) ) ++canAnnounce;
|
|
}
|
|
}
|
|
|
|
const bool haveSelection( selected > 0 );
|
|
ui.action_Verify->setEnabled( haveSelection );
|
|
ui.action_Remove->setEnabled( haveSelection );
|
|
ui.action_Delete->setEnabled( haveSelection );
|
|
ui.action_Properties->setEnabled( haveSelection );
|
|
ui.action_DeselectAll->setEnabled( haveSelection );
|
|
ui.action_SetLocation->setEnabled( haveSelection );
|
|
|
|
const bool oneSelection( selected == 1 );
|
|
ui.action_OpenFolder->setEnabled( oneSelection && mySession.isLocal( ) );
|
|
ui.action_CopyMagnetToClipboard->setEnabled( oneSelection );
|
|
|
|
ui.action_SelectAll->setEnabled( selected < rowCount );
|
|
ui.action_StartAll->setEnabled( paused > 0 );
|
|
ui.action_PauseAll->setEnabled( paused < rowCount );
|
|
ui.action_Start->setEnabled( selectedAndPaused > 0 );
|
|
ui.action_StartNow->setEnabled( selectedAndPaused + selectedAndQueued > 0 );
|
|
ui.action_Pause->setEnabled( selectedAndPaused < selected );
|
|
ui.action_Announce->setEnabled( selected > 0 && ( canAnnounce == selected ) );
|
|
|
|
ui.action_QueueMoveTop->setEnabled( haveSelection );
|
|
ui.action_QueueMoveUp->setEnabled( haveSelection );
|
|
ui.action_QueueMoveDown->setEnabled( haveSelection );
|
|
ui.action_QueueMoveBottom->setEnabled( haveSelection );
|
|
|
|
if( myDetailsDialog )
|
|
myDetailsDialog->setIds( getSelectedTorrents( ) );
|
|
}
|
|
|
|
/**
|
|
***
|
|
**/
|
|
|
|
void
|
|
TrMainWindow :: clearSelection( )
|
|
{
|
|
ui.action_DeselectAll->trigger( );
|
|
}
|
|
|
|
QSet<int>
|
|
TrMainWindow :: getSelectedTorrents( ) const
|
|
{
|
|
QSet<int> ids;
|
|
|
|
foreach( QModelIndex index, ui.listView->selectionModel( )->selectedRows( ) )
|
|
{
|
|
const Torrent * tor( index.data( TorrentModel::TorrentRole ).value<const Torrent*>( ) );
|
|
ids.insert( tor->id( ) );
|
|
}
|
|
|
|
return ids;
|
|
}
|
|
|
|
void
|
|
TrMainWindow :: startSelected( )
|
|
{
|
|
mySession.startTorrents( getSelectedTorrents( ) );
|
|
}
|
|
void
|
|
TrMainWindow :: startSelectedNow( )
|
|
{
|
|
mySession.startTorrentsNow( getSelectedTorrents( ) );
|
|
}
|
|
void
|
|
TrMainWindow :: pauseSelected( )
|
|
{
|
|
mySession.pauseTorrents( getSelectedTorrents( ) );
|
|
}
|
|
void
|
|
TrMainWindow :: queueMoveTop( )
|
|
{
|
|
mySession.queueMoveTop( getSelectedTorrents( ) );
|
|
}
|
|
void
|
|
TrMainWindow :: queueMoveUp( )
|
|
{
|
|
mySession.queueMoveUp( getSelectedTorrents( ) );
|
|
}
|
|
void
|
|
TrMainWindow :: queueMoveDown( )
|
|
{
|
|
mySession.queueMoveDown( getSelectedTorrents( ) );
|
|
}
|
|
void
|
|
TrMainWindow :: queueMoveBottom( )
|
|
{
|
|
mySession.queueMoveBottom( getSelectedTorrents( ) );
|
|
}
|
|
void
|
|
TrMainWindow :: startAll( )
|
|
{
|
|
mySession.startTorrents( );
|
|
}
|
|
void
|
|
TrMainWindow :: pauseAll( )
|
|
{
|
|
mySession.pauseTorrents( );
|
|
}
|
|
void
|
|
TrMainWindow :: removeSelected( )
|
|
{
|
|
removeTorrents( false );
|
|
}
|
|
void
|
|
TrMainWindow :: deleteSelected( )
|
|
{
|
|
removeTorrents( true );
|
|
}
|
|
void
|
|
TrMainWindow :: verifySelected( )
|
|
{
|
|
mySession.verifyTorrents( getSelectedTorrents( ) );
|
|
}
|
|
void
|
|
TrMainWindow :: reannounceSelected( )
|
|
{
|
|
mySession.reannounceTorrents( getSelectedTorrents( ) );
|
|
}
|
|
|
|
/**
|
|
***
|
|
**/
|
|
|
|
void TrMainWindow :: showTotalRatio ( ) { myPrefs.set( Prefs::STATUSBAR_STATS, "total-ratio"); }
|
|
void TrMainWindow :: showTotalTransfer ( ) { myPrefs.set( Prefs::STATUSBAR_STATS, "total-transfer"); }
|
|
void TrMainWindow :: showSessionRatio ( ) { myPrefs.set( Prefs::STATUSBAR_STATS, "session-ratio"); }
|
|
void TrMainWindow :: showSessionTransfer ( ) { myPrefs.set( Prefs::STATUSBAR_STATS, "session-transfer"); }
|
|
|
|
/**
|
|
***
|
|
**/
|
|
|
|
void
|
|
TrMainWindow :: setCompactView( bool visible )
|
|
{
|
|
myPrefs.set( Prefs :: COMPACT_VIEW, visible );
|
|
}
|
|
void
|
|
TrMainWindow :: toggleSpeedMode( )
|
|
{
|
|
bool mode;
|
|
|
|
myPrefs.toggleBool( Prefs :: ALT_SPEED_LIMIT_ENABLED );
|
|
mode = myPrefs.get<bool>( Prefs::ALT_SPEED_LIMIT_ENABLED );
|
|
myAltSpeedAction->setIcon( mode ? mySpeedModeOnIcon : mySpeedModeOffIcon );
|
|
}
|
|
void
|
|
TrMainWindow :: setToolbarVisible( bool visible )
|
|
{
|
|
myPrefs.set( Prefs::TOOLBAR, visible );
|
|
}
|
|
void
|
|
TrMainWindow :: setFilterbarVisible( bool visible )
|
|
{
|
|
myPrefs.set( Prefs::FILTERBAR, visible );
|
|
}
|
|
void
|
|
TrMainWindow :: setStatusbarVisible( bool visible )
|
|
{
|
|
myPrefs.set( Prefs::STATUSBAR, visible );
|
|
}
|
|
|
|
/**
|
|
***
|
|
**/
|
|
|
|
void
|
|
TrMainWindow :: toggleWindows( bool doShow )
|
|
{
|
|
if( !doShow )
|
|
{
|
|
hide( );
|
|
}
|
|
else
|
|
{
|
|
if ( !isVisible( ) ) show( );
|
|
if ( isMinimized( ) ) showNormal( );
|
|
//activateWindow( );
|
|
raise( );
|
|
QApplication::setActiveWindow( this );
|
|
}
|
|
}
|
|
|
|
void
|
|
TrMainWindow :: trayActivated( QSystemTrayIcon::ActivationReason reason )
|
|
{
|
|
if( reason == QSystemTrayIcon::Trigger )
|
|
{
|
|
if( isMinimized ( ) )
|
|
toggleWindows( true );
|
|
else
|
|
toggleWindows( !isVisible() );
|
|
}
|
|
}
|
|
|
|
|
|
void
|
|
TrMainWindow :: refreshPref( int key )
|
|
{
|
|
bool b;
|
|
int i;
|
|
QString str;
|
|
|
|
switch( key )
|
|
{
|
|
case Prefs::STATUSBAR_STATS:
|
|
str = myPrefs.getString( key );
|
|
ui.action_TotalRatio->setChecked ( str == "total-ratio" );
|
|
ui.action_TotalTransfer->setChecked ( str == "total-transfer" );
|
|
ui.action_SessionRatio->setChecked ( str == "session-ratio" );
|
|
ui.action_SessionTransfer->setChecked( str == "session-transfer" );
|
|
refreshStatusBar( );
|
|
break;
|
|
|
|
case Prefs::SORT_REVERSED:
|
|
ui.action_ReverseSortOrder->setChecked( myPrefs.getBool( key ) );
|
|
break;
|
|
|
|
case Prefs::SORT_MODE:
|
|
i = myPrefs.get<SortMode>(key).mode( );
|
|
ui.action_SortByActivity->setChecked ( i == SortMode::SORT_BY_ACTIVITY );
|
|
ui.action_SortByAge->setChecked ( i == SortMode::SORT_BY_AGE );
|
|
ui.action_SortByETA->setChecked ( i == SortMode::SORT_BY_ETA );
|
|
ui.action_SortByName->setChecked ( i == SortMode::SORT_BY_NAME );
|
|
ui.action_SortByProgress->setChecked ( i == SortMode::SORT_BY_PROGRESS );
|
|
ui.action_SortByQueue->setChecked ( i == SortMode::SORT_BY_QUEUE );
|
|
ui.action_SortByRatio->setChecked ( i == SortMode::SORT_BY_RATIO );
|
|
ui.action_SortBySize->setChecked ( i == SortMode::SORT_BY_SIZE );
|
|
ui.action_SortByState->setChecked ( i == SortMode::SORT_BY_STATE );
|
|
break;
|
|
|
|
case Prefs::DSPEED_ENABLED:
|
|
(myPrefs.get<bool>(key) ? myDlimitOnAction : myDlimitOffAction)->setChecked( true );
|
|
break;
|
|
|
|
case Prefs::DSPEED:
|
|
myDlimitOnAction->setText( tr( "Limited at %1" ).arg( Formatter::speedToString( Speed::fromKBps( myPrefs.get<int>(key) ) ) ) );
|
|
break;
|
|
|
|
case Prefs::USPEED_ENABLED:
|
|
(myPrefs.get<bool>(key) ? myUlimitOnAction : myUlimitOffAction)->setChecked( true );
|
|
break;
|
|
|
|
case Prefs::USPEED:
|
|
myUlimitOnAction->setText( tr( "Limited at %1" ).arg( Formatter::speedToString( Speed::fromKBps( myPrefs.get<int>(key) ) ) ) );
|
|
break;
|
|
|
|
case Prefs::RATIO_ENABLED:
|
|
(myPrefs.get<bool>(key) ? myRatioOnAction : myRatioOffAction)->setChecked( true );
|
|
break;
|
|
|
|
case Prefs::RATIO:
|
|
myRatioOnAction->setText( tr( "Stop at Ratio (%1)" ).arg( Formatter::ratioToString( myPrefs.get<double>(key) ) ) );
|
|
break;
|
|
|
|
case Prefs::FILTERBAR:
|
|
b = myPrefs.getBool( key );
|
|
myFilterBar->setVisible( b );
|
|
ui.action_Filterbar->setChecked( b );
|
|
break;
|
|
|
|
case Prefs::STATUSBAR:
|
|
b = myPrefs.getBool( key );
|
|
myStatusBar->setVisible( b );
|
|
ui.action_Statusbar->setChecked( b );
|
|
break;
|
|
|
|
case Prefs::TOOLBAR:
|
|
b = myPrefs.getBool( key );
|
|
ui.toolBar->setVisible( b );
|
|
ui.action_Toolbar->setChecked( b );
|
|
break;
|
|
|
|
case Prefs::SHOW_TRAY_ICON:
|
|
b = myPrefs.getBool( key );
|
|
ui.action_TrayIcon->setChecked( b );
|
|
myTrayIcon.setVisible( b );
|
|
dynamic_cast<MyApp*>(QCoreApplication::instance())->setQuitOnLastWindowClosed(!b);
|
|
refreshTrayIconSoon( );
|
|
break;
|
|
|
|
case Prefs::COMPACT_VIEW: {
|
|
QItemSelectionModel * selectionModel( ui.listView->selectionModel( ) );
|
|
const QItemSelection selection( selectionModel->selection( ) );
|
|
const QModelIndex currentIndex( selectionModel->currentIndex( ) );
|
|
b = myPrefs.getBool( key );
|
|
ui.action_CompactView->setChecked( b );
|
|
ui.listView->setItemDelegate( b ? myTorrentDelegateMin : myTorrentDelegate );
|
|
selectionModel->clear( );
|
|
ui.listView->reset( ); // force the rows to resize
|
|
selectionModel->select( selection, QItemSelectionModel::Select );
|
|
selectionModel->setCurrentIndex( currentIndex, QItemSelectionModel::NoUpdate );
|
|
break;
|
|
}
|
|
|
|
case Prefs::MAIN_WINDOW_X:
|
|
case Prefs::MAIN_WINDOW_Y:
|
|
case Prefs::MAIN_WINDOW_WIDTH:
|
|
case Prefs::MAIN_WINDOW_HEIGHT:
|
|
setGeometry( myPrefs.getInt( Prefs::MAIN_WINDOW_X ),
|
|
myPrefs.getInt( Prefs::MAIN_WINDOW_Y ),
|
|
myPrefs.getInt( Prefs::MAIN_WINDOW_WIDTH ),
|
|
myPrefs.getInt( Prefs::MAIN_WINDOW_HEIGHT ) );
|
|
break;
|
|
|
|
case Prefs :: ALT_SPEED_LIMIT_ENABLED:
|
|
case Prefs :: ALT_SPEED_LIMIT_UP:
|
|
case Prefs :: ALT_SPEED_LIMIT_DOWN: {
|
|
b = myPrefs.getBool( Prefs :: ALT_SPEED_LIMIT_ENABLED );
|
|
myAltSpeedButton->setChecked( b );
|
|
myAltSpeedButton->setIcon( b ? mySpeedModeOnIcon : mySpeedModeOffIcon );
|
|
const QString fmt = b ? tr( "Click to disable Temporary Speed Limits\n(%1 down, %2 up)" )
|
|
: tr( "Click to enable Temporary Speed Limits\n(%1 down, %2 up)" );
|
|
const Speed d = Speed::fromKBps( myPrefs.getInt( Prefs::ALT_SPEED_LIMIT_DOWN ) );
|
|
const Speed u = Speed::fromKBps( myPrefs.getInt( Prefs::ALT_SPEED_LIMIT_UP ) );
|
|
myAltSpeedButton->setToolTip( fmt.arg( Formatter::speedToString( d ) )
|
|
.arg( Formatter::speedToString( u ) ) );
|
|
break;
|
|
}
|
|
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
/***
|
|
****
|
|
***/
|
|
|
|
void
|
|
TrMainWindow :: newTorrent( )
|
|
{
|
|
MakeDialog * dialog = new MakeDialog( mySession, this );
|
|
dialog->show( );
|
|
}
|
|
|
|
void
|
|
TrMainWindow :: openTorrent( )
|
|
{
|
|
QFileDialog * myFileDialog;
|
|
myFileDialog = new QFileDialog( this,
|
|
tr( "Open Torrent" ),
|
|
myPrefs.getString( Prefs::OPEN_DIALOG_FOLDER ),
|
|
tr( "Torrent Files (*.torrent);;All Files (*.*)" ) );
|
|
myFileDialog->setFileMode( QFileDialog::ExistingFiles );
|
|
myFileDialog->setAttribute( Qt::WA_DeleteOnClose );
|
|
|
|
QCheckBox * button = new QCheckBox( tr( "Show &options dialog" ) );
|
|
button->setChecked( myPrefs.getBool( Prefs::OPTIONS_PROMPT ) );
|
|
QGridLayout * layout = dynamic_cast<QGridLayout*>(myFileDialog->layout());
|
|
layout->addWidget( button, layout->rowCount( ), 0, 1, -1, Qt::AlignLeft );
|
|
myFileDialogOptionsCheck = button;
|
|
|
|
connect( myFileDialog, SIGNAL(filesSelected(const QStringList&)),
|
|
this, SLOT(addTorrents(const QStringList&)));
|
|
|
|
myFileDialog->show( );
|
|
}
|
|
|
|
void
|
|
TrMainWindow :: openURL( )
|
|
{
|
|
QString str = QApplication::clipboard()->text( QClipboard::Selection );
|
|
|
|
if( !AddData::isSupported( str ) )
|
|
str = QApplication::clipboard()->text( QClipboard::Clipboard );
|
|
|
|
if( !AddData::isSupported( str ) )
|
|
str.clear();
|
|
|
|
openURL( str );
|
|
}
|
|
|
|
void
|
|
TrMainWindow :: openURL( QString url )
|
|
{
|
|
bool ok;
|
|
const QString key = QInputDialog::getText( this,
|
|
tr( "Open Link" ),
|
|
tr( "Open URL or Magnet Link" ),
|
|
QLineEdit::Normal,
|
|
url,
|
|
&ok,
|
|
Qt::WindowStaysOnTopHint );
|
|
if( ok && !key.isEmpty( ) )
|
|
mySession.addTorrent( key );
|
|
}
|
|
|
|
void
|
|
TrMainWindow :: addTorrents( const QStringList& filenames )
|
|
{
|
|
foreach( const QString& filename, filenames )
|
|
addTorrent( filename );
|
|
}
|
|
|
|
void
|
|
TrMainWindow :: addTorrent( const QString& filename )
|
|
{
|
|
if( !myFileDialogOptionsCheck->isChecked( ) ) {
|
|
mySession.addTorrent( filename );
|
|
QApplication :: alert ( this );
|
|
} else {
|
|
Options * o = new Options( mySession, myPrefs, filename, this );
|
|
o->show( );
|
|
QApplication :: alert( o );
|
|
}
|
|
}
|
|
|
|
void
|
|
TrMainWindow :: removeTorrents( const bool deleteFiles )
|
|
{
|
|
QSet<int> ids;
|
|
QMessageBox msgBox( this );
|
|
QString primary_text, secondary_text;
|
|
int incomplete = 0;
|
|
int connected = 0;
|
|
int count;
|
|
|
|
foreach( QModelIndex index, ui.listView->selectionModel( )->selectedRows( ) )
|
|
{
|
|
const Torrent * tor( index.data( TorrentModel::TorrentRole ).value<const Torrent*>( ) );
|
|
ids.insert( tor->id( ) );
|
|
if( tor->connectedPeers( ) )
|
|
++connected;
|
|
if( !tor->isDone( ) )
|
|
++incomplete;
|
|
}
|
|
|
|
if( ids.isEmpty() )
|
|
return;
|
|
count = ids.size();
|
|
|
|
if( !deleteFiles )
|
|
{
|
|
primary_text = ( count == 1 )
|
|
? tr( "Remove torrent?" )
|
|
: tr( "Remove %1 torrents?" ).arg( count );
|
|
}
|
|
else
|
|
{
|
|
primary_text = ( count == 1 )
|
|
? tr( "Delete this torrent's downloaded files?" )
|
|
: tr( "Delete these %1 torrents' downloaded files?" ).arg( count );
|
|
}
|
|
|
|
if( !incomplete && !connected )
|
|
{
|
|
secondary_text = ( count == 1 )
|
|
? tr( "Once removed, continuing the transfer will require the torrent file or magnet link." )
|
|
: tr( "Once removed, continuing the transfers will require the torrent files or magnet links." );
|
|
}
|
|
else if( count == incomplete )
|
|
{
|
|
secondary_text = ( count == 1 )
|
|
? tr( "This torrent has not finished downloading." )
|
|
: tr( "These torrents have not finished downloading." );
|
|
}
|
|
else if( count == connected )
|
|
{
|
|
secondary_text = ( count == 1 )
|
|
? tr( "This torrent is connected to peers." )
|
|
: tr( "These torrents are connected to peers." );
|
|
}
|
|
else
|
|
{
|
|
if( connected )
|
|
{
|
|
secondary_text = ( connected == 1 )
|
|
? tr( "One of these torrents is connected to peers." )
|
|
: tr( "Some of these torrents are connected to peers." );
|
|
}
|
|
|
|
if( connected && incomplete )
|
|
{
|
|
secondary_text += "\n";
|
|
}
|
|
|
|
if( incomplete )
|
|
{
|
|
secondary_text += ( incomplete == 1 )
|
|
? tr( "One of these torrents has not finished downloading." )
|
|
: tr( "Some of these torrents have not finished downloading." );
|
|
}
|
|
}
|
|
|
|
msgBox.setWindowTitle( QString(" ") );
|
|
msgBox.setText( QString( "<big><b>%1</big></b>" ).arg( primary_text ) );
|
|
msgBox.setInformativeText( secondary_text );
|
|
msgBox.setStandardButtons( QMessageBox::Ok | QMessageBox::Cancel );
|
|
msgBox.setDefaultButton( QMessageBox::Cancel );
|
|
msgBox.setIcon( QMessageBox::Question );
|
|
/* hack needed to keep the dialog from being too narrow */
|
|
QGridLayout* layout = (QGridLayout*)msgBox.layout();
|
|
QSpacerItem* spacer = new QSpacerItem( 450, 0, QSizePolicy::Minimum, QSizePolicy::Expanding );
|
|
layout->addItem( spacer, layout->rowCount(), 0, 1, layout->columnCount() );
|
|
|
|
if( msgBox.exec() == QMessageBox::Ok )
|
|
{
|
|
ui.listView->selectionModel()->clear();
|
|
mySession.removeTorrents( ids, deleteFiles );
|
|
}
|
|
}
|
|
|
|
/***
|
|
****
|
|
***/
|
|
|
|
void
|
|
TrMainWindow :: updateNetworkIcon( )
|
|
{
|
|
const time_t now = time( NULL );
|
|
const int period = 3;
|
|
const time_t secondsSinceLastSend = now - myLastSendTime;
|
|
const time_t secondsSinceLastRead = now - myLastReadTime;
|
|
const bool isSending = secondsSinceLastSend <= period;
|
|
const bool isReading = secondsSinceLastRead <= period;
|
|
const char * key;
|
|
|
|
if( isSending && isReading )
|
|
key = "network-transmit-receive";
|
|
else if( isSending )
|
|
key = "network-transmit";
|
|
else if( isReading )
|
|
key = "network-receive";
|
|
else
|
|
key = "network-idle";
|
|
const QIcon icon = getStockIcon (key, QStyle::SP_DriveNetIcon);
|
|
const QPixmap pixmap = icon.pixmap (16, 16);
|
|
|
|
QString tip;
|
|
const QString url = mySession.getRemoteUrl().host();
|
|
if( !myLastReadTime )
|
|
tip = tr( "%1 has not responded yet" ).arg (url);
|
|
else if( secondsSinceLastRead < 60 )
|
|
tip = tr( "%1 is responding" ).arg (url);
|
|
else if( secondsSinceLastRead < (60*10) )
|
|
tip = tr( "%1 last responded %2 ago" ).arg(url).arg(Formatter::timeToString(secondsSinceLastRead));
|
|
else
|
|
tip = tr( "%1 is not responding" ).arg (url);
|
|
|
|
myNetworkLabel->setPixmap (pixmap);
|
|
myNetworkLabel->setToolTip (tip);
|
|
}
|
|
|
|
void
|
|
TrMainWindow :: onNetworkTimer( )
|
|
{
|
|
updateNetworkIcon( );
|
|
}
|
|
|
|
void
|
|
TrMainWindow :: dataReadProgress( )
|
|
{
|
|
myLastReadTime = time( NULL );
|
|
updateNetworkIcon( );
|
|
}
|
|
|
|
void
|
|
TrMainWindow :: dataSendProgress( )
|
|
{
|
|
myLastSendTime = time( NULL );
|
|
updateNetworkIcon( );
|
|
}
|
|
|
|
void
|
|
TrMainWindow :: wrongAuthentication( )
|
|
{
|
|
mySession.stop( );
|
|
mySessionDialog->show( );
|
|
}
|
|
|
|
/***
|
|
****
|
|
***/
|
|
|
|
void
|
|
TrMainWindow :: dragEnterEvent( QDragEnterEvent * event )
|
|
{
|
|
const QMimeData * mime = event->mimeData( );
|
|
|
|
if( mime->hasFormat("application/x-bittorrent")
|
|
|| mime->text().trimmed().endsWith(".torrent", Qt::CaseInsensitive)
|
|
|| mime->text().startsWith("magnet:", Qt::CaseInsensitive) )
|
|
event->acceptProposedAction();
|
|
}
|
|
|
|
void
|
|
TrMainWindow :: dropEvent( QDropEvent * event )
|
|
{
|
|
QString key = event->mimeData()->text().trimmed();
|
|
|
|
const QUrl url( key );
|
|
if( url.scheme() == "file" )
|
|
key = QUrl::fromPercentEncoding( url.path().toUtf8( ) );
|
|
|
|
dynamic_cast<MyApp*>(QApplication::instance())->addTorrent( key );
|
|
}
|
|
|
|
/***
|
|
****
|
|
***/
|
|
|
|
void
|
|
TrMainWindow :: contextMenuEvent( QContextMenuEvent * event )
|
|
{
|
|
QMenu * menu = new QMenu( this );
|
|
|
|
menu->addAction( ui.action_Properties );
|
|
menu->addAction( ui.action_OpenFolder );
|
|
|
|
QAction * sep = new QAction( this );
|
|
sep->setSeparator( true );
|
|
menu->addAction( sep );
|
|
menu->addAction( ui.action_Start );
|
|
menu->addAction( ui.action_StartNow );
|
|
menu->addAction( ui.action_Announce );
|
|
QMenu * queueMenu = menu->addMenu( tr( "Queue" ) );
|
|
queueMenu->addAction( ui.action_QueueMoveTop );
|
|
queueMenu->addAction( ui.action_QueueMoveUp );
|
|
queueMenu->addAction( ui.action_QueueMoveDown );
|
|
queueMenu->addAction( ui.action_QueueMoveBottom );
|
|
menu->addAction( ui.action_Pause );
|
|
|
|
sep = new QAction( this );
|
|
sep->setSeparator( true );
|
|
menu->addAction( sep );
|
|
menu->addAction( ui.action_Verify );
|
|
menu->addAction( ui.action_SetLocation );
|
|
menu->addAction( ui.action_CopyMagnetToClipboard );
|
|
|
|
sep = new QAction( this );
|
|
sep->setSeparator( true );
|
|
menu->addAction( sep );
|
|
menu->addAction( ui.action_Remove );
|
|
menu->addAction( ui.action_Delete );
|
|
|
|
menu->popup( event->globalPos( ) );
|
|
}
|