(trunk) add the Qt beta into svn

This commit is contained in:
Charles Kerr 2009-04-09 18:55:47 +00:00
parent f7eaf6ccd9
commit 8cb90fd28f
61 changed files with 13050 additions and 0 deletions

42
qt/README.txt Normal file
View File

@ -0,0 +1,42 @@
STATUS
This application is very new and is in beta. There may be bugs!
Also, the name "QTransmission" is a placeholder.
VOLUNTEERS WANTED
- If you find a bug, please report it at http://trac.transmissionbt.com/
- New translations are encouraged
- Windows devs: it would be interesting to see if/how this works on Windows
- Suggestions for a better name than "QTransmission" would be good ;)
ABOUT QTRANSMISSION
QTransmission is a GUI for Transmission loosely based on the GTK+ client.
This is the only Transmission client that can act as its own self-contained
session (as the GTK+ and Mac clients do), and can also connect to a remote
session (as the web client and transmission-remote terminal client do).
Use Case 1: If you like to run BitTorrent for awhile from your desktop,
then the Mac, GTK+, and Qt clients are a good match.
Use Case 2: If you like to leave BitTorrent running nonstop on your
computer or router, and want to control it from your desktop or
from a remote site, then transmission-remote and the web and Qt clients
are a good match.
To use the Qt client as a remote: "qtransmission -r http://host:port/"
The Qt client is also the most likely to wind up running on Windows,
though that's not a high priority at the moment...
BUILDING
This currently is a little awkward. We're working on it...
1. Prerequisites: Qt >= 4.x and its development packages
2. Build Transmission as normal
3. In the qt/ directory, type "qmake-qt4 qtransmission.pro"
4. In the qt/ directory, type "make"

46
qt/about.cc Normal file
View File

@ -0,0 +1,46 @@
/*
* This file Copyright (C) 2009 Charles Kerr <charles@transmissionbt.com>
*
* This file is licensed by the GPL version 2. Works owned by the
* Transmission project are granted a special exemption to clause 2(b)
* so that the bulk of its code can remain under the MIT license.
* This exemption does not extend to derived works not owned by
* the Transmission project.
*
* $Id:$
*/
#include <QAbstractButton>
#include <QMovie>
#include <libtransmission/transmission.h>
#include <libtransmission/version.h>
#include "about.h"
namespace
{
QMovie * movie;
}
AboutDialog :: AboutDialog( QWidget * parent ):
QDialog( parent, Qt::Dialog )
{
ui.setupUi( this );
ui.label->setText( "Transmission " LONG_VERSION_STRING );
connect( ui.buttonBox, SIGNAL(clicked(QAbstractButton*)), this, SLOT(hide()));
movie = new QMovie( ":/icons/dance.gif" );
connect( movie, SIGNAL(frameChanged(int)), this, SLOT(onFrameChanged()));
movie->start( );
}
AboutDialog :: ~AboutDialog( )
{
}
void
AboutDialog :: onFrameChanged( )
{
ui.aboutLogo->setPixmap( movie->currentPixmap( ) );
}

38
qt/about.h Normal file
View File

@ -0,0 +1,38 @@
/*
* This file Copyright (C) 2009 Charles Kerr <charles@transmissionbt.com>
*
* This file is licensed by the GPL version 2. Works owned by the
* Transmission project are granted a special exemption to clause 2(b)
* so that the bulk of its code can remain under the MIT license.
* This exemption does not extend to derived works not owned by
* the Transmission project.
*
* $Id:$
*/
#ifndef ABOUT_DIALOG_H
#define ABOUT_DIALOG_H
class QAbstractButton;
class QIcon;
#include <QDialog>
#include "ui_about.h"
class AboutDialog: public QDialog
{
Q_OBJECT
private:
Ui_AboutDialog ui;
private slots:
void onFrameChanged( );
public:
AboutDialog( QWidget * parent = 0 );
~AboutDialog( );
};
#endif

576
qt/about.ui Normal file
View File

@ -0,0 +1,576 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>AboutDialog</class>
<widget class="QDialog" name="AboutDialog">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>327</width>
<height>245</height>
</rect>
</property>
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="windowTitle">
<string>About Transmission</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QLabel" name="aboutLogo">
<property name="text">
<string/>
</property>
<property name="pixmap">
<pixmap resource="application.qrc">:/icons/transmission-48.png</pixmap>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="label">
<property name="font">
<font>
<pointsize>16</pointsize>
<weight>75</weight>
<bold>true</bold>
</font>
</property>
<property name="text">
<string>Transmission 1.60</string>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
</widget>
</item>
<item>
<spacer name="verticalSpacer_2">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeType">
<enum>QSizePolicy::Fixed</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>300</width>
<height>3</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QLabel" name="label_2">
<property name="text">
<string>A fast and easy BitTorrent client</string>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="label_3">
<property name="text">
<string>Copyright 2005-2009 by the Transmission Project</string>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
</widget>
</item>
<item>
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeType">
<enum>QSizePolicy::Fixed</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>4</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QLabel" name="label_4">
<property name="palette">
<palette>
<active>
<colorrole role="WindowText">
<brush brushstyle="SolidPattern">
<color alpha="255">
<red>0</red>
<green>85</green>
<blue>255</blue>
</color>
</brush>
</colorrole>
<colorrole role="Button">
<brush brushstyle="SolidPattern">
<color alpha="255">
<red>0</red>
<green>85</green>
<blue>255</blue>
</color>
</brush>
</colorrole>
<colorrole role="Light">
<brush brushstyle="SolidPattern">
<color alpha="255">
<red>127</red>
<green>170</green>
<blue>255</blue>
</color>
</brush>
</colorrole>
<colorrole role="Midlight">
<brush brushstyle="SolidPattern">
<color alpha="255">
<red>63</red>
<green>127</green>
<blue>255</blue>
</color>
</brush>
</colorrole>
<colorrole role="Dark">
<brush brushstyle="SolidPattern">
<color alpha="255">
<red>0</red>
<green>42</green>
<blue>127</blue>
</color>
</brush>
</colorrole>
<colorrole role="Mid">
<brush brushstyle="SolidPattern">
<color alpha="255">
<red>0</red>
<green>56</green>
<blue>170</blue>
</color>
</brush>
</colorrole>
<colorrole role="Text">
<brush brushstyle="SolidPattern">
<color alpha="255">
<red>0</red>
<green>0</green>
<blue>0</blue>
</color>
</brush>
</colorrole>
<colorrole role="BrightText">
<brush brushstyle="SolidPattern">
<color alpha="255">
<red>255</red>
<green>255</green>
<blue>255</blue>
</color>
</brush>
</colorrole>
<colorrole role="ButtonText">
<brush brushstyle="SolidPattern">
<color alpha="255">
<red>0</red>
<green>0</green>
<blue>0</blue>
</color>
</brush>
</colorrole>
<colorrole role="Base">
<brush brushstyle="SolidPattern">
<color alpha="255">
<red>255</red>
<green>255</green>
<blue>255</blue>
</color>
</brush>
</colorrole>
<colorrole role="Window">
<brush brushstyle="SolidPattern">
<color alpha="255">
<red>0</red>
<green>85</green>
<blue>255</blue>
</color>
</brush>
</colorrole>
<colorrole role="Shadow">
<brush brushstyle="SolidPattern">
<color alpha="255">
<red>0</red>
<green>0</green>
<blue>0</blue>
</color>
</brush>
</colorrole>
<colorrole role="AlternateBase">
<brush brushstyle="SolidPattern">
<color alpha="255">
<red>127</red>
<green>170</green>
<blue>255</blue>
</color>
</brush>
</colorrole>
<colorrole role="ToolTipBase">
<brush brushstyle="SolidPattern">
<color alpha="255">
<red>255</red>
<green>255</green>
<blue>220</blue>
</color>
</brush>
</colorrole>
<colorrole role="ToolTipText">
<brush brushstyle="SolidPattern">
<color alpha="255">
<red>0</red>
<green>0</green>
<blue>0</blue>
</color>
</brush>
</colorrole>
</active>
<inactive>
<colorrole role="WindowText">
<brush brushstyle="SolidPattern">
<color alpha="255">
<red>0</red>
<green>85</green>
<blue>255</blue>
</color>
</brush>
</colorrole>
<colorrole role="Button">
<brush brushstyle="SolidPattern">
<color alpha="255">
<red>0</red>
<green>85</green>
<blue>255</blue>
</color>
</brush>
</colorrole>
<colorrole role="Light">
<brush brushstyle="SolidPattern">
<color alpha="255">
<red>127</red>
<green>170</green>
<blue>255</blue>
</color>
</brush>
</colorrole>
<colorrole role="Midlight">
<brush brushstyle="SolidPattern">
<color alpha="255">
<red>63</red>
<green>127</green>
<blue>255</blue>
</color>
</brush>
</colorrole>
<colorrole role="Dark">
<brush brushstyle="SolidPattern">
<color alpha="255">
<red>0</red>
<green>42</green>
<blue>127</blue>
</color>
</brush>
</colorrole>
<colorrole role="Mid">
<brush brushstyle="SolidPattern">
<color alpha="255">
<red>0</red>
<green>56</green>
<blue>170</blue>
</color>
</brush>
</colorrole>
<colorrole role="Text">
<brush brushstyle="SolidPattern">
<color alpha="255">
<red>0</red>
<green>0</green>
<blue>0</blue>
</color>
</brush>
</colorrole>
<colorrole role="BrightText">
<brush brushstyle="SolidPattern">
<color alpha="255">
<red>255</red>
<green>255</green>
<blue>255</blue>
</color>
</brush>
</colorrole>
<colorrole role="ButtonText">
<brush brushstyle="SolidPattern">
<color alpha="255">
<red>0</red>
<green>0</green>
<blue>0</blue>
</color>
</brush>
</colorrole>
<colorrole role="Base">
<brush brushstyle="SolidPattern">
<color alpha="255">
<red>255</red>
<green>255</green>
<blue>255</blue>
</color>
</brush>
</colorrole>
<colorrole role="Window">
<brush brushstyle="SolidPattern">
<color alpha="255">
<red>0</red>
<green>85</green>
<blue>255</blue>
</color>
</brush>
</colorrole>
<colorrole role="Shadow">
<brush brushstyle="SolidPattern">
<color alpha="255">
<red>0</red>
<green>0</green>
<blue>0</blue>
</color>
</brush>
</colorrole>
<colorrole role="AlternateBase">
<brush brushstyle="SolidPattern">
<color alpha="255">
<red>127</red>
<green>170</green>
<blue>255</blue>
</color>
</brush>
</colorrole>
<colorrole role="ToolTipBase">
<brush brushstyle="SolidPattern">
<color alpha="255">
<red>255</red>
<green>255</green>
<blue>220</blue>
</color>
</brush>
</colorrole>
<colorrole role="ToolTipText">
<brush brushstyle="SolidPattern">
<color alpha="255">
<red>0</red>
<green>0</green>
<blue>0</blue>
</color>
</brush>
</colorrole>
</inactive>
<disabled>
<colorrole role="WindowText">
<brush brushstyle="SolidPattern">
<color alpha="255">
<red>0</red>
<green>42</green>
<blue>127</blue>
</color>
</brush>
</colorrole>
<colorrole role="Button">
<brush brushstyle="SolidPattern">
<color alpha="255">
<red>0</red>
<green>85</green>
<blue>255</blue>
</color>
</brush>
</colorrole>
<colorrole role="Light">
<brush brushstyle="SolidPattern">
<color alpha="255">
<red>127</red>
<green>170</green>
<blue>255</blue>
</color>
</brush>
</colorrole>
<colorrole role="Midlight">
<brush brushstyle="SolidPattern">
<color alpha="255">
<red>63</red>
<green>127</green>
<blue>255</blue>
</color>
</brush>
</colorrole>
<colorrole role="Dark">
<brush brushstyle="SolidPattern">
<color alpha="255">
<red>0</red>
<green>42</green>
<blue>127</blue>
</color>
</brush>
</colorrole>
<colorrole role="Mid">
<brush brushstyle="SolidPattern">
<color alpha="255">
<red>0</red>
<green>56</green>
<blue>170</blue>
</color>
</brush>
</colorrole>
<colorrole role="Text">
<brush brushstyle="SolidPattern">
<color alpha="255">
<red>0</red>
<green>42</green>
<blue>127</blue>
</color>
</brush>
</colorrole>
<colorrole role="BrightText">
<brush brushstyle="SolidPattern">
<color alpha="255">
<red>255</red>
<green>255</green>
<blue>255</blue>
</color>
</brush>
</colorrole>
<colorrole role="ButtonText">
<brush brushstyle="SolidPattern">
<color alpha="255">
<red>0</red>
<green>42</green>
<blue>127</blue>
</color>
</brush>
</colorrole>
<colorrole role="Base">
<brush brushstyle="SolidPattern">
<color alpha="255">
<red>0</red>
<green>85</green>
<blue>255</blue>
</color>
</brush>
</colorrole>
<colorrole role="Window">
<brush brushstyle="SolidPattern">
<color alpha="255">
<red>0</red>
<green>85</green>
<blue>255</blue>
</color>
</brush>
</colorrole>
<colorrole role="Shadow">
<brush brushstyle="SolidPattern">
<color alpha="255">
<red>0</red>
<green>0</green>
<blue>0</blue>
</color>
</brush>
</colorrole>
<colorrole role="AlternateBase">
<brush brushstyle="SolidPattern">
<color alpha="255">
<red>0</red>
<green>85</green>
<blue>255</blue>
</color>
</brush>
</colorrole>
<colorrole role="ToolTipBase">
<brush brushstyle="SolidPattern">
<color alpha="255">
<red>255</red>
<green>255</green>
<blue>220</blue>
</color>
</brush>
</colorrole>
<colorrole role="ToolTipText">
<brush brushstyle="SolidPattern">
<color alpha="255">
<red>0</red>
<green>0</green>
<blue>0</blue>
</color>
</brush>
</colorrole>
</disabled>
</palette>
</property>
<property name="font">
<font>
<underline>true</underline>
</font>
</property>
<property name="cursor">
<cursorShape>PointingHandCursor</cursorShape>
</property>
<property name="text">
<string>&lt;a href=&quot;http://www.transmissionbt.com&quot;&gt;http://www.transmissionbt.com/&lt;/a&gt;</string>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
<property name="openExternalLinks">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<spacer name="verticalSpacer_3">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeType">
<enum>QSizePolicy::Fixed</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>10</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QDialogButtonBox" name="buttonBox">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="standardButtons">
<set>QDialogButtonBox::Close</set>
</property>
<property name="centerButtons">
<bool>true</bool>
</property>
</widget>
</item>
</layout>
</widget>
<resources>
<include location="application.qrc"/>
</resources>
<connections/>
</ui>

261
qt/app.cc Normal file
View File

@ -0,0 +1,261 @@
/*
* This file Copyright (C) 2009 Charles Kerr <charles@transmissionbt.com>
*
* This file is licensed by the GPL version 2. Works owned by the
* Transmission project are granted a special exemption to clause 2(b)
* so that the bulk of its code can remain under the MIT license.
* This exemption does not extend to derived works not owned by
* the Transmission project.
*
* $Id:$
*/
#include <cassert>
#include <ctime>
#include <iostream>
#include <QIcon>
#include <QLibraryInfo>
#include <QRect>
#include <QTranslator>
#include <libtransmission/transmission.h>
#include <libtransmission/tr-getopt.h>
#include "app.h"
#include "mainwin.h"
#include "options.h"
#include "prefs.h"
#include "torrent-model.h"
#include "session.h"
#include "utils.h"
#include "watchdir.h"
namespace
{
const char * MY_NAME( "transmission" );
const tr_option opts[] =
{
{ 'g', "config-dir", "Where to look for configuration files", "g", 1, "<path>" },
{ 'm', "minimized", "Start minimized in system tray", "m", 0, NULL },
{ 'p', "paused", "Pause all torrents on sartup", "p", 0, NULL },
{ 'r', "remote", "Remotely control a pre-existing session", "r", 1, "<URL>" },
{ 'v', "version", "Show version number and exit", "v", 0, NULL },
{ 0, NULL, NULL, NULL, 0, NULL }
};
const char*
getUsage( void )
{
return "Transmission " LONG_VERSION_STRING "\n"
"http://www.transmissionbt.com/\n"
"A fast and easy BitTorrent client";
}
void
showUsage( void )
{
tr_getopt_usage( MY_NAME, getUsage( ), opts );
exit( 0 );
}
enum
{
STATS_REFRESH_INTERVAL_MSEC = 3000,
SESSION_REFRESH_INTERVAL_MSEC = 3000,
MODEL_REFRESH_INTERVAL_MSEC = 3000
};
}
MyApp :: MyApp( int& argc, char ** argv ):
QApplication( argc, argv ),
myLastFullUpdateTime( 0 )
{
setApplicationName( MY_NAME );
// install the qt translator
QTranslator * t = new QTranslator( );
t->load( "qt_" + QLocale::system().name(), QLibraryInfo::location(QLibraryInfo::TranslationsPath));
installTranslator( t );
// install the transmission translator
t = new QTranslator( );
t->load( QString(MY_NAME) + "_" + QLocale::system().name() );
installTranslator( t );
// set the default icon
QIcon icon;
icon.addPixmap( QPixmap( ":/icons/transmission-16.png" ) );
icon.addPixmap( QPixmap( ":/icons/transmission-22.png" ) );
icon.addPixmap( QPixmap( ":/icons/transmission-24.png" ) );
icon.addPixmap( QPixmap( ":/icons/transmission-32.png" ) );
icon.addPixmap( QPixmap( ":/icons/transmission-48.png" ) );
setWindowIcon( icon );
// parse the command-line arguments
int c;
bool paused = false;
bool minimized = false;
const char * optarg;
const char * configDir = 0;
const char * url = 0;
while( ( c = tr_getopt( getUsage( ), argc, (const char**)argv, opts, &optarg ) ) ) {
switch( c ) {
case 'g': configDir = optarg; break;
case 'm': minimized = true; break;
case 'p': paused = true; break;
case 'r': url = optarg; break;
case 'v': Utils::toStderr( QObject::tr( "transmission %1" ).arg( LONG_VERSION_STRING ) ); exit( 0 ); break;
case TR_OPT_ERR: Utils::toStderr( QObject::tr( "Invalid option" ) ); showUsage( ); break;
default: Utils::toStderr( QObject::tr( "Got opt %1" ).arg((int)c) ); showUsage( ); break;
}
}
// set the fallback config dir
if( configDir == 0 )
configDir = tr_getDefaultConfigDir( MY_NAME );
myPrefs = new Prefs ( configDir );
mySession = new Session( configDir, *myPrefs, url, paused );
myModel = new TorrentModel( *myPrefs );
myWindow = new TrMainWindow( *mySession, *myPrefs, *myModel, minimized );
myWatchDir = new WatchDir( *myModel );
/* when the session gets torrent info, update the model */
connect( mySession, SIGNAL(torrentsUpdated(tr_benc*,bool)), myModel, SLOT(updateTorrents(tr_benc*,bool)) );
connect( mySession, SIGNAL(torrentsUpdated(tr_benc*,bool)), myWindow, SLOT(refreshActionSensitivity()) );
connect( mySession, SIGNAL(torrentsRemoved(tr_benc*)), myModel, SLOT(removeTorrents(tr_benc*)) );
/* when the model sees a torrent for the first time, ask the session for full info on it */
connect( myModel, SIGNAL(torrentsAdded(QSet<int>)), mySession, SLOT(initTorrents(QSet<int>)) );
mySession->initTorrents( );
mySession->refreshSessionStats( );
/* when torrents are added to the watch directory, tell the session */
connect( myWatchDir, SIGNAL(torrentFileAdded(QString)), this, SLOT(addTorrent(QString)) );
/* init from preferences */
QList<int> initKeys;
initKeys << Prefs::DIR_WATCH;
foreach( int key, initKeys )
refreshPref( key );
connect( myPrefs, SIGNAL(changed(int)), this, SLOT(refreshPref(const int)) );
QTimer * timer = &myModelTimer;
connect( timer, SIGNAL(timeout()), this, SLOT(refreshTorrents()) );
timer->setSingleShot( false );
timer->setInterval( MODEL_REFRESH_INTERVAL_MSEC );
timer->start( );
timer = &myStatsTimer;
connect( timer, SIGNAL(timeout()), mySession, SLOT(refreshSessionStats()) );
timer->setSingleShot( false );
timer->setInterval( STATS_REFRESH_INTERVAL_MSEC );
timer->start( );
timer = &mySessionTimer;
connect( timer, SIGNAL(timeout()), mySession, SLOT(refreshSessionInfo()) );
timer->setSingleShot( false );
timer->setInterval( SESSION_REFRESH_INTERVAL_MSEC );
timer->start( );
maybeUpdateBlocklist( );
}
MyApp :: ~MyApp( )
{
const QRect mainwinRect( myWindow->geometry( ) );
delete myWatchDir;
delete myWindow;
delete myModel;
delete mySession;
myPrefs->set( Prefs :: MAIN_WINDOW_HEIGHT, std::max( 100, mainwinRect.height( ) ) );
myPrefs->set( Prefs :: MAIN_WINDOW_WIDTH, std::max( 100, mainwinRect.width( ) ) );
myPrefs->set( Prefs :: MAIN_WINDOW_X, mainwinRect.x( ) );
myPrefs->set( Prefs :: MAIN_WINDOW_Y, mainwinRect.y( ) );
delete myPrefs;
}
/***
****
***/
void
MyApp :: refreshPref( int key )
{
switch( key )
{
case Prefs :: BLOCKLIST_UPDATES_ENABLED:
maybeUpdateBlocklist( );
break;
case Prefs :: DIR_WATCH:
case Prefs :: DIR_WATCH_ENABLED: {
const QString path( myPrefs->getString( Prefs::DIR_WATCH ) );
const bool isEnabled( myPrefs->getBool( Prefs::DIR_WATCH_ENABLED ) );
myWatchDir->setPath( path, isEnabled );
break;
}
default:
break;
}
}
void
MyApp :: maybeUpdateBlocklist( )
{
if( !myPrefs->getBool( Prefs :: BLOCKLIST_UPDATES_ENABLED ) )
return;
const QDateTime lastUpdatedAt = myPrefs->getDateTime( Prefs :: BLOCKLIST_DATE );
const QDateTime nextUpdateAt = lastUpdatedAt.addDays( 7 );
const QDateTime now = QDateTime::currentDateTime( );
if( now < nextUpdateAt )
{
mySession->updateBlocklist( );
myPrefs->set( Prefs :: BLOCKLIST_DATE, now );
}
}
void
MyApp :: refreshTorrents( )
{
// usually we just poll the torrents that have shown recent activity,
// but we also periodically ask for updates on the others to ensure
// nothing's falling through the cracks.
const time_t now = time( NULL );
if( myLastFullUpdateTime + 60 >= now )
mySession->refreshActiveTorrents( );
else {
myLastFullUpdateTime = now;
mySession->refreshAllTorrents( );
}
}
void
MyApp :: addTorrent( const QString& filename )
{
if( myPrefs->getBool( Prefs :: OPTIONS_PROMPT ) ) {
Options * o = new Options( *mySession, *myPrefs, filename, myWindow );
o->show( );
QApplication :: alert( o );
} else {
mySession->addTorrent( filename );
QApplication :: alert ( myWindow );
}
}
/***
****
***/
int
main( int argc, char * argv[] )
{
MyApp app( argc, argv );
return app.exec( );
}

53
qt/app.h Normal file
View File

@ -0,0 +1,53 @@
/*
* This file Copyright (C) 2009 Charles Kerr <charles@transmissionbt.com>
*
* This file is licensed by the GPL version 2. Works owned by the
* Transmission project are granted a special exemption to clause 2(b)
* so that the bulk of its code can remain under the MIT license.
* This exemption does not extend to derived works not owned by
* the Transmission project.
*
* $Id:$
*/
#ifndef QTR_APP_H
#define QTR_APP_H
#include <QApplication>
#include <QTimer>
class Prefs;
class Session;
class TorrentModel;
class TrMainWindow;
class WatchDir;
class MyApp: public QApplication
{
Q_OBJECT
public:
MyApp( int& argc, char ** argv );
virtual ~MyApp( );
private:
Prefs * myPrefs;
Session * mySession;
TorrentModel * myModel;
TrMainWindow * myWindow;
WatchDir * myWatchDir;
QTimer myModelTimer;
QTimer myStatsTimer;
QTimer mySessionTimer;
time_t myLastFullUpdateTime;
private slots:
void refreshPref( int key );
void refreshTorrents( );
void addTorrent( const QString& );
private:
void maybeUpdateBlocklist( );
};
#endif

13
qt/application.qrc Normal file
View File

@ -0,0 +1,13 @@
<RCC version="1.0">
<qresource prefix="icons" >
<file alias="transmission-16.png">icons/hicolor_apps_16x16_transmission.png</file>
<file alias="transmission-22.png">icons/hicolor_apps_22x22_transmission.png</file>
<file alias="transmission-24.png">icons/hicolor_apps_24x24_transmission.png</file>
<file alias="transmission-32.png">icons/hicolor_apps_32x32_transmission.png</file>
<file alias="transmission-48.png">icons/hicolor_apps_48x48_transmission.png</file>
<file alias="alt-limit-off.png">icons/turtle-grey.png</file>
<file alias="alt-limit-on.png">icons/turtle-blue.png</file>
<file alias="encrypted.png">icons/lock.png</file>
<file alias="dance.gif">icons/dance.gif</file>
</qresource>
</RCC>

608
qt/details.cc Normal file
View File

@ -0,0 +1,608 @@
/*
* This file Copyright (C) 2009 Charles Kerr <charles@transmissionbt.com>
*
* This file is licensed by the GPL version 2. Works owned by the
* Transmission project are granted a special exemption to clause 2(b)
* so that the bulk of its code can remain under the MIT license.
* This exemption does not extend to derived works not owned by
* the Transmission project.
*
* $Id:$
*/
#include <cassert>
#include <ctime>
#include <iostream>
#include <QCheckBox>
#include <QEvent>
#include <QHeaderView>
#include <QResizeEvent>
#include <QDialogButtonBox>
#include <QDoubleSpinBox>
#include <QFont>
#include <QFontMetrics>
#include <QHBoxLayout>
#include <QVBoxLayout>
#include <QLabel>
#include <QLocale>
#include <QPushButton>
#include <QSpinBox>
#include <QRadioButton>
#include <QStyle>
#include <QTabWidget>
#include <QTreeView>
#include <QTextBrowser>
#include <QDateTime>
#include <QTreeWidget>
#include <QTreeWidgetItem>
#include <QVBoxLayout>
#include <QHBoxLayout>
#include "details.h"
#include "file-tree.h"
#include "hig.h"
#include "session.h"
#include "squeezelabel.h"
#include "torrent.h"
#include "utils.h"
class Prefs;
class Session;
/****
*****
****/
namespace
{
const int REFRESH_INTERVAL_MSEC = 3500;
enum // peer columns
{
COL_LOCK,
COL_UP,
COL_DOWN,
COL_PERCENT,
COL_STATUS,
COL_ADDRESS,
COL_CLIENT,
N_COLUMNS
};
}
/***
****
***/
class PeerItem: public QTreeWidgetItem
{
Peer peer;
int quads[4];
QString status;
bool quadsLessThan( const PeerItem * that ) const {
for( int i=0; i<4; ++i ) {
if( quads[i] < that->quads[i] ) return true;
if( quads[i] > that->quads[i] ) return false;
}
return false;
}
public:
PeerItem( ) { }
virtual ~PeerItem( ) { }
public:
void setStatus( const QString& s ) {
status = s;
}
void setPeer( const Peer& p ) {
peer = p;
const QStringList tokens( p.address.split(".") );
for( int i=0; i<4; ++i )
quads[i] = tokens.at(i).toInt();
}
virtual bool operator< ( const QTreeWidgetItem & other ) const {
const PeerItem * that = dynamic_cast<const PeerItem*>(&other);
QTreeWidget * tw( treeWidget( ) );
const int column = tw ? tw->sortColumn() : 0;
switch( column ) {
case COL_UP: return peer.rateToPeer < that->peer.rateToPeer;
case COL_DOWN: return peer.rateToClient < that->peer.rateToClient;
case COL_PERCENT: return peer.progress < that->peer.progress;
case COL_STATUS: return status < that->status;
case COL_ADDRESS: return quadsLessThan( that );
case COL_CLIENT: return peer.clientName < that->peer.clientName;
default: /*COL_LOCK*/ return peer.isEncrypted && !that->peer.isEncrypted;
}
}
};
/***
****
***/
Details :: Details( Session& session, Torrent& torrent, QWidget * parent ):
QDialog( parent, Qt::Dialog ),
mySession( session ),
myTorrent( torrent )
{
QVBoxLayout * layout = new QVBoxLayout( this );
setWindowTitle( tr( "%1 Properties" ).arg( torrent.name( ) ) );
QTabWidget * t = new QTabWidget( this );
t->addTab( createActivityTab( ), tr( "Activity" ) );
t->addTab( createPeersTab( ), tr( "Peers" ) );
t->addTab( createTrackerTab( ), tr( "Tracker" ) );
t->addTab( createInfoTab( ), tr( "Information" ) );
t->addTab( createFilesTab( ), tr( "Files" ) );
t->addTab( createOptionsTab( ), tr( "Options" ) );
layout->addWidget( t );
QDialogButtonBox * buttons = new QDialogButtonBox( QDialogButtonBox::Close, Qt::Horizontal, this );
connect( buttons, SIGNAL(rejected()), this, SLOT(deleteLater()) ); // "close" triggers rejected
layout->addWidget( buttons );
connect( &myTorrent, SIGNAL(torrentChanged(int)), this, SLOT(onTorrentChanged()) );
connect( &myTorrent, SIGNAL(destroyed(QObject*)), this, SLOT(deleteLater()) );
connect( &myTimer, SIGNAL(timeout()), this, SLOT(onTimer()) );
onTimer( );
myTimer.setSingleShot( false );
myTimer.start( REFRESH_INTERVAL_MSEC );
}
Details :: ~Details( )
{
}
/***
****
***/
void
Details :: onTimer( )
{
mySession.refreshExtraStats( myTorrent.id( ) );
}
void
Details :: onTorrentChanged( )
{
QLocale locale;
const QFontMetrics fm( fontMetrics( ) );
// activity tab
myStateLabel->setText( myTorrent.activityString( ) );
myProgressLabel->setText( locale.toString( myTorrent.percentDone( )*100.0, 'f', 2 ) );
myHaveLabel->setText( tr( "%1 (%2 verified in %L3 pieces)" )
.arg( Utils::sizeToString( myTorrent.haveTotal( ) ) )
.arg( Utils::sizeToString( myTorrent.haveVerified( ) ) )
.arg( myTorrent.haveVerified()/myTorrent.pieceSize() ) );
myDownloadedLabel->setText( Utils::sizeToString( myTorrent.downloadedEver( ) ) );
myUploadedLabel->setText( Utils::sizeToString( myTorrent.uploadedEver( ) ) );
myFailedLabel->setText( Utils::sizeToString( myTorrent.failedEver( ) ) );
myRatioLabel->setText( Utils :: ratioToString( myTorrent.ratio( ) ) );
mySwarmSpeedLabel->setText( Utils::speedToString( myTorrent.swarmSpeed( ) ) );
myAddedDateLabel->setText( myTorrent.dateAdded().toString() );
QDateTime dt = myTorrent.lastActivity( );
myActivityLabel->setText( dt.isNull() ? tr("Never") : dt.toString() );
QString s = myTorrent.getError( );
myErrorLabel->setText( s.isEmpty() ? tr("None") : s );
// information tab
myPiecesLabel->setText( tr( "%L1 Pieces @ %2" ).arg( myTorrent.pieceCount() )
.arg( Utils::sizeToString(myTorrent.pieceSize()) ) );
myHashLabel->setText( myTorrent.hashString( ) );
myPrivacyLabel->setText( myTorrent.isPrivate( ) ? tr( "Private to this tracker -- PEX disabled" )
: tr( "Public torrent" ) );
myCommentBrowser->setText( myTorrent.comment( ) );
QString str = myTorrent.creator( );
if( str.isEmpty( ) )
str = tr( "Unknown" );
myCreatorLabel->setText( str );
myDateCreatedLabel->setText( myTorrent.dateCreated( ).toString( ) );
myDestinationLabel->setText( myTorrent.getPath( ) );
myTorrentFileLabel->setText( myTorrent.torrentFile( ) );
// options tab
mySessionLimitCheck->setChecked( myTorrent.honorsSessionLimits( ) );
mySingleDownCheck->setChecked( myTorrent.downloadIsLimited( ) );
mySingleUpCheck->setChecked( myTorrent.uploadIsLimited( ) );
mySingleDownSpin->setValue( (int)myTorrent.downloadLimit().kbps() );
mySingleUpSpin->setValue( (int)myTorrent.uploadLimit().kbps() );
myPeerLimitSpin->setValue( myTorrent.peerLimit( ) );
QRadioButton * rb;
switch( myTorrent.seedRatioMode( ) ) {
case TR_RATIOLIMIT_GLOBAL: rb = mySeedGlobalRadio; break;
case TR_RATIOLIMIT_SINGLE: rb = mySeedCustomRadio; break;
case TR_RATIOLIMIT_UNLIMITED: rb = mySeedForeverRadio; break;
}
rb->setChecked( true );
mySeedCustomSpin->setValue( myTorrent.seedRatioLimit( ) );
// tracker tab
const time_t now( time( 0 ) );
myScrapeTimePrevLabel->setText( myTorrent.lastScrapeTime().toString() );
myScrapeResponseLabel->setText( myTorrent.scrapeResponse() );
myScrapeTimeNextLabel->setText( Utils :: timeToString( myTorrent.nextScrapeTime().toTime_t() - now ) );
myAnnounceTimePrevLabel->setText( myTorrent.lastScrapeTime().toString() );
myAnnounceTimeNextLabel->setText( Utils :: timeToString( myTorrent.nextAnnounceTime().toTime_t() - now ) );
myAnnounceManualLabel->setText( Utils :: timeToString( myTorrent.manualAnnounceTime().toTime_t() - now ) );
myAnnounceResponseLabel->setText( myTorrent.announceResponse( ) );
const QUrl url( myTorrent.announceUrl( ) );
myTrackerLabel->setText( url.host( ) );
// peers tab
mySeedersLabel->setText( locale.toString( myTorrent.seeders( ) ) );
myLeechersLabel->setText( locale.toString( myTorrent.leechers( ) ) );
myTimesCompletedLabel->setText( locale.toString( myTorrent.timesCompleted( ) ) );
//const PeerList peers( myTorrent.peers( ) );
PeerList peers( myTorrent.peers( ) );
#if 0
static double progress = 0.01;
{
Peer peer;
peer.address = "127.0.0.1";
peer.isEncrypted = true;
peer.progress = progress;
peer.rateToPeer = Speed::fromKbps(20);
peers << peer;
progress += 0.01;
}
#endif
QMap<QString,QTreeWidgetItem*> peers2;
QList<QTreeWidgetItem*> newItems;
static const QIcon myEncryptionIcon( ":/icons/encrypted.png" );
static const QIcon myEmptyIcon;
foreach( Peer peer, peers )
{
PeerItem * item = (PeerItem*) myPeers.value( peer.address, 0 );
if( item == 0 ) { // new peer has connected
item = new PeerItem;
item->setTextAlignment( COL_UP, Qt::AlignRight );
item->setTextAlignment( COL_DOWN, Qt::AlignRight );
item->setTextAlignment( COL_PERCENT, Qt::AlignRight );
newItems << item;
}
QString code;
if( peer.isDownloadingFrom ) { code += 'D'; }
else if( peer.clientIsInterested ) { code += 'd'; }
if( peer.isUploadingTo ) { code += 'U'; }
else if( peer.peerIsInterested ) { code += 'u'; }
if( !peer.clientIsChoked && !peer.clientIsInterested ) { code += 'K'; }
if( !peer.peerIsChoked && !peer.peerIsInterested ) { code += '?'; }
if( peer.isEncrypted ) { code += 'E'; }
if( peer.isIncoming ) { code += 'I'; }
item->setPeer( peer );
item->setStatus( code );
QString codeTip;
foreach( QChar ch, code ) {
QString txt;
switch( ch.toAscii() ) {
case 'O': txt = tr( "Optimistic unchoke" ); break;
case 'D': txt = tr( "Downloading from this peer" ); break;
case 'd': txt = tr( "We would download from this peer if they would let us" ); break;
case 'U': txt = tr( "Uploading to peer" ); break;
case 'u': txt = tr( "We would upload to this peer if they asked" ); break;
case 'K': txt = tr( "Peer has unchoked us, but we're not interested" ); break;
case '?': txt = tr( "We unchoked this peer, but they're not interested" ); break;
case 'E': txt = tr( "Encrypted connection" ); break;
case 'X': txt = tr( "Peer was discovered through Peer Exchange (PEX)" ); break;
case 'I': txt = tr( "Peer is an incoming connection" ); break;
}
if( !txt.isEmpty( ) )
codeTip += QString("%1: %2\n").arg(ch).arg(txt);
}
if( !codeTip.isEmpty() )
codeTip.resize( codeTip.size()-1 ); // eat the trailing linefeed
item->setIcon( COL_LOCK, peer.isEncrypted ? myEncryptionIcon : myEmptyIcon );
item->setToolTip( COL_LOCK, peer.isEncrypted ? tr( "Encrypted connection" ) : "" );
item->setText( COL_UP, peer.rateToPeer.isZero() ? "" : Utils::speedToString( peer.rateToPeer ) );
item->setText( COL_DOWN, peer.rateToClient.isZero() ? "" : Utils::speedToString( peer.rateToClient ) );
item->setText( COL_PERCENT, peer.progress > 0 ? QString( "%1%" ).arg( locale.toString((int)(peer.progress*100.0))) : "" );
item->setText( COL_STATUS, code );
item->setToolTip( COL_STATUS, codeTip );
item->setText( COL_ADDRESS, peer.address );
item->setText( COL_CLIENT, peer.clientName );
peers2.insert( peer.address, item );
}
myPeerTree->addTopLevelItems( newItems );
foreach( QString key, myPeers.keys() ) {
if( !peers2.contains( key ) ) { // old peer has disconnected
QTreeWidgetItem * item = myPeers.value( key, 0 );
myPeerTree->takeTopLevelItem( myPeerTree->indexOfTopLevelItem( item ) );
delete item;
}
}
myPeers = peers2;
myFileTreeView->update( myTorrent.files( ) );
}
void
Details :: enableWhenChecked( QCheckBox * box, QWidget * w )
{
connect( box, SIGNAL(toggled(bool)), w, SLOT(setEnabled(bool)) );
w->setEnabled( box->isChecked( ) );
}
/***
****
***/
QWidget *
Details :: createActivityTab( )
{
HIG * hig = new HIG( this );
hig->addSectionTitle( tr( "Transfer" ) );
hig->addRow( tr( "State:" ), myStateLabel = new SqueezeLabel );
hig->addRow( tr( "Progress:" ), myProgressLabel = new SqueezeLabel );
hig->addRow( tr( "Have:" ), myHaveLabel = new SqueezeLabel );
hig->addRow( tr( "Downloaded:" ), myDownloadedLabel = new SqueezeLabel );
hig->addRow( tr( "Uploaded:" ), myUploadedLabel = new SqueezeLabel );
hig->addRow( tr( "Failed DL:" ), myFailedLabel = new SqueezeLabel );
hig->addRow( tr( "Ratio:" ), myRatioLabel = new SqueezeLabel );
hig->addRow( tr( "Swarm Rate:" ), mySwarmSpeedLabel = new SqueezeLabel );
hig->addRow( tr( "Error:" ), myErrorLabel = new SqueezeLabel );
hig->addSectionDivider( );
hig->addSectionTitle( tr( "Dates" ) );
hig->addRow( tr( "Added on:" ), myAddedDateLabel = new SqueezeLabel );
hig->addRow( tr( "Last activity on:" ), myActivityLabel = new SqueezeLabel );
hig->finish( );
return hig;
}
/***
****
***/
void
Details :: onSessionLimitsToggled( bool b )
{
mySession.torrentSet( myTorrent.id(), "honorsSessionLimits", b );
}
QWidget *
Details :: createOptionsTab( )
{
//QWidget * l;
QSpinBox * s;
QCheckBox * c;
QHBoxLayout * h;
QRadioButton * r;
QDoubleSpinBox * ds;
HIG * hig = new HIG( this );
hig->addSectionTitle( tr( "Speed Limits" ) );
c = new QCheckBox( tr( "Honor global &limits" ) );
mySessionLimitCheck = c;
hig->addWideControl( c );
connect( c, SIGNAL(toggled(bool)), this, SLOT(onSessionLimitsToggled(bool)) );
c = new QCheckBox( tr( "Limit &download speed (KB/s)" ) );
mySingleDownCheck = c;
s = new QSpinBox( );
mySingleDownSpin = s;
s->setRange( 0, INT_MAX );
hig->addRow( c, s );
enableWhenChecked( c, s );
c = new QCheckBox( tr( "Limit &upload speed (KB/s)" ) );
mySingleUpCheck = c;
s = new QSpinBox( );
mySingleUpSpin = s;
s->setRange( 0, INT_MAX );
hig->addRow( c, s );
enableWhenChecked( c, s );
hig->addSectionDivider( );
hig->addSectionTitle( tr( "Seed-Until Ratio" ) );
r = new QRadioButton( tr( "Use &global setting" ) );
mySeedGlobalRadio = r;
hig->addWideControl( r );
r = new QRadioButton( tr( "Seed &regardless of ratio" ) );
mySeedForeverRadio = r;
hig->addWideControl( r );
h = new QHBoxLayout( );
h->setSpacing( HIG :: PAD );
r = new QRadioButton( tr( "&Stop seeding when a torrent's ratio reaches" ) );
mySeedCustomRadio = r;
h->addWidget( r );
ds = new QDoubleSpinBox( );
ds->setRange( 0.5, INT_MAX );
mySeedCustomSpin = ds;
h->addWidget( ds );
hig->addWideControl( h );
hig->addSectionDivider( );
hig->addSectionTitle( tr( "Peer Connections" ) );
s = new QSpinBox( );
s->setRange( 1, 300 );
myPeerLimitSpin = s;
hig->addRow( tr( "&Maximum Peers" ), s );
hig->finish( );
return hig;
}
/***
****
***/
QWidget *
Details :: createInfoTab( )
{
HIG * hig = new HIG( );
hig->addSectionTitle( tr( "Details" ) );
hig->addRow( tr( "Pieces:" ), myPiecesLabel = new SqueezeLabel );
hig->addRow( tr( "Hash:" ), myHashLabel = new SqueezeLabel );
hig->addRow( tr( "Privacy:" ), myPrivacyLabel = new SqueezeLabel );
hig->addRow( tr( "Comment:" ), myCommentBrowser = new QTextBrowser );
hig->addSectionDivider( );
hig->addSectionTitle( tr( "Origins" ) );
hig->addRow( tr( "Creator:" ), myCreatorLabel = new SqueezeLabel );
hig->addRow( tr( "Date:" ), myDateCreatedLabel = new SqueezeLabel );
hig->addSectionDivider( );
hig->addSectionTitle( tr( "Origins" ) );
hig->addRow( tr( "Destination folder:" ), myDestinationLabel = new SqueezeLabel );
hig->addRow( tr( "Torrent file:" ), myTorrentFileLabel = new SqueezeLabel );
const int h = QFontMetrics(myCommentBrowser->font()).lineSpacing() * 4;
myTorrentFileLabel->setMinimumWidth( 300 );
myTorrentFileLabel->setSizePolicy ( QSizePolicy::Expanding, QSizePolicy::Preferred );
myCommentBrowser->setMinimumHeight( h );
myCommentBrowser->setMaximumHeight( h );
hig->finish( );
return hig;
}
/***
****
***/
QWidget *
Details :: createTrackerTab( )
{
HIG * hig = new HIG( );
hig->addSectionTitle( tr( "Scrape" ) );
hig->addRow( tr( "Last scrape at:" ), myScrapeTimePrevLabel = new SqueezeLabel );
hig->addRow( tr( "Tracker responded:" ), myScrapeResponseLabel = new SqueezeLabel );
hig->addRow( tr( "Next scrape in:" ), myScrapeTimeNextLabel = new SqueezeLabel );
hig->addSectionDivider( );
hig->addSectionTitle( tr( "Announce" ) );
hig->addRow( tr( "Tracker:" ), myTrackerLabel = new SqueezeLabel );
hig->addRow( tr( "Last announce at:" ), myAnnounceTimePrevLabel = new SqueezeLabel );
hig->addRow( tr( "Tracker responded:" ), myAnnounceResponseLabel = new SqueezeLabel );
hig->addRow( tr( "Next announce in:" ), myAnnounceTimeNextLabel = new SqueezeLabel );
hig->addRow( tr( "Manual announce allowed in:" ), myAnnounceManualLabel = new SqueezeLabel );
hig->finish( );
myTrackerLabel->setScaledContents( true );
return hig;
}
/***
****
***/
QWidget *
Details :: createPeersTab( )
{
QWidget * top = new QWidget;
QVBoxLayout * v = new QVBoxLayout( top );
v->setSpacing( HIG :: PAD_BIG );
v->setContentsMargins( HIG::PAD_BIG, HIG::PAD_BIG, HIG::PAD_BIG, HIG::PAD_BIG );
QStringList headers;
headers << QString() << tr("Up") << tr("Down") << tr("%") << tr("Status") << tr("Address") << tr("Client");
myPeerTree = new QTreeWidget;
myPeerTree->setUniformRowHeights( true );
myPeerTree->setHeaderLabels( headers );
myPeerTree->setColumnWidth( 0, 20 );
myPeerTree->setSortingEnabled( true );
myPeerTree->setRootIsDecorated( false );
myPeerTree->setTextElideMode( Qt::ElideRight );
v->addWidget( myPeerTree, 1 );
const QFontMetrics m( font( ) );
QSize size = m.size( 0, "1024 MB/s" );
myPeerTree->setColumnWidth( COL_UP, size.width( ) );
myPeerTree->setColumnWidth( COL_DOWN, size.width( ) );
size = m.size( 0, " 100% " );
myPeerTree->setColumnWidth( COL_PERCENT, size.width( ) );
size = m.size( 0, "ODUK?EXI" );
myPeerTree->setColumnWidth( COL_STATUS, size.width( ) );
size = m.size( 0, "888.888.888.888" );
myPeerTree->setColumnWidth( COL_ADDRESS, size.width( ) );
size = m.size( 0, "Some BitTorrent Client" );
myPeerTree->setColumnWidth( COL_CLIENT, size.width( ) );
//myPeerTree->sortItems( myTorrent.isDone() ? COL_UP : COL_DOWN, Qt::DescendingOrder );
myPeerTree->setAlternatingRowColors( true );
QHBoxLayout * h = new QHBoxLayout;
h->setSpacing( HIG :: PAD );
v->addLayout( h );
QLabel * l = new QLabel( "Seeders:" );
l->setStyleSheet( "font: bold" );
h->addWidget( l );
l = mySeedersLabel = new QLabel( "a" );
h->addWidget( l );
h->addStretch( 1 );
l = new QLabel( "Leechers:" );
l->setStyleSheet( "font: bold" );
h->addWidget( l );
l = myLeechersLabel = new QLabel( "b" );
h->addWidget( l );
h->addStretch( 1 );
l = new QLabel( "Times Completed:" );
l->setStyleSheet( "font: bold" );
h->addWidget( l );
l = myTimesCompletedLabel = new QLabel( "c" );
h->addWidget( l );
return top;
}
/***
****
***/
QWidget *
Details :: createFilesTab( )
{
myFileTreeView = new FileTreeView( );
connect( myFileTreeView, SIGNAL( priorityChanged(const QSet<int>&, int)),
this, SLOT( onFilePriorityChanged(const QSet<int>&, int)));
connect( myFileTreeView, SIGNAL( wantedChanged(const QSet<int>&, bool)),
this, SLOT( onFileWantedChanged(const QSet<int>&, bool)));
return myFileTreeView;
}
void
Details :: onFilePriorityChanged( const QSet<int>& indices, int priority )
{
QString key;
switch( priority ) {
case TR_PRI_LOW: key = "priority-low"; break;
case TR_PRI_HIGH: key = "priority-high"; break;
default: key = "priority-normal"; break;
}
mySession.torrentSet( myTorrent.id( ), key, indices.toList( ) );
}
void
Details :: onFileWantedChanged( const QSet<int>& indices, bool wanted )
{
QString key( wanted ? "files-wanted" : "files-unwanted" );
mySession.torrentSet( myTorrent.id( ), key, indices.toList( ) );
}

124
qt/details.h Normal file
View File

@ -0,0 +1,124 @@
/*
* This file Copyright (C) 2009 Charles Kerr <charles@transmissionbt.com>
*
* This file is licensed by the GPL version 2. Works owned by the
* Transmission project are granted a special exemption to clause 2(b)
* so that the bulk of its code can remain under the MIT license.
* This exemption does not extend to derived works not owned by
* the Transmission project.
*
* $Id:$
*/
#ifndef DETAILS_DIALOG_H
#define DETAILS_DIALOG_H
#include <QDialog>
#include <QString>
#include <QMap>
#include <QSet>
#include <QTimer>
#include "prefs.h"
class FileTreeView;
class QTreeView;
class QCheckBox;
class QDoubleSpinBox;
class QLabel;
class QRadioButton;
class QSpinBox;
class QTextBrowser;
class QTreeWidget;
class QTreeWidgetItem;
class Session;
class Torrent;
class Details: public QDialog
{
Q_OBJECT
private slots:
void onTorrentChanged( );
void onTimer( );
public:
Details( Session&, Torrent&, QWidget * parent = 0 );
~Details( );
private:
QWidget * createActivityTab( );
QWidget * createPeersTab( );
QWidget * createTrackerTab( );
QWidget * createInfoTab( );
QWidget * createFilesTab( );
QWidget * createOptionsTab( );
private:
QString trimToDesiredWidth( const QString& str );
void enableWhenChecked( QCheckBox *, QWidget * );
private:
Session& mySession;
Torrent& myTorrent;
QTimer myTimer;
QLabel * myStateLabel;
QLabel * myProgressLabel;
QLabel * myHaveLabel;
QLabel * myDownloadedLabel;
QLabel * myUploadedLabel;
QLabel * myFailedLabel;
QLabel * myRatioLabel;
QLabel * mySwarmSpeedLabel;
QLabel * myErrorLabel;
QLabel * myAddedDateLabel;
QLabel * myActivityLabel;
QCheckBox * mySessionLimitCheck;
QCheckBox * mySingleDownCheck;
QCheckBox * mySingleUpCheck;
QSpinBox * mySingleDownSpin;
QSpinBox * mySingleUpSpin;
QRadioButton * mySeedGlobalRadio;
QRadioButton * mySeedForeverRadio;
QRadioButton * mySeedCustomRadio;
QDoubleSpinBox * mySeedCustomSpin;
QSpinBox * myPeerLimitSpin;
QLabel * myPiecesLabel;
QLabel * myHashLabel;
QLabel * myPrivacyLabel;
QLabel * myCreatorLabel;
QLabel * myDateCreatedLabel;
QLabel * myDestinationLabel;
QLabel * myTorrentFileLabel;
QTextBrowser * myCommentBrowser;
QLabel * myTrackerLabel;
QLabel * myScrapeTimePrevLabel;
QLabel * myScrapeTimeNextLabel;
QLabel * myScrapeResponseLabel;
QLabel * myAnnounceTimePrevLabel;
QLabel * myAnnounceTimeNextLabel;
QLabel * myAnnounceResponseLabel;
QLabel * myAnnounceManualLabel;
QLabel * mySeedersLabel;
QLabel * myLeechersLabel;
QLabel * myTimesCompletedLabel;
QTreeWidget * myPeerTree;
QMap<QString,QTreeWidgetItem*> myPeers;
FileTreeView * myFileTreeView;
private slots:
void onSessionLimitsToggled( bool );
void onFilePriorityChanged( const QSet<int>& fileIndices, int );
void onFileWantedChanged( const QSet<int>& fileIndices, bool );
};
#endif

648
qt/file-tree.cc Normal file
View File

@ -0,0 +1,648 @@
/*
* This file Copyright (C) 2009 Charles Kerr <charles@transmissionbt.com>
*
* This file is licensed by the GPL version 2. Works owned by the
* Transmission project are granted a special exemption to clause 2(b)
* so that the bulk of its code can remain under the MIT license.
* This exemption does not extend to derived works not owned by
* the Transmission project.
*
* $Id:$
*/
#include <cassert>
#include <iostream>
#include <QApplication>
#include <QPainter>
#include <QResizeEvent>
#include <QHeaderView>
#include <QStringList>
#include <libtransmission/transmission.h> // priorities
#include "file-tree.h"
#include "hig.h"
#include "torrent.h" // FileList
#include "utils.h" // mime icons
enum
{
COL_NAME,
COL_PROGRESS,
COL_WANTED,
COL_PRIORITY,
NUM_COLUMNS
};
/****
*****
****/
FileTreeItem :: ~FileTreeItem( )
{
assert( myChildren.isEmpty( ) );
if( myParent )
if( !myParent->myChildren.removeOne( this ) )
assert( 0 && "failed to remove" );
}
void
FileTreeItem :: appendChild( FileTreeItem * child )
{
child->myParent = this;
myChildren.append( child );
}
FileTreeItem *
FileTreeItem :: child( const QString& filename )
{
foreach( FileTreeItem * c, myChildren )
if( c->name() == filename )
return c;
return 0;
}
int
FileTreeItem :: row( ) const
{
int i(0);
if( myParent )
i = myParent->myChildren.indexOf( const_cast<FileTreeItem*>(this) );
return i;
}
QVariant
FileTreeItem :: data( int column ) const
{
QVariant value;
switch( column ) {
case COL_NAME: value.setValue( name() ); break;
case COL_PROGRESS: value.setValue( progress( ) ); break;
case COL_WANTED: value.setValue( isSubtreeWanted( ) ); break;
case COL_PRIORITY: value.setValue( priorityString( ) ); break;
}
return value;
}
void
FileTreeItem :: getSubtreeSize( uint64_t& have, uint64_t& total ) const
{
have += myHaveSize;
total += myTotalSize;
foreach( const FileTreeItem * i, myChildren )
i->getSubtreeSize( have, total );
}
double
FileTreeItem :: progress( ) const
{
double d(0);
uint64_t have(0), total(0);
getSubtreeSize( have, total );
if( total )
d = have / (double)total;
return d;
}
bool
FileTreeItem :: update( int index, bool wanted, int priority, uint64_t totalSize, uint64_t haveSize )
{
bool changed = false;
if( myIndex != index )
{
myIndex = index;
changed = true;
}
if( myIsWanted != wanted )
{
myIsWanted = wanted;
changed = true;
}
if( myPriority != priority )
{
myPriority = priority;
changed = true;
}
if( myTotalSize != totalSize )
{
myTotalSize = totalSize;
changed = true;
}
if( myHaveSize != haveSize )
{
myHaveSize = haveSize;
changed = true;
}
return changed;
}
QString
FileTreeItem :: priorityString( ) const
{
const int i( priority( ) );
if( i == LOW ) return tr( "Low" );
if( i == HIGH ) return tr( "High" );
if( i == NORMAL ) return tr( "Normal" );
return tr( "Mixed" );
}
int
FileTreeItem :: priority( ) const
{
int i( 0 );
if( myChildren.isEmpty( ) ) switch( myPriority ) {
case TR_PRI_LOW: i |= LOW; break;
case TR_PRI_HIGH: i |= HIGH; break;
default: i |= NORMAL; break;
}
foreach( const FileTreeItem * child, myChildren )
i |= child->priority( );
return i;
}
void
FileTreeItem :: setSubtreePriority( int i, QSet<int>& ids )
{
if( myPriority != i ) {
myPriority = i;
if( myIndex >= 0 )
ids.insert( myIndex );
}
foreach( FileTreeItem * child, myChildren )
child->setSubtreePriority( i, ids );
}
void
FileTreeItem :: twiddlePriority( QSet<int>& ids, int& p )
{
const int old( priority( ) );
if ( old & LOW ) p = TR_PRI_NORMAL;
else if( old & NORMAL ) p = TR_PRI_HIGH;
else p = TR_PRI_LOW;
setSubtreePriority( p, ids );
}
int
FileTreeItem :: isSubtreeWanted( ) const
{
if( myChildren.isEmpty( ) )
return myIsWanted ? Qt::Checked : Qt::Unchecked;
int wanted( -1 );
foreach( const FileTreeItem * child, myChildren ) {
const int childWanted = child->isSubtreeWanted( );
if( wanted == -1 )
wanted = childWanted;
if( wanted != childWanted )
wanted = Qt::PartiallyChecked;
if( wanted == Qt::PartiallyChecked )
return wanted;
}
return wanted;
}
void
FileTreeItem :: setSubtreeWanted( bool b, QSet<int>& ids )
{
if( myIsWanted != b ) {
myIsWanted = b;
if( myIndex >= 0 )
ids.insert( myIndex );
}
foreach( FileTreeItem * child, myChildren )
child->setSubtreeWanted( b, ids );
}
void
FileTreeItem :: twiddleWanted( QSet<int>& ids, bool& wanted )
{
wanted = isSubtreeWanted( ) != Qt::Checked;
setSubtreeWanted( wanted, ids );
}
/***
****
****
***/
FileTreeModel :: FileTreeModel( QObject *parent ):
QAbstractItemModel(parent)
{
rootItem = new FileTreeItem( -1 );
}
FileTreeModel :: ~FileTreeModel( )
{
clear( );
delete rootItem;
}
QVariant
FileTreeModel :: data( const QModelIndex &index, int role ) const
{
QVariant value;
if( index.isValid() && role==Qt::DisplayRole )
{
FileTreeItem *item = static_cast<FileTreeItem*>(index.internalPointer());
value = item->data( index.column( ) );
}
return value;
}
Qt::ItemFlags
FileTreeModel :: flags( const QModelIndex& index ) const
{
int i( Qt::ItemIsSelectable | Qt::ItemIsEnabled );
if( index.column( ) == COL_WANTED )
i |= Qt::ItemIsUserCheckable | Qt::ItemIsTristate;
return (Qt::ItemFlags)i;
}
QVariant
FileTreeModel :: headerData( int column, Qt::Orientation orientation, int role ) const
{
QVariant data;
if( orientation==Qt::Horizontal && role==Qt::DisplayRole ) {
switch( column ) {
case COL_NAME: data.setValue( tr( "File" ) ); break;
case COL_PROGRESS: data.setValue( tr( "Progress" ) ); break;
case COL_WANTED: data.setValue( tr( "Download" ) ); break;
case COL_PRIORITY: data.setValue( tr( "Priority" ) ); break;
default: break;
}
}
return data;
}
QModelIndex
FileTreeModel :: index( int row, int column, const QModelIndex& parent ) const
{
QModelIndex i;
if( !hasIndex( row, column, parent ) )
{
std::cerr << " I don't have this index " << std::endl;
}
else
{
FileTreeItem * parentItem;
if( !parent.isValid( ) )
parentItem = rootItem;
else
parentItem = static_cast<FileTreeItem*>(parent.internalPointer());
FileTreeItem * childItem = parentItem->child( row );
if( childItem )
i = createIndex( row, column, childItem );
//std::cerr << "FileTreeModel::index(row("<<row<<"),col("<<column<<"),parent("<<qPrintable(parentItem->name())<<")) is returning " << qPrintable(childItem->name()) << ": internalPointer " << i.internalPointer() << " row " << i.row() << " col " << i.column() << std::endl;
}
return i;
}
QModelIndex
FileTreeModel :: parent( const QModelIndex& child ) const
{
return parent( child, 0 ); // QAbstractItemModel::parent() wants col 0
}
QModelIndex
FileTreeModel :: parent( const QModelIndex& child, int column ) const
{
if( !child.isValid( ) )
return QModelIndex( );
FileTreeItem * childItem = static_cast<FileTreeItem*>(child.internalPointer());
return indexOf( childItem->parent( ), column );
}
int
FileTreeModel :: rowCount( const QModelIndex& parent ) const
{
FileTreeItem * parentItem;
if( !parent.isValid( ) )
parentItem = rootItem;
else
parentItem = static_cast<FileTreeItem*>(parent.internalPointer());
return parentItem->childCount();
}
int
FileTreeModel :: columnCount( const QModelIndex &parent ) const
{
Q_UNUSED( parent );
return 4;
}
QModelIndex
FileTreeModel :: indexOf( FileTreeItem * item, int column ) const
{
if( !item || item==rootItem )
return QModelIndex( );
return createIndex( item->row( ), column, item );
}
void
FileTreeModel :: clearSubtree( const QModelIndex& top )
{
while( hasChildren( top ) )
clearSubtree( index( 0, 0, top ) );
delete static_cast<FileTreeItem*>(top.internalPointer());
}
void
FileTreeModel :: clear( )
{
clearSubtree( QModelIndex( ) );
reset( );
}
void
FileTreeModel :: addFile( int index,
const QString & filename,
bool wanted,
int priority,
uint64_t size,
uint64_t have,
QList<QModelIndex> & rowsAdded )
{
FileTreeItem * i( rootItem );
foreach( QString token, filename.split( "/" ) )
{
FileTreeItem * child( i->child( token ) );
if( !child )
{
QModelIndex parentIndex( indexOf( i, 0 ) );
const int n( i->childCount( ) );
beginInsertRows( parentIndex, n, n );
i->appendChild(( child = new FileTreeItem( -1, token )));
endInsertRows( );
rowsAdded.append( indexOf( child, 0 ) );
}
i = child;
}
if( i != rootItem )
if( i->update( index, wanted, priority, size, have ) )
dataChanged( indexOf( i, 0 ), indexOf( i, NUM_COLUMNS-1 ) );
}
void
FileTreeModel :: parentsChanged( const QModelIndex& index, int column )
{
QModelIndex walk = index;
for( ;; ) {
walk = parent( walk, column );
if( !walk.isValid( ) )
break;
dataChanged( walk, walk );
}
}
void
FileTreeModel :: subtreeChanged( const QModelIndex& index, int column )
{
const int childCount = rowCount( index );
if( !childCount )
return;
// tell everyone that this tier changed
dataChanged( index.child(0,column), index.child(childCount-1,column) );
// walk the subtiers
for( int i=0; i<childCount; ++i )
subtreeChanged( index.child(i,column), column );
}
void
FileTreeModel :: clicked( const QModelIndex& index )
{
const int column( index.column( ) );
if( !index.isValid( ) )
return;
if( column == COL_WANTED )
{
FileTreeItem * item( static_cast<FileTreeItem*>(index.internalPointer()));
bool want;
QSet<int> fileIds;
item->twiddleWanted( fileIds, want );
emit wantedChanged( fileIds, want );
dataChanged( index, index );
parentsChanged( index, column );
subtreeChanged( index, column );
}
else if( column == COL_PRIORITY )
{
FileTreeItem * item( static_cast<FileTreeItem*>(index.internalPointer()));
int priority;
QSet<int>fileIds;
item->twiddlePriority( fileIds, priority );
emit priorityChanged( fileIds, priority );
dataChanged( index, index );
parentsChanged( index, column );
subtreeChanged( index, column );
}
}
/****
*****
****/
void
FileTreeDelegate :: paint( QPainter * painter,
const QStyleOptionViewItem & option,
const QModelIndex & index ) const
{
const int column( index.column( ) );
if( ( column != COL_PROGRESS ) && ( column != COL_WANTED ) && ( column != COL_NAME ) )
{
QItemDelegate::paint(painter, option, index);
return;
}
QStyle * style( QApplication :: style( ) );
if( option.state & QStyle::State_Selected )
painter->fillRect( option.rect, option.palette.highlight( ) );
painter->save();
if( option.state & QStyle::State_Selected )
painter->setBrush(option.palette.highlightedText());
if( column == COL_NAME )
{
// draw the file icon
static const int iconSize( style->pixelMetric( QStyle :: PM_SmallIconSize ) );
const QRect iconArea( option.rect.x(),
option.rect.y() + (option.rect.height()-iconSize)/2,
iconSize, iconSize );
QIcon icon;
if( index.model()->hasChildren( index ) )
icon = style->standardIcon( QStyle::StandardPixmap( QStyle::SP_DirOpenIcon ) );
else
icon = Utils :: guessMimeIcon( index.model()->data(index).toString( ) );
icon.paint( painter, iconArea, Qt::AlignCenter, QIcon::Normal, QIcon::On );
// draw the name
QStyleOptionViewItem tmp( option );
tmp.rect.setWidth( option.rect.width( ) - iconArea.width( ) - HIG::PAD_SMALL );
tmp.rect.moveRight( option.rect.right( ) );
QItemDelegate::paint( painter, tmp, index );
}
else if( column == COL_PROGRESS )
{
QStyleOptionProgressBar p;
p.state = QStyle::State_Enabled;
p.direction = QApplication::layoutDirection();
p.rect = option.rect;
p.rect.setSize( QSize( option.rect.width()-2, option.rect.height()-2 ) );
p.rect.moveCenter( option.rect.center( ) );
p.fontMetrics = QApplication::fontMetrics();
p.minimum = 0;
p.maximum = 100;
p.textAlignment = Qt::AlignCenter;
p.textVisible = true;
p.progress = (int)(100.0*index.model()->data(index).toDouble());
p.text = QString( ).sprintf( "%d%%", p.progress );
style->drawControl( QStyle::CE_ProgressBar, &p, painter );
}
else if( column == COL_WANTED )
{
QStyleOptionButton o;
o.state = QStyle::State_Enabled;
o.direction = QApplication::layoutDirection();
o.rect.setSize( QSize( 20, option.rect.height( ) ) );
o.rect.moveCenter( option.rect.center( ) );
o.fontMetrics = QApplication::fontMetrics();
switch( index.model()->data(index).toInt() ) {
case Qt::Unchecked: o.state |= QStyle::State_Off; break;
case Qt::Checked: o.state |= QStyle::State_On; break;
default: o.state |= QStyle::State_NoChange;break;
}
style->drawControl( QStyle::CE_CheckBox, &o, painter );
}
painter->restore( );
}
/****
*****
*****
*****
****/
FileTreeView :: FileTreeView( QWidget * parent ):
QTreeView( parent ),
myModel( this ),
myDelegate( this )
{
setAlternatingRowColors( true );
setSelectionBehavior( QAbstractItemView::SelectRows );
setSelectionMode( QAbstractItemView::ExtendedSelection );
setModel( &myModel );
setItemDelegate( &myDelegate );
setHorizontalScrollBarPolicy( Qt::ScrollBarAlwaysOff );
installEventFilter( this );
for( int i=0; i<=NUM_COLUMNS; ++i )
header()->setResizeMode( i, QHeaderView::Fixed );
connect( this, SIGNAL(clicked(const QModelIndex&)),
&myModel, SLOT(clicked(const QModelIndex&)));
connect( &myModel, SIGNAL(priorityChanged(const QSet<int>&, int)),
this, SIGNAL(priorityChanged(const QSet<int>&, int)));
connect( &myModel, SIGNAL(wantedChanged(const QSet<int>&, bool)),
this, SIGNAL(wantedChanged(const QSet<int>&, bool)));
}
bool
FileTreeView :: eventFilter( QObject * o, QEvent * event )
{
if( o != this )
return false;
// this is kind of a hack to get the last three columns be the
// right size, and to have the filename column use whatever
// space is left over...
if( event->type() == QEvent::Resize )
{
QResizeEvent * r = dynamic_cast<QResizeEvent*>(event);
int left = r->size().width();
const QFontMetrics fontMetrics( font( ) );
for( int column=0; column<NUM_COLUMNS; ++column ) {
if( column == COL_NAME )
continue;
if( isColumnHidden( column ) )
continue;
const QString header = myModel.headerData( column, Qt::Horizontal ).toString( ) + " ";
const int width = fontMetrics.size( 0, header ).width( );
setColumnWidth( column, width );
left -= width;
}
left -= 20; // not sure why this is necessary. it works in different themes + font sizes though...
setColumnWidth( COL_NAME, std::max(left,0) );
return false;
}
return false;
}
void
FileTreeView :: update( const FileList& files )
{
foreach( const TrFile file, files ) {
QList<QModelIndex> added;
myModel.addFile( file.index, file.filename, file.wanted, file.priority, file.size, file.have, added );
foreach( QModelIndex i, added )
expand( i );
}
}
void
FileTreeView :: clear( )
{
myModel.clear( );
}

156
qt/file-tree.h Normal file
View File

@ -0,0 +1,156 @@
/*
* This file Copyright (C) 2009 Charles Kerr <charles@transmissionbt.com>
*
* This file is licensed by the GPL version 2. Works owned by the
* Transmission project are granted a special exemption to clause 2(b)
* so that the bulk of its code can remain under the MIT license.
* This exemption does not extend to derived works not owned by
* the Transmission project.
*
* $Id:$
*/
#ifndef QTR_TREE_FILE_MODEL
#define QTR_TREE_FILE_MODEL
#include <QAbstractItemModel>
#include <QObject>
#include <QList>
#include <QString>
#include <QSet>
#include <QTreeView>
#include <QVariant>
#include <QItemDelegate>
#include "torrent.h" // FileList
/****
*****
****/
class FileTreeItem: public QObject
{
Q_OBJECT;
enum { LOW=(1<<0), NORMAL=(1<<1), HIGH=(1<<2) };
public:
virtual ~FileTreeItem( );
FileTreeItem( int fileIndex, const QString& name="" ):
myIndex(fileIndex), myParent(0), myName(name),
myPriority(0), myIsWanted(0),
myHaveSize(0), myTotalSize(0) { }
public:
void appendChild( FileTreeItem *child );
FileTreeItem * child( const QString& filename );
FileTreeItem * child( int row ) { return myChildren.at( row ); }
int childCount( ) const { return myChildren.size( ); }
FileTreeItem * parent( ) { return myParent; }
const FileTreeItem * parent( ) const { return myParent; }
int row( ) const;
const QString& name( ) const { return myName; }
QVariant data( int column ) const;
bool update( int index, bool want, int priority, uint64_t total, uint64_t have );
void twiddleWanted( QSet<int>& fileIds, bool& );
void twiddlePriority( QSet<int>& fileIds, int& );
private:
void setSubtreePriority( int priority, QSet<int>& fileIds );
void setSubtreeWanted( bool, QSet<int>& fileIds );
QString priorityString( ) const;
void getSubtreeSize( uint64_t& have, uint64_t& total ) const;
double progress( ) const;
int priority( ) const;
int isSubtreeWanted( ) const;
int myIndex;
FileTreeItem * myParent;
QList<FileTreeItem*> myChildren;
const QString myName;
int myPriority;
bool myIsWanted;
uint64_t myHaveSize;
uint64_t myTotalSize;
};
class FileTreeModel: public QAbstractItemModel
{
Q_OBJECT
public:
FileTreeModel( QObject *parent = 0);
~FileTreeModel( );
public:
QVariant data( const QModelIndex &index, int role = Qt::DisplayRole ) const;
Qt::ItemFlags flags( const QModelIndex& index ) const;
QVariant headerData( int section, Qt::Orientation orientation, int role = Qt::DisplayRole ) const;
QModelIndex index( int row, int column, const QModelIndex& parent = QModelIndex() ) const;
QModelIndex parent( const QModelIndex& child ) const;
QModelIndex parent( const QModelIndex& child, int column ) const;
int rowCount( const QModelIndex& parent = QModelIndex( ) ) const;
int columnCount( const QModelIndex &parent = QModelIndex( ) ) const;
signals:
void priorityChanged( const QSet<int>& fileIndices, int );
void wantedChanged( const QSet<int>& fileIndices, bool );
public:
void clear( );
void addFile( int index, const QString& filename,
bool wanted, int priority,
uint64_t size, uint64_t have,
QList<QModelIndex>& rowsAdded );
private:
void clearSubtree( const QModelIndex & );
QModelIndex indexOf( FileTreeItem *, int column ) const;
void parentsChanged( const QModelIndex &, int column );
void subtreeChanged( const QModelIndex &, int column );
private:
FileTreeItem * rootItem;
public slots:
void clicked ( const QModelIndex & index );
};
class FileTreeDelegate: public QItemDelegate
{
Q_OBJECT
public:
FileTreeDelegate( QObject * parent=0 ): QItemDelegate( parent ) { }
virtual ~FileTreeDelegate( ) { }
void paint( QPainter * painter,
const QStyleOptionViewItem & option,
const QModelIndex & index ) const;
};
class FileTreeView: public QTreeView
{
Q_OBJECT
public:
FileTreeView( QWidget * parent=0 );
virtual ~FileTreeView( ) { }
void clear( );
void update( const FileList& files );
signals:
void priorityChanged( const QSet<int>& fileIndices, int );
void wantedChanged( const QSet<int>& fileIndices, bool );
protected:
bool eventFilter( QObject *, QEvent * );
private:
FileTreeModel myModel;
FileTreeDelegate myDelegate;
};
#endif

202
qt/hig.cc Normal file
View File

@ -0,0 +1,202 @@
/*
* This file Copyright (C) 2009 Charles Kerr <charles@transmissionbt.com>
*
* This file is licensed by the GPL version 2. Works owned by the
* Transmission project are granted a special exemption to clause 2(b)
* so that the bulk of its code can remain under the MIT license.
* This exemption does not extend to derived works not owned by
* the Transmission project.
*
* $Id:$
*/
#include <QCheckBox>
#include <QGridLayout>
#include <QHBoxLayout>
#include <QLabel>
#include "hig.h"
HIG :: HIG( QWidget * parent ):
QWidget( parent ),
myRow( 0 ),
myGrid( new QGridLayout( this ) )
{
myGrid->setContentsMargins( PAD_BIG, PAD_BIG, PAD_BIG, PAD_BIG );
myGrid->setHorizontalSpacing( PAD_BIG );
myGrid->setVerticalSpacing( PAD );
myGrid->setColumnStretch ( 1, 1 );
}
HIG :: ~HIG( )
{
delete myGrid;
}
/***
****
***/
void
HIG :: addSectionDivider( )
{
QWidget * w = new QWidget( this );
myGrid->addWidget( w, myRow, 0, 1, 2 );
++myRow;
}
void
HIG :: addSectionTitle( const QString& title )
{
QLabel * label = new QLabel( this );
label->setText( title );
label->setStyleSheet( "font: bold" );
label->setAlignment( Qt::AlignLeft|Qt::AlignVCenter );
addSectionTitle( label );
}
void
HIG :: addSectionTitle( QWidget * w )
{
myGrid->addWidget( w, myRow, 0, 1, 2, Qt::AlignLeft|Qt::AlignVCenter );
++myRow;
}
void
HIG :: addSectionTitle( QLayout * l )
{
myGrid->addLayout( l, myRow, 0, 1, 2, Qt::AlignLeft|Qt::AlignVCenter );
++myRow;
}
QLayout *
HIG :: addRow( QWidget * w )
{
QHBoxLayout * h = new QHBoxLayout( );
h->addSpacing( 18 );
h->addWidget( w );
QLabel * l;
if( ( l = qobject_cast<QLabel*>(w) ) )
l->setAlignment( Qt::AlignLeft );
return h;
}
void
HIG :: addWideControl( QLayout * l )
{
QHBoxLayout * h = new QHBoxLayout( );
h->addSpacing( 18 );
h->addLayout( l );
myGrid->addLayout( h, myRow, 0, 1, 2, Qt::AlignLeft|Qt::AlignVCenter );
++myRow;
}
void
HIG :: addWideControl( QWidget * w )
{
QHBoxLayout * h = new QHBoxLayout( );
h->addSpacing( 18 );
h->addWidget( w );
myGrid->addLayout( h, myRow, 0, 1, 2, Qt::AlignLeft|Qt::AlignVCenter );
++myRow;
}
QCheckBox*
HIG :: addWideCheckBox( const QString& text, bool isChecked )
{
QCheckBox * check = new QCheckBox( text, this );
check->setChecked( isChecked );
addWideControl( check );
return check;
}
void
HIG :: addLabel( QWidget * w )
{
QHBoxLayout * h = new QHBoxLayout( );
h->addSpacing( 18 );
h->addWidget( w );
myGrid->addLayout( h, myRow, 0, 1, 1, Qt::AlignLeft|Qt::AlignVCenter );
}
QLabel*
HIG :: addLabel( const QString& text )
{
QLabel * label = new QLabel( this );
label->setText( text );
label->setAlignment( Qt::AlignLeft );
addLabel( label );
return label;
}
void
HIG :: addControl( QWidget * w )
{
myGrid->addWidget( w, myRow, 1, 1, 1 );
}
void
HIG :: addControl( QLayout * l )
{
myGrid->addLayout( l, myRow, 1, 1, 1 );
}
QLabel *
HIG :: addRow( const QString& text, QWidget * control, QWidget * buddy )
{
QLabel * label = addLabel( text );
addControl( control );
label->setBuddy( buddy ? buddy : control );
++myRow;
return label;
}
QLabel *
HIG :: addRow( const QString& text, QLayout * control, QWidget * buddy )
{
QLabel * label = addLabel( text );
addControl( control );
if( buddy != 0 )
label->setBuddy( buddy );
++myRow;
return label;
}
void
HIG :: addRow( QWidget * label, QWidget * control, QWidget * buddy )
{
addLabel( label );
if( control ) {
addControl( control );
QLabel * l = qobject_cast<QLabel*>( label );
if( l != 0 )
l->setBuddy( buddy ? buddy : control );
}
++myRow;
}
void
HIG :: addRow( QWidget * label, QLayout * control, QWidget * buddy )
{
addLabel( label );
if( control ) {
addControl( control );
QLabel * l = qobject_cast<QLabel*>( label );
if( l != 0 && buddy != 0 )
l->setBuddy( buddy );
}
++myRow;
}
void
HIG :: finish( )
{
QWidget * w = new QWidget( this );
myGrid->addWidget( w, myRow, 0, 1, 2 );
myGrid->setRowStretch( myRow, 100 );
++myRow;
}

66
qt/hig.h Normal file
View File

@ -0,0 +1,66 @@
/*
* This file Copyright (C) 2009 Charles Kerr <charles@transmissionbt.com>
*
* This file is licensed by the GPL version 2. Works owned by the
* Transmission project are granted a special exemption to clause 2(b)
* so that the bulk of its code can remain under the MIT license.
* This exemption does not extend to derived works not owned by
* the Transmission project.
*
* $Id:$
*/
#ifndef QTR_HIG_H
#define QTR_HIG_H
#include <QWidget>
class QCheckBox;
class QLabel;
class QString;
class QGridLayout;
class QLayout;
class HIG: public QWidget
{
Q_OBJECT
public:
enum {
PAD_SMALL = 3,
PAD = 6,
PAD_BIG = 12,
PAD_LARGE = PAD_BIG
};
public:
HIG( QWidget * parent = 0 );
virtual ~HIG( );
public:
void addSectionDivider( );
void addSectionTitle( const QString& );
void addSectionTitle( QWidget* );
void addSectionTitle( QLayout* );
void addWideControl( QLayout * );
void addWideControl( QWidget * );
QCheckBox* addWideCheckBox( const QString&, bool isChecked );
QLabel* addLabel( const QString& );
void addLabel( QWidget * );
void addControl( QWidget * );
void addControl( QLayout * );
QLabel* addRow( const QString & label, QWidget * control, QWidget * buddy=0 );
QLabel* addRow( const QString & label, QLayout * control, QWidget * buddy );
void addRow( QWidget * label, QWidget * control, QWidget * buddy=0 );
void addRow( QWidget * label, QLayout * control, QWidget * buddy );
void finish( );
private:
QLayout* addRow( QWidget* w );
private:
int myRow;
QGridLayout * myGrid;
};
#endif // QTR_HIG_H

BIN
qt/icons/dance.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 841 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

BIN
qt/icons/lock.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 515 B

BIN
qt/icons/transmission.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

BIN
qt/icons/turtle-blue.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 398 B

BIN
qt/icons/turtle-grey.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 456 B

800
qt/mainwin.cc Normal file
View File

@ -0,0 +1,800 @@
/*
* This file Copyright (C) 2009 Charles Kerr <charles@transmissionbt.com>
*
* This file is licensed by the GPL version 2. Works owned by the
* Transmission project are granted a special exemption to clause 2(b)
* so that the bulk of its code can remain under the MIT license.
* This exemption does not extend to derived works not owned by
* the Transmission project.
*
* $Id:$
*/
#include <cassert>
#include <iostream>
#include <QCheckBox>
#include <QDesktopServices>
#include <QFileDialog>
#include <QSize>
#include <QStyle>
#include <QHBoxLayout>
#include <QSystemTrayIcon>
#include <QUrl>
#include "about.h"
#include "details.h"
#include "mainwin.h"
#include "make-dialog.h"
#include "options.h"
#include "prefs.h"
#include "prefs-dialog.h"
#include "session.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 "ui_mainwin.h"
#include "utils.h"
#include "qticonloader.h"
#define PREFS_KEY "prefs-key";
QIcon
TrMainWindow :: getStockIcon( const QString& freedesktop_name, int fallback )
{
QIcon fallbackIcon;
if( fallback > 0 )
fallbackIcon = style()->standardIcon( QStyle::StandardPixmap( fallback ), 0, this );
return QtIconLoader::icon( freedesktop_name, fallbackIcon );
}
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( ) );
}
void setTextButtonSizeHint( QPushButton * button )
{
/* this is kind of a hack, possibly coming from my being new to Qt.
* Qt 4.4's sizeHint calculations for QPushButton have it include
* space for an icon, even if no icon is used. because of this,
* default pushbuttons look way too wide in the filterbar...
* so this routine recalculates the sizeHint without icons.
* If there's a Right Way to do this that I've missed, let me know */
button->setMaximumSize( calculateTextButtonSizeHint( button ) );
}
}
TrMainWindow :: TrMainWindow( Session& session, Prefs& prefs, TorrentModel& model, bool minimized ):
myLastFullUpdateTime( 0 ),
myPrefsDialog( new PrefsDialog( session, prefs, this ) ),
myAboutDialog( new AboutDialog( this ) ),
myStatsDialog( new StatsDialog( session, this ) ),
myFileDialog( 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 )
{
QAction * sep = new QAction( this );
sep->setSeparator( true );
ui.setupUi( this );
QString title( "Transmission" );
const QUrl remoteUrl( session.getRemoteUrl( ) );
if( !remoteUrl.isEmpty( ) )
title += tr( " - %1" ).arg( remoteUrl.toString() );
setWindowTitle( title );
QStyle * style = this->style();
int i = style->pixelMetric( QStyle::PM_SmallIconSize, 0, this );
const QSize smallIconSize( i, i );
// icons
ui.action_Add->setIcon( getStockIcon( "list-add", 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_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.statusbarStatsButton->setIcon( getStockIcon( "view-refresh", QStyle::SP_BrowserReload ) );
ui.downloadIconLabel->setPixmap( getStockIcon( "go-down", QStyle::SP_ArrowDown ).pixmap( smallIconSize ) );
ui.uploadIconLabel->setPixmap( getStockIcon( "go-up", QStyle::SP_ArrowUp ).pixmap( smallIconSize ) );
ui.filterEntryModeButton->setIcon( getStockIcon( "edit-find", QStyle::SP_ArrowForward ) );
ui.filterEntryClearButton->setIcon( getStockIcon( "edit-clear", QStyle::SP_DialogCloseButton ) );
// ui signals
// ccc
connect( ui.action_Toolbar, SIGNAL(toggled(bool)), this, SLOT(setToolbarVisible(bool)));
connect( ui.action_TrayIcon, SIGNAL(toggled(bool)), this, SLOT(setTrayIconVisible(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_MinimalView, SIGNAL(toggled(bool)), this, SLOT(setMinimalView(bool)));
connect( ui.action_SortByActivity, SIGNAL(toggled(bool)), &myFilterModel, SLOT(sortByActivity()) );
connect( ui.action_SortByAge, SIGNAL(toggled(bool)), &myFilterModel, SLOT(sortByAge()) );
connect( ui.action_SortByETA, SIGNAL(toggled(bool)), &myFilterModel, SLOT(sortByETA()));
connect( ui.action_SortByName, SIGNAL(toggled(bool)), &myFilterModel, SLOT(sortByName()));
connect( ui.action_SortByProgress, SIGNAL(toggled(bool)), &myFilterModel, SLOT(sortByProgress()));
connect( ui.action_SortByRatio, SIGNAL(toggled(bool)), &myFilterModel, SLOT(sortByRatio()));
connect( ui.action_SortBySize, SIGNAL(toggled(bool)), &myFilterModel, SLOT(sortBySize()));
connect( ui.action_SortByState, SIGNAL(toggled(bool)), &myFilterModel, SLOT(sortByState()));
connect( ui.action_SortByTracker, SIGNAL(toggled(bool)), &myFilterModel, SLOT(sortByTracker()));
connect( ui.action_ReverseSortOrder, SIGNAL(toggled(bool)), &myFilterModel, SLOT(setAscending(bool)));
connect( ui.action_Start, SIGNAL(triggered()), this, SLOT(startSelected()));
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_Add, SIGNAL(triggered()), this, SLOT(openTorrent()));
connect( ui.action_New, SIGNAL(triggered()), this, SLOT(newTorrent()));
connect( ui.action_Preferences, SIGNAL(triggered()), myPrefsDialog, SLOT(show()));
connect( ui.action_Statistics, SIGNAL(triggered()), myStatsDialog, SLOT(show()));
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_Properties, SIGNAL(triggered()), this, SLOT(openProperties()));
connect( ui.listView, SIGNAL(activated(const QModelIndex&)), ui.action_Properties, SLOT(trigger()));
// context menu
QList<QAction*> actions;
actions << ui.action_Properties
<< ui.action_OpenFolder
<< sep
<< ui.action_Start
<< ui.action_Pause
<< ui.action_Verify
<< ui.action_Announce
<< sep
<< ui.action_Remove
<< ui.action_Delete;
addActions( actions );
setContextMenuPolicy( Qt::ActionsContextMenu );
// signals
connect( ui.speedLimitModeButton, SIGNAL(clicked()), this, SLOT(toggleSpeedMode()));
connect( ui.filterAll, SIGNAL(clicked()), this, SLOT(showAll()));
connect( ui.filterActive, SIGNAL(clicked()), this, SLOT(showActive()));
connect( ui.filterDownloading, SIGNAL(clicked()), this, SLOT(showDownloading()));
connect( ui.filterSeeding, SIGNAL(clicked()), this, SLOT(showSeeding()));
connect( ui.filterPaused, SIGNAL(clicked()), this, SLOT(showPaused()));
connect( ui.filterEntryClearButton, SIGNAL(clicked()), ui.filterEntry, SLOT(clear()));
connect( ui.filterEntry, SIGNAL(textChanged(QString)), &myFilterModel, SLOT(setText(QString)));
connect( ui.action_SelectAll, SIGNAL(triggered()), ui.listView, SLOT(selectAll()));
connect( ui.action_DeselectAll, SIGNAL(triggered()), ui.listView, SLOT(clearSelection()));
setTextButtonSizeHint( ui.filterAll );
setTextButtonSizeHint( ui.filterActive );
setTextButtonSizeHint( ui.filterDownloading );
setTextButtonSizeHint( ui.filterSeeding );
setTextButtonSizeHint( ui.filterPaused );
setShowMode( myFilterModel.getShowMode( ) );
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(refreshActionSensitivity()));
connect( &myFilterModel, SIGNAL(rowsRemoved(const QModelIndex&,int,int)), this, SLOT(refreshActionSensitivity()));
connect( ui.action_Quit, SIGNAL(triggered()), QCoreApplication::instance(), SLOT(quit()) );
// torrent view
myFilterModel.setSourceModel( &myModel );
ui.listView->setModel( &myFilterModel );
connect( ui.listView->selectionModel(), SIGNAL(selectionChanged(const QItemSelection&,const QItemSelection&)), this, SLOT(refreshActionSensitivity()));
QActionGroup * actionGroup = new QActionGroup( this );
actionGroup->addAction( ui.action_FilterByName );
actionGroup->addAction( ui.action_FilterByFiles );
actionGroup->addAction( ui.action_FilterByTracker );
QMenu * menu = new QMenu( );
menu->addAction( ui.action_FilterByName );
menu->addAction( ui.action_FilterByFiles );
menu->addAction( ui.action_FilterByTracker );
ui.filterEntryModeButton->setMenu( menu );
connect( ui.action_FilterByName, SIGNAL(triggered()), this, SLOT(filterByName()));
connect( ui.action_FilterByFiles, SIGNAL(triggered()), this, SLOT(filterByFiles()));
connect( ui.action_FilterByTracker, SIGNAL(triggered()), this, SLOT(filterByTracker()));
ui.action_FilterByName->setChecked( true );
actionGroup = new QActionGroup( this );
actionGroup->addAction( ui.action_TotalRatio );
actionGroup->addAction( ui.action_TotalTransfer );
actionGroup->addAction( ui.action_SessionRatio );
actionGroup->addAction( ui.action_SessionTransfer );
menu = new QMenu( );
menu->addAction( ui.action_TotalRatio );
menu->addAction( ui.action_TotalTransfer );
menu->addAction( ui.action_SessionRatio );
menu->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()));
ui.statusbarStatsButton->setMenu( menu );
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_SortByRatio );
actionGroup->addAction( ui.action_SortBySize );
actionGroup->addAction( ui.action_SortByState );
actionGroup->addAction( ui.action_SortByTracker );
menu = new QMenu( );
menu->addAction( ui.action_Add );
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->addSeparator( );
menu->addAction( ui.action_Quit );
myTrayIcon.setContextMenu( menu );
myTrayIcon.setIcon( QApplication::windowIcon( ) );
connect( ui.action_ShowMainWindow, SIGNAL(toggled(bool)), this, SLOT(toggleWindows()));
connect( &myTrayIcon, SIGNAL(activated(QSystemTrayIcon::ActivationReason)), this, SLOT(trayActivated(QSystemTrayIcon::ActivationReason)));
connect( &myPrefs, SIGNAL(changed(int)), this, SLOT(refreshPref(int)) );
ui.action_ShowMainWindow->setChecked( !minimized );
ui.action_TrayIcon->setChecked( minimized || prefs.getBool( Prefs::SHOW_TRAY_ICON ) );
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 :: MINIMAL_VIEW;
foreach( int key, initKeys )
refreshPref( key );
connect( &mySession, SIGNAL(statsUpdated()), this, SLOT(refreshStatusBar()) );
connect( &mySession, SIGNAL(dataReadProgress()), this, SLOT(dataReadProgress()) );
connect( &mySession, SIGNAL(dataSendProgress()), this, SLOT(dataSendProgress()) );
if( mySession.isServer( ) )
ui.networkLabel->hide( );
else {
connect( &myNetworkTimer, SIGNAL(timeout()), this, SLOT(onNetworkTimer()));
myNetworkTimer.start( 1000 );
}
refreshActionSensitivity( );
refreshStatusBar( );
refreshVisibleCount( );
}
TrMainWindow :: ~TrMainWindow( )
{
}
/****
*****
****/
void
TrMainWindow :: openProperties( )
{
const int id( *getSelectedTorrents().begin() );
Torrent * torrent( myModel.getTorrentFromId( id ) );
assert( torrent != 0 );
QDialog * d( new Details( mySession, *torrent, this ) );
d->show( );
}
void
TrMainWindow :: openFolder( )
{
const int torrentId( *getSelectedTorrents().begin() );
const Torrent * tor( myModel.getTorrentFromId( torrentId ) );
const QString path( tor->getPath( ) );
QDesktopServices :: openUrl( QUrl::fromLocalFile( path ) );
}
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];
snprintf( url, sizeof( url ), fmt, major, minor/10 );
QDesktopServices :: openUrl( QUrl( QString( url ) ) );
}
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 );
ui.visibleCountLabel->setText( str );
}
void
TrMainWindow :: refreshStatusBar( )
{
const Speed up( myModel.getUploadSpeed( ) );
const Speed down( myModel.getDownloadSpeed( ) );
ui.uploadTextLabel->setText( Utils :: speedToString( up ) );
ui.downloadTextLabel->setText( Utils :: speedToString( down ) );
const QString mode( myPrefs.getString( Prefs::STATUSBAR_STATS ) );
QString str;
if( mode == "session-ratio" )
{
str = tr( "Ratio: %1" ).arg( Utils :: ratioToString( mySession.getStats().ratio ) );
}
else if( mode == "session-transfer" )
{
const tr_session_stats& stats( mySession.getStats( ) );
str = tr( "Down: %1, Up: %2" ).arg( Utils :: sizeToString( stats.downloadedBytes ) )
.arg( Utils :: sizeToString( stats.uploadedBytes ) );
}
else if( mode == "total-transfer" )
{
const tr_session_stats& stats( mySession.getCumulativeStats( ) );
str = tr( "Down: %1, Up: %2" ).arg( Utils :: sizeToString( stats.downloadedBytes ) )
.arg( Utils :: sizeToString( stats.uploadedBytes ) );
}
else /* default is "total-ratio" */
{
str = tr( "Ratio: %1" ).arg( Utils :: ratioToString( mySession.getCumulativeStats().ratio ) );
}
ui.statusbarStatsLabel->setText( str );
}
void
TrMainWindow :: refreshActionSensitivity( )
{
int selected( 0 );
int paused( 0 );
int selectedAndPaused( 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*>( ) );
const bool isSelected( selectionModel->isSelected( modelIndex ) );
const bool isPaused( tor->isPaused( ) );
if( isSelected )
++selected;
if( isPaused )
++ paused;
if( isSelected && isPaused )
++selectedAndPaused;
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_DeselectAll->setEnabled( haveSelection );
const bool oneSelection( selected == 1 );
ui.action_Properties->setEnabled( oneSelection );
ui.action_OpenFolder->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_Pause->setEnabled( selectedAndPaused < selected );
ui.action_Announce->setEnabled( selected > 0 && ( canAnnounce == selected ) );
}
/**
***
**/
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.model()->data( index, TorrentModel::TorrentRole ).value<const Torrent*>( ) );
ids.insert( tor->id( ) );
}
return ids;
}
void
TrMainWindow :: startSelected( )
{
mySession.start( getSelectedTorrents( ) );
}
void
TrMainWindow :: pauseSelected( )
{
mySession.pause( getSelectedTorrents( ) );
}
void
TrMainWindow :: startAll( )
{
mySession.start( );
}
void
TrMainWindow :: pauseAll( )
{
mySession.pause( );
}
void
TrMainWindow :: removeSelected( )
{
mySession.removeTorrents( getSelectedTorrents( ), false );
}
void
TrMainWindow :: deleteSelected( )
{
mySession.removeTorrents( getSelectedTorrents( ), true );
}
void
TrMainWindow :: verifySelected( )
{
mySession.verifyTorrents( getSelectedTorrents( ) );
}
void
TrMainWindow :: reannounceSelected( )
{
mySession.reannounceTorrents( getSelectedTorrents( ) );
}
/**
***
**/
void
TrMainWindow :: setShowMode( TorrentFilter :: ShowMode mode )
{
ui.filterAll->setChecked( mode == TorrentFilter::SHOW_ALL );
ui.filterActive->setChecked( mode == TorrentFilter::SHOW_ACTIVE );
ui.filterDownloading->setChecked( mode == TorrentFilter::SHOW_DOWNLOADING );
ui.filterSeeding->setChecked( mode == TorrentFilter::SHOW_SEEDING );
ui.filterPaused->setChecked( mode == TorrentFilter::SHOW_PAUSED );
myFilterModel.setShowMode( mode );
}
void TrMainWindow :: showAll ( ) { setShowMode( TorrentFilter :: SHOW_ALL ); }
void TrMainWindow :: showActive ( ) { setShowMode( TorrentFilter :: SHOW_ACTIVE ); }
void TrMainWindow :: showDownloading ( ) { setShowMode( TorrentFilter :: SHOW_DOWNLOADING ); }
void TrMainWindow :: showSeeding ( ) { setShowMode( TorrentFilter :: SHOW_SEEDING ); }
void TrMainWindow :: showPaused ( ) { setShowMode( TorrentFilter :: SHOW_PAUSED ); }
void TrMainWindow :: filterByName ( ) { myFilterModel.setTextMode( TorrentFilter :: FILTER_BY_NAME ); }
void TrMainWindow :: filterByTracker ( ) { myFilterModel.setTextMode( TorrentFilter :: FILTER_BY_TRACKER ); }
void TrMainWindow :: filterByFiles ( ) { myFilterModel.setTextMode( TorrentFilter :: FILTER_BY_FILES ); }
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 :: setMinimalView( bool visible )
{
myPrefs.set( Prefs :: MINIMAL_VIEW, visible );
}
void
TrMainWindow :: setTrayIconVisible( bool visible )
{
myPrefs.set( Prefs :: SHOW_TRAY_ICON, visible );
}
void
TrMainWindow :: toggleSpeedMode( )
{
myPrefs.toggleBool( Prefs :: ALT_SPEED_LIMIT_ENABLED );
}
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( )
{
setVisible( !isVisible( ) );
}
void
TrMainWindow :: trayActivated( QSystemTrayIcon::ActivationReason reason )
{
if( reason == QSystemTrayIcon::Trigger )
ui.action_ShowMainWindow->toggle( );
}
void
TrMainWindow :: refreshPref( int key )
{
bool b;
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:
str = myPrefs.getString( key );
ui.action_SortByActivity->setChecked ( str == "sort-by-activity" );
ui.action_SortByAge->setChecked ( str == "sort-by-age" );
ui.action_SortByETA->setChecked ( str == "sort-by-eta" );
ui.action_SortByName->setChecked ( str == "sort-by-name" );
ui.action_SortByProgress->setChecked ( str == "sort-by-progress" );
ui.action_SortByRatio->setChecked ( str == "sort-by-ratio" );
ui.action_SortBySize->setChecked ( str == "sort-by-size" );
ui.action_SortByState->setChecked ( str == "sort-by-state" );
ui.action_SortByTracker->setChecked ( str == "sort-by-tracker" );
break;
case Prefs::FILTERBAR:
b = myPrefs.getBool( key );
ui.filterbar->setVisible( b );
ui.action_Filterbar->setChecked( b );
break;
case Prefs::STATUSBAR:
b = myPrefs.getBool( key );
ui.statusbar->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 );
break;
case Prefs::MINIMAL_VIEW:
b = myPrefs.getBool( key );
ui.action_MinimalView->setChecked( b );
ui.listView->setItemDelegate( b ? myTorrentDelegateMin : myTorrentDelegate );
ui.listView->reset( ); // force the rows to resize
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:
b = myPrefs.getBool( key );
ui.speedLimitModeButton->setChecked( b );
ui.speedLimitModeButton->setIcon( b ? mySpeedModeOnIcon : mySpeedModeOffIcon );
ui.speedLimitModeButton->setToolTip( b ? tr( "Click to disable Speed Limit Mode" )
: tr( "Click to enable Speed Limit Mode" ) );
break;
default:
break;
}
}
/***
****
***/
void
TrMainWindow :: newTorrent( )
{
MakeDialog * d = new MakeDialog( mySession, this );
d->show( );
}
void
TrMainWindow :: openTorrent( )
{
if( myFileDialog == 0 )
{
myFileDialog = new QFileDialog( this,
tr( "Add Torrent" ),
myPrefs.getString( Prefs::OPEN_DIALOG_FOLDER ),
tr( "Torrent Files (*.torrent);;All Files (*.*)" ) );
myFileDialog->setFileMode( QFileDialog::ExistingFiles );
QCheckBox * button = new QCheckBox( tr( "Display &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 :: 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 :: updateNetworkIcon( )
{
const time_t now = time( NULL );
const int period = 3;
const bool isSending = now - myLastSendTime <= period;
const bool isReading = now - myLastReadTime <= 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";
QIcon icon = getStockIcon( key, QStyle::SP_DriveNetIcon );
QPixmap pixmap = icon.pixmap ( 16, 16 );
ui.networkLabel->setPixmap( pixmap );
ui.networkLabel->setToolTip( isSending || isReading
? tr( "Transmission server is responding" )
: tr( "Last response from server was %1 ago" ).arg( Utils::timeToString( now-std::max(myLastReadTime,myLastSendTime))));
}
void
TrMainWindow :: onNetworkTimer( )
{
updateNetworkIcon( );
}
void
TrMainWindow :: dataReadProgress( )
{
myLastReadTime = time( NULL );
updateNetworkIcon( );
}
void
TrMainWindow :: dataSendProgress( )
{
myLastSendTime = time( NULL );
updateNetworkIcon( );
}

132
qt/mainwin.h Normal file
View File

@ -0,0 +1,132 @@
/*
* This file Copyright (C) 2009 Charles Kerr <charles@transmissionbt.com>
*
* This file is licensed by the GPL version 2. Works owned by the
* Transmission project are granted a special exemption to clause 2(b)
* so that the bulk of its code can remain under the MIT license.
* This exemption does not extend to derived works not owned by
* the Transmission project.
*
* $Id:$
*/
#ifndef MAIN_WINDOW_H
#define MAIN_WINDOW_H
#include <ctime>
#include <QCheckBox>
#include <QFileDialog>
#include <QIcon>
#include <QMainWindow>
#include <QSet>
#include <QSystemTrayIcon>
#include <QTimer>
#include <QWidgetList>
extern "C" {
struct tr_benc;
};
#include "torrent-filter.h"
#include "ui_mainwin.h"
class ActionDelegator;
class Prefs;
class Session;
class TorrentDelegate;
class TorrentDelegateMin;
class TorrentModel;
class QModelIndex;
class QSortFilterProxyModel;
class TrMainWindow: public QMainWindow
{
Q_OBJECT
private:
time_t myLastFullUpdateTime;
QDialog * myPrefsDialog;
QDialog * myAboutDialog;
QDialog * myStatsDialog;
QFileDialog * myFileDialog;
QCheckBox * myFileDialogOptionsCheck;
QSystemTrayIcon myTrayIcon;
TorrentFilter myFilterModel;
TorrentDelegate * myTorrentDelegate;
TorrentDelegateMin * myTorrentDelegateMin;
Session& mySession;
Prefs& myPrefs;
TorrentModel& myModel;
Ui_MainWindow ui;
QIcon mySpeedModeOffIcon;
QIcon mySpeedModeOnIcon;
time_t myLastSendTime;
time_t myLastReadTime;
QTimer myNetworkTimer;
private:
QIcon getStockIcon( const QString&, int fallback=-1 );
private:
void setShowMode( TorrentFilter::ShowMode );
QSet<int> getSelectedTorrents( ) const;
void updateNetworkIcon( );
QWidgetList myHidden;
private slots:
void showAll( );
void showActive( );
void showDownloading( );
void showSeeding( );
void showPaused( );
void filterByName( );
void filterByFiles( );
void filterByTracker( );
void showTotalRatio( );
void showTotalTransfer( );
void showSessionRatio( );
void showSessionTransfer( );
void refreshVisibleCount( );
void refreshStatusBar( );
void openTorrent( );
void newTorrent( );
void trayActivated( QSystemTrayIcon::ActivationReason );
void refreshPref( int key );
void addTorrents( const QStringList& filenames );
void openHelp( );
void openFolder( );
void openProperties( );
void toggleSpeedMode( );
void dataReadProgress( );
void dataSendProgress( );
void toggleWindows( );
public slots:
void startAll( );
void startSelected( );
void pauseAll( );
void pauseSelected( );
void removeSelected( );
void deleteSelected( );
void verifySelected( );
void reannounceSelected( );
void addTorrent( const QString& filename );
void onNetworkTimer( );
private:
void clearSelection( );
public slots:
void setToolbarVisible( bool );
void setFilterbarVisible( bool );
void setStatusbarVisible( bool );
void setTrayIconVisible( bool );
void setMinimalView( bool );
void refreshActionSensitivity( );
public:
TrMainWindow( Session&, Prefs&, TorrentModel&, bool minized );
virtual ~TrMainWindow( );
};
#endif

894
qt/mainwin.ui Normal file
View File

@ -0,0 +1,894 @@
<ui version="4.0" >
<class>MainWindow</class>
<widget class="QMainWindow" name="MainWindow" >
<property name="geometry" >
<rect>
<x>0</x>
<y>0</y>
<width>792</width>
<height>393</height>
</rect>
</property>
<property name="windowTitle" >
<string>Transmission</string>
</property>
<widget class="QWidget" name="centralwidget" >
<layout class="QVBoxLayout" name="verticalLayout" >
<property name="spacing" >
<number>0</number>
</property>
<property name="margin" >
<number>0</number>
</property>
<item>
<widget class="QWidget" native="1" name="filterbar" >
<property name="sizePolicy" >
<sizepolicy vsizetype="Preferred" hsizetype="Preferred" >
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<layout class="QHBoxLayout" name="horizontalLayout_2" >
<property name="spacing" >
<number>3</number>
</property>
<property name="sizeConstraint" >
<enum>QLayout::SetDefaultConstraint</enum>
</property>
<property name="margin" >
<number>2</number>
</property>
<item>
<widget class="QPushButton" name="filterAll" >
<property name="toolTip" >
<string>Show all torrents</string>
</property>
<property name="text" >
<string>A&amp;ll</string>
</property>
<property name="checkable" >
<bool>true</bool>
</property>
<property name="flat" >
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="filterActive" >
<property name="sizePolicy" >
<sizepolicy vsizetype="Fixed" hsizetype="Preferred" >
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="toolTip" >
<string>Show active torrents</string>
</property>
<property name="text" >
<string>&amp;Active</string>
</property>
<property name="checkable" >
<bool>true</bool>
</property>
<property name="flat" >
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="filterDownloading" >
<property name="sizePolicy" >
<sizepolicy vsizetype="Fixed" hsizetype="Preferred" >
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="baseSize" >
<size>
<width>0</width>
<height>0</height>
</size>
</property>
<property name="toolTip" >
<string>Show torrents being downloaded</string>
</property>
<property name="text" >
<string>&amp;Downloading</string>
</property>
<property name="checkable" >
<bool>true</bool>
</property>
<property name="flat" >
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="filterSeeding" >
<property name="sizePolicy" >
<sizepolicy vsizetype="Fixed" hsizetype="Preferred" >
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="toolTip" >
<string>Show torrents being seeded</string>
</property>
<property name="text" >
<string>&amp;Seeding</string>
</property>
<property name="checkable" >
<bool>true</bool>
</property>
<property name="flat" >
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="filterPaused" >
<property name="sizePolicy" >
<sizepolicy vsizetype="Fixed" hsizetype="Preferred" >
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="toolTip" >
<string>Show paused torrents</string>
</property>
<property name="text" >
<string>&amp;Paused</string>
</property>
<property name="checkable" >
<bool>true</bool>
</property>
<property name="flat" >
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QWidget" native="1" name="filterBarSpacer" >
<property name="sizePolicy" >
<sizepolicy vsizetype="Fixed" hsizetype="Expanding" >
<horstretch>1</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="filterEntryModeButton" >
<property name="sizePolicy" >
<sizepolicy vsizetype="Fixed" hsizetype="Minimum" >
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="toolTip" >
<string>Set text filter mode</string>
</property>
<property name="text" >
<string/>
</property>
<property name="flat" >
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QLineEdit" name="filterEntry" >
<property name="sizePolicy" >
<sizepolicy vsizetype="Fixed" hsizetype="Preferred" >
<horstretch>3</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="toolTip" >
<string>Show torrents matching this text</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="filterEntryClearButton" >
<property name="toolTip" >
<string>Clear the filter text</string>
</property>
<property name="text" >
<string/>
</property>
<property name="flat" >
<bool>true</bool>
</property>
</widget>
</item>
</layout>
<zorder>filterAll</zorder>
<zorder>filterActive</zorder>
<zorder>filterDownloading</zorder>
<zorder>filterSeeding</zorder>
<zorder>filterPaused</zorder>
<zorder>filterBarSpacer</zorder>
<zorder>filterEntry</zorder>
<zorder>filterEntryClearButton</zorder>
<zorder>filterEntryModeButton</zorder>
</widget>
</item>
<item>
<widget class="QListView" name="listView" >
<property name="sizePolicy" >
<sizepolicy vsizetype="Expanding" hsizetype="Preferred" >
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="horizontalScrollBarPolicy" >
<enum>Qt::ScrollBarAlwaysOff</enum>
</property>
<property name="alternatingRowColors" >
<bool>true</bool>
</property>
<property name="selectionMode" >
<enum>QAbstractItemView::ExtendedSelection</enum>
</property>
<property name="uniformItemSizes" >
<bool>false</bool>
</property>
</widget>
</item>
<item>
<widget class="QWidget" native="1" name="statusbar" >
<property name="sizePolicy" >
<sizepolicy vsizetype="Fixed" hsizetype="Preferred" >
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<layout class="QHBoxLayout" name="horizontalLayout" >
<property name="spacing" >
<number>3</number>
</property>
<property name="margin" >
<number>3</number>
</property>
<item>
<widget class="QPushButton" name="speedLimitModeButton" >
<property name="flat" >
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="networkLabel" >
<property name="minimumSize" >
<size>
<width>16</width>
<height>16</height>
</size>
</property>
<property name="text" >
<string/>
</property>
<property name="textFormat" >
<enum>Qt::PlainText</enum>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer_4" >
<property name="orientation" >
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeType" >
<enum>QSizePolicy::Expanding</enum>
</property>
<property name="sizeHint" stdset="0" >
<size>
<width>1</width>
<height>1</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QLabel" name="visibleCountLabel" >
<property name="text" >
<string>TextLabel</string>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer" >
<property name="orientation" >
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeType" >
<enum>QSizePolicy::Expanding</enum>
</property>
<property name="sizeHint" stdset="0" >
<size>
<width>1</width>
<height>1</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QPushButton" name="statusbarStatsButton" >
<property name="text" >
<string/>
</property>
<property name="iconSize" >
<size>
<width>16</width>
<height>16</height>
</size>
</property>
<property name="flat" >
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="statusbarStatsLabel" >
<property name="text" >
<string>TextLabel</string>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer_3" >
<property name="orientation" >
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeType" >
<enum>QSizePolicy::Fixed</enum>
</property>
<property name="sizeHint" stdset="0" >
<size>
<width>20</width>
<height>0</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QLabel" name="downloadIconLabel" >
<property name="text" >
<string>TextLabel</string>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="downloadTextLabel" >
<property name="toolTip" >
<string>Overall download speed</string>
</property>
<property name="text" >
<string>TextLabel</string>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer_2" >
<property name="orientation" >
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeType" >
<enum>QSizePolicy::Fixed</enum>
</property>
<property name="sizeHint" stdset="0" >
<size>
<width>20</width>
<height>0</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QLabel" name="uploadIconLabel" >
<property name="text" >
<string>TextLabel</string>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="uploadTextLabel" >
<property name="toolTip" >
<string>Overall upload speed</string>
</property>
<property name="text" >
<string>TextLabel</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
</layout>
<zorder>listView</zorder>
<zorder>statusbar</zorder>
<zorder>filterbar</zorder>
</widget>
<widget class="QMenuBar" name="menubar" >
<property name="geometry" >
<rect>
<x>0</x>
<y>0</y>
<width>792</width>
<height>25</height>
</rect>
</property>
<property name="sizePolicy" >
<sizepolicy vsizetype="Fixed" hsizetype="MinimumExpanding" >
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<widget class="QMenu" name="menuTorrent" >
<property name="title" >
<string>&amp;Torrent</string>
</property>
<addaction name="action_Add" />
<addaction name="action_New" />
<addaction name="separator" />
<addaction name="action_Properties" />
<addaction name="action_OpenFolder" />
<addaction name="separator" />
<addaction name="action_Start" />
<addaction name="action_Announce" />
<addaction name="action_Pause" />
<addaction name="action_Verify" />
<addaction name="action_Remove" />
<addaction name="action_Delete" />
<addaction name="separator" />
<addaction name="action_StartAll" />
<addaction name="action_PauseAll" />
<addaction name="separator" />
<addaction name="action_Quit" />
</widget>
<widget class="QMenu" name="menuEdit" >
<property name="title" >
<string>&amp;Edit</string>
</property>
<addaction name="action_SelectAll" />
<addaction name="action_DeselectAll" />
<addaction name="separator" />
<addaction name="action_Preferences" />
</widget>
<widget class="QMenu" name="menu_Help" >
<property name="title" >
<string>&amp;Help</string>
</property>
<addaction name="action_ShowMessageLog" />
<addaction name="action_Statistics" />
<addaction name="separator" />
<addaction name="action_Contents" />
<addaction name="action_About" />
</widget>
<widget class="QMenu" name="menu_View" >
<property name="title" >
<string>&amp;View</string>
</property>
<addaction name="action_MinimalView" />
<addaction name="separator" />
<addaction name="action_Toolbar" />
<addaction name="action_Filterbar" />
<addaction name="action_Statusbar" />
<addaction name="action_TrayIcon" />
<addaction name="separator" />
<addaction name="action_SortByActivity" />
<addaction name="action_SortByAge" />
<addaction name="action_SortByETA" />
<addaction name="action_SortByName" />
<addaction name="action_SortByProgress" />
<addaction name="action_SortByRatio" />
<addaction name="action_SortBySize" />
<addaction name="action_SortByState" />
<addaction name="action_SortByTracker" />
<addaction name="separator" />
<addaction name="action_ReverseSortOrder" />
</widget>
<addaction name="menuTorrent" />
<addaction name="menuEdit" />
<addaction name="menu_View" />
<addaction name="menu_Help" />
</widget>
<widget class="QToolBar" name="toolBar" >
<property name="windowTitle" >
<string>toolBar</string>
</property>
<property name="movable" >
<bool>false</bool>
</property>
<property name="allowedAreas" >
<set>Qt::TopToolBarArea</set>
</property>
<property name="iconSize" >
<size>
<width>24</width>
<height>24</height>
</size>
</property>
<property name="toolButtonStyle" >
<enum>Qt::ToolButtonTextUnderIcon</enum>
</property>
<property name="floatable" >
<bool>false</bool>
</property>
<attribute name="toolBarArea" >
<enum>TopToolBarArea</enum>
</attribute>
<attribute name="toolBarBreak" >
<bool>false</bool>
</attribute>
<addaction name="action_Add" />
<addaction name="action_Start" />
<addaction name="action_Pause" />
<addaction name="action_Remove" />
<addaction name="separator" />
<addaction name="action_Properties" />
</widget>
<action name="action_Add" >
<property name="text" >
<string>&amp;Add...</string>
</property>
<property name="toolTip" >
<string>Add a torrent</string>
</property>
<property name="shortcut" >
<string>Ctrl+D</string>
</property>
</action>
<action name="action_New" >
<property name="text" >
<string>&amp;New...</string>
</property>
<property name="toolTip" >
<string>Create a new torrent</string>
</property>
<property name="shortcut" >
<string>Ctrl+N</string>
</property>
</action>
<action name="action_Properties" >
<property name="text" >
<string>&amp;Properties</string>
</property>
<property name="toolTip" >
<string>Show torrent properties</string>
</property>
<property name="shortcut" >
<string>Alt+Enter</string>
</property>
</action>
<action name="action_OpenFolder" >
<property name="text" >
<string>&amp;Open Folder</string>
</property>
<property name="toolTip" >
<string>Open the torrent's folder</string>
</property>
<property name="shortcut" >
<string>Ctrl+O</string>
</property>
</action>
<action name="action_Start" >
<property name="text" >
<string>&amp;Start</string>
</property>
<property name="toolTip" >
<string>Start torrent</string>
</property>
<property name="shortcut" >
<string>Ctrl+S</string>
</property>
</action>
<action name="action_Announce" >
<property name="text" >
<string>Ask Tracker for &amp;More Peers</string>
</property>
<property name="toolTip" >
<string>Ask tracker for more peers</string>
</property>
</action>
<action name="action_Pause" >
<property name="text" >
<string>&amp;Pause</string>
</property>
<property name="toolTip" >
<string>Pause torrent</string>
</property>
<property name="shortcut" >
<string>Ctrl+P</string>
</property>
</action>
<action name="action_Verify" >
<property name="text" >
<string>&amp;Verify Local Data</string>
</property>
<property name="toolTip" >
<string>Verify local data</string>
</property>
</action>
<action name="action_Remove" >
<property name="text" >
<string>&amp;Remove</string>
</property>
<property name="toolTip" >
<string>Remove torrent</string>
</property>
<property name="shortcut" >
<string>Del</string>
</property>
</action>
<action name="action_Delete" >
<property name="text" >
<string>&amp;Delete Files and Remove</string>
</property>
<property name="toolTip" >
<string>Remove torrent and delete its files</string>
</property>
<property name="shortcut" >
<string>Shift+Del</string>
</property>
</action>
<action name="action_StartAll" >
<property name="text" >
<string>&amp;Start All</string>
</property>
</action>
<action name="action_PauseAll" >
<property name="text" >
<string>&amp;Pause All</string>
</property>
</action>
<action name="action_Quit" >
<property name="text" >
<string>&amp;Quit</string>
</property>
<property name="shortcut" >
<string>Ctrl+Q</string>
</property>
</action>
<action name="action_SelectAll" >
<property name="text" >
<string>&amp;Select All</string>
</property>
<property name="shortcut" >
<string>Ctrl+A</string>
</property>
</action>
<action name="action_DeselectAll" >
<property name="text" >
<string>&amp;Deselect All</string>
</property>
<property name="shortcut" >
<string>Ctrl+Shift+A</string>
</property>
</action>
<action name="action_Preferences" >
<property name="text" >
<string>&amp;Preferences</string>
</property>
</action>
<action name="action_MinimalView" >
<property name="checkable" >
<bool>true</bool>
</property>
<property name="text" >
<string>&amp;Minimal View</string>
</property>
<property name="shortcut" >
<string>Alt+M</string>
</property>
</action>
<action name="action_Toolbar" >
<property name="checkable" >
<bool>true</bool>
</property>
<property name="text" >
<string>&amp;Toolbar</string>
</property>
</action>
<action name="action_Filterbar" >
<property name="checkable" >
<bool>true</bool>
</property>
<property name="text" >
<string>&amp;Filterbar</string>
</property>
</action>
<action name="action_Statusbar" >
<property name="checkable" >
<bool>true</bool>
</property>
<property name="text" >
<string>&amp;Statusbar</string>
</property>
</action>
<action name="action_SortByActivity" >
<property name="checkable" >
<bool>true</bool>
</property>
<property name="text" >
<string>Sort by &amp;Activity</string>
</property>
</action>
<action name="action_SortByAge" >
<property name="checkable" >
<bool>true</bool>
</property>
<property name="text" >
<string>Sort by A&amp;ge</string>
</property>
</action>
<action name="action_SortByETA" >
<property name="checkable" >
<bool>true</bool>
</property>
<property name="text" >
<string>Sort by &amp;ETA</string>
</property>
</action>
<action name="action_SortByName" >
<property name="checkable" >
<bool>true</bool>
</property>
<property name="text" >
<string>Sort by &amp;Name</string>
</property>
</action>
<action name="action_SortByProgress" >
<property name="checkable" >
<bool>true</bool>
</property>
<property name="text" >
<string>Sort by &amp;Progress</string>
</property>
</action>
<action name="action_SortByRatio" >
<property name="checkable" >
<bool>true</bool>
</property>
<property name="text" >
<string>Sort by &amp;Ratio</string>
</property>
</action>
<action name="action_SortBySize" >
<property name="checkable" >
<bool>true</bool>
</property>
<property name="text" >
<string>Sort by Si&amp;ze</string>
</property>
</action>
<action name="action_SortByState" >
<property name="checkable" >
<bool>true</bool>
</property>
<property name="text" >
<string>Sort by &amp;State</string>
</property>
</action>
<action name="action_SortByTracker" >
<property name="checkable" >
<bool>true</bool>
</property>
<property name="text" >
<string>Sort by &amp;Tracker</string>
</property>
</action>
<action name="action_ShowMessageLog" >
<property name="checkable" >
<bool>false</bool>
</property>
<property name="text" >
<string>Message &amp;Log</string>
</property>
</action>
<action name="action_Statistics" >
<property name="checkable" >
<bool>false</bool>
</property>
<property name="text" >
<string>&amp;Statistics</string>
</property>
</action>
<action name="action_Contents" >
<property name="text" >
<string>&amp;Contents</string>
</property>
</action>
<action name="action_About" >
<property name="text" >
<string>&amp;About</string>
</property>
</action>
<action name="action_ReverseSortOrder" >
<property name="checkable" >
<bool>true</bool>
</property>
<property name="text" >
<string>&amp;Reverse Sort Order</string>
</property>
</action>
<action name="action_FilterByName" >
<property name="checkable" >
<bool>true</bool>
</property>
<property name="text" >
<string>&amp;Name</string>
</property>
</action>
<action name="action_FilterByFiles" >
<property name="checkable" >
<bool>true</bool>
</property>
<property name="text" >
<string>&amp;Files</string>
</property>
</action>
<action name="action_FilterByTracker" >
<property name="checkable" >
<bool>true</bool>
</property>
<property name="text" >
<string>&amp;Tracker</string>
</property>
</action>
<action name="action_TotalRatio" >
<property name="checkable" >
<bool>true</bool>
</property>
<property name="text" >
<string>Total Ratio</string>
</property>
</action>
<action name="action_SessionRatio" >
<property name="checkable" >
<bool>true</bool>
</property>
<property name="text" >
<string>Session Ratio</string>
</property>
</action>
<action name="action_TotalTransfer" >
<property name="checkable" >
<bool>true</bool>
</property>
<property name="text" >
<string>Total Transfer</string>
</property>
</action>
<action name="action_SessionTransfer" >
<property name="checkable" >
<bool>true</bool>
</property>
<property name="text" >
<string>Session Transfer</string>
</property>
</action>
<action name="action_ShowMainWindow" >
<property name="checkable" >
<bool>true</bool>
</property>
<property name="text" >
<string>&amp;Main Window</string>
</property>
</action>
<action name="action_TrayIcon" >
<property name="checkable" >
<bool>true</bool>
</property>
<property name="text" >
<string>Tray &amp;Icon</string>
</property>
</action>
</widget>
<resources>
<include location="application.qrc" />
</resources>
<connections/>
</ui>

331
qt/make-dialog.cc Normal file
View File

@ -0,0 +1,331 @@
/*
* This file Copyright (C) 2009 Charles Kerr <charles@transmissionbt.com>
*
* This file is licensed by the GPL version 2. Works owned by the
* Transmission project are granted a special exemption to clause 2(b)
* so that the bulk of its code can remain under the MIT license.
* This exemption does not extend to derived works not owned by
* the Transmission project.
*
* $Id:$
*/
#include <cassert>
#include <iostream>
#include <QDialogButtonBox>
#include <QTimer>
#include <QFileDialog>
#include <QLabel>
#include <QStyle>
#include <QPushButton>
#include <QLineEdit>
#include <QPlainTextEdit>
#include <QCheckBox>
#include <QHBoxLayout>
#include <QVBoxLayout>
#include <QList>
#include <QProgressBar>
#include <libtransmission/transmission.h>
#include <libtransmission/makemeta.h>
#include <libtransmission/utils.h>
#include "hig.h"
#include "make-dialog.h"
#include "squeezelabel.h"
#include "qticonloader.h"
#include "utils.h"
MakeDialog :: MakeDialog( Session & mySession, QWidget * parent ):
QDialog( parent, Qt::Dialog ),
myBuilder( 0 ),
myIsBuilding( 0 )
{
Q_UNUSED( mySession );
connect( &myTimer, SIGNAL(timeout()), this, SLOT(onProgress()) );
setWindowTitle( tr( "New Torrent" ) );
QVBoxLayout * top = new QVBoxLayout( this );
top->setSpacing( HIG :: PAD );
HIG * hig = new HIG;
hig->setContentsMargins( 0, 0, 0, 0 );
hig->addSectionTitle( tr( "Source" ) );
hig->addWideControl( mySourceEdit = new QLineEdit );
connect( mySourceEdit, SIGNAL(textChanged(const QString&)), this, SLOT(refresh()));
connect( mySourceEdit, SIGNAL(editingFinished()), this, SLOT(onSourceChanged()));
QHBoxLayout * h = new QHBoxLayout;
h->setContentsMargins( 0, 0, 0, 0 );
h->addWidget( mySourceLabel = new QLabel( tr( "<i>No source selected</i>" ) ) );
mySourceLabel->setMinimumWidth( fontMetrics().size( 0, "420 KB in 412 Files; 402 Pieces @ 42 KB each" ).width( ) );
h->addStretch( 1 );
h->setSpacing( HIG :: PAD );
QPushButton * b = new QPushButton( style()->standardIcon( QStyle::SP_DirIcon ), tr( "F&older" ) );
connect( b, SIGNAL(clicked(bool)), this, SLOT(onFolderButtonPressed()));
h->addWidget( b );
b = new QPushButton( style()->standardIcon( QStyle::SP_FileIcon ), tr( "&File" ) );
connect( b, SIGNAL(clicked(bool)), this, SLOT(onFileButtonPressed()));
h->addWidget( b );
hig->addWideControl( h );
hig->addSectionDivider( );
hig->addSectionTitle( tr( "Trackers" ) );
hig->addWideControl( myTrackerEdit = new QPlainTextEdit );
connect( myTrackerEdit, SIGNAL(textChanged()), this, SLOT(refresh()) );
const int height = fontMetrics().size( 0, "\n\n\n\n" ).height( );
myTrackerEdit->setMinimumHeight( height );
myTrackerEdit->setMaximumHeight( height );
hig->addWideControl( new QLabel( tr( "Separate tiers with an empty line" ) ) );
hig->addSectionDivider( );
hig->addSectionTitle( tr( "Options" ) );
hig->addRow( tr( "Commen&t:" ), myCommentEdit = new QLineEdit );
hig->addWideControl( myPrivateCheck = new QCheckBox( "&Private torrent" ) );
hig->addSectionDivider( );
hig->addSectionDivider( );
hig->addSectionTitle( tr( "Progress" ) );
QProgressBar * p = myProgressBar = new QProgressBar;
p->setTextVisible( false );
p->setMinimum( 0 );
p->setMaximum( 100 );
p->setValue( 0 );
hig->addWideControl( p );
hig->addWideControl( myProgressLabel = new QLabel( "<i>No source selected</i>" ) );
hig->finish( );
top->addWidget( hig, 1 );
//QFrame * f = new QFrame;
//f->setFrameShape( QFrame :: HLine );
//top->addWidget( f );
QIcon icon;
QDialogButtonBox * buttons = new QDialogButtonBox( this );
icon = style()->standardIcon( QStyle::SP_FileDialogNewFolder );
icon = QtIconLoader :: icon( "document-new", icon );
b = myMakeButton = new QPushButton( icon, tr( "&New Torrent" ) );
buttons->addButton( b, QDialogButtonBox::ActionRole );
icon = style()->standardIcon( QStyle::SP_DialogCancelButton );
icon = QtIconLoader :: icon( "process-stop", icon );
b = myStopButton = new QPushButton( icon, tr( "&Stop" ) );
buttons->addButton( b, QDialogButtonBox::RejectRole );
icon = style()->standardIcon( QStyle::SP_DialogCloseButton );
icon = QtIconLoader :: icon( "window-close", icon );
b = myCloseButton = new QPushButton( icon, tr( "&Close" ) );
buttons->addButton( b, QDialogButtonBox::AcceptRole );
connect( buttons, SIGNAL(clicked(QAbstractButton*)),
this, SLOT(onButtonBoxClicked(QAbstractButton*)) );
top->addWidget( buttons );
refresh( );
}
MakeDialog :: ~MakeDialog( )
{
if( myBuilder )
tr_metaInfoBuilderFree( myBuilder );
}
/***
****
***/
QString
MakeDialog :: getResult( ) const
{
QString str;
switch( myBuilder->result )
{
case TR_MAKEMETA_OK:
str = tr( "%1.torrent created!" ).arg( myBuilder->top );
break;
case TR_MAKEMETA_URL:
str = tr( "Error: Invalid URL" );
break;
case TR_MAKEMETA_CANCELLED:
str = tr( "Torrent creation cancelled" );
break;
case TR_MAKEMETA_IO_READ:
str = tr( "Error: Couldn't read \"%1\": %2" )
.arg( myBuilder->errfile )
.arg( tr_strerror( myBuilder->my_errno ) );
break;
case TR_MAKEMETA_IO_WRITE:
str = tr( "Error: Couldn't create \"%1\": %2" )
.arg( myBuilder->errfile )
.arg( tr_strerror( myBuilder->my_errno ) );
break;
}
return str;
}
void
MakeDialog :: refresh( )
{
QString progressText;
bool canBuild = true;
if( myIsBuilding ) {
progressText = tr( "Creating %1.torrent" ).arg( myBuilder->top );
canBuild = false;
} else if( mySourceEdit->text().trimmed().isEmpty() ) {
progressText = tr( "<i>No source selected<i>" );
canBuild = false;
} else if( myTrackerEdit->toPlainText().isEmpty() ) {
progressText = tr( "<i>No tracker announce URLs listed</i>" );
canBuild = false;
} else if( myBuilder && myBuilder->isDone ) {
progressText = getResult( );
canBuild = true;
}
myProgressLabel->setText( progressText );
myMakeButton->setEnabled( canBuild && myBuilder );
myCloseButton->setEnabled( !myIsBuilding );
myStopButton->setEnabled( myIsBuilding );
}
void
MakeDialog :: setIsBuilding( bool isBuilding )
{
myIsBuilding = isBuilding;
if( myBuilder )
myBuilder->result = TR_MAKEMETA_OK;
if( isBuilding )
myProgressBar->setValue( 0 );
if( isBuilding )
myTimer.start( 100 );
else
myTimer.stop( );
refresh( );
}
void
MakeDialog :: onProgress( )
{
const double denom = myBuilder->pieceCount ? myBuilder->pieceCount : 1;
myProgressBar->setValue( (int) ((100.0 * myBuilder->pieceIndex) / denom ) );
refresh( );
if( myBuilder->isDone )
setIsBuilding( false );
//tr_metainfo_builder_err result;
}
void
MakeDialog :: makeTorrent( )
{
if( !myBuilder )
return;
int tier = 0;
QList<tr_tracker_info> trackers;
foreach( QString line, myTrackerEdit->toPlainText().split("\n") ) {
line = line.trimmed( );
if( line.isEmpty( ) )
++tier;
else {
tr_tracker_info tmp;
tmp.announce = tr_strdup( line.toUtf8().constData( ) );
tmp.tier = tier;
std::cerr << "tier [" << tmp.tier << "] announce [" << tmp.announce << ']' << std::endl;
trackers.append( tmp );
}
}
tr_makeMetaInfo( myBuilder,
NULL,
&trackers.front(),
trackers.size(),
myCommentEdit->text().toUtf8().constData(),
myPrivateCheck->isChecked() );
refresh( );
setIsBuilding( true );
}
void
MakeDialog :: onButtonBoxClicked( QAbstractButton * button )
{
if( button == myMakeButton )
makeTorrent( );
if( button == myStopButton )
myBuilder->abortFlag = true;
if( button == myCloseButton )
deleteLater( );
}
/***
****
***/
void
MakeDialog :: onSourceChanged( )
{
if( myBuilder ) {
tr_metaInfoBuilderFree( myBuilder );
myBuilder = 0;
}
const QString filename = mySourceEdit->text( );
if( !filename.isEmpty( ) )
myBuilder = tr_metaInfoBuilderCreate( filename.toUtf8().constData() );
QString text;
if( !myBuilder )
text = tr( "<i>No source selected<i>" );
else {
QString files = tr( "%Ln File(s)", 0, myBuilder->fileCount );
QString pieces = tr( "%Ln Piece(s)", 0, myBuilder->pieceCount );
text = tr( "%1 in %2; %3 @ %4" )
.arg( Utils::sizeToString( myBuilder->totalSize ) )
.arg( files )
.arg( pieces )
.arg( Utils::sizeToString( myBuilder->pieceSize ) );
}
mySourceLabel->setText( text );
refresh( );
}
void
MakeDialog :: onFileSelectedInDialog( const QString& path )
{
mySourceEdit->setText( path );
onSourceChanged( );
}
void
MakeDialog :: onFolderButtonPressed( )
{
QFileDialog * f = new QFileDialog( this );
f->setFileMode( QFileDialog :: Directory );
connect( f, SIGNAL(fileSelected(const QString&)), this, SLOT(onFileSelectedInDialog(const QString&)));
f->show( );
}
void
MakeDialog :: onFileButtonPressed( )
{
QFileDialog * f = new QFileDialog( this );
f->setFileMode( QFileDialog :: ExistingFile );
connect( f, SIGNAL(fileSelected(const QString&)), this, SLOT(onFileSelectedInDialog(const QString&)));
f->show( );
}

73
qt/make-dialog.h Normal file
View File

@ -0,0 +1,73 @@
/*
* This file Copyright (C) 2009 Charles Kerr <charles@transmissionbt.com>
*
* This file is licensed by the GPL version 2. Works owned by the
* Transmission project are granted a special exemption to clause 2(b)
* so that the bulk of its code can remain under the MIT license.
* This exemption does not extend to derived works not owned by
* the Transmission project.
*
* $Id:$
*/
#ifndef MAKE_DIALOG_H
#define MAKE_DIALOG_H
#include <QDialog>
#include <QTimer>
struct QAbstractButton;
struct QPlainTextEdit;
struct QLineEdit;
struct QCheckBox;
struct QLabel;
struct QProgressBar;
struct QPushButton;
struct Session;
extern "C"
{
struct tr_metainfo_builder;
}
class MakeDialog: public QDialog
{
Q_OBJECT
private slots:
void onFolderButtonPressed( );
void onFileButtonPressed( );
void onFileSelectedInDialog( const QString& path );
void onSourceChanged( );
void onButtonBoxClicked( QAbstractButton* );
void onProgress( );
void refresh( );
private:
void makeTorrent( );
void refreshButtons( );
void setIsBuilding( bool );
QString getResult( ) const;
private:
QTimer myTimer;
QLineEdit * mySourceEdit;
QLabel * mySourceLabel;
QPlainTextEdit * myTrackerEdit;
QLineEdit * myCommentEdit;
QCheckBox * myPrivateCheck;
QProgressBar * myProgressBar;
QLabel * myProgressLabel;
QPushButton * myMakeButton;
QPushButton * myCloseButton;
QPushButton * myStopButton;
struct tr_metainfo_builder * myBuilder;
bool myIsBuilding;
public:
MakeDialog( Session&, QWidget * parent = 0 );
~MakeDialog( );
};
#endif

5
qt/my-valgrind.sh Executable file
View File

@ -0,0 +1,5 @@
#/bin/sh
export G_SLICE=always-malloc
export G_DEBUG=gc-friendly
export GLIBCXX_FORCE_NEW=1
valgrind --tool=memcheck --leak-check=full --leak-resolution=high --num-callers=64 --log-file=x-valgrind --show-reachable=yes ./qtr -r http://localhost:9091/

467
qt/options.cc Normal file
View File

@ -0,0 +1,467 @@
/*
* This file Copyright (C) 2009 Charles Kerr <charles@transmissionbt.com>
*
* This file is licensed by the GPL version 2. Works owned by the
* Transmission project are granted a special exemption to clause 2(b)
* so that the bulk of its code can remain under the MIT license.
* This exemption does not extend to derived works not owned by
* the Transmission project.
*
* $Id:$
*/
#include <cstdio>
#include <iostream>
#include <QEvent>
#include <QResizeEvent>
#include <QFileDialog>
#include <QGridLayout>
#include <QLabel>
#include <QCheckBox>
#include <QFileInfo>
#include <QDialogButtonBox>
#include <QPushButton>
#include <QLabel>
#include <QSet>
#include <QWidget>
#include <QVBoxLayout>
#include <libtransmission/transmission.h>
#include <libtransmission/bencode.h>
#include <libtransmission/utils.h> /* mime64 */
#include "file-tree.h"
#include "hig.h"
#include "options.h"
#include "prefs.h"
#include "qticonloader.h"
#include "session.h"
#include "torrent.h"
/***
****
***/
Options :: Options( Session& session, const Prefs& prefs, const QString& filename, QWidget * parent ):
QDialog( parent, Qt::Dialog ),
mySession( session ),
myFile( filename ),
myHaveInfo( false ),
myDestinationButton( 0 ),
myVerifyButton( 0 ),
myVerifyFile( 0 ),
myVerifyHash( QCryptographicHash::Sha1 )
{
setWindowTitle( tr( "Add Torrent" ) );
QFontMetrics fontMetrics( font( ) );
QGridLayout * layout = new QGridLayout( this );
int row = 0;
const int iconSize( style( )->pixelMetric( QStyle :: PM_SmallIconSize ) );
QIcon fileIcon = style( )->standardIcon( QStyle::SP_FileIcon );
const QPixmap filePixmap = fileIcon.pixmap( iconSize );
QPushButton * p;
int width = fontMetrics.size( 0, "This is a pretty long torrent filename indeed.torrent" ).width( );
QLabel * l = new QLabel( tr( "&Torrent file:" ) );
layout->addWidget( l, row, 0, Qt::AlignLeft );
p = myFileButton = new QPushButton;
p->setIcon( filePixmap );
p->setMinimumWidth( width );
p->setStyleSheet( "text-align: left; padding-left: 5; padding-right: 5" );
p->installEventFilter( this );
layout->addWidget( p, row, 1 );
l->setBuddy( p );
connect( p, SIGNAL(clicked(bool)), this, SLOT(onFilenameClicked()));
if( session.isLocal( ) )
{
const QIcon folderIcon = QtIconLoader :: icon( "folder", style()->standardIcon( QStyle::SP_DirIcon ) );
const QPixmap folderPixmap = folderIcon.pixmap( iconSize );
l = new QLabel( tr( "&Destination folder:" ) );
layout->addWidget( l, ++row, 0, Qt::AlignLeft );
myDestination.setPath( prefs.getString( Prefs :: DOWNLOAD_DIR ) );
p = myDestinationButton = new QPushButton;
p->setIcon( folderPixmap );
p->setStyleSheet( "text-align: left; padding-left: 5; padding-right: 5" );
p->installEventFilter( this );
layout->addWidget( p, row, 1 );
l->setBuddy( p );
connect( p, SIGNAL(clicked(bool)), this, SLOT(onDestinationClicked()));
}
myTree = new FileTreeView;
layout->addWidget( myTree, ++row, 0, 1, 2 );
if( !session.isLocal( ) )
myTree->hideColumn( 1 ); // hide the % done, since we've no way of knowing
if( session.isLocal( ) )
{
p = myVerifyButton = new QPushButton( tr( "&Verify Local Data" ) );
layout->addWidget( p, ++row, 0, Qt::AlignLeft );
}
QCheckBox * c;
c = myStartCheck = new QCheckBox( tr( "&Start when added" ) );
c->setChecked( prefs.getBool( Prefs :: START ) );
layout->addWidget( c, ++row, 0, 1, 2, Qt::AlignLeft );
c = myTrashCheck = new QCheckBox( tr( "&Move source file to Trash" ) );
c->setChecked( prefs.getBool( Prefs :: TRASH_ORIGINAL ) );
layout->addWidget( c, ++row, 0, 1, 2, Qt::AlignLeft );
QDialogButtonBox * b = new QDialogButtonBox( QDialogButtonBox::Ok|QDialogButtonBox::Cancel, Qt::Horizontal, this );
connect( b, SIGNAL(rejected()), this, SLOT(deleteLater()) );
connect( b, SIGNAL(accepted()), this, SLOT(onAccepted()) );
layout->addWidget( b, ++row, 0, 1, 2 );
layout->setRowStretch( 2, 2 );
layout->setColumnStretch( 1, 2 );
layout->setSpacing( HIG :: PAD );
connect( myTree, SIGNAL(priorityChanged(const QSet<int>&,int)), this, SLOT(onPriorityChanged(const QSet<int>&,int)));
connect( myTree, SIGNAL(wantedChanged(const QSet<int>&,bool)), this, SLOT(onWantedChanged(const QSet<int>&,bool)));
connect( myVerifyButton, SIGNAL(clicked(bool)), this, SLOT(onVerify()));
connect( &myVerifyTimer, SIGNAL(timeout()), this, SLOT(onTimeout()));
reload( );
}
Options :: ~Options( )
{
clearInfo( );
}
/***
****
***/
void
Options :: refreshButton( QPushButton * p, const QString& text, int width )
{
if( width <= 0 ) width = p->width( );
width -= 15;
QFontMetrics fontMetrics( font( ) );
QString str = fontMetrics.elidedText( text, Qt::ElideRight, width );
p->setText( str );
}
void
Options :: refreshFileButton( int width )
{
refreshButton( myFileButton, QFileInfo(myFile).baseName(), width );
}
void
Options :: refreshDestinationButton( int width )
{
if( myDestinationButton != 0 )
refreshButton( myDestinationButton, myDestination.absolutePath(), width );
}
bool
Options :: eventFilter( QObject * o, QEvent * event )
{
if( o==myFileButton && event->type() == QEvent::Resize )
{
refreshFileButton( dynamic_cast<QResizeEvent*>(event)->size().width() );
}
if( o==myDestinationButton && event->type() == QEvent::Resize )
{
refreshDestinationButton( dynamic_cast<QResizeEvent*>(event)->size().width() );
}
return false;
}
/***
****
***/
void
Options :: clearInfo( )
{
if( myHaveInfo )
tr_metainfoFree( &myInfo );
myHaveInfo = false;
myFiles.clear( );
}
void
Options :: reload( )
{
clearInfo( );
clearVerify( );
tr_ctor * ctor = tr_ctorNew( 0 );
tr_ctorSetMetainfoFromFile( ctor, myFile.toUtf8().constData() );
const int err = tr_torrentParse( ctor, &myInfo );
myHaveInfo = !err;
tr_ctorFree( ctor );
myTree->clear( );
myFiles.clear( );
myPriorities.clear( );
myWanted.clear( );
if( myHaveInfo )
{
myPriorities.insert( 0, myInfo.fileCount, TR_PRI_NORMAL );
myWanted.insert( 0, myInfo.fileCount, true );
for( tr_file_index_t i=0; i<myInfo.fileCount; ++i ) {
TrFile file;
file.index = i;
file.priority = myPriorities[i];
file.wanted = myWanted[i];
file.size = myInfo.files[i].length;
file.have = 0;
file.filename = QString::fromUtf8( myInfo.files[i].name );
myFiles.append( file );
}
}
myTree->update( myFiles );
}
void
Options :: onPriorityChanged( const QSet<int>& fileIndices, int priority )
{
foreach( int i, fileIndices )
myPriorities[i] = priority;
}
void
Options :: onWantedChanged( const QSet<int>& fileIndices, bool isWanted )
{
foreach( int i, fileIndices )
myWanted[i] = isWanted;
}
void
Options :: onAccepted( )
{
// rpc spec section 3.4 "adding a torrent"
tr_benc top;
tr_bencInitDict( &top, 3 );
tr_bencDictAddStr( &top, "method", "torrent-add" );
tr_bencDictAddInt( &top, "tag", Session::ADD_TORRENT_TAG );
tr_benc * args( tr_bencDictAddDict( &top, "arguments", 10 ) );
// "download-dir"
if( myDestinationButton )
tr_bencDictAddStr( args, "download-dir", myDestination.absolutePath().toUtf8().constData() );
// "metainfo"
QFile file( myFile );
file.open( QIODevice::ReadOnly );
const QByteArray metainfo( file.readAll( ) );
file.close( );
int base64Size = 0;
char * base64 = tr_base64_encode( metainfo.constData(), metainfo.size(), &base64Size );
tr_bencDictAddRaw( args, "metainfo", base64, base64Size );
tr_free( base64 );
// paused
tr_bencDictAddBool( args, "paused", !myStartCheck->isChecked( ) );
// files-unwanted
int count = myWanted.count( false );
if( count > 0 ) {
tr_benc * l = tr_bencDictAddList( args, "files-unwanted", count );
for( int i=0, n=myWanted.size(); i<n; ++i )
if( myWanted.at(i) == false )
tr_bencListAddInt( l, i );
}
// priority-low
count = myPriorities.count( TR_PRI_LOW );
if( count > 0 ) {
tr_benc * l = tr_bencDictAddList( args, "priority-low", count );
for( int i=0, n=myPriorities.size(); i<n; ++i )
if( myPriorities.at(i) == TR_PRI_LOW )
tr_bencListAddInt( l, i );
}
// priority-high
count = myPriorities.count( TR_PRI_HIGH );
if( count > 0 ) {
tr_benc * l = tr_bencDictAddList( args, "priority-high", count );
for( int i=0, n=myPriorities.size(); i<n; ++i )
if( myPriorities.at(i) == TR_PRI_HIGH )
tr_bencListAddInt( l, i );
}
mySession.exec( &top );
tr_bencFree( &top );
deleteLater( );
// maybe the source .torrent
if( myTrashCheck->isChecked( ) )
QFile(myFile).remove( );
}
void
Options :: onFilenameClicked( )
{
QFileDialog * d = new QFileDialog( this,
tr( "Add Torrent" ),
QFileInfo(myFile).absolutePath(),
tr( "Torrent Files (*.torrent);;All Files (*.*)" ) );
d->setFileMode( QFileDialog::ExistingFile );
connect( d, SIGNAL(filesSelected(const QStringList&)), this, SLOT(onFilesSelected(const QStringList&)) );
d->show( );
}
void
Options :: onFilesSelected( const QStringList& files )
{
if( files.size() == 1 )
{
myFile = files.at( 0 );
refreshFileButton( );
reload( );
}
}
void
Options :: onDestinationClicked( )
{
QFileDialog * d = new QFileDialog( this,
tr( "Select Destination" ),
myDestination.absolutePath( ) );
d->setFileMode( QFileDialog::Directory );
connect( d, SIGNAL(filesSelected(const QStringList&)), this, SLOT(onDestinationsSelected(const QStringList&)) );
d->show( );
}
void
Options :: onDestinationsSelected( const QStringList& destinations )
{
if( destinations.size() == 1 )
{
const QString& destination( destinations.first( ) );
myDestination.setPath( destination );
refreshDestinationButton( );
}
}
/***
****
**** VERIFY
****
***/
void
Options :: clearVerify( )
{
myVerifyHash.reset( );
myVerifyFile.close( );
myVerifyFilePos = 0;
myVerifyFlags.clear( );
myVerifyFileIndex = 0;
myVerifyPieceIndex = 0;
myVerifyPiecePos = 0;
myVerifyTimer.stop( );
for( int i=0, n=myFiles.size(); i<n; ++i )
myFiles[i].have = 0;
myTree->update( myFiles );
}
void
Options :: onVerify( )
{
//std::cerr << "starting to verify..." << std::endl;
clearVerify( );
myVerifyFlags.insert( 0, myInfo.pieceCount, false );
myVerifyTimer.setSingleShot( false );
myVerifyTimer.start( 0 );
}
namespace
{
uint64_t getPieceSize( const tr_info * info, tr_piece_index_t pieceIndex )
{
if( pieceIndex != info->pieceCount - 1 )
return info->pieceSize;
return info->totalSize % info->pieceSize;
}
}
void
Options :: onTimeout( )
{
const tr_file * file = &myInfo.files[myVerifyFileIndex];
if( !myVerifyFilePos && !myVerifyFile.isOpen( ) )
{
const QFileInfo fileInfo( myDestination, QString::fromUtf8( file->name ) );
myVerifyFile.setFileName( fileInfo.absoluteFilePath( ) );
//std::cerr << "opening file" << qPrintable(fileInfo.absoluteFilePath()) << std::endl;
myVerifyFile.open( QIODevice::ReadOnly );
}
int64_t leftInPiece = getPieceSize( &myInfo, myVerifyPieceIndex ) - myVerifyPiecePos;
int64_t leftInFile = file->length - myVerifyFilePos;
int64_t bytesThisPass = std::min( leftInFile, leftInPiece );
bytesThisPass = std::min( bytesThisPass, (int64_t)sizeof( myVerifyBuf ) );
if( myVerifyFile.isOpen() && myVerifyFile.seek( myVerifyFilePos ) ) {
int64_t numRead = myVerifyFile.read( myVerifyBuf, bytesThisPass );
if( numRead == bytesThisPass )
myVerifyHash.addData( myVerifyBuf, numRead );
}
leftInPiece -= bytesThisPass;
leftInFile -= bytesThisPass;
myVerifyPiecePos += bytesThisPass;
myVerifyFilePos += bytesThisPass;
myVerifyBins[myVerifyFileIndex] += bytesThisPass;
if( leftInPiece == 0 )
{
const QByteArray result( myVerifyHash.result( ) );
const bool matches = !memcmp( result.constData(),
myInfo.pieces[myVerifyPieceIndex].hash,
SHA_DIGEST_LENGTH );
myVerifyFlags[myVerifyPieceIndex] = matches;
myVerifyPiecePos = 0;
++myVerifyPieceIndex;
myVerifyHash.reset( );
FileList changedFiles;
if( matches ) {
mybins_t::const_iterator i;
for( i=myVerifyBins.begin(); i!=myVerifyBins.end(); ++i ) {
TrFile& f( myFiles[i.key( )] );
f.have += i.value( );
changedFiles.append( f );
}
}
myTree->update( changedFiles );
myVerifyBins.clear( );
}
if( leftInFile == 0 )
{
//std::cerr << "closing file" << std::endl;
myVerifyFile.close( );
++myVerifyFileIndex;
myVerifyFilePos = 0;
}
const bool done = myVerifyPieceIndex >= myInfo.pieceCount;
if( done )
myVerifyTimer.stop( );
}

98
qt/options.h Normal file
View File

@ -0,0 +1,98 @@
/*
* This file Copyright (C) 2009 Charles Kerr <charles@transmissionbt.com>
*
* This file is licensed by the GPL version 2. Works owned by the
* Transmission project are granted a special exemption to clause 2(b)
* so that the bulk of its code can remain under the MIT license.
* This exemption does not extend to derived works not owned by
* the Transmission project.
*
* $Id:$
*/
#ifndef OPTIONS_DIALOG_H
#define OPTIONS_DIALOG_H
#include <QDialog>
#include <QEvent>
#include <QString>
#include <QDir>
#include <QVector>
#include <QMap>
#include <QPushButton>
#include <QStringList>
#include <QCryptographicHash>
#include <QFile>
#include <QTimer>
#include <libtransmission/transmission.h>
#include "file-tree.h"
class FileTreeView;
class Prefs;
class QCheckBox;
class Session;
class Options: public QDialog
{
Q_OBJECT
public:
Options( Session& session, const Prefs& prefs, const QString& filename, QWidget * parent = 0 );
~Options( );
private:
void reload( );
void clearInfo( );
void refreshFileButton( int width=-1 );
void refreshDestinationButton( int width=-1 );
void refreshButton( QPushButton *, const QString&, int width=-1 );
private:
Session& mySession;
QString myFile;
QDir myDestination;
bool myHaveInfo;
tr_info myInfo;
FileTreeView * myTree;
QCheckBox * myStartCheck;
QCheckBox * myTrashCheck;
QPushButton * myFileButton;
QPushButton * myDestinationButton;
QPushButton * myVerifyButton;
QVector<int> myPriorities;
QVector<bool> myWanted;
FileList myFiles;
private slots:
void onAccepted( );
void onPriorityChanged( const QSet<int>& fileIndices, int );
void onWantedChanged( const QSet<int>& fileIndices, bool );
void onVerify( );
void onTimeout( );
void onFilenameClicked( );
void onDestinationClicked( );
void onFilesSelected( const QStringList& );
void onDestinationsSelected( const QStringList& );
private:
bool eventFilter( QObject *, QEvent * );
private:
QTimer myVerifyTimer;
char myVerifyBuf[2048*4];
QFile myVerifyFile;
uint64_t myVerifyFilePos;
int myVerifyFileIndex;
uint32_t myVerifyPieceIndex;
uint32_t myVerifyPiecePos;
void clearVerify( );
QVector<bool> myVerifyFlags;
QCryptographicHash myVerifyHash;
typedef QMap<uint32_t,int32_t> mybins_t;
mybins_t myVerifyBins;
};
#endif

728
qt/prefs-dialog.cc Normal file
View File

@ -0,0 +1,728 @@
/*
* This file Copyright (C) 2009 Charles Kerr <charles@transmissionbt.com>
*
* This file is licensed by the GPL version 2. Works owned by the
* Transmission project are granted a special exemption to clause 2(b)
* so that the bulk of its code can remain under the MIT license.
* This exemption does not extend to derived works not owned by
* the Transmission project.
*
* $Id:$
*/
#include <cassert>
#include <iostream>
#include <QCoreApplication>
#include <QCheckBox>
#include <QFileDialog>
#include <QComboBox>
#include <QDialogButtonBox>
#include <QDoubleSpinBox>
#include <QHBoxLayout>
#include <QLabel>
#include <QLineEdit>
#include <QFileInfo>
#include <QList>
#include <QPushButton>
#include <QIcon>
#include <QSpinBox>
#include <QStyle>
#include <QTabWidget>
#include <QTime>
#include <QTimeEdit>
#include <QTimer>
#include <QVBoxLayout>
#include <QHttp>
#include <QMessageBox>
#include "hig.h"
#include "prefs.h"
#include "prefs-dialog.h"
#include "qticonloader.h"
#include "session.h"
/***
****
***/
namespace
{
const char * PREF_KEY( "pref-key" );
};
void
PrefsDialog :: checkBoxToggled( bool checked )
{
const int key( sender( )->property( PREF_KEY ).toInt( ) );
myPrefs.set( key, checked );
}
QCheckBox *
PrefsDialog :: checkBoxNew( const QString& text, int key )
{
QCheckBox * box = new QCheckBox( text );
box->setChecked( myPrefs.getBool( key ) );
box->setProperty( PREF_KEY, key );
connect( box, SIGNAL(toggled(bool)), this, SLOT(checkBoxToggled(bool)));
myWidgets.insert( key, box );
return box;
}
void
PrefsDialog :: enableBuddyWhenChecked( QCheckBox * box, QWidget * buddy )
{
connect( box, SIGNAL(toggled(bool)), buddy, SLOT(setEnabled(bool)) );
buddy->setEnabled( box->isChecked( ) );
}
void
PrefsDialog :: spinBoxChangedIdle( )
{
const QObject * spin( sender()->property( "SPIN" ).value<QObject*>( ) );
const int key( spin->property( PREF_KEY ).toInt( ) );
const QDoubleSpinBox * d = qobject_cast<const QDoubleSpinBox*>( spin );
if( d != 0 )
myPrefs.set( key, d->value( ) );
else
myPrefs.set( key, qobject_cast<const QSpinBox*>(spin)->value( ) );
}
void
PrefsDialog :: spinBoxChanged( int value )
{
Q_UNUSED( value );
static const QString timerName( "TIMER_CHILD" );
QObject * o( sender( ) );
// user may be spinning through many values, so let's hold off
// for a moment to kekep from flooding a bunch of prefs changes
QTimer * timer( o->findChild<QTimer*>( timerName ) );
if( timer == 0 )
{
timer = new QTimer( o );
timer->setObjectName( timerName );
timer->setSingleShot( true );
timer->setProperty( "SPIN", qVariantFromValue( o ) );
connect( timer, SIGNAL(timeout()), this, SLOT(spinBoxChangedIdle()));
}
timer->start( 200 );
}
QSpinBox *
PrefsDialog :: spinBoxNew( int key, int low, int high, int step )
{
QSpinBox * spin = new QSpinBox( );
spin->setRange( low, high );
spin->setSingleStep( step );
spin->setValue( myPrefs.getInt( key ) );
spin->setProperty( PREF_KEY, key );
connect( spin, SIGNAL(valueChanged(int)), this, SLOT(spinBoxChanged(int)));
myWidgets.insert( key, spin );
return spin;
}
void
PrefsDialog :: doubleSpinBoxChanged( double value )
{
Q_UNUSED( value );
spinBoxChanged( 0 );
}
QDoubleSpinBox *
PrefsDialog :: doubleSpinBoxNew( int key, double low, double high, double step, int decimals )
{
QDoubleSpinBox * spin = new QDoubleSpinBox( );
spin->setRange( low, high );
spin->setSingleStep( step );
spin->setDecimals( decimals );
spin->setValue( myPrefs.getDouble( key ) );
spin->setProperty( PREF_KEY, key );
connect( spin, SIGNAL(valueChanged(double)), this, SLOT(doubleSpinBoxChanged(double)));
myWidgets.insert( key, spin );
return spin;
}
void
PrefsDialog :: timeChanged( const QTime& time )
{
const int key( sender()->property( PREF_KEY ).toInt( ) );
const int seconds( QTime().secsTo( time ) );
myPrefs.set( key, seconds / 60 );
}
QTimeEdit*
PrefsDialog :: timeEditNew( int key )
{
const int minutes( myPrefs.getInt( key ) );
QTimeEdit * e = new QTimeEdit( );
e->setDisplayFormat( "hh:mm" );
e->setProperty( PREF_KEY, key );
e->setTime( QTime().addSecs( minutes * 60 ) );
myWidgets.insert( key, e );
connect( e, SIGNAL(timeChanged(const QTime&)), this, SLOT(timeChanged(const QTime&)) );
return e;
}
void
PrefsDialog :: textChanged( const QString& text )
{
const int key( sender()->property( PREF_KEY ).toInt( ) );
myPrefs.set( key, qPrintable(text) );
}
QLineEdit*
PrefsDialog :: lineEditNew( int key, int echoMode )
{
QLineEdit * e = new QLineEdit( myPrefs.getString( key ) );
e->setProperty( PREF_KEY, key );
e->setEchoMode( QLineEdit::EchoMode( echoMode ) );
myWidgets.insert( key, e );
connect( e, SIGNAL(textChanged(const QString&)), this, SLOT(textChanged(const QString&)) );
return e;
}
/***
****
***/
QWidget *
PrefsDialog :: createTrackerTab( )
{
QWidget *l, *r;
HIG * hig = new HIG( );
hig->addSectionTitle( tr( "Tracker Proxy" ) );
hig->addWideControl( l = checkBoxNew( tr( "Connect to tracker via a pro&xy" ), Prefs::PROXY_ENABLED ) );
myUnsupportedWhenRemote << l;
l = hig->addRow( tr( "Proxy &server:" ), r = lineEditNew( Prefs::PROXY ) );
myProxyWidgets << l << r;
l = hig->addRow( tr( "Proxy &port:" ), r = spinBoxNew( Prefs::PROXY_PORT, 1, 65535, 1 ) );
myProxyWidgets << l << r;
hig->addWideControl( l = checkBoxNew( tr( "Require &authentication" ), Prefs::PROXY_AUTH_ENABLED ) );
myProxyWidgets << l;
l = hig->addRow( tr( "&Username:" ), r = lineEditNew( Prefs::PROXY_USERNAME ) );
myProxyAuthWidgets << l << r;
l = hig->addRow( tr( "Pass&word:" ), r = lineEditNew( Prefs::PROXY_PASSWORD, QLineEdit::Password ) );
myProxyAuthWidgets << l << r;
myUnsupportedWhenRemote << myProxyAuthWidgets;
hig->finish( );
return hig;
}
/***
****
***/
QWidget *
PrefsDialog :: createWebTab( Session& session )
{
HIG * hig = new HIG( this );
hig->addSectionTitle( tr( "Web Interface" ) );
QWidget * w;
QHBoxLayout * h = new QHBoxLayout( );
QIcon i( style()->standardIcon( QStyle::StandardPixmap( QStyle::SP_DirOpenIcon ) ) );
QPushButton * b = new QPushButton( i, tr( "&Open web interface" ) );
connect( b, SIGNAL(clicked()), &session, SLOT(launchWebInterface()) );
h->addWidget( b, 0, Qt::AlignRight );
QWidget * l = checkBoxNew( tr( "&Enable web interface" ), Prefs::RPC_ENABLED );
myUnsupportedWhenRemote << l;
hig->addRow( l, h, 0 );
l = hig->addRow( tr( "Listening &port:" ), w = spinBoxNew( Prefs::RPC_PORT, 0, 65535, 1 ) );
myWebWidgets << l << w;
hig->addWideControl( w = checkBoxNew( tr( "&Require username" ), Prefs::RPC_AUTH_REQUIRED ) );
myWebWidgets << w;
l = hig->addRow( tr( "&Username:" ), w = lineEditNew( Prefs::RPC_USERNAME ) );
myWebAuthWidgets << l << w;
l = hig->addRow( tr( "Pass&word:" ), w = lineEditNew( Prefs::RPC_PASSWORD, QLineEdit::Password ) );
myWebAuthWidgets << l << w;
hig->addWideControl( w = checkBoxNew( tr( "Only allow the following IP &addresses to connect:" ), Prefs::RPC_WHITELIST_ENABLED ) );
myWebWidgets << w;
l = hig->addRow( tr( "Addresses:" ), w = lineEditNew( Prefs::RPC_WHITELIST ) );
myWebWhitelistWidgets << l << w;
myUnsupportedWhenRemote << myWebWidgets << myWebAuthWidgets << myWebWhitelistWidgets;
hig->finish( );
return hig;
}
/***
****
***/
void
PrefsDialog :: altSpeedDaysEdited( int i )
{
const int value = qobject_cast<QComboBox*>(sender())->itemData(i).toInt();
myPrefs.set( Prefs::ALT_SPEED_LIMIT_TIME_DAY, value );
}
QWidget *
PrefsDialog :: createBandwidthTab( )
{
QWidget *l, *r;
HIG * hig = new HIG( this );
hig->addSectionTitle( tr( "Global Bandwidth Limits" ) );
l = checkBoxNew( tr( "Limit &download speed (KB/s):" ), Prefs::DSPEED_ENABLED );
r = spinBoxNew( Prefs::DSPEED, 0, INT_MAX, 5 );
hig->addRow( l, r );
enableBuddyWhenChecked( qobject_cast<QCheckBox*>(l), r );
l = checkBoxNew( tr( "Limit &upload speed (KB/s):" ), Prefs::USPEED_ENABLED );
r = spinBoxNew( Prefs::USPEED, 0, INT_MAX, 5 );
hig->addRow( l, r );
enableBuddyWhenChecked( qobject_cast<QCheckBox*>(l), r );
hig->addSectionDivider( );
QHBoxLayout * h = new QHBoxLayout;
h->setSpacing( HIG :: PAD );
QLabel * label = new QLabel;
label->setPixmap( QPixmap( ":/icons/alt-limit-off.png" ) );
label->setAlignment( Qt::AlignLeft|Qt::AlignVCenter );
h->addWidget( label );
label = new QLabel( tr( "Speed Limit Mode" ) );
label->setStyleSheet( "font: bold" );
label->setAlignment( Qt::AlignLeft|Qt::AlignVCenter );
h->addWidget( label );
hig->addSectionTitle( h );
QString s = tr( "Limit d&ownload speed (KB/s):" );
r = spinBoxNew( Prefs :: ALT_SPEED_LIMIT_DOWN, 0, INT_MAX, 5 );
hig->addRow( s, r );
s = tr( "Limit u&pload speed (KB/s):" );
r = spinBoxNew( Prefs :: ALT_SPEED_LIMIT_UP, 0, INT_MAX, 5 );
hig->addRow( s, r );
QCheckBox * c = checkBoxNew( tr( "Use Speed Limit Mode &between" ), Prefs::ALT_SPEED_LIMIT_TIME_ENABLED );
h = new QHBoxLayout( );
h->setSpacing( HIG::PAD );
QWidget * w = timeEditNew( Prefs :: ALT_SPEED_LIMIT_TIME_BEGIN );
h->addWidget( w, 1 );
mySchedWidgets << w;
w = new QLabel( "and" );
h->addWidget( w );
mySchedWidgets << w;
w = timeEditNew( Prefs :: ALT_SPEED_LIMIT_TIME_END );
h->addWidget( w, 1 );
mySchedWidgets << w;
hig->addRow( c, h, 0 );
s = tr( "&On days:" );
QComboBox * box = new QComboBox;
const QIcon noIcon;
box->addItem( noIcon, tr( "Every Day" ), QVariant( TR_SCHED_ALL ) );
box->addItem( noIcon, tr( "Weekdays" ), QVariant( TR_SCHED_WEEKDAY ) );
box->addItem( noIcon, tr( "Weekends" ), QVariant( TR_SCHED_WEEKEND ) );
box->addItem( noIcon, tr( "Sunday" ), QVariant( TR_SCHED_SUN ) );
box->addItem( noIcon, tr( "Monday" ), QVariant( TR_SCHED_MON ) );
box->addItem( noIcon, tr( "Tuesday" ), QVariant( TR_SCHED_TUES ) );
box->addItem( noIcon, tr( "Wednesday" ), QVariant( TR_SCHED_WED ) );
box->addItem( noIcon, tr( "Thursday" ), QVariant( TR_SCHED_THURS ) );
box->addItem( noIcon, tr( "Friday" ), QVariant( TR_SCHED_FRI ) );
box->addItem( noIcon, tr( "Saturday" ), QVariant( TR_SCHED_SAT ) );
box->setCurrentIndex( box->findData( myPrefs.getInt( Prefs :: ALT_SPEED_LIMIT_TIME_DAY ) ) );
connect( box, SIGNAL(activated(int)), this, SLOT(altSpeedDaysEdited(int)) );
w = hig->addRow( s, box );
mySchedWidgets << w << box;
hig->finish( );
return hig;
}
/***
****
***/
void
PrefsDialog :: onPortTested( bool isOpen )
{
myPortButton->setEnabled( true );
myWidgets[Prefs::PEER_PORT]->setEnabled( true );
myPortLabel->setText( isOpen ? tr( "Port is <b>open</b>" )
: tr( "Port is <b>closed</b>" ) );
}
void
PrefsDialog :: onPortTest( )
{
myPortLabel->setText( tr( "Testing..." ) );
myPortButton->setEnabled( false );
myWidgets[Prefs::PEER_PORT]->setEnabled( false );
mySession.portTest( );
}
QWidget *
PrefsDialog :: createNetworkTab( )
{
HIG * hig = new HIG( this );
hig->addSectionTitle( tr( "Incoming Peers" ) );
QSpinBox * s = spinBoxNew( Prefs::PEER_PORT, 1, 65535, 1 );
QHBoxLayout * h = new QHBoxLayout( );
QPushButton * b = myPortButton = new QPushButton( tr( "&Test Port" ) );
QLabel * l = myPortLabel = new QLabel( tr( "Status unknown" ) );
h->addWidget( l );
h->addSpacing( HIG :: PAD_BIG );
h->addWidget( b );
h->setStretchFactor( l, 1 );
connect( b, SIGNAL(clicked(bool)), this, SLOT(onPortTest()));
connect( &mySession, SIGNAL(portTested(bool)), this, SLOT(onPortTested(bool)));
hig->addRow( tr( "&Port for incoming connections:" ), s );
hig->addRow( "", h, 0 );
hig->addWideControl( checkBoxNew( tr( "Randomize the port every launch" ), Prefs :: PEER_PORT_RANDOM_ON_START ) );
hig->addWideControl( checkBoxNew( tr( "Use UPnP or NAT-PMP port &forwarding from my router" ), Prefs::PORT_FORWARDING ) );
hig->finish( );
return hig;
}
/***
****
***/
void
PrefsDialog :: onBlocklistDialogDestroyed( QObject * o )
{
Q_UNUSED( o );
myBlocklistDialog = 0;
}
void
PrefsDialog :: onUpdateBlocklistCancelled( )
{
disconnect( &mySession, SIGNAL(blocklistUpdated(int)), this, SLOT(onBlocklistUpdated(int))) ;
myBlocklistDialog->deleteLater( );
}
void
PrefsDialog :: onBlocklistUpdated( int n )
{
myBlocklistDialog->setText( tr( "<b>Update succeeded!</b><p>Blocklist now has %Ln rules.", 0, n ) );
myBlocklistDialog->setTextFormat( Qt::RichText );
}
void
PrefsDialog :: onUpdateBlocklistClicked( )
{
myBlocklistDialog = new QMessageBox( QMessageBox::Information,
"",
tr( "<b>Update Blocklist</b><p>Getting new blocklist..." ),
QMessageBox::Close,
this );
QPixmap pixmap;
QIcon icon = QtIconLoader :: icon( "dialog-information" );
if( !icon.isNull( ) ) {
const int size = style()->pixelMetric( QStyle::PM_LargeIconSize );
myBlocklistDialog->setIconPixmap( icon.pixmap( size, size ) );
}
connect( myBlocklistDialog, SIGNAL(rejected()), this, SLOT(onUpdateBlocklistCancelled()) );
connect( &mySession, SIGNAL(blocklistUpdated(int)), this, SLOT(onBlocklistUpdated(int))) ;
myBlocklistDialog->show( );
mySession.updateBlocklist( );
}
void
PrefsDialog :: encryptionEdited( int i )
{
const int value( qobject_cast<QComboBox*>(sender())->itemData(i).toInt( ) );
myPrefs.set( Prefs::ENCRYPTION, value );
}
QWidget *
PrefsDialog :: createPeersTab( const Session& session )
{
HIG * hig = new HIG( this );
hig->addSectionTitle( tr( "Blocklist" ) );
QHBoxLayout * h = new QHBoxLayout( );
QIcon i( style()->standardIcon( QStyle::StandardPixmap( QStyle::SP_BrowserReload ) ) );
QPushButton * w = new QPushButton( i, tr( "&Update blocklist" ) );
connect( w, SIGNAL(clicked(bool)), this, SLOT(onUpdateBlocklistClicked()));
myBlockWidgets << w;
QWidget * l = checkBoxNew( tr( "Enable &blocklist (contains %Ln rule(s))", 0, session.blocklistSize( ) ), Prefs::BLOCKLIST_ENABLED );
h->addWidget( l );
h->addStretch( 1 );
h->addWidget( w );
hig->addWideControl( h );
l = checkBoxNew( tr( "Enable &automatic updates" ), Prefs::BLOCKLIST_UPDATES_ENABLED );
myBlockWidgets << l;
hig->addWideControl( l );
hig->addSectionDivider( );
hig->addSectionTitle( tr( "Limits" ) );
hig->addRow( tr( "Maximum peers &overall:" ), spinBoxNew( Prefs::PEER_LIMIT_GLOBAL, 1, 3000, 5 ) );
hig->addRow( tr( "Maximum peers per &torrent:" ), spinBoxNew( Prefs::PEER_LIMIT_TORRENT, 1, 300, 5 ) );
QComboBox * box = new QComboBox( );
box->addItem( tr( "Plaintext Preferred" ), 0 );
box->addItem( tr( "Encryption Preferred" ), 1 );
box->addItem( tr( "Encryption Required" ), 2 );
myWidgets.insert( Prefs :: ENCRYPTION, box );
connect( box, SIGNAL(activated(int)), this, SLOT(encryptionEdited(int)));
hig->addSectionDivider( );
hig->addSectionTitle( tr( "Privacy" ) );
hig->addRow( tr( "Encryption &mode:" ), box );
hig->addWideControl( checkBoxNew( tr( "Use peer e&xchange" ), Prefs::PEX_ENABLED ) );
hig->finish( );
myUnsupportedWhenRemote << myBlockWidgets;
return hig;
}
/***
****
***/
void
PrefsDialog :: onWatchClicked( void )
{
QFileDialog * d = new QFileDialog( this,
tr( "Select Watch Directory" ),
myPrefs.getString( Prefs::DIR_WATCH ) );
d->setFileMode( QFileDialog::Directory );
connect( d, SIGNAL(filesSelected(const QStringList&)), this, SLOT(onWatchSelected(const QStringList&)) );
d->show( );
}
void
PrefsDialog :: onWatchSelected( const QStringList& list )
{
if( list.size() == 1 )
myPrefs.set( Prefs::DIR_WATCH, list.first( ) );
}
void
PrefsDialog :: onDestinationClicked( void )
{
QFileDialog * d = new QFileDialog( this,
tr( "Select Destination" ),
myPrefs.getString( Prefs::DOWNLOAD_DIR ) );
d->setFileMode( QFileDialog::Directory );
connect( d, SIGNAL(filesSelected(const QStringList&)), this, SLOT(onDestinationSelected(const QStringList&)) );
d->show( );
}
void
PrefsDialog :: onDestinationSelected( const QStringList& list )
{
if( list.size() == 1 )
myPrefs.set( Prefs::DOWNLOAD_DIR, list.first( ) );
}
QWidget *
PrefsDialog :: createTorrentsTab( )
{
const int iconSize( style( )->pixelMetric( QStyle :: PM_SmallIconSize ) );
const QIcon folderIcon = QtIconLoader :: icon( "folder", style()->standardIcon( QStyle::SP_DirIcon ) );
const QPixmap folderPixmap = folderIcon.pixmap( iconSize );
QWidget *l, *r;
HIG * hig = new HIG( this );
hig->addSectionTitle( tr( "Adding Torrents" ) );
l = checkBoxNew( tr( "Automatically &add torrents from:" ), Prefs::DIR_WATCH_ENABLED );
QPushButton * b = myWatchButton = new QPushButton;
b->setIcon( folderPixmap );
b->setStyleSheet( "text-align: left; padding-left: 5; padding-right: 5" );
connect( b, SIGNAL(clicked(bool)), this, SLOT(onWatchClicked(void)) );
hig->addRow( l, b );
enableBuddyWhenChecked( qobject_cast<QCheckBox*>(l), b );
hig->addWideControl( checkBoxNew( tr( "Display &options dialog" ), Prefs::OPTIONS_PROMPT ) );
hig->addWideControl( checkBoxNew( tr( "&Start when added" ), Prefs::START ) );
hig->addWideControl( checkBoxNew( tr( "Mo&ve source files to Trash" ), Prefs::TRASH_ORIGINAL ) );
b = myDestinationButton = new QPushButton;
b->setIcon( folderPixmap );
b->setStyleSheet( "text-align: left; padding-left: 5; padding-right: 5" );
connect( b, SIGNAL(clicked(bool)), this, SLOT(onDestinationClicked(void)) );
hig->addRow( tr( "&Destination folder:" ), b );
hig->addSectionDivider( );
hig->addSectionTitle( tr( "Limits" ) );
l = checkBoxNew( tr( "&Stop seeding torrents at ratio:" ), Prefs::RATIO_ENABLED );
r = doubleSpinBoxNew( Prefs::RATIO, 0.5, INT_MAX, 0.5, 2 );
hig->addRow( l, r );
enableBuddyWhenChecked( qobject_cast<QCheckBox*>(l), r );
hig->finish( );
return hig;
}
/***
****
***/
PrefsDialog :: PrefsDialog( Session& session, Prefs& prefs, QWidget * parent ):
QDialog( parent ),
myIsServer( session.isServer( ) ),
mySession( session ),
myPrefs( prefs ),
myLayout( new QVBoxLayout( this ) )
{
setWindowTitle( tr( "Transmission Preferences" ) );
QTabWidget * t = new QTabWidget( this );
t->addTab( createTorrentsTab( ), tr( "Torrents" ) );
t->addTab( createPeersTab( session ), tr( "Peers" ) );
t->addTab( createBandwidthTab( ), tr( "Speed" ) );
t->addTab( createNetworkTab( ), tr( "Network" ) );
t->addTab( createWebTab( session ), tr( "Web" ) );
//t->addTab( createTrackerTab( ), tr( "Trackers" ) );
myLayout->addWidget( t );
QDialogButtonBox * buttons = new QDialogButtonBox( QDialogButtonBox::Close, Qt::Horizontal, this );
connect( buttons, SIGNAL(rejected()), this, SLOT(hide()) ); // "close" triggers rejected
myLayout->addWidget( buttons );
connect( &myPrefs, SIGNAL(changed(int)), this, SLOT(updatePref(int)));
connect( &mySession, SIGNAL(sessionUpdated()), this, SLOT(sessionUpdated()));
QList<int> keys;
keys << Prefs :: RPC_ENABLED
<< Prefs :: PROXY_ENABLED
<< Prefs :: ALT_SPEED_LIMIT_ENABLED
<< Prefs :: ALT_SPEED_LIMIT_TIME_ENABLED
<< Prefs :: ENCRYPTION
<< Prefs :: BLOCKLIST_ENABLED
<< Prefs :: DIR_WATCH
<< Prefs :: DOWNLOAD_DIR;
foreach( int key, keys )
updatePref( key );
// if it's a remote session, disable the preferences
// that don't work in remote sessions
if( !myIsServer ) {
foreach( QWidget * w, myUnsupportedWhenRemote ) {
w->setToolTip( tr( "Not supported by remote sessions" ) );
w->setEnabled( false );
}
}
}
PrefsDialog :: ~PrefsDialog( )
{
}
/***
****
***/
void
PrefsDialog :: sessionUpdated( )
{
QCheckBox * box = qobject_cast<QCheckBox*>( myWidgets[Prefs::BLOCKLIST_ENABLED] );
box->setText( tr( "Enable &blocklist (%Ln rules)", 0, mySession.blocklistSize( ) ) );
}
void
PrefsDialog :: updatePref( int key )
{
switch( key )
{
case Prefs :: RPC_ENABLED:
case Prefs :: RPC_WHITELIST_ENABLED:
case Prefs :: RPC_AUTH_REQUIRED: {
const bool enabled( myPrefs.getBool( Prefs::RPC_ENABLED ) );
const bool whitelist( myPrefs.getBool( Prefs::RPC_WHITELIST_ENABLED ) );
const bool auth( myPrefs.getBool( Prefs::RPC_AUTH_REQUIRED ) );
foreach( QWidget * w, myWebWhitelistWidgets ) w->setEnabled( enabled && whitelist );
foreach( QWidget * w, myWebAuthWidgets ) w->setEnabled( enabled && auth );
foreach( QWidget * w, myWebWidgets ) w->setEnabled( enabled );
break;
}
case Prefs :: PROXY_ENABLED:
case Prefs :: PROXY_AUTH_ENABLED: {
const bool enabled( myPrefs.getBool( Prefs::PROXY_ENABLED ) );
const bool auth( myPrefs.getBool( Prefs::PROXY_AUTH_ENABLED ) );
foreach( QWidget * w, myProxyAuthWidgets ) w->setEnabled( enabled && auth );
foreach( QWidget * w, myProxyWidgets ) w->setEnabled( enabled );
break;
}
case Prefs :: ALT_SPEED_LIMIT_TIME_ENABLED: {
const bool enabled = myPrefs.getBool( key );
foreach( QWidget * w, mySchedWidgets ) w->setEnabled( enabled );
break;
}
case Prefs :: BLOCKLIST_ENABLED: {
const bool enabled = myPrefs.getBool( key );
foreach( QWidget * w, myBlockWidgets ) w->setEnabled( enabled );
break;
}
case Prefs :: DIR_WATCH:
myWatchButton->setText( QFileInfo(myPrefs.getString(Prefs::DIR_WATCH)).fileName() );
break;
case Prefs :: PEER_PORT:
myPortLabel->setText( tr( "Status unknown" ) );
myPortButton->setEnabled( true );
break;
case Prefs :: DOWNLOAD_DIR: {
QString path( myPrefs.getString( Prefs :: DOWNLOAD_DIR ) );
myDestinationButton->setText( QFileInfo(path).fileName() );
break;
}
default:
break;
}
key2widget_t::iterator it( myWidgets.find( key ) );
if( it != myWidgets.end( ) )
{
QWidget * w( it.value( ) );
QCheckBox * checkBox;
QSpinBox * spin;
QDoubleSpinBox * doubleSpin;
QTimeEdit * timeEdit;
QLineEdit * lineEdit;
if(( checkBox = qobject_cast<QCheckBox*>(w)))
{
checkBox->setChecked( myPrefs.getBool( key ) );
}
else if(( spin = qobject_cast<QSpinBox*>(w)))
{
spin->setValue( myPrefs.getInt( key ) );
}
else if(( doubleSpin = qobject_cast<QDoubleSpinBox*>(w)))
{
doubleSpin->setValue( myPrefs.getDouble( key ) );
}
else if(( timeEdit = qobject_cast<QTimeEdit*>(w)))
{
const int minutes( myPrefs.getInt( key ) );
timeEdit->setTime( QTime().addSecs( minutes * 60 ) );
}
else if(( lineEdit = qobject_cast<QLineEdit*>(w)))
{
lineEdit->setText( myPrefs.getString( key ) );
}
else if( key == Prefs::ENCRYPTION )
{
QComboBox * comboBox( qobject_cast<QComboBox*>( w ) );
const int index = comboBox->findData( myPrefs.getInt( key ) );
comboBox->setCurrentIndex( index );
}
}
}
bool
PrefsDialog :: isAllowed( int key ) const
{
Q_UNUSED( key );
return true;
}

111
qt/prefs-dialog.h Normal file
View File

@ -0,0 +1,111 @@
/*
* This file Copyright (C) 2009 Charles Kerr <charles@transmissionbt.com>
*
* This file is licensed by the GPL version 2. Works owned by the
* Transmission project are granted a special exemption to clause 2(b)
* so that the bulk of its code can remain under the MIT license.
* This exemption does not extend to derived works not owned by
* the Transmission project.
*
* $Id:$
*/
#ifndef PREFS_DIALOG_H
#define PREFS_DIALOG_H
#include <QDialog>
#include <QSet>
#include "prefs.h"
class QAbstractButton;
class QCheckBox;
class QString;
class QDoubleSpinBox;
class QSpinBox;
class QLabel;
class QLineEdit;
class QVBoxLayout;
class QTime;
class QTimeEdit;
class QWidget;
class QPushButton;
class QMessageBox;
class QHttp;
class Prefs;
class Session;
class PrefsDialog: public QDialog
{
Q_OBJECT
private slots:
void checkBoxToggled( bool checked );
void spinBoxChanged( int value );
void doubleSpinBoxChanged( double value );
void spinBoxChangedIdle( );
void timeChanged( const QTime& );
void textChanged( const QString& );
void updatePref( int key );
void encryptionEdited( int );
void altSpeedDaysEdited( int );
void sessionUpdated( );
void onWatchClicked( );
void onWatchSelected( const QStringList& );
void onDestinationClicked( );
void onDestinationSelected( const QStringList& );
void onPortTested( bool );
void onPortTest( );
void onUpdateBlocklistClicked( );
void onUpdateBlocklistCancelled( );
void onBlocklistDialogDestroyed( QObject * );
void onBlocklistUpdated( int n );
private:
QDoubleSpinBox * doubleSpinBoxNew( int key, double low, double high, double step, int decimals );
QCheckBox * checkBoxNew( const QString& text, int key );
QSpinBox * spinBoxNew( int key, int low, int high, int step );
QTimeEdit * timeEditNew( int key );
QLineEdit * lineEditNew( int key, int mode = 0 );
void enableBuddyWhenChecked( QCheckBox *, QWidget * );
public:
PrefsDialog( Session&, Prefs&, QWidget * parent = 0 );
~PrefsDialog( );
private:
bool isAllowed( int key ) const;
QWidget * createTorrentsTab( );
QWidget * createPeersTab( const Session& session );
QWidget * createNetworkTab( );
QWidget * createBandwidthTab( );
QWidget * createWebTab( Session& );
QWidget * createTrackerTab( );
private:
typedef QMap<int,QWidget*> key2widget_t;
key2widget_t myWidgets;
const bool myIsServer;
Session& mySession;
Prefs& myPrefs;
QVBoxLayout * myLayout;
QLabel * myPortLabel;
QPushButton * myPortButton;
QPushButton * myWatchButton;
QPushButton * myDestinationButton;
QWidgetList myWebWidgets;
QWidgetList myWebAuthWidgets;
QWidgetList myWebWhitelistWidgets;
QWidgetList myProxyWidgets;
QWidgetList myProxyAuthWidgets;
QWidgetList mySchedWidgets;
QWidgetList myBlockWidgets;
QWidgetList myUnsupportedWhenRemote;
int myBlocklistHttpTag;
QHttp * myBlocklistHttp;
QMessageBox * myBlocklistDialog;
};
#endif

291
qt/prefs.cc Normal file
View File

@ -0,0 +1,291 @@
/*
* This file Copyright (C) 2009 Charles Kerr <charles@transmissionbt.com>
*
* This file is licensed by the GPL version 2. Works owned by the
* Transmission project are granted a special exemption to clause 2(b)
* so that the bulk of its code can remain under the MIT license.
* This exemption does not extend to derived works not owned by
* the Transmission project.
*
* $Id:$
*/
#include <cassert>
#include <iostream>
#include <cstdlib> // strtod
#include <QDir>
#include <QFile>
#include <libtransmission/transmission.h>
#include <libtransmission/bencode.h>
#include <libtransmission/json.h>
#include <libtransmission/utils.h>
#include "prefs.h"
/***
****
***/
Prefs::PrefItem Prefs::myItems[] =
{
/* gui settings */
{ OPTIONS_PROMPT, "show-options-window", QVariant::Bool },
{ OPEN_DIALOG_FOLDER, "open-dialog-dir", QVariant::String },
{ INHIBIT_HIBERNATION, "inhibit-desktop-hibernation", QVariant::Bool },
{ DIR_WATCH, "watch-dir", QVariant::String },
{ DIR_WATCH_ENABLED, "watch-dir-enabled", QVariant::Bool },
{ SHOW_TRAY_ICON, "show-notification-area-icon", QVariant::Bool },
{ SHOW_DESKTOP_NOTIFICATION, "show-desktop-notification", QVariant::Bool },
{ START, "start-added-torrents", QVariant::Bool },
{ TRASH_ORIGINAL, "trash-original-torrent-files", QVariant::Bool },
{ ASKQUIT, "prompt-before-exit", QVariant::Bool },
{ SORT_MODE, "sort-mode", QVariant::String },
{ SORT_REVERSED, "sort-reversed", QVariant::Bool },
{ MINIMAL_VIEW, "minimal-view", QVariant::Bool },
{ FILTERBAR, "show-filterbar", QVariant::Bool },
{ STATUSBAR, "show-statusbar", QVariant::Bool },
{ STATUSBAR_STATS, "statusbar-stats", QVariant::String },
{ TOOLBAR, "show-toolbar" , QVariant::Bool },
{ BLOCKLIST_DATE, "blocklist-date", QVariant::DateTime },
{ BLOCKLIST_UPDATES_ENABLED, "blocklist-updates-enabled" , QVariant::Bool },
{ MAIN_WINDOW_LAYOUT_ORDER, "main-window-layout-order", QVariant::Int },
{ MAIN_WINDOW_HEIGHT, "main-window-height", QVariant::Int },
{ MAIN_WINDOW_WIDTH, "main-window-width", QVariant::Int },
{ MAIN_WINDOW_X, "main-window-x", QVariant::Int },
{ MAIN_WINDOW_Y, "main-window-y", QVariant::Int },
/* libtransmission settings */
{ ALT_SPEED_LIMIT_UP, TR_PREFS_KEY_ALT_SPEED_UP, QVariant::Int },
{ ALT_SPEED_LIMIT_DOWN, TR_PREFS_KEY_ALT_SPEED_DOWN, QVariant::Int },
{ ALT_SPEED_LIMIT_ENABLED, TR_PREFS_KEY_ALT_SPEED_ENABLED, QVariant::Bool },
{ ALT_SPEED_LIMIT_TIME_BEGIN, TR_PREFS_KEY_ALT_SPEED_TIME_BEGIN, QVariant::Int },
{ ALT_SPEED_LIMIT_TIME_END, TR_PREFS_KEY_ALT_SPEED_TIME_END, QVariant::Int },
{ ALT_SPEED_LIMIT_TIME_ENABLED, TR_PREFS_KEY_ALT_SPEED_TIME_ENABLED, QVariant::Bool },
{ ALT_SPEED_LIMIT_TIME_DAY, TR_PREFS_KEY_ALT_SPEED_TIME_DAY, QVariant::Int },
{ BLOCKLIST_ENABLED, TR_PREFS_KEY_BLOCKLIST_ENABLED, QVariant::Bool },
{ DSPEED, TR_PREFS_KEY_DSPEED, QVariant::Int },
{ DSPEED_ENABLED, TR_PREFS_KEY_DSPEED_ENABLED, QVariant::Bool },
{ DOWNLOAD_DIR, TR_PREFS_KEY_DOWNLOAD_DIR, QVariant::String },
{ ENCRYPTION, TR_PREFS_KEY_ENCRYPTION, QVariant::Int },
{ LAZY_BITFIELD, TR_PREFS_KEY_LAZY_BITFIELD, QVariant::Bool },
{ MSGLEVEL, TR_PREFS_KEY_MSGLEVEL, QVariant::Int },
{ OPEN_FILE_LIMIT, TR_PREFS_KEY_OPEN_FILE_LIMIT, QVariant::Int },
{ PEER_LIMIT_GLOBAL, TR_PREFS_KEY_PEER_LIMIT_GLOBAL, QVariant::Int },
{ PEER_LIMIT_TORRENT, TR_PREFS_KEY_PEER_LIMIT_TORRENT, QVariant::Int },
{ PEER_PORT, TR_PREFS_KEY_PEER_PORT, QVariant::Int },
{ PEER_PORT_RANDOM_ON_START, TR_PREFS_KEY_PEER_PORT_RANDOM_ON_START, QVariant::Bool },
{ PEER_PORT_RANDOM_LOW, TR_PREFS_KEY_PEER_PORT_RANDOM_LOW, QVariant::Int },
{ PEER_PORT_RANDOM_HIGH, TR_PREFS_KEY_PEER_PORT_RANDOM_HIGH, QVariant::Int },
{ SOCKET_TOS, TR_PREFS_KEY_PEER_SOCKET_TOS, QVariant::Int },
{ PEX_ENABLED, TR_PREFS_KEY_PEX_ENABLED, QVariant::Bool },
{ PORT_FORWARDING, TR_PREFS_KEY_PORT_FORWARDING, QVariant::Bool },
{ PROXY_AUTH_ENABLED, TR_PREFS_KEY_PROXY_AUTH_ENABLED, QVariant::Bool },
{ PREALLOCATION, TR_PREFS_KEY_PREALLOCATION, QVariant::Int },
{ PROXY_ENABLED, TR_PREFS_KEY_PROXY_ENABLED, QVariant::Bool },
{ PROXY_PASSWORD, TR_PREFS_KEY_PROXY_PASSWORD, QVariant::String },
{ PROXY_PORT, TR_PREFS_KEY_PROXY_PORT, QVariant::Int },
{ PROXY, TR_PREFS_KEY_PROXY, QVariant::String },
{ PROXY_TYPE, TR_PREFS_KEY_PROXY_TYPE, QVariant::Int },
{ PROXY_USERNAME, TR_PREFS_KEY_PROXY_USERNAME, QVariant::String },
{ RATIO, TR_PREFS_KEY_RATIO, QVariant::Double },
{ RATIO_ENABLED, TR_PREFS_KEY_RATIO_ENABLED, QVariant::Bool },
{ RPC_AUTH_REQUIRED, TR_PREFS_KEY_RPC_AUTH_REQUIRED, QVariant::Bool },
{ RPC_ENABLED, TR_PREFS_KEY_RPC_ENABLED, QVariant::Bool },
{ RPC_PASSWORD, TR_PREFS_KEY_RPC_PASSWORD, QVariant::String },
{ RPC_PORT, TR_PREFS_KEY_RPC_PORT, QVariant::Int },
{ RPC_USERNAME, TR_PREFS_KEY_RPC_USERNAME, QVariant::String },
{ RPC_WHITELIST_ENABLED, TR_PREFS_KEY_RPC_WHITELIST_ENABLED, QVariant::Bool },
{ RPC_WHITELIST, TR_PREFS_KEY_RPC_WHITELIST, QVariant::String },
{ SEED_RATIO_LIMIT, TR_PREFS_KEY_RATIO, QVariant::Double },
{ SEED_RATIO_LIMITED, TR_PREFS_KEY_RATIO_ENABLED, QVariant::Bool },
{ USPEED_ENABLED, TR_PREFS_KEY_USPEED_ENABLED, QVariant::Bool },
{ USPEED, TR_PREFS_KEY_USPEED, QVariant::Int },
{ UPLOAD_SLOTS_PER_TORRENT, TR_PREFS_KEY_UPLOAD_SLOTS_PER_TORRENT, QVariant::Int }
};
/***
****
***/
Prefs :: Prefs( const char * configDir ):
myConfigDir( configDir )
{
assert( sizeof(myItems) / sizeof(myItems[0]) == PREFS_COUNT );
for( int i=0; i<PREFS_COUNT; ++i )
assert( myItems[i].id == i );
tr_benc top;
tr_bencInitDict( &top, 0 );
initDefaults( &top );
tr_sessionLoadSettings( &top, configDir, NULL );
for( int i=0; i<PREFS_COUNT; ++i )
{
double d;
tr_bool boolVal;
int64_t intVal;
const char * str;
tr_benc * b( tr_bencDictFind( &top, myItems[i].key ) );
switch( myItems[i].type )
{
case QVariant::Int:
if( tr_bencGetInt( b, &intVal ) )
myValues[i].setValue( qlonglong(intVal) );
break;
case QVariant::String:
if( tr_bencGetStr( b, &str ) )
myValues[i].setValue( QString::fromUtf8(str) );
break;
case QVariant::Bool:
if( tr_bencGetBool( b, &boolVal ) )
myValues[i].setValue( bool(boolVal) );
break;
case QVariant::Double:
if( tr_bencGetReal( b, &d ) )
myValues[i].setValue( d );
break;
case QVariant::DateTime:
if( tr_bencGetInt( b, &intVal ) )
myValues[i].setValue( QDateTime :: fromTime_t( intVal ) );
break;
default:
assert( "unhandled type" && 0 );
break;
}
}
tr_bencFree( &top );
}
Prefs :: ~Prefs( )
{
tr_benc top;
/* load in the existing preferences file */
QFile file( QDir( myConfigDir ).absoluteFilePath( "settings.json" ) );
file.open( QIODevice::ReadOnly | QIODevice::Text );
const QByteArray oldPrefs = file.readAll( );
file.close( );
if( tr_jsonParse( oldPrefs.data(), oldPrefs.length(), &top, NULL ) )
tr_bencInitDict( &top, PREFS_COUNT );
/* merge our own settings with the ones already in the file */
for( int i=0; i<PREFS_COUNT; ++i ) {
const char * key = myItems[i].key;
const QVariant& val = myValues[i];
switch( myItems[i].type ) {
case QVariant::Int:
tr_bencDictAddInt( &top, key, val.toInt() );
break;
case QVariant::String:
tr_bencDictAddStr( &top, key, val.toString().toUtf8().constData() );
break;
case QVariant::Bool:
tr_bencDictAddBool( &top, key, val.toBool() );
break;
case QVariant::Double:
tr_bencDictAddReal( &top, key, val.toDouble() );
break;
case QVariant::DateTime:
tr_bencDictAddInt( &top, key, val.toDateTime().toTime_t() );
break;
default:
assert( "unhandled type" && 0 );
break;
}
}
/* write back out the serialized preferences */
char * json = tr_bencToJSON( &top );
if( json && *json ) {
file.open( QIODevice::WriteOnly | QIODevice::Text );
file.write( json );
file.close( );
}
tr_free( json );
tr_bencFree( &top );
}
/**
* This is where we initialize the preferences file with the default values.
* If you add a new preferences key, you /must/ add a default value here.
*/
void
Prefs :: initDefaults( tr_benc * d )
{
tr_bencDictAddStr( d, keyStr(DIR_WATCH), tr_getDefaultDownloadDir( ) );
tr_bencDictAddInt( d, keyStr(DIR_WATCH_ENABLED), false );
tr_bencDictAddInt( d, keyStr(INHIBIT_HIBERNATION), false );
tr_bencDictAddInt( d, keyStr(BLOCKLIST_DATE), 0 );
tr_bencDictAddInt( d, keyStr(BLOCKLIST_UPDATES_ENABLED), true );
tr_bencDictAddStr( d, keyStr(OPEN_DIALOG_FOLDER), QDir::home().absolutePath().toLatin1() );
tr_bencDictAddInt( d, keyStr(TOOLBAR), true );
tr_bencDictAddInt( d, keyStr(FILTERBAR), true );
tr_bencDictAddInt( d, keyStr(STATUSBAR), true );
tr_bencDictAddInt( d, keyStr(SHOW_TRAY_ICON), false );
tr_bencDictAddInt( d, keyStr(SHOW_DESKTOP_NOTIFICATION), true );
tr_bencDictAddStr( d, keyStr(STATUSBAR_STATS), "total-ratio" );
tr_bencDictAddInt( d, keyStr(OPTIONS_PROMPT), true );
tr_bencDictAddInt( d, keyStr(MAIN_WINDOW_HEIGHT), 500 );
tr_bencDictAddInt( d, keyStr(MAIN_WINDOW_WIDTH), 300 );
tr_bencDictAddInt( d, keyStr(MAIN_WINDOW_X), 50 );
tr_bencDictAddInt( d, keyStr(MAIN_WINDOW_Y), 50 );
tr_bencDictAddStr( d, keyStr(MAIN_WINDOW_LAYOUT_ORDER), "menu,toolbar,filter,list,statusbar" );
tr_bencDictAddStr( d, keyStr(DOWNLOAD_DIR), tr_getDefaultDownloadDir( ) );
tr_bencDictAddInt( d, keyStr(ASKQUIT), true );
tr_bencDictAddStr( d, keyStr(SORT_MODE), "sort-by-name" );
tr_bencDictAddInt( d, keyStr(SORT_REVERSED), false );
tr_bencDictAddInt( d, keyStr(MINIMAL_VIEW), false );
tr_bencDictAddInt( d, keyStr(START), true );
tr_bencDictAddInt( d, keyStr(TRASH_ORIGINAL), false );
}
/***
****
***/
bool
Prefs :: getBool( int key ) const
{
assert( myItems[key].type == QVariant::Bool );
return myValues[key].toBool( );
}
QString
Prefs :: getString( int key ) const
{
assert( myItems[key].type == QVariant::String );
return myValues[key].toString( );
}
int
Prefs :: getInt( int key ) const
{
assert( myItems[key].type == QVariant::Int );
return myValues[key].toInt( );
}
double
Prefs :: getDouble( int key ) const
{
assert( myItems[key].type == QVariant::Double );
return myValues[key].toDouble( );
}
QDateTime
Prefs :: getDateTime( int key ) const
{
assert( myItems[key].type == QVariant::DateTime );
return myValues[key].toDateTime( );
}
/***
****
***/
void
Prefs :: toggleBool( int key )
{
set( key, !getBool( key ) );
}

157
qt/prefs.h Normal file
View File

@ -0,0 +1,157 @@
/*
* This file Copyright (C) 2009 Charles Kerr <charles@transmissionbt.com>
*
* This file is licensed by the GPL version 2. Works owned by the
* Transmission project are granted a special exemption to clause 2(b)
* so that the bulk of its code can remain under the MIT license.
* This exemption does not extend to derived works not owned by
* the Transmission project.
*
* $Id:$
*/
#ifndef QTR_PREFS_H
#define QTR_PREFS_H
#include <QDateTime>
#include <QObject>
#include <QString>
#include <QVariant>
#include <libtransmission/transmission.h>
#include <libtransmission/bencode.h>
class Prefs: public QObject
{
Q_OBJECT;
public:
enum
{
/* client prefs */
OPTIONS_PROMPT,
OPEN_DIALOG_FOLDER,
INHIBIT_HIBERNATION,
DIR_WATCH,
DIR_WATCH_ENABLED,
SHOW_TRAY_ICON,
SHOW_DESKTOP_NOTIFICATION,
START,
TRASH_ORIGINAL,
ASKQUIT,
SORT_MODE,
SORT_REVERSED,
MINIMAL_VIEW,
FILTERBAR,
STATUSBAR,
STATUSBAR_STATS,
TOOLBAR,
BLOCKLIST_DATE,
BLOCKLIST_UPDATES_ENABLED,
MAIN_WINDOW_LAYOUT_ORDER,
MAIN_WINDOW_HEIGHT,
MAIN_WINDOW_WIDTH,
MAIN_WINDOW_X,
MAIN_WINDOW_Y,
/* core prefs */
FIRST_CORE_PREF,
ALT_SPEED_LIMIT_UP = FIRST_CORE_PREF,
ALT_SPEED_LIMIT_DOWN,
ALT_SPEED_LIMIT_ENABLED,
ALT_SPEED_LIMIT_TIME_BEGIN,
ALT_SPEED_LIMIT_TIME_END,
ALT_SPEED_LIMIT_TIME_ENABLED,
ALT_SPEED_LIMIT_TIME_DAY,
BLOCKLIST_ENABLED,
DSPEED,
DSPEED_ENABLED,
DOWNLOAD_DIR,
ENCRYPTION,
LAZY_BITFIELD,
MSGLEVEL,
OPEN_FILE_LIMIT,
PEER_LIMIT_GLOBAL,
PEER_LIMIT_TORRENT,
PEER_PORT,
PEER_PORT_RANDOM_ON_START,
PEER_PORT_RANDOM_LOW,
PEER_PORT_RANDOM_HIGH,
SOCKET_TOS,
PEX_ENABLED,
PORT_FORWARDING,
PROXY_AUTH_ENABLED,
PREALLOCATION,
PROXY_ENABLED,
PROXY_PASSWORD,
PROXY_PORT,
PROXY,
PROXY_TYPE,
PROXY_USERNAME,
RATIO,
RATIO_ENABLED,
RPC_AUTH_REQUIRED,
RPC_ENABLED,
RPC_PASSWORD,
RPC_PORT,
RPC_USERNAME,
RPC_WHITELIST_ENABLED,
RPC_WHITELIST,
SEED_RATIO_LIMIT,
SEED_RATIO_LIMITED,
USPEED_ENABLED,
USPEED,
UPLOAD_SLOTS_PER_TORRENT,
LAST_CORE_PREF = UPLOAD_SLOTS_PER_TORRENT,
PREFS_COUNT
};
private:
struct PrefItem {
int id;
const char * key;
QVariant::Type type;
};
static PrefItem myItems[];
private:
QString myConfigDir;
QVariant myValues[PREFS_COUNT];
void initDefaults( tr_benc* );
public:
bool isCore( int key ) const { return FIRST_CORE_PREF<=key && key<=LAST_CORE_PREF; }
bool isClient( int key ) const { return !isCore( key ); }
const char * keyStr( int i ) const { return myItems[i].key; }
QVariant::Type type( int i ) const { return myItems[i].type; }
const QVariant& variant( int i ) const { return myValues[i]; }
Prefs( const char * configDir );
~Prefs( );
int getInt( int key ) const;
bool getBool( int key) const;
QString getString( int key ) const;
double getDouble( int key) const;
QDateTime getDateTime( int key ) const;
template<typename T> void set( int key, const T& value ) {
QVariant& v( myValues[key] );
const QVariant tmp( value );
if( v.isNull() || (v!=tmp) ) {
v = tmp;
emit changed( key );
}
}
void toggleBool( int key );
signals:
void changed( int key );
};
#endif

340
qt/qticonloader.cc Normal file
View File

@ -0,0 +1,340 @@
/****************************************************************************
**
** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies).
** Contact: Qt Software Information (qt-info@nokia.com)
**
** This file is part of the QtGui module of the Qt Toolkit.
**
** Commercial Usage
** Licensees holding valid Qt Commercial licenses may use this file in
** accordance with the Qt Commercial License Agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and Nokia.
**
** GNU Lesser General Public License Usage
** Alternatively, this file may be used under the terms of the GNU Lesser
** General Public License version 2.1 as published by the Free Software
** Foundation and appearing in the file LICENSE.LGPL included in the
** packaging of this file. Please review the following information to
** ensure the GNU Lesser General Public License version 2.1 requirements
** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
**
** In addition, as a special exception, Nokia gives you certain
** additional rights. These rights are described in the Nokia Qt LGPL
** Exception version 1.0, included in the file LGPL_EXCEPTION.txt in this
** package.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3.0 as published by the Free Software
** Foundation and appearing in the file LICENSE.GPL included in the
** packaging of this file. Please review the following information to
** ensure the GNU General Public License version 3.0 requirements will be
** met: http://www.gnu.org/copyleft/gpl.html.
**
** If you are unsure which license is appropriate for your use, please
** contact the sales department at qt-sales@nokia.com.
**
****************************************************************************/
#include "qticonloader.h"
#include <QtGui/QPixmapCache>
#include <QtCore/QList>
#include <QtCore/QHash>
#include <QtCore/QDir>
#include <QtCore/QString>
#include <QtCore/QLibrary>
#include <QtCore/QSettings>
#include <QtCore/QTextStream>
#ifdef Q_WS_X11
class QIconTheme
{
public:
QIconTheme(QHash <int, QString> dirList, QStringList parents) :
_dirList(dirList), _parents(parents), _valid(true){ }
QIconTheme() : _valid(false){ }
QHash <int, QString> dirList() {return _dirList;}
QStringList parents() {return _parents;}
bool isValid() {return _valid;}
private:
QHash <int, QString> _dirList;
QStringList _parents;
bool _valid;
};
class QtIconLoaderImplementation
{
public:
QtIconLoaderImplementation();
QPixmap findIcon(int size, const QString &name) const;
private:
QIconTheme parseIndexFile(const QString &themeName) const;
void lookupIconTheme() const;
QPixmap findIconHelper(int size,
const QString &themeName,
const QString &iconName,
QStringList &visited) const;
mutable QString themeName;
mutable QStringList iconDirs;
mutable QHash <QString, QIconTheme> themeList;
};
Q_GLOBAL_STATIC(QtIconLoaderImplementation, iconLoaderInstance)
#endif
/*!
Returns the standard icon for the given icon /a name
as specified in the freedesktop icon spec
http://standards.freedesktop.org/icon-naming-spec/icon-naming-spec-latest.html
/a fallback is an optional argument to specify the icon to be used if
no icon is found on the platform. This is particularily useful for
crossplatform code.
*/
QIcon QtIconLoader::icon(const QString &name, const QIcon &fallback)
{
QIcon icon;
#ifdef Q_WS_X11
QString pngExtension(QLatin1String(".png"));
QList<int> iconSizes;
iconSizes << 16 << 24 << 32 << 48 << 64;
Q_FOREACH (int size, iconSizes) {
icon.addPixmap(iconLoaderInstance()->findIcon(size, name + pngExtension));
}
#endif
if (icon.isNull())
icon = fallback;
Q_UNUSED(name);
return icon;
}
#ifdef Q_WS_X11
QtIconLoaderImplementation::QtIconLoaderImplementation()
{
lookupIconTheme();
}
extern "C" {
struct GConfClient;
struct GError;
typedef void (*Ptr_g_type_init)();
typedef GConfClient* (*Ptr_gconf_client_get_default)();
typedef char* (*Ptr_gconf_client_get_string)(GConfClient*, const char*, GError **);
typedef void (*Ptr_g_object_unref)(void *);
typedef void (*Ptr_g_error_free)(GError *);
typedef void (*Ptr_g_free)(void*);
static Ptr_g_type_init p_g_type_init = 0;
static Ptr_gconf_client_get_default p_gconf_client_get_default = 0;
static Ptr_gconf_client_get_string p_gconf_client_get_string = 0;
static Ptr_g_object_unref p_g_object_unref = 0;
static Ptr_g_error_free p_g_error_free = 0;
static Ptr_g_free p_g_free = 0;
}
static int kdeVersion()
{
static int version = qgetenv("KDE_SESSION_VERSION").toInt();
return version;
}
static QString kdeHome()
{
static QString kdeHomePath;
if (kdeHomePath.isEmpty()) {
kdeHomePath = QFile::decodeName(qgetenv("KDEHOME"));
if (kdeHomePath.isEmpty()) {
int kdeSessionVersion = kdeVersion();
QDir homeDir(QDir::homePath());
QString kdeConfDir(QLatin1String("/.kde"));
if (4 == kdeSessionVersion && homeDir.exists(QLatin1String(".kde4")))
kdeConfDir = QLatin1String("/.kde4");
kdeHomePath = QDir::homePath() + kdeConfDir;
}
}
return kdeHomePath;
}
void QtIconLoaderImplementation::lookupIconTheme() const
{
#ifdef Q_WS_X11
QString dataDirs = QFile::decodeName(getenv("XDG_DATA_DIRS"));
if (dataDirs.isEmpty())
dataDirs = QLatin1String("/usr/local/share/:/usr/share/");
dataDirs.prepend(QDir::homePath() + QLatin1String("/:"));
iconDirs = dataDirs.split(QLatin1Char(':'));
// If we are running GNOME we resolve and use GConf. In all other
// cases we currently use the KDE icon theme
if (qgetenv("DESKTOP_SESSION") == "gnome" ||
!qgetenv("GNOME_DESKTOP_SESSION_ID").isEmpty()) {
if (themeName.isEmpty()) {
// Resolve glib and gconf
p_g_type_init = (Ptr_g_type_init)QLibrary::resolve(QLatin1String("gobject-2.0"), 0, "g_type_init");
p_gconf_client_get_default = (Ptr_gconf_client_get_default)QLibrary::resolve(QLatin1String("gconf-2"), 4, "gconf_client_get_default");
p_gconf_client_get_string = (Ptr_gconf_client_get_string)QLibrary::resolve(QLatin1String("gconf-2"), 4, "gconf_client_get_string");
p_g_object_unref = (Ptr_g_object_unref)QLibrary::resolve(QLatin1String("gobject-2.0"), 0, "g_object_unref");
p_g_error_free = (Ptr_g_error_free)QLibrary::resolve(QLatin1String("glib-2.0"), 0, "g_error_free");
p_g_free = (Ptr_g_free)QLibrary::resolve(QLatin1String("glib-2.0"), 0, "g_free");
if (p_g_type_init && p_gconf_client_get_default &&
p_gconf_client_get_string && p_g_object_unref &&
p_g_error_free && p_g_free) {
p_g_type_init();
GConfClient* client = p_gconf_client_get_default();
GError *err = 0;
char *str = p_gconf_client_get_string(client, "/desktop/gnome/interface/icon_theme", &err);
if (!err) {
themeName = QString::fromUtf8(str);
p_g_free(str);
}
p_g_object_unref(client);
if (err)
p_g_error_free (err);
}
if (themeName.isEmpty())
themeName = QLatin1String("gnome");
}
if (!themeName.isEmpty())
return;
}
// KDE (and others)
if (dataDirs.isEmpty())
dataDirs = QLatin1String("/usr/local/share/:/usr/share/");
dataDirs += QLatin1Char(':') + kdeHome() + QLatin1String("/share");
dataDirs.prepend(QDir::homePath() + QLatin1String("/:"));
QStringList kdeDirs = QFile::decodeName(getenv("KDEDIRS")).split(QLatin1Char(':'));
Q_FOREACH (const QString dirName, kdeDirs)
dataDirs.append(QLatin1Char(':') + dirName + QLatin1String("/share"));
iconDirs = dataDirs.split(QLatin1Char(':'));
QFileInfo fileInfo(QLatin1String("/usr/share/icons/default.kde"));
QDir dir(fileInfo.canonicalFilePath());
QString kdeDefault = kdeVersion() >= 4 ? QString::fromLatin1("oxygen") : QString::fromLatin1("crystalsvg");
QString defaultTheme = fileInfo.exists() ? dir.dirName() : kdeDefault;
QSettings settings(kdeHome() + QLatin1String("/share/config/kdeglobals"), QSettings::IniFormat);
settings.beginGroup(QLatin1String("Icons"));
themeName = settings.value(QLatin1String("Theme"), defaultTheme).toString();
#endif
}
QIconTheme QtIconLoaderImplementation::parseIndexFile(const QString &themeName) const
{
QIconTheme theme;
QFile themeIndex;
QStringList parents;
QHash <int, QString> dirList;
for ( int i = 0 ; i < iconDirs.size() && !themeIndex.exists() ; ++i) {
const QString &contentDir = QLatin1String(iconDirs[i].startsWith(QDir::homePath()) ? "/.icons/" : "/icons/");
themeIndex.setFileName(iconDirs[i] + contentDir + themeName + QLatin1String("/index.theme"));
}
if (themeIndex.exists()) {
QSettings indexReader(themeIndex.fileName(), QSettings::IniFormat);
Q_FOREACH (const QString &key, indexReader.allKeys()) {
if (key.endsWith("/Size")) {
if (int size = indexReader.value(key).toInt())
dirList.insertMulti(size, key.left(key.size() - 5));
}
}
// Parent themes provide fallbacks for missing icons
parents = indexReader.value(QLatin1String("Icon Theme/Inherits")).toString().split(QLatin1Char(','));
}
if (kdeVersion() >= 3) {
QFileInfo fileInfo(QLatin1String("/usr/share/icons/default.kde"));
QDir dir(fileInfo.canonicalFilePath());
QString defaultKDETheme = dir.exists() ? dir.dirName() : kdeVersion() == 3 ?
QString::fromLatin1("crystalsvg") : QString::fromLatin1("oxygen");
if (!parents.contains(defaultKDETheme) && themeName != defaultKDETheme)
parents.append(defaultKDETheme);
} else if (parents.isEmpty() && themeName != QLatin1String("hicolor")) {
parents.append(QLatin1String("hicolor"));
}
theme = QIconTheme(dirList, parents);
return theme;
}
QPixmap QtIconLoaderImplementation::findIconHelper(int size, const QString &themeName,
const QString &iconName, QStringList &visited) const
{
QPixmap pixmap;
if (!themeName.isEmpty()) {
visited << themeName;
QIconTheme theme = themeList.value(themeName);
if (!theme.isValid()) {
theme = parseIndexFile(themeName);
themeList.insert(themeName, theme);
}
if (!theme.isValid())
return QPixmap();
QList <QString> subDirs = theme.dirList().values(size);
for ( int i = 0 ; i < iconDirs.size() ; ++i) {
for ( int j = 0 ; j < subDirs.size() ; ++j) {
QString contentDir = (iconDirs[i].startsWith(QDir::homePath())) ?
QLatin1String("/.icons/") : QLatin1String("/icons/");
QString fileName = iconDirs[i] + contentDir + themeName + QLatin1Char('/') + subDirs[j] + QLatin1Char('/') + iconName;
QFile file(fileName);
if (file.exists())
pixmap.load(fileName);
if (!pixmap.isNull())
break;
}
}
if (pixmap.isNull()) {
QStringList parents = theme.parents();
//search recursively through inherited themes
for (int i = 0 ; pixmap.isNull() && i < parents.size() ; ++i) {
QString parentTheme = parents[i].trimmed();
if (!visited.contains(parentTheme)) //guard against endless recursion
pixmap = findIconHelper(size, parentTheme, iconName, visited);
}
}
}
return pixmap;
}
QPixmap QtIconLoaderImplementation::findIcon(int size, const QString &name) const
{
QPixmap pixmap;
QString pixmapName = QLatin1String("$qt") + name + QString::number(size);
if (QPixmapCache::find(pixmapName, pixmap))
return pixmap;
if (!themeName.isEmpty()) {
QStringList visited;
pixmap = findIconHelper(size, themeName, name, visited);
}
QPixmapCache::insert(pixmapName, pixmap);
return pixmap;
}
#endif //Q_WS_X11

56
qt/qticonloader.h Normal file
View File

@ -0,0 +1,56 @@
/****************************************************************************
**
** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies).
** Contact: Qt Software Information (qt-info@nokia.com)
**
** This file is part of the QtGui module of the Qt Toolkit.
**
** Commercial Usage
** Licensees holding valid Qt Commercial licenses may use this file in
** accordance with the Qt Commercial License Agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and Nokia.
**
** GNU Lesser General Public License Usage
** Alternatively, this file may be used under the terms of the GNU Lesser
** General Public License version 2.1 as published by the Free Software
** Foundation and appearing in the file LICENSE.LGPL included in the
** packaging of this file. Please review the following information to
** ensure the GNU Lesser General Public License version 2.1 requirements
** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
**
** In addition, as a special exception, Nokia gives you certain
** additional rights. These rights are described in the Nokia Qt LGPL
** Exception version 1.0, included in the file LGPL_EXCEPTION.txt in this
** package.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3.0 as published by the Free Software
** Foundation and appearing in the file LICENSE.GPL included in the
** packaging of this file. Please review the following information to
** ensure the GNU General Public License version 3.0 requirements will be
** met: http://www.gnu.org/copyleft/gpl.html.
**
** If you are unsure which license is appropriate for your use, please
** contact the sales department at qt-sales@nokia.com.
**
****************************************************************************/
#ifndef QTICONLOADER_H
#define QTICONLOADER_H
#include <QtGui/QIcon>
// This is the QtIconLoader
// Version 0.1
//
class QtIconLoader
{
public:
static QIcon icon(const QString &name, const QIcon &fallback = QIcon());
};
#endif // QTICONLOADER_H

65
qt/qtransmission.pro Normal file
View File

@ -0,0 +1,65 @@
VERSION = 1.6.0
CONFIG += qt thread debug link_pkgconfig
QT += network
PKGCONFIG = fontconfig libcurl openssl
INCLUDEPATH += .
TRANSMISSION_TOP = ..
INCLUDEPATH += $${TRANSMISSION_TOP}
LIBS += $${TRANSMISSION_TOP}/libtransmission/libtransmission.a
LIBS += $${TRANSMISSION_TOP}/third-party/miniupnp/libminiupnp.a
LIBS += $${TRANSMISSION_TOP}/third-party/libnatpmp/libnatpmp.a
LIBS += $${TRANSMISSION_TOP}/third-party/libevent/.libs/libevent.a
FORMS += mainwin.ui about.ui
RESOURCES += application.qrc
HEADERS += about.h \
app.h \
details.h \
file-tree.h \
hig.h \
mainwin.h \
make-dialog.h \
options.h \
prefs-dialog.h \
prefs.h \
qticonloader.h \
session.h \
speed.h \
squeezelabel.h \
stats-dialog.h \
torrent-delegate.h \
torrent-delegate-min.h \
torrent-filter.h \
torrent.h \
torrent-model.h \
types.h \
utils.h \
watchdir.h \
SOURCES += about.cc \
app.cc \
details.cc \
file-tree.cc \
hig.cc \
mainwin.cc \
make-dialog.cc \
options.cc \
prefs.cc \
prefs-dialog.cc \
qticonloader.cc \
session.cc \
squeezelabel.cc \
stats-dialog.cc \
torrent.cc \
torrent-delegate.cc \
torrent-delegate-min.cc \
torrent-filter.cc \
torrent-model.cc \
utils.cc \
watchdir.cc
TRANSLATIONS += transmission_en.ts

797
qt/session.cc Normal file
View File

@ -0,0 +1,797 @@
/*
* This file Copyright (C) 2009 Charles Kerr <charles@transmissionbt.com>
*
* This file is licensed by the GPL version 2. Works owned by the
* Transmission project are granted a special exemption to clause 2(b)
* so that the bulk of its code can remain under the MIT license.
* This exemption does not extend to derived works not owned by
* the Transmission project.
*
* $Id:$
*/
#include <cassert>
#include <iostream>
#include <QApplication>
#include <QByteArray>
#include <QCoreApplication>
#include <QDesktopServices>
#include <QMessageBox>
#include <QSet>
#include <QStyle>
#include <libtransmission/transmission.h>
#include <libtransmission/bencode.h>
#include <libtransmission/rpcimpl.h>
#include <libtransmission/utils.h> /* tr_free */
#include <libtransmission/version.h> /* LONG_VERSION */
#include "prefs.h"
#include "qticonloader.h"
#include "session.h"
#include "torrent.h"
// #define DEBUG_HTTP
namespace
{
enum
{
TAG_SOME_TORRENTS,
TAG_ALL_TORRENTS,
TAG_SESSION_STATS,
TAG_SESSION_INFO,
TAG_BLOCKLIST_UPDATE,
TAG_ADD_TORRENT,
TAG_PORT_TEST
};
}
/***
****
***/
namespace
{
typedef Torrent::KeyList KeyList;
const KeyList& getInfoKeys( ) { return Torrent::getInfoKeys( ); }
const KeyList& getStatKeys( ) { return Torrent::getStatKeys( ); }
const KeyList& getExtraStatKeys( ) { return Torrent::getExtraStatKeys( ); }
void
addList( tr_benc * list, const KeyList& strings )
{
tr_bencListReserve( list, strings.size( ) );
foreach( const char * str, strings )
tr_bencListAddStr( list, str );
}
}
/***
****
***/
void
Session :: sessionSet( const char * key, const QVariant& value )
{
tr_benc top;
tr_bencInitDict( &top, 2 );
tr_bencDictAddStr( &top, "method", "session-set" );
tr_benc * args( tr_bencDictAddDict( &top, "arguments", 1 ) );
switch( value.type( ) ) {
case QVariant::Bool: tr_bencDictAddBool ( args, key, value.toBool() ); break;
case QVariant::Int: tr_bencDictAddInt ( args, key, value.toInt() ); break;
case QVariant::Double: tr_bencDictAddReal ( args, key, value.toDouble() ); break;
case QVariant::String: tr_bencDictAddStr ( args, key, value.toString().toUtf8() ); break;
default: assert( "unknown type" );
}
std::cerr << "request: " << tr_bencToJSON(&top) << std::endl;
exec( &top );
tr_bencFree( &top );
}
void
Session :: portTest( )
{
tr_benc top;
tr_bencInitDict( &top, 2 );
tr_bencDictAddStr( &top, "method", "port-test" );
tr_bencDictAddInt( &top, "tag", TAG_PORT_TEST );
exec( &top );
std::cerr << "request: " << tr_bencToJSON(&top) << std::endl;
tr_bencFree( &top );
}
void
Session :: updatePref( int key )
{
if( myPrefs.isCore( key ) ) switch( key )
{
case Prefs :: ALT_SPEED_LIMIT_UP:
case Prefs :: ALT_SPEED_LIMIT_DOWN:
case Prefs :: ALT_SPEED_LIMIT_ENABLED:
case Prefs :: ALT_SPEED_LIMIT_TIME_BEGIN:
case Prefs :: ALT_SPEED_LIMIT_TIME_END:
case Prefs :: ALT_SPEED_LIMIT_TIME_ENABLED:
case Prefs :: ALT_SPEED_LIMIT_TIME_DAY:
case Prefs :: BLOCKLIST_ENABLED:
case Prefs :: BLOCKLIST_DATE:
case Prefs :: DOWNLOAD_DIR:
case Prefs :: PEER_LIMIT_GLOBAL:
case Prefs :: PEER_LIMIT_TORRENT:
case Prefs :: SEED_RATIO_LIMIT:
case Prefs :: SEED_RATIO_LIMITED:
case Prefs :: USPEED_ENABLED:
case Prefs :: USPEED:
case Prefs :: DSPEED_ENABLED:
case Prefs :: DSPEED:
case Prefs :: PEX_ENABLED:
case Prefs :: PORT_FORWARDING:
case Prefs :: PEER_PORT:
case Prefs :: PEER_PORT_RANDOM_ON_START:
sessionSet( myPrefs.keyStr(key), myPrefs.variant(key) );
break;
case Prefs :: RPC_AUTH_REQUIRED:
if( mySession )
tr_sessionSetRPCEnabled( mySession, myPrefs.getBool(key) );
break;
case Prefs :: RPC_ENABLED:
if( mySession )
tr_sessionSetRPCEnabled( mySession, myPrefs.getBool(key) );
break;
case Prefs :: RPC_PASSWORD:
if( mySession )
tr_sessionSetRPCPassword( mySession, myPrefs.getString(key).toUtf8().constData() );
break;
case Prefs :: RPC_PORT:
if( mySession )
tr_sessionSetRPCPort( mySession, myPrefs.getInt(key) );
break;
case Prefs :: RPC_USERNAME:
if( mySession )
tr_sessionSetRPCUsername( mySession, myPrefs.getString(key).toUtf8().constData() );
break;
case Prefs :: RPC_WHITELIST_ENABLED:
std::cerr << "setting whitelist enabled" << std::endl;
if( mySession )
tr_sessionSetRPCWhitelistEnabled( mySession, myPrefs.getBool(key) );
break;
case Prefs :: RPC_WHITELIST:
std::cerr << "setting whitelist" << std::endl;
if( mySession )
tr_sessionSetRPCWhitelist( mySession, myPrefs.getString(key).toUtf8().constData() );
break;
default:
std::cerr << "unhandled pref: " << key << std::endl;
}
}
/***
****
***/
Session :: Session( const char * configDir, Prefs& prefs, const char * url, bool paused ):
myBlocklistSize( ),
myPrefs( prefs ),
mySession( 0 ),
myUrl( url )
{
myStats.ratio = TR_RATIO_NA;
myStats.uploadedBytes = 0;
myStats.downloadedBytes = 0;
myStats.filesAdded = 0;
myStats.sessionCount = 0;
myStats.secondsActive = 0;
myCumulativeStats = myStats;
if( url != 0 )
{
connect( &myHttp, SIGNAL(requestStarted(int)), this, SLOT(onRequestStarted(int)));
connect( &myHttp, SIGNAL(requestFinished(int,bool)), this, SLOT(onRequestFinished(int,bool)));
connect( &myHttp, SIGNAL(dataReadProgress(int,int)), this, SIGNAL(dataReadProgress()));
connect( &myHttp, SIGNAL(dataSendProgress(int,int)), this, SIGNAL(dataSendProgress()));
myHttp.setHost( myUrl.host( ), myUrl.port( ) );
myHttp.setUser( myUrl.userName( ), myUrl.password( ) );
myBuffer.open( QIODevice::ReadWrite );
if( paused )
exec( "{ \"method\": \"torrent-stop\" }" );
}
else
{
tr_benc settings;
tr_bencInitDict( &settings, 0 );
tr_sessionGetDefaultSettings( &settings );
tr_sessionLoadSettings( &settings, configDir, "qt" );
mySession = tr_sessionInit( "qt", configDir, true, &settings );
tr_bencFree( &settings );
tr_ctor * ctor = tr_ctorNew( mySession );
if( paused )
tr_ctorSetPaused( ctor, TR_FORCE, TRUE );
int torrentCount;
tr_torrent ** torrents = tr_sessionLoadTorrents( mySession, ctor, &torrentCount );
tr_free( torrents );
tr_ctorFree( ctor );
}
connect( &myPrefs, SIGNAL(changed(int)), this, SLOT(updatePref(int)) );
}
Session :: ~Session( )
{
if( mySession )
tr_sessionClose( mySession );
}
bool
Session :: isServer( ) const
{
return mySession != 0;
}
bool
Session :: isLocal( ) const
{
if( mySession != 0 )
return true;
if( myUrl.host() == "127.0.0.1" )
return true;
if( !myUrl.host().compare( "localhost", Qt::CaseInsensitive ) )
return true;
return false;
}
/***
****
***/
namespace
{
tr_benc *
buildRequest( const char * method, tr_benc& top, int tag=-1 )
{
tr_bencInitDict( &top, 3 );
tr_bencDictAddStr( &top, "method", method );
if( tag >= 0 )
tr_bencDictAddInt( &top, "tag", tag );
return tr_bencDictAddDict( &top, "arguments", 0 );
}
void
addOptionalIds( tr_benc * args, const QSet<int>& ids )
{
if( !ids.isEmpty( ) )
{
tr_benc * idList( tr_bencDictAddList( args, "ids", ids.size( ) ) );
foreach( int i, ids )
tr_bencListAddInt( idList, i );
}
}
}
const int Session :: ADD_TORRENT_TAG = TAG_ADD_TORRENT;
void
Session :: torrentSet( int id, const QString& key, bool value )
{
tr_benc top;
tr_bencInitDict( &top, 2 );
tr_bencDictAddStr( &top, "method", "torrent-set" );
tr_benc * args( tr_bencDictAddDict( &top, "arguments", 2 ) );
tr_bencDictAddInt( args, key.toUtf8(), value );
tr_bencListAddInt( tr_bencDictAddList( args, "ids", 1 ), id );
exec( &top );
tr_bencFree( &top );
}
void
Session :: torrentSet( int id, const QString& key, const QList<int>& value )
{
tr_benc top;
tr_bencInitDict( &top, 2 );
tr_bencDictAddStr( &top, "method", "torrent-set" );
tr_benc * args( tr_bencDictAddDict( &top, "arguments", 2 ) );
tr_bencListAddInt( tr_bencDictAddList( args, "ids", 1 ), id );
tr_benc * list( tr_bencDictAddList( args, key.toUtf8(), value.size( ) ) );
foreach( int i, value )
tr_bencListAddInt( list, i );
exec( &top );
tr_bencFree( &top );
}
void
Session :: refreshTorrents( const QSet<int>& ids )
{
if( ids.empty( ) )
{
refreshAllTorrents( );
}
else
{
tr_benc top;
tr_bencInitDict( &top, 3 );
tr_bencDictAddStr( &top, "method", "torrent-get" );
tr_bencDictAddInt( &top, "tag", TAG_SOME_TORRENTS );
tr_benc * args( tr_bencDictAddDict( &top, "arguments", 2 ) );
addList( tr_bencDictAddList( args, "fields", 0 ), getStatKeys( ) );
addOptionalIds( args, ids );
exec( &top );
tr_bencFree( &top );
}
}
void
Session :: refreshExtraStats( int id )
{
tr_benc top;
tr_bencInitDict( &top, 3 );
tr_bencDictAddStr( &top, "method", "torrent-get" );
tr_bencDictAddInt( &top, "tag", TAG_SOME_TORRENTS );
tr_benc * args( tr_bencDictAddDict( &top, "arguments", 2 ) );
tr_bencListAddInt( tr_bencDictAddList( args, "ids", 1 ), id );
addList( tr_bencDictAddList( args, "fields", 0 ), getStatKeys( ) + getExtraStatKeys( ));
exec( &top );
tr_bencFree( &top );
}
void
Session :: sendTorrentRequest( const char * request, const QSet<int>& ids )
{
tr_benc top;
tr_benc * args( buildRequest( request, top ) );
addOptionalIds( args, ids );
exec( &top );
tr_bencFree( &top );
refreshTorrents( ids );
}
void
Session :: pause( const QSet<int>& ids )
{
sendTorrentRequest( "torrent-stop", ids );
}
void
Session :: start( const QSet<int>& ids )
{
sendTorrentRequest( "torrent-start", ids );
}
void
Session :: refreshActiveTorrents( )
{
tr_benc top;
tr_bencInitDict( &top, 3 );
tr_bencDictAddStr( &top, "method", "torrent-get" );
tr_bencDictAddInt( &top, "tag", TAG_SOME_TORRENTS );
tr_benc * args( tr_bencDictAddDict( &top, "arguments", 2 ) );
tr_bencDictAddStr( args, "ids", "recently-active" );
addList( tr_bencDictAddList( args, "fields", 0 ), getStatKeys( ) );
exec( &top );
tr_bencFree( &top );
}
void
Session :: refreshAllTorrents( )
{
tr_benc top;
tr_bencInitDict( &top, 3 );
tr_bencDictAddStr( &top, "method", "torrent-get" );
tr_bencDictAddInt( &top, "tag", TAG_ALL_TORRENTS );
tr_benc * args( tr_bencDictAddDict( &top, "arguments", 1 ) );
addList( tr_bencDictAddList( args, "fields", 0 ), getStatKeys( ) );
exec( &top );
tr_bencFree( &top );
}
void
Session :: initTorrents( const QSet<int>& ids )
{
tr_benc top;
const int tag( ids.isEmpty() ? TAG_ALL_TORRENTS : TAG_SOME_TORRENTS );
tr_benc * args( buildRequest( "torrent-get", top, tag ) );
addOptionalIds( args, ids );
addList( tr_bencDictAddList( args, "fields", 0 ), getStatKeys()+getInfoKeys() );
exec( &top );
tr_bencFree( &top );
}
void
Session :: refreshSessionStats( )
{
tr_benc top;
tr_bencInitDict( &top, 2 );
tr_bencDictAddStr( &top, "method", "session-stats" );
tr_bencDictAddInt( &top, "tag", TAG_SESSION_STATS );
exec( &top );
tr_bencFree( &top );
}
void
Session :: refreshSessionInfo( )
{
tr_benc top;
tr_bencInitDict( &top, 2 );
tr_bencDictAddStr( &top, "method", "session-get" );
tr_bencDictAddInt( &top, "tag", TAG_SESSION_INFO );
exec( &top );
tr_bencFree( &top );
}
void
Session :: updateBlocklist( )
{
tr_benc top;
tr_bencInitDict( &top, 2 );
tr_bencDictAddStr( &top, "method", "blocklist-update" );
tr_bencDictAddInt( &top, "tag", TAG_BLOCKLIST_UPDATE );
exec( &top );
tr_bencFree( &top );
}
/***
****
***/
void
Session :: exec( const tr_benc * request )
{
char * str( tr_bencToJSON( request ) );
exec( str );
tr_free( str );
}
void
Session :: localSessionCallback( tr_session * session, const char * json, size_t len, void * self )
{
Q_UNUSED( session );
((Session*)self)->parseResponse( json, len );
}
void
Session :: exec( const char * request )
{
if( mySession )
{
tr_rpc_request_exec_json( mySession, request, strlen( request ), localSessionCallback, this );
}
else
{
const QByteArray data( request, strlen( request ) );
static const QString path( "/transmission/rpc" );
QHttpRequestHeader header( "POST", path );
header.setValue( "User-Agent", QCoreApplication::instance()->applicationName() + "/" + LONG_VERSION_STRING );
header.setValue( "Content-Type", "application/json; charset=UTF-8" );
myHttp.request( header, data, &myBuffer );
#ifdef DEBUG_HTTP
std::cerr << "sending " << qPrintable(header.toString()) << "\nBody:\n" << request << std::endl;
#endif
}
}
void
Session :: onRequestStarted( int id )
{
Q_UNUSED( id );
assert( myBuffer.atEnd( ) );
}
void
Session :: onRequestFinished( int id, bool error )
{
Q_UNUSED( id );
#ifdef DEBUG_HTTP
std::cerr << "http request " << id << " ended.. response header: "
<< qPrintable( myHttp.lastResponse().toString() )
<< std::endl
<< "json: " << myBuffer.buffer( ).constData( )
<< std::endl;
#endif
if( error )
std::cerr << "http error: " << qPrintable(myHttp.errorString()) << std::endl;
else {
const QByteArray& response( myBuffer.buffer( ) );
const char * json( response.constData( ) );
int jsonLength( response.size( ) );
if( json[jsonLength-1] == '\n' ) --jsonLength;
parseResponse( json, jsonLength );
}
myBuffer.buffer( ).clear( );
myBuffer.reset( );
assert( myBuffer.bytesAvailable( ) < 1 );
}
void
Session :: parseResponse( const char * json, size_t jsonLength )
{
tr_benc top;
const uint8_t * end( 0 );
const int err( tr_jsonParse( json, jsonLength, &top, &end ) );
if( !err )
{
int64_t tag;
const char * str;
tr_benc *args, *torrents;
if( tr_bencDictFindInt( &top, "tag", &tag ) )
{
switch( tag )
{
case TAG_SOME_TORRENTS:
case TAG_ALL_TORRENTS:
if( tr_bencDictFindDict( &top, "arguments", &args ) ) {
if( tr_bencDictFindList( args, "torrents", &torrents ) )
emit torrentsUpdated( torrents, tag==TAG_ALL_TORRENTS );
if( tr_bencDictFindList( args, "removed", &torrents ) )
emit torrentsRemoved( torrents );
}
break;
case TAG_SESSION_STATS:
if( tr_bencDictFindDict( &top, "arguments", &args ) )
updateStats( args );
break;
case TAG_SESSION_INFO:
if( tr_bencDictFindDict( &top, "arguments", &args ) )
updateInfo( args );
break;
case TAG_BLOCKLIST_UPDATE: {
int64_t intVal = 0;
if( tr_bencDictFindDict( &top, "arguments", &args ) )
if( tr_bencDictFindInt( args, "blocklist-size", &intVal ) )
setBlocklistSize( intVal );
break;
}
case TAG_PORT_TEST: {
std::cerr << "response: " << json << std::endl;
tr_bool isOpen = 0;
if( tr_bencDictFindDict( &top, "arguments", &args ) )
tr_bencDictFindBool( args, "port-is-open", &isOpen );
emit portTested( (bool)isOpen );
}
case TAG_ADD_TORRENT:
str = "";
if( tr_bencDictFindStr( &top, "result", &str ) && strcmp( str, "success" ) ) {
QMessageBox * d = new QMessageBox( QMessageBox::Information,
tr( "Add Torrent" ),
QString::fromUtf8(str),
QMessageBox::Close,
QApplication::activeWindow());
QPixmap pixmap;
QIcon icon = QtIconLoader :: icon( "dialog-information" );
if( !icon.isNull( ) ) {
const int size = QApplication::style()->pixelMetric( QStyle::PM_LargeIconSize );
d->setIconPixmap( icon.pixmap( size, size ) );
}
connect( d, SIGNAL(rejected()), d, SLOT(deleteLater()) );
d->show( );
}
break;
default:
break;
}
}
tr_bencFree( &top );
}
}
void
Session :: updateStats( tr_benc * d, struct tr_session_stats * stats )
{
int64_t i;
if( tr_bencDictFindInt( d, "uploadedBytes", &i ) )
stats->uploadedBytes = i;
if( tr_bencDictFindInt( d, "downloadedBytes", &i ) )
stats->downloadedBytes = i;
if( tr_bencDictFindInt( d, "filesAdded", &i ) )
stats->filesAdded = i;
if( tr_bencDictFindInt( d, "sessionCount", &i ) )
stats->sessionCount = i;
if( tr_bencDictFindInt( d, "secondsActive", &i ) )
stats->secondsActive = i;
stats->ratio = tr_getRatio( stats->uploadedBytes, stats->downloadedBytes );
}
void
Session :: updateStats( tr_benc * d )
{
tr_benc * c;
if( tr_bencDictFindDict( d, "current-stats", &c ) )
updateStats( c, &myStats );
if( tr_bencDictFindDict( d, "cumulative-stats", &c ) )
updateStats( c, &myCumulativeStats );
emit statsUpdated( );
}
void
Session :: updateInfo( tr_benc * d )
{
int64_t i;
const char * str;
disconnect( &myPrefs, SIGNAL(changed(int)), this, SLOT(updatePref(int)) );
for( int i=Prefs::FIRST_CORE_PREF; i<=Prefs::LAST_CORE_PREF; ++i )
{
const tr_benc * b( tr_bencDictFind( d, myPrefs.keyStr( i ) ) );
if( !b )
continue;
switch( myPrefs.type( i ) )
{
case QVariant :: Int: {
int64_t val;
if( tr_bencGetInt( b, &val ) )
myPrefs.set( i, (int)val );
break;
}
case QVariant :: Double: {
double val;
if( tr_bencGetReal( b, &val ) )
myPrefs.set( i, val );
break;
}
case QVariant :: Bool: {
tr_bool val;
if( tr_bencGetBool( b, &val ) )
myPrefs.set( i, (bool)val );
break;
}
case QVariant :: String: {
const char * val;
if( tr_bencGetStr( b, &val ) )
myPrefs.set( i, QString(val) );
break;
}
default:
break;
}
}
/* Use the C API to get settings that, for security reasons, aren't supported by RPC */
if( mySession != 0 )
{
myPrefs.set( Prefs::RPC_ENABLED, tr_sessionIsRPCEnabled ( mySession ) );
myPrefs.set( Prefs::RPC_AUTH_REQUIRED, tr_sessionIsRPCPasswordEnabled ( mySession ) );
myPrefs.set( Prefs::RPC_PASSWORD, tr_sessionGetRPCPassword ( mySession ) );
myPrefs.set( Prefs::RPC_PORT, tr_sessionGetRPCPort ( mySession ) );
myPrefs.set( Prefs::RPC_USERNAME, tr_sessionGetRPCUsername ( mySession ) );
myPrefs.set( Prefs::RPC_WHITELIST_ENABLED, tr_sessionGetRPCWhitelistEnabled ( mySession ) );
myPrefs.set( Prefs::RPC_WHITELIST, tr_sessionGetRPCWhitelist ( mySession ) );
}
if( tr_bencDictFindInt( d, "blocklist-size", &i ) && i!=blocklistSize( ) )
setBlocklistSize( i );
if( tr_bencDictFindStr( d, "version", &str ) && ( mySessionVersion != str ) )
mySessionVersion = str;
//std::cerr << "Session :: updateInfo end" << std::endl;
connect( &myPrefs, SIGNAL(changed(int)), this, SLOT(updatePref(int)) );
emit sessionUpdated( );
}
void
Session :: setBlocklistSize( int64_t i )
{
myBlocklistSize = i;
emit blocklistUpdated( i );
}
void
Session :: addTorrent( QString filename )
{
QFile file( filename );
file.open( QIODevice::ReadOnly );
QByteArray raw( file.readAll( ) );
file.close( );
if( !raw.isEmpty( ) )
{
int b64len = 0;
char * b64 = tr_base64_encode( raw.constData(), raw.size(), &b64len );
tr_benc top, *args;
tr_bencInitDict( &top, 2 );
tr_bencDictAddStr( &top, "method", "torrent-add" );
args = tr_bencDictAddDict( &top, "arguments", 3 );
tr_bencDictAddStr( args, "download-dir", qPrintable(myPrefs.getString(Prefs::DOWNLOAD_DIR)) );
tr_bencDictAddRaw( args, "metainfo", b64, b64len );
tr_bencDictAddInt( args, "paused", !myPrefs.getBool( Prefs::START ) );
exec( &top );
tr_free( b64 );
tr_bencFree( &top );
}
}
void
Session :: removeTorrents( const QSet<int>& ids, bool deleteFiles )
{
if( !ids.isEmpty( ) )
{
tr_benc top, *args;
tr_bencInitDict( &top, 2 );
tr_bencDictAddStr( &top, "method", "torrent-remove" );
args = tr_bencDictAddDict( &top, "arguments", 2 );
addOptionalIds( args, ids );
tr_bencDictAddInt( args, "delete-local-data", deleteFiles );
exec( &top );
tr_bencFree( &top );
}
}
void
Session :: verifyTorrents( const QSet<int>& ids )
{
if( !ids.isEmpty( ) )
{
tr_benc top, *args;
tr_bencInitDict( &top, 2 );
tr_bencDictAddStr( &top, "method", "torrent-verify" );
args = tr_bencDictAddDict( &top, "arguments", 1 );
addOptionalIds( args, ids );
exec( &top );
tr_bencFree( &top );
}
}
void
Session :: reannounceTorrents( const QSet<int>& ids )
{
if( !ids.isEmpty( ) )
{
tr_benc top, *args;
tr_bencInitDict( &top, 2 );
tr_bencDictAddStr( &top, "method", "torrent-reannounce" );
args = tr_bencDictAddDict( &top, "arguments", 1 );
addOptionalIds( args, ids );
exec( &top );
tr_bencFree( &top );
}
}
/***
****
***/
void
Session :: launchWebInterface( )
{
QUrl url;
if( !mySession) // remote session
url = myUrl;
else { // local session
url.setHost( "localhost" );
url.setPort( myPrefs.getInt( Prefs::RPC_PORT ) );
}
std::cerr << qPrintable(url.toString()) << std::endl;
QDesktopServices :: openUrl( url );
}

124
qt/session.h Normal file
View File

@ -0,0 +1,124 @@
/*
* This file Copyright (C) 2009 Charles Kerr <charles@transmissionbt.com>
*
* This file is licensed by the GPL version 2. Works owned by the
* Transmission project are granted a special exemption to clause 2(b)
* so that the bulk of its code can remain under the MIT license.
* This exemption does not extend to derived works not owned by
* the Transmission project.
*
* $Id:$
*/
#ifndef TR_APP_SESSION_H
#define TR_APP_SESSION_H
#include <QObject>
#include <QSet>
#include <QBuffer>
#include <QFileInfoList>
#include <QString>
#include <QHttp>
#include <QUrl>
#include <libtransmission/transmission.h>
#include <libtransmission/json.h>
#include "speed.h"
class Prefs;
class Session: public QObject
{
Q_OBJECT
public:
Session( const char * configDir, Prefs& prefs, const char * remoteUrl, bool paused );
~Session( );
static const int ADD_TORRENT_TAG;
public:
const QUrl& getRemoteUrl( ) const { return myUrl; }
const struct tr_session_stats& getStats( ) const { return myStats; }
const struct tr_session_stats& getCumulativeStats( ) const { return myCumulativeStats; }
const QString& sessionVersion( ) const { return mySessionVersion; }
public:
int64_t blocklistSize( ) const { return myBlocklistSize; }
void setBlocklistSize( int64_t i );
void updateBlocklist( );
void portTest( );
public:
bool isRemote( ) const { return !isLocal( ); }
bool isLocal( ) const;
bool isServer( ) const;
private:
void updateStats( tr_benc * args );
void updateInfo( tr_benc * args );
void parseResponse( const char * json, size_t len );
static void localSessionCallback( tr_session *, const char *, size_t, void * );
public:
void exec( const char * request );
void exec( const tr_benc * request );
private:
void sessionSet( const char * key, const QVariant& variant );
void pumpRequests( );
void sendTorrentRequest( const char * request, const QSet<int>& torrentIds );
static void updateStats( tr_benc * d, struct tr_session_stats * stats );
void refreshTorrents( const QSet<int>& torrentIds );
public:
void torrentSet( int id, const QString& key, bool val );
void torrentSet( int id, const QString& key, const QList<int>& val );
public slots:
void pause( const QSet<int>& torrentIds = QSet<int>() );
void start( const QSet<int>& torrentIds = QSet<int>() );
void refreshSessionInfo( );
void refreshSessionStats( );
void refreshActiveTorrents( );
void refreshAllTorrents( );
void initTorrents( const QSet<int>& ids = QSet<int>() );
void addTorrent( QString filename );
void removeTorrents( const QSet<int>& torrentIds, bool deleteFiles=false );
void verifyTorrents( const QSet<int>& torrentIds );
void reannounceTorrents( const QSet<int>& torrentIds );
void launchWebInterface( );
void updatePref( int key );
/** request a refresh for statistics, including the ones only used by the properties dialog, for a specific torrent */
void refreshExtraStats( int torrent );
private slots:
void onRequestStarted( int id );
void onRequestFinished( int id, bool error );
signals:
void portTested( bool isOpen );
void statsUpdated( );
void sessionUpdated( );
void blocklistUpdated( int );
void torrentsUpdated( tr_benc * torrentList, bool completeList );
void torrentsRemoved( tr_benc * torrentList );
void dataReadProgress( );
void dataSendProgress( );
private:
int64_t myBlocklistSize;
Prefs& myPrefs;
tr_session * mySession;
QUrl myUrl;
QBuffer myBuffer;
QHttp myHttp;
struct tr_session_stats myStats;
struct tr_session_stats myCumulativeStats;
QString mySessionVersion;
};
#endif

35
qt/speed.h Normal file
View File

@ -0,0 +1,35 @@
/*
* This file Copyright (C) 2009 Charles Kerr <charles@transmissionbt.com>
*
* This file is licensed by the GPL version 2. Works owned by the
* Transmission project are granted a special exemption to clause 2(b)
* so that the bulk of its code can remain under the MIT license.
* This exemption does not extend to derived works not owned by
* the Transmission project.
*
* $Id:$
*/
#ifndef QTR_SPEED_H
#define QTR_SPEED_H
class Speed
{
private:
double _kbps;
Speed( double kbps ): _kbps(kbps) { }
public:
Speed( ): _kbps(0) { }
double kbps( ) const { return _kbps; }
double bps( ) const { return kbps()*1024.0; }
bool isZero( ) const { return _kbps < 0.001; }
static Speed fromKbps( double kbps ) { return Speed( kbps ); }
static Speed fromBps( double bps ) { return Speed( bps/1024.0 ); }
void setKbps( double kbps ) { _kbps = kbps; }
void setBps( double bps ) { _kbps = bps/1024.0; }
Speed operator+( const Speed& that ) const { return Speed( kbps() + that.kbps() ); }
Speed& operator+=( const Speed& that ) { _kbps += that._kbps; return *this; }
bool operator<( const Speed& that ) const { return kbps() < that.kbps(); }
};
#endif

65
qt/squeezelabel.cc Normal file
View File

@ -0,0 +1,65 @@
/****************************************************************************
**
** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies).
** Contact: Qt Software Information (qt-info@nokia.com)
**
** This file is part of the demonstration applications of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:LGPL$
** Commercial Usage
** Licensees holding valid Qt Commercial licenses may use this file in
** accordance with the Qt Commercial License Agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and Nokia.
**
** GNU Lesser General Public License Usage
** Alternatively, this file may be used under the terms of the GNU Lesser
** General Public License version 2.1 as published by the Free Software
** Foundation and appearing in the file LICENSE.LGPL included in the
** packaging of this file. Please review the following information to
** ensure the GNU Lesser General Public License version 2.1 requirements
** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
**
** In addition, as a special exception, Nokia gives you certain
** additional rights. These rights are described in the Nokia Qt LGPL
** Exception version 1.0, included in the file LGPL_EXCEPTION.txt in this
** package.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3.0 as published by the Free Software
** Foundation and appearing in the file LICENSE.GPL included in the
** packaging of this file. Please review the following information to
** ensure the GNU General Public License version 3.0 requirements will be
** met: http://www.gnu.org/copyleft/gpl.html.
**
** If you are unsure which license is appropriate for your use, please
** contact the sales department at qt-sales@nokia.com.
** $QT_END_LICENSE$
**
****************************************************************************/
#include "squeezelabel.h"
SqueezeLabel::SqueezeLabel(const QString& text, QWidget *parent) : QLabel(text, parent)
{
}
SqueezeLabel::SqueezeLabel(QWidget *parent) : QLabel(parent)
{
}
void SqueezeLabel::paintEvent(QPaintEvent *event)
{
QFontMetrics fm = fontMetrics();
if (fm.width(text()) > contentsRect().width()) {
QString elided = fm.elidedText(text(), Qt::ElideMiddle, width());
QString oldText = text();
setText(elided);
QLabel::paintEvent(event);
setText(oldText);
} else {
QLabel::paintEvent(event);
}
}

61
qt/squeezelabel.h Normal file
View File

@ -0,0 +1,61 @@
/****************************************************************************
**
** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies).
** Contact: Qt Software Information (qt-info@nokia.com)
**
** This file is part of the demonstration applications of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:LGPL$
** Commercial Usage
** Licensees holding valid Qt Commercial licenses may use this file in
** accordance with the Qt Commercial License Agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and Nokia.
**
** GNU Lesser General Public License Usage
** Alternatively, this file may be used under the terms of the GNU Lesser
** General Public License version 2.1 as published by the Free Software
** Foundation and appearing in the file LICENSE.LGPL included in the
** packaging of this file. Please review the following information to
** ensure the GNU Lesser General Public License version 2.1 requirements
** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
**
** In addition, as a special exception, Nokia gives you certain
** additional rights. These rights are described in the Nokia Qt LGPL
** Exception version 1.0, included in the file LGPL_EXCEPTION.txt in this
** package.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3.0 as published by the Free Software
** Foundation and appearing in the file LICENSE.GPL included in the
** packaging of this file. Please review the following information to
** ensure the GNU General Public License version 3.0 requirements will be
** met: http://www.gnu.org/copyleft/gpl.html.
**
** If you are unsure which license is appropriate for your use, please
** contact the sales department at qt-sales@nokia.com.
** $QT_END_LICENSE$
**
****************************************************************************/
#ifndef SQUEEZELABEL_H
#define SQUEEZELABEL_H
#include <QtGui/QLabel>
class SqueezeLabel : public QLabel
{
Q_OBJECT
public:
SqueezeLabel(QWidget *parent = 0);
SqueezeLabel(const QString& text, QWidget *parent = 0);
protected:
void paintEvent(QPaintEvent *event);
};
#endif // SQUEEZELABEL_H

99
qt/stats-dialog.cc Normal file
View File

@ -0,0 +1,99 @@
/*
* This file Copyright (C) 2009 Charles Kerr <charles@transmissionbt.com>
*
* This file is licensed by the GPL version 2. Works owned by the
* Transmission project are granted a special exemption to clause 2(b)
* so that the bulk of its code can remain under the MIT license.
* This exemption does not extend to derived works not owned by
* the Transmission project.
*
* $Id:$
*/
#include <QDialogButtonBox>
#include <QLabel>
#include <QTimer>
#include <QVBoxLayout>
#include "hig.h"
#include "session.h"
#include "stats-dialog.h"
#include "utils.h"
enum
{
REFRESH_INTERVAL_MSEC = (15*1000)
};
StatsDialog :: StatsDialog( Session & session, QWidget * parent ):
QDialog( parent, Qt::Dialog ),
mySession( session ),
myTimer( new QTimer( this ) )
{
myTimer->setSingleShot( false );
connect( myTimer, SIGNAL(timeout()), this, SLOT(onTimer()) );
setWindowTitle( tr( "Statistics" ) );
HIG * hig = new HIG( );
hig->addSectionTitle( tr( "Current Session" ) );
hig->addRow( tr( "Uploaded:" ), myCurrentUp = new QLabel( ) );
hig->addRow( tr( "Downloaded:" ), myCurrentDown = new QLabel( ) );
hig->addRow( tr( "Ratio:" ), myCurrentRatio = new QLabel( ) );
hig->addRow( tr( "Duration:" ), myCurrentDuration = new QLabel( ) );
hig->addSectionDivider( );
hig->addSectionTitle( tr( "Total" ) );
hig->addRow( myStartCount = new QLabel( tr( "Started %n time(s)", 0, 1 ) ), 0 );
hig->addRow( tr( "Uploaded:" ), myTotalUp = new QLabel( ) );
hig->addRow( tr( "Downloaded:" ), myTotalDown = new QLabel( ) );
hig->addRow( tr( "Ratio:" ), myTotalRatio = new QLabel( ) );
hig->addRow( tr( "Duration:" ), myTotalDuration = new QLabel( ) );
hig->finish( );
QLayout * layout = new QVBoxLayout( this );
layout->addWidget( hig );
QDialogButtonBox * buttons = new QDialogButtonBox( QDialogButtonBox::Close, Qt::Horizontal, this );
connect( buttons, SIGNAL(rejected()), this, SLOT(hide()) ); // "close" triggers rejected
layout->addWidget( buttons );
connect( &mySession, SIGNAL(statsUpdated()), this, SLOT(updateStats()) );
updateStats( );
mySession.refreshSessionStats( );
}
StatsDialog :: ~StatsDialog( )
{
}
void
StatsDialog :: setVisible( bool visible )
{
myTimer->stop( );
if( visible )
myTimer->start( REFRESH_INTERVAL_MSEC );
QDialog::setVisible( visible );
}
void
StatsDialog :: onTimer( )
{
mySession.refreshSessionStats( );
}
void
StatsDialog :: updateStats( )
{
const struct tr_session_stats& current( mySession.getStats( ) );
const struct tr_session_stats& total( mySession.getCumulativeStats( ) );
myCurrentUp->setText( Utils :: sizeToString( current.uploadedBytes ) );
myCurrentDown->setText( Utils :: sizeToString( current.downloadedBytes ) );
myCurrentRatio->setText( Utils :: ratioToString( current.ratio ) );
myCurrentDuration->setText( Utils :: timeToString( current.secondsActive ) );
myTotalUp->setText( Utils :: sizeToString( total.uploadedBytes ) );
myTotalDown->setText( Utils :: sizeToString( total.downloadedBytes ) );
myTotalRatio->setText( Utils :: ratioToString( total.ratio ) );
myTotalDuration->setText( Utils :: timeToString( total.secondsActive ) );
myStartCount->setText( tr( "Started %n time(s)", 0, total.sessionCount ) );
}

54
qt/stats-dialog.h Normal file
View File

@ -0,0 +1,54 @@
/*
* This file Copyright (C) 2009 Charles Kerr <charles@transmissionbt.com>
*
* This file is licensed by the GPL version 2. Works owned by the
* Transmission project are granted a special exemption to clause 2(b)
* so that the bulk of its code can remain under the MIT license.
* This exemption does not extend to derived works not owned by
* the Transmission project.
*
* $Id:$
*/
#ifndef STATS_DIALOG_H
#define STATS_DIALOG_H
#include <QDialog>
class Session;
class QLabel;
class QTimer;
class StatsDialog: public QDialog
{
Q_OBJECT
signals:
void accepted( );
public slots:
void updateStats( );
private slots:
void onTimer( );
public:
StatsDialog( Session&, QWidget * parent = 0 );
~StatsDialog( );
virtual void setVisible( bool visible );
private:
Session & mySession;
QTimer * myTimer;
QLabel * myCurrentUp;
QLabel * myCurrentDown;
QLabel * myCurrentRatio;
QLabel * myCurrentDuration;
QLabel * myStartCount;
QLabel * myTotalUp;
QLabel * myTotalDown;
QLabel * myTotalRatio;
QLabel * myTotalDuration;
};
#endif

157
qt/torrent-delegate-min.cc Normal file
View File

@ -0,0 +1,157 @@
/*
* This file Copyright (C) 2009 Charles Kerr <charles@transmissionbt.com>
*
* This file is licensed by the GPL version 2. Works owned by the
* Transmission project are granted a special exemption to clause 2(b)
* so that the bulk of its code can remain under the MIT license.
* This exemption does not extend to derived works not owned by
* the Transmission project.
*
* $Id:$
*/
#include <iostream>
#include <QApplication>
#include <QBrush>
#include <QFont>
#include <QFontMetrics>
#include <QIcon>
#include <QModelIndex>
#include <QPainter>
#include <QPixmap>
#include <QPixmapCache>
#include <QStyleOptionProgressBarV2>
#include "torrent.h"
#include "torrent-delegate-min.h"
#include "torrent-model.h"
#include "utils.h"
#include "qticonloader.h"
enum
{
GUI_PAD = 6,
BAR_HEIGHT = 12,
LINE_SPACING = 4
};
/***
****
**** +---------+-----------------------------------------------+
**** | Icon | Title shortStatusString |
**** | Icon | [ Progressbar.......................... ] |
**** +-------- +-----------------------------------------------+
****
***/
QSize
TorrentDelegateMin :: sizeHint( const QStyleOptionViewItem& option, const Torrent& tor ) const
{
const QStyle* style( QApplication::style( ) );
static const int iconSize( style->pixelMetric( QStyle :: PM_SmallIconSize ) );
QFont nameFont( option.font );
const QFontMetrics nameFM( nameFont );
const QString nameStr( tor.name( ) );
const QSize nameSize( nameFM.size( 0, nameStr ) );
QFont statusFont( option.font );
statusFont.setPointSize( int( option.font.pointSize( ) * 0.85 ) );
const QFontMetrics statusFM( statusFont );
const QString statusStr( shortStatusString( tor ) );
const QSize statusSize( statusFM.size( 0, statusStr ) );
const QSize m( margin( *style ) );
return QSize( m.width() + iconSize + GUI_PAD + nameSize.width() + GUI_PAD + statusSize.width() + m.width(),
m.height() + nameSize.height() + LINE_SPACING + BAR_HEIGHT + m.height() );
}
void
TorrentDelegateMin :: drawTorrent( QPainter * painter, const QStyleOptionViewItem& option, const Torrent& tor ) const
{
const bool isPaused( tor.isPaused( ) );
const QStyle * style( QApplication::style( ) );
static const int iconSize( style->pixelMetric( QStyle :: PM_SmallIconSize ) );
QFont nameFont( option.font );
const QFontMetrics nameFM( nameFont );
const QString nameStr( tor.name( ) );
const QSize nameSize( nameFM.size( 0, nameStr ) );
QFont statusFont( option.font );
statusFont.setPointSize( int( option.font.pointSize( ) * 0.85 ) );
const QFontMetrics statusFM( statusFont );
const QString statusStr( shortStatusString( tor ) );
const QSize statusSize( statusFM.size( 0, statusStr ) );
painter->save( );
if (option.state & QStyle::State_Selected) {
QPalette::ColorGroup cg = option.state & QStyle::State_Enabled
? QPalette::Normal : QPalette::Disabled;
if (cg == QPalette::Normal && !(option.state & QStyle::State_Active))
cg = QPalette::Inactive;
painter->fillRect(option.rect, option.palette.brush(cg, QPalette::Highlight));
}
QIcon::Mode im;
if( isPaused || !(option.state & QStyle::State_Enabled ) ) im = QIcon::Disabled;
else if( option.state & QStyle::State_Selected ) im = QIcon::Selected;
else im = QIcon::Normal;
QIcon::State qs;
if( isPaused ) qs = QIcon::Off;
else qs = QIcon::On;
QPalette::ColorGroup cg = QPalette::Normal;
if( isPaused || !(option.state & QStyle::State_Enabled ) ) cg = QPalette::Disabled;
if( cg == QPalette::Normal && !(option.state & QStyle::State_Active ) ) cg = QPalette::Inactive;
QPalette::ColorRole cr;
if( option.state & QStyle::State_Selected ) cr = QPalette::HighlightedText;
else cr = QPalette::Text;
// layout
const QSize m( margin( *style ) );
QRect fillArea( option.rect );
fillArea.adjust( m.width(), m.height(), -m.width(), -m.height() );
const QRect iconArea( fillArea.x( ),
fillArea.y( ) + ( fillArea.height( ) - iconSize ) / 2,
iconSize,
iconSize );
const QRect statusArea( fillArea.width( ) - statusSize.width( ),
fillArea.top( ) + ((nameSize.height()-statusSize.height())/2),
statusSize.width( ),
statusSize.height( ) );
const QRect nameArea( iconArea.x( ) + iconArea.width( ) + GUI_PAD,
fillArea.y( ),
fillArea.width( ) - statusArea.width( ) - (GUI_PAD*2) - iconArea.width( ),
nameSize.height( ) );
const QRect barArea( nameArea.left( ),
nameArea.bottom( ),
statusArea.right( ) - nameArea.left( ),
BAR_HEIGHT );
// render
if( tor.hasError( ) )
painter->setPen( QColor( "red" ) );
else
painter->setPen( option.palette.color( cg, cr ) );
tor.getMimeTypeIcon().paint( painter, iconArea, Qt::AlignCenter, im, qs );
painter->setFont( nameFont );
painter->drawText( nameArea, 0, nameFM.elidedText( nameStr, Qt::ElideRight, nameArea.width( ) ) );
painter->setFont( statusFont );
painter->drawText( statusArea, 0, statusStr );
myProgressBarStyle->rect = barArea;
myProgressBarStyle->direction = option.direction;
myProgressBarStyle->palette = option.palette;
myProgressBarStyle->palette.setCurrentColorGroup( QPalette::Disabled );
myProgressBarStyle->state = QStyle::State_Off;
myProgressBarStyle->progress = int(myProgressBarStyle->minimum + ((tor.percentDone() * (myProgressBarStyle->maximum - myProgressBarStyle->minimum))));
style->drawControl( QStyle::CE_ProgressBar, myProgressBarStyle, painter );
painter->restore( );
}

37
qt/torrent-delegate-min.h Normal file
View File

@ -0,0 +1,37 @@
/*
* This file Copyright (C) 2009 Charles Kerr <charles@transmissionbt.com>
*
* This file is licensed by the GPL version 2. Works owned by the
* Transmission project are granted a special exemption to clause 2(b)
* so that the bulk of its code can remain under the MIT license.
* This exemption does not extend to derived works not owned by
* the Transmission project.
*
* $Id:$
*/
#ifndef QTR_TORRENT_DELEGATE_MIN_H
#define QTR_TORRENT_DELEGATE_MIN_H
#include <QSize>
#include "torrent-delegate.h"
class QStyleOptionViewItem;
class QStyle;
class Session;
class Torrent;
class TorrentDelegateMin: public TorrentDelegate
{
Q_OBJECT
protected:
virtual QSize sizeHint( const QStyleOptionViewItem&, const Torrent& ) const;
void drawTorrent( QPainter* painter, const QStyleOptionViewItem& option, const Torrent& ) const;
public:
explicit TorrentDelegateMin( QObject * parent=0 ): TorrentDelegate(parent) { }
virtual ~TorrentDelegateMin( ) { }
};
#endif

373
qt/torrent-delegate.cc Normal file
View File

@ -0,0 +1,373 @@
/*
* This file Copyright (C) 2009 Charles Kerr <charles@transmissionbt.com>
*
* This file is licensed by the GPL version 2. Works owned by the
* Transmission project are granted a special exemption to clause 2(b)
* so that the bulk of its code can remain under the MIT license.
* This exemption does not extend to derived works not owned by
* the Transmission project.
*
* $Id:$
*/
#include <iostream>
#include <QApplication>
#include <QBrush>
#include <QFont>
#include <QFontMetrics>
#include <QIcon>
#include <QModelIndex>
#include <QPainter>
#include <QPixmap>
#include <QPixmapCache>
#include <QStyleOptionProgressBarV2>
#include "torrent.h"
#include "torrent-delegate.h"
#include "torrent-model.h"
#include "utils.h"
#include "qticonloader.h"
enum
{
GUI_PAD = 6,
BAR_HEIGHT = 12
};
TorrentDelegate :: TorrentDelegate( QObject * parent ):
QItemDelegate( parent ),
myProgressBarStyle( new QStyleOptionProgressBarV2 )
{
myProgressBarStyle->minimum = 0;
myProgressBarStyle->maximum = 1000;
}
TorrentDelegate :: ~TorrentDelegate( )
{
delete myProgressBarStyle;
}
/***
****
***/
QSize
TorrentDelegate :: margin( const QStyle& style ) const
{
Q_UNUSED( style );
return QSize( 4, 4 );
}
QString
TorrentDelegate :: progressString( const Torrent& tor ) const
{
const bool isDone( tor.isDone( ) );
const bool isSeed( tor.isSeed( ) );
const uint64_t haveTotal( tor.haveTotal( ) );
QString str;
double seedRatio;
bool hasSeedRatio;
if( !isDone )
{
/* %1 is how much we've got,
%2 is how much we'll have when done,
%3 is a percentage of the two */
str = tr( "%1 of %2 (%3%)" ).arg( Utils::sizeToString( haveTotal ) )
.arg( Utils::sizeToString( tor.sizeWhenDone( ) ) )
.arg( tor.percentDone( ) * 100.0, 0, 'f', 2 );
}
else if( !isSeed )
{
/* %1 is how much we've got,
%2 is the torrent's total size,
%3 is a percentage of the two,
%4 is how much we've uploaded,
%5 is our upload-to-download ratio */
str = tr( "%1 of %2 (%3%), uploaded %4 (Ratio: %5)" )
.arg( Utils::sizeToString( haveTotal ) )
.arg( Utils::sizeToString( tor.sizeWhenDone( ) ) )
.arg( tor.percentDone( ) * 100.0, 0, 'f', 2 )
.arg( Utils::sizeToString( tor.uploadedEver( ) ) )
.arg( Utils::ratioToString( tor.ratio( ) ) );
}
else if(( hasSeedRatio = tor.getSeedRatio( seedRatio )))
{
/* %1 is the torrent's total size,
%2 is how much we've uploaded,
%3 is our upload-to-download ratio,
$4 is the ratio we want to reach before we stop uploading */
str = tr( "%1, uploaded %2 (Ratio: %3 Goal %4)" )
.arg( Utils::sizeToString( haveTotal ) )
.arg( Utils::sizeToString( tor.uploadedEver( ) ) )
.arg( Utils::ratioToString( tor.ratio( ) ) )
.arg( Utils::ratioToString( seedRatio ) );
}
else /* seeding w/o a ratio */
{
/* %1 is the torrent's total size,
%2 is how much we've uploaded,
%3 is our upload-to-download ratio */
str = tr( "%1, uploaded %2 (Ratio: %3)" )
.arg( Utils::sizeToString( haveTotal ) )
.arg( Utils::sizeToString( tor.uploadedEver( ) ) )
.arg( Utils::ratioToString( tor.ratio( ) ) );
}
/* add time when downloading */
if( hasSeedRatio || tor.isDownloading( ) )
{
str += tr( " - " );
if( tor.hasETA( ) )
str += tr( "%1 left" ).arg( Utils::timeToString( tor.getETA( ) ) );
else
str += tr( "Remaining time unknown" );
}
return str;
}
QString
TorrentDelegate :: shortTransferString( const Torrent& tor ) const
{
const bool haveDown( tor.peersWeAreDownloadingFrom( ) > 0 );
const bool haveUp( tor.peersWeAreUploadingTo( ) > 0 );
QString downStr, upStr, str;
if( haveDown )
downStr = Utils :: speedToString( tor.downloadSpeed( ) );
if( haveUp )
upStr = Utils :: speedToString( tor.uploadSpeed( ) );
if( haveDown && haveUp )
str = tr( "Down: %1, Up: %2" ).arg(downStr).arg(upStr);
else if( haveDown )
str = tr( "Down: %1" ).arg( downStr );
else if( haveUp )
str = tr( "Up: %1" ).arg( upStr );
else
str = tr( "Idle" );
return str;
}
QString
TorrentDelegate :: shortStatusString( const Torrent& tor ) const
{
QString str;
switch( tor.getActivity( ) )
{
case TR_STATUS_STOPPED:
str = tr( "Paused" );
break;
case TR_STATUS_CHECK_WAIT:
str = tr( "Waiting to verify local data" );
break;
case TR_STATUS_CHECK:
str = tr( "Verifying local data (%1% tested)" ).arg( tor.getVerifyProgress()*100.0, 0, 'f', 1 );
break;
case TR_STATUS_DOWNLOAD:
case TR_STATUS_SEED:
if( !tor.isDownloading( ) )
str = tr( "Ratio: %1, " ).arg( Utils::ratioToString( tor.ratio( ) ) );
str += shortTransferString( tor );
break;
default:
break;
}
return str;
}
QString
TorrentDelegate :: statusString( const Torrent& tor ) const
{
QString str;
if( tor.hasError( ) )
{
str = tor.getError( );
}
else switch( tor.getActivity( ) )
{
case TR_STATUS_STOPPED:
case TR_STATUS_CHECK_WAIT:
case TR_STATUS_CHECK:
str = shortStatusString( tor );
break;
case TR_STATUS_DOWNLOAD:
str = tr( "Downloading from %1 of %n connected peer(s)", 0, tor.connectedPeersAndWebseeds( ) )
.arg( tor.peersWeAreDownloadingFrom( ) );
break;
case TR_STATUS_SEED:
str = tr( "Seeding to %1 of %n connected peer(s)", 0, tor.connectedPeers( ) )
.arg( tor.peersWeAreUploadingTo( ) );
break;
default:
str = "Error";
break;
}
if( !tor.isChecking( ) && !tor.isPaused( ) )
str += tr( " - " ) + shortTransferString( tor );
//str += "asdfasdf";
return str;
}
/***
****
***/
namespace
{
int MAX3( int a, int b, int c )
{
const int ab( a > b ? a : b );
return ab > c ? ab : c;
}
}
QSize
TorrentDelegate :: sizeHint( const QStyleOptionViewItem& option, const Torrent& tor ) const
{
const QStyle* style( QApplication::style( ) );
static const int iconSize( style->pixelMetric( QStyle::PM_MessageBoxIconSize ) );
QFont nameFont( option.font );
nameFont.setWeight( QFont::Bold );
const QFontMetrics nameFM( nameFont );
const QString nameStr( tor.name( ) );
const QSize nameSize( nameFM.size( 0, nameStr ) );
QFont statusFont( option.font );
statusFont.setPointSize( int( option.font.pointSize( ) * 0.9 ) );
const QFontMetrics statusFM( statusFont );
const QString statusStr( statusString( tor ) );
const QSize statusSize( statusFM.size( 0, statusStr ) );
QFont progressFont( statusFont );
const QFontMetrics progressFM( progressFont );
const QString progressStr( progressString( tor ) );
const QSize progressSize( progressFM.size( 0, progressStr ) );
const QSize m( margin( *style ) );
return QSize( m.width()*2 + iconSize + GUI_PAD + MAX3( nameSize.width(), statusSize.width(), progressSize.width() ),
//m.height()*3 + nameFM.lineSpacing() + statusFM.lineSpacing()*2 + progressFM.lineSpacing() );
m.height()*3 + nameFM.lineSpacing() + statusFM.lineSpacing() + BAR_HEIGHT + progressFM.lineSpacing() );
}
QSize
TorrentDelegate :: sizeHint( const QStyleOptionViewItem & option,
const QModelIndex & index ) const
{
const Torrent * tor( index.model()->data( index, TorrentModel::TorrentRole ).value<const Torrent*>() );
return sizeHint( option, *tor );
}
void
TorrentDelegate :: paint( QPainter * painter,
const QStyleOptionViewItem & option,
const QModelIndex & index) const
{
const Torrent * tor( index.model()->data( index, TorrentModel::TorrentRole ).value<const Torrent*>() );
painter->save( );
painter->setClipRect( option.rect );
drawBackground( painter, option, index );
drawTorrent( painter, option, *tor );
drawFocus(painter, option, option.rect );
painter->restore( );
}
void
TorrentDelegate :: drawTorrent( QPainter * painter, const QStyleOptionViewItem& option, const Torrent& tor ) const
{
const QStyle * style( QApplication::style( ) );
static const int iconSize( style->pixelMetric( QStyle::PM_LargeIconSize ) );
QFont nameFont( option.font );
nameFont.setWeight( QFont::Bold );
const QFontMetrics nameFM( nameFont );
const QString nameStr( tor.name( ) );
const QSize nameSize( nameFM.size( 0, nameStr ) );
QFont statusFont( option.font );
statusFont.setPointSize( int( option.font.pointSize( ) * 0.9 ) );
const QFontMetrics statusFM( statusFont );
const QString statusStr( progressString( tor ) );
QFont progressFont( statusFont );
const QFontMetrics progressFM( progressFont );
const QString progressStr( statusString( tor ) );
const bool isPaused( tor.isPaused( ) );
painter->save( );
if (option.state & QStyle::State_Selected) {
QPalette::ColorGroup cg = option.state & QStyle::State_Enabled
? QPalette::Normal : QPalette::Disabled;
if (cg == QPalette::Normal && !(option.state & QStyle::State_Active))
cg = QPalette::Inactive;
painter->fillRect(option.rect, option.palette.brush(cg, QPalette::Highlight));
}
QIcon::Mode im;
if( isPaused || !(option.state & QStyle::State_Enabled ) ) im = QIcon::Disabled;
else if( option.state & QStyle::State_Selected ) im = QIcon::Selected;
else im = QIcon::Normal;
QIcon::State qs;
if( isPaused ) qs = QIcon::Off;
else qs = QIcon::On;
QPalette::ColorGroup cg = QPalette::Normal;
if( isPaused || !(option.state & QStyle::State_Enabled ) ) cg = QPalette::Disabled;
if( cg == QPalette::Normal && !(option.state & QStyle::State_Active ) ) cg = QPalette::Inactive;
QPalette::ColorRole cr;
if( option.state & QStyle::State_Selected ) cr = QPalette::HighlightedText;
else cr = QPalette::Text;
// layout
const QSize m( margin( *style ) );
QRect fillArea( option.rect );
fillArea.adjust( m.width(), m.height(), -m.width(), -m.height() );
QRect iconArea( fillArea.x( ), fillArea.y( ) + ( fillArea.height( ) - iconSize ) / 2, iconSize, iconSize );
QRect nameArea( iconArea.x( ) + iconArea.width( ) + GUI_PAD, fillArea.y( ),
fillArea.width( ) - GUI_PAD - iconArea.width( ), nameSize.height( ) );
QRect statusArea( nameArea );
statusArea.moveTop( nameArea.y( ) + nameFM.lineSpacing( ) );
statusArea.setHeight( nameSize.height( ) );
QRect barArea( statusArea );
barArea.setHeight( BAR_HEIGHT );
barArea.moveTop( statusArea.y( ) + statusFM.lineSpacing( ) );
QRect progArea( statusArea );
progArea.moveTop( barArea.y( ) + barArea.height( ) );
// render
if( tor.hasError( ) )
painter->setPen( QColor( "red" ) );
else
painter->setPen( option.palette.color( cg, cr ) );
tor.getMimeTypeIcon().paint( painter, iconArea, Qt::AlignCenter, im, qs );
painter->setFont( nameFont );
painter->drawText( nameArea, 0, nameFM.elidedText( nameStr, Qt::ElideRight, nameArea.width( ) ) );
painter->setFont( statusFont );
painter->drawText( statusArea, 0, statusFM.elidedText( statusStr, Qt::ElideRight, statusArea.width( ) ) );
painter->setFont( progressFont );
painter->drawText( progArea, 0, progressFM.elidedText( progressStr, Qt::ElideRight, progArea.width( ) ) );
myProgressBarStyle->rect = barArea;
myProgressBarStyle->direction = option.direction;
myProgressBarStyle->palette = option.palette;
myProgressBarStyle->palette.setCurrentColorGroup( QPalette::Disabled );
myProgressBarStyle->state = QStyle::State_Off;
myProgressBarStyle->progress = int(myProgressBarStyle->minimum + ((tor.percentDone() * (myProgressBarStyle->maximum - myProgressBarStyle->minimum))));
style->drawControl( QStyle::CE_ProgressBar, myProgressBarStyle, painter );
painter->restore( );
}

51
qt/torrent-delegate.h Normal file
View File

@ -0,0 +1,51 @@
/*
* This file Copyright (C) 2009 Charles Kerr <charles@transmissionbt.com>
*
* This file is licensed by the GPL version 2. Works owned by the
* Transmission project are granted a special exemption to clause 2(b)
* so that the bulk of its code can remain under the MIT license.
* This exemption does not extend to derived works not owned by
* the Transmission project.
*
* $Id:$
*/
#ifndef QTR_TORRENT_DELEGATE_H
#define QTR_TORRENT_DELEGATE_H
#include <QItemDelegate>
#include <QSize>
class QStyleOptionProgressBarV2;
class QStyleOptionViewItem;
class QStyle;
class Session;
class Torrent;
class TorrentDelegate: public QItemDelegate
{
Q_OBJECT
protected:
QStyleOptionProgressBarV2 * myProgressBarStyle;
protected:
QString statusString( const Torrent& tor ) const;
QString progressString( const Torrent& tor ) const;
QString shortStatusString( const Torrent& tor ) const;
QString shortTransferString( const Torrent& tor ) const;
protected:
QSize margin( const QStyle& style ) const;
virtual QSize sizeHint( const QStyleOptionViewItem&, const Torrent& ) const;
virtual void drawTorrent( QPainter* painter, const QStyleOptionViewItem& option, const Torrent& ) const;
public:
explicit TorrentDelegate( QObject * parent=0 );
virtual ~TorrentDelegate( );
QSize sizeHint(const QStyleOptionViewItem& option, const QModelIndex& index) const;
void paint(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const;
};
#endif

225
qt/torrent-filter.cc Normal file
View File

@ -0,0 +1,225 @@
/*
* This file Copyright (C) 2009 Charles Kerr <charles@transmissionbt.com>
*
* This file is licensed by the GPL version 2. Works owned by the
* Transmission project are granted a special exemption to clause 2(b)
* so that the bulk of its code can remain under the MIT license.
* This exemption does not extend to derived works not owned by
* the Transmission project.
*
* $Id:$
*/
#include <iostream>
#include "prefs.h"
#include "torrent.h"
#include "torrent-filter.h"
#include "torrent-model.h"
TorrentFilter :: TorrentFilter( Prefs& prefs ):
myPrefs( prefs ),
myShowMode( SHOW_ALL ),
myTextMode( FILTER_BY_NAME ),
mySortMode( SORT_BY_ID ),
myIsAscending( FALSE )
{
}
TorrentFilter :: ~TorrentFilter( )
{
}
/***
****
***/
void
TorrentFilter :: setShowMode( int showMode )
{
if( myShowMode != showMode )
{
myShowMode = ShowMode( showMode );
invalidateFilter( );
}
}
void
TorrentFilter :: setTextMode( int textMode )
{
if( myTextMode != textMode )
{
myTextMode = TextMode( textMode );
invalidateFilter( );
}
}
void
TorrentFilter :: setText( QString text )
{
QString trimmed = text.trimmed( );
if( myText != trimmed )
{
myText = trimmed;
invalidateFilter( );
}
}
bool
TorrentFilter :: filterAcceptsRow( int sourceRow, const QModelIndex& sourceParent ) const
{
QModelIndex childIndex = sourceModel()->index( sourceRow, 0, sourceParent );
const Torrent * tor = childIndex.model()->data( childIndex, TorrentModel::TorrentRole ).value<const Torrent*>();
const tr_torrent_activity activity = tor->getActivity( );
bool accepts;
switch( myShowMode )
{
case SHOW_ALL:
accepts = true;
break;
case SHOW_ACTIVE:
accepts = tor->peersWeAreUploadingTo( ) > 0 || tor->peersWeAreDownloadingFrom( ) > 0;
break;
case SHOW_DOWNLOADING:
accepts = activity == TR_STATUS_DOWNLOAD;
break;
case SHOW_SEEDING:
accepts = activity == TR_STATUS_SEED;
break;
case SHOW_PAUSED:
accepts = activity == TR_STATUS_STOPPED;
break;
}
if( accepts && !myText.isEmpty( ) ) switch( myTextMode )
{
case FILTER_BY_NAME:
accepts = tor->name().contains( myText, Qt::CaseInsensitive );
break;
case FILTER_BY_FILES:
accepts = tor->hasFileSubstring( myText );
break;
case FILTER_BY_TRACKER:
accepts = tor->hasTrackerSubstring( myText );
break;
}
return accepts;
}
/***
****
***/
const char*
TorrentFilter :: getSortKey( int modeIn )
{
switch( modeIn < 0 ? getSortMode( ) : SortMode( modeIn ) )
{
case SORT_BY_ACTIVITY: return "sort-by-activity";
case SORT_BY_AGE: return "sort-by-age";
case SORT_BY_ETA: return "sort-by-eta";
case SORT_BY_PROGRESS: return "sort-by-progress";
case SORT_BY_RATIO: return "sort-by-ratio";
case SORT_BY_SIZE: return "sort-by-size";
case SORT_BY_STATE: return "sort-by-state";
case SORT_BY_TRACKER: return "sort-by-tracker";
default: return "sort-by-name";
}
}
void
TorrentFilter :: resort( )
{
invalidate( );
sort( 0, myIsAscending ? Qt::AscendingOrder : Qt::DescendingOrder );
}
void
TorrentFilter :: setAscending( bool b )
{
if( myIsAscending != b )
{
myIsAscending = b;
resort( );
}
}
void
TorrentFilter :: setSortMode( int sortMode )
{
if( mySortMode != sortMode )
{
myPrefs.set( Prefs :: SORT_MODE, getSortKey( sortMode ) );
mySortMode = SortMode( sortMode );
setDynamicSortFilter ( true );
resort( );
}
}
void TorrentFilter :: sortByActivity ( ) { setSortMode( SORT_BY_ACTIVITY ); }
void TorrentFilter :: sortByAge ( ) { setSortMode( SORT_BY_AGE ); }
void TorrentFilter :: sortByETA ( ) { setSortMode( SORT_BY_ETA ); }
void TorrentFilter :: sortById ( ) { setSortMode( SORT_BY_ID ); }
void TorrentFilter :: sortByName ( ) { setSortMode( SORT_BY_NAME ); }
void TorrentFilter :: sortByProgress ( ) { setSortMode( SORT_BY_PROGRESS ); }
void TorrentFilter :: sortByRatio ( ) { setSortMode( SORT_BY_RATIO ); }
void TorrentFilter :: sortBySize ( ) { setSortMode( SORT_BY_SIZE ); }
void TorrentFilter :: sortByState ( ) { setSortMode( SORT_BY_STATE ); }
void TorrentFilter :: sortByTracker ( ) { setSortMode( SORT_BY_TRACKER ); }
bool
TorrentFilter :: lessThan( const QModelIndex& left, const QModelIndex& right ) const
{
const Torrent * a = sourceModel()->data( left, TorrentModel::TorrentRole ).value<const Torrent*>();
const Torrent * b = sourceModel()->data( right, TorrentModel::TorrentRole ).value<const Torrent*>();
bool less;
switch( getSortMode( ) )
{
case SORT_BY_SIZE:
less = a->sizeWhenDone() < b->sizeWhenDone();
break;
case SORT_BY_ACTIVITY:
less = a->downloadSpeed() + a->uploadSpeed() < b->downloadSpeed() + b->uploadSpeed();
break;
case SORT_BY_AGE:
less = a->dateAdded() < b->dateAdded();
break;
case SORT_BY_ID:
less = a->id() < b->id();
break;
case SORT_BY_RATIO:
less = a->compareRatio( *b ) < 0;
break;
case SORT_BY_PROGRESS:
less = a->percentDone() < b->percentDone();
break;
case SORT_BY_ETA:
less = a->compareETA( *b ) < 0;
break;
case SORT_BY_STATE:
if( a->hasError() != b->hasError() )
less = a->hasError();
else
less = a->getActivity() < b->getActivity();
break;
case SORT_BY_TRACKER:
less = a->compareTracker( *b ) < 0;
break;
default:
less = a->name().compare( b->name(), Qt::CaseInsensitive ) > 0;
break;
}
return less;
}
int
TorrentFilter :: hiddenRowCount( ) const
{
return sourceModel()->rowCount( ) - rowCount( );
}

77
qt/torrent-filter.h Normal file
View File

@ -0,0 +1,77 @@
/*
* This file Copyright (C) 2009 Charles Kerr <charles@transmissionbt.com>
*
* This file is licensed by the GPL version 2. Works owned by the
* Transmission project are granted a special exemption to clause 2(b)
* so that the bulk of its code can remain under the MIT license.
* This exemption does not extend to derived works not owned by
* the Transmission project.
*
* $Id:$
*/
#ifndef QTR_TORRENT_FILTER_H
#define QTR_TORRENT_FILTER_H
#include <QSortFilterProxyModel>
struct Prefs;
class TorrentFilter: public QSortFilterProxyModel
{
Q_OBJECT
public:
TorrentFilter( Prefs& prefs );
virtual ~TorrentFilter( );
public:
enum ShowMode { SHOW_ALL, SHOW_ACTIVE, SHOW_DOWNLOADING, SHOW_SEEDING, SHOW_PAUSED };
ShowMode getShowMode( ) const { return myShowMode; }
enum TextMode { FILTER_BY_NAME, FILTER_BY_FILES, FILTER_BY_TRACKER };
TextMode getTextMode( ) const { return myTextMode; }
enum SortMode{ SORT_BY_ACTIVITY, SORT_BY_AGE, SORT_BY_ETA, SORT_BY_NAME,
SORT_BY_PROGRESS, SORT_BY_RATIO, SORT_BY_SIZE,
SORT_BY_STATE, SORT_BY_TRACKER, SORT_BY_ID };
const char * getSortKey( int mode=-1 );
SortMode getSortMode( ) const { return mySortMode; }
bool isAscending( ) const { return myIsAscending; }
int hiddenRowCount( ) const;
public slots:
void setShowMode( int showMode );
void setTextMode( int textMode );
void setSortMode( int sortMode );
void setText( QString );
void sortByActivity( );
void sortByAge( );
void sortByETA( );
void sortById( );
void sortByName( );
void sortByProgress( );
void sortByRatio( );
void sortBySize( );
void sortByState( );
void sortByTracker( );
void setAscending( bool );
void resort( );
protected:
virtual bool filterAcceptsRow( int, const QModelIndex& ) const;
virtual bool lessThan( const QModelIndex&, const QModelIndex& ) const;
private:
Prefs& myPrefs;
ShowMode myShowMode;
TextMode myTextMode;
SortMode mySortMode;
bool myIsAscending;
QString myText;
};
#endif

245
qt/torrent-model.cc Normal file
View File

@ -0,0 +1,245 @@
/*
* This file Copyright (C) 2009 Charles Kerr <charles@transmissionbt.com>
*
* This file is licensed by the GPL version 2. Works owned by the
* Transmission project are granted a special exemption to clause 2(b)
* so that the bulk of its code can remain under the MIT license.
* This exemption does not extend to derived works not owned by
* the Transmission project.
*
* $Id:$
*/
#include <cassert>
#include <iostream>
#include <libtransmission/transmission.h>
#include <libtransmission/bencode.h>
#include "torrent-delegate.h"
#include "torrent-model.h"
int
TorrentModel :: rowCount( const QModelIndex& parent ) const
{
Q_UNUSED( parent );
return myTorrents.size( );
}
QVariant
TorrentModel :: data( const QModelIndex& index, int role ) const
{
QVariant var;
const int row = index.row( );
if( row<0 || row>=myTorrents.size() )
return QVariant( );
const Torrent* t = myTorrents.at( row );
switch( role )
{
case Qt::DisplayRole:
var = QString( t->name() );
break;
case Qt::DecorationRole:
var = t->getMimeTypeIcon( );
break;
case TorrentRole:
var = qVariantFromValue( t );
break;
default:
//std::cerr << "Unhandled role: " << role << std::endl;
break;
}
return var;
}
/***
****
***/
void
TorrentModel :: addTorrent( Torrent * t )
{
myIdToTorrent.insert( t->id( ), t );
myIdToRow.insert( t->id( ), myTorrents.size( ) );
myTorrents.append( t );
}
TorrentModel :: TorrentModel( Prefs& prefs ):
myPrefs( prefs )
{
}
TorrentModel :: ~TorrentModel( )
{
foreach( Torrent * tor, myTorrents )
delete tor;
myTorrents.clear( );
}
/***
****
***/
Torrent*
TorrentModel :: getTorrentFromId( int id )
{
id_to_torrent_t::iterator it( myIdToTorrent.find( id ) );
return it == myIdToTorrent.end() ? 0 : it.value( );
}
const Torrent*
TorrentModel :: getTorrentFromId( int id ) const
{
id_to_torrent_t::const_iterator it( myIdToTorrent.find( id ) );
return it == myIdToTorrent.end() ? 0 : it.value( );
}
/***
****
***/
void
TorrentModel :: onTorrentChanged( int torrentId )
{
const int row( myIdToRow.value( torrentId, -1 ) );
if( row >= 0 ) {
QModelIndex qmi( index( row, 0 ) );
dataChanged( qmi, qmi );
}
}
void
TorrentModel :: removeTorrents( tr_benc * torrents )
{
int i = 0;
tr_benc * child;
while(( child = tr_bencListChild( torrents, i++ ))) {
int64_t intVal;
if( tr_bencGetInt( child, &intVal ) )
removeTorrent( intVal );
}
}
void
TorrentModel :: updateTorrents( tr_benc * torrents, bool isCompleteList )
{
QList<Torrent*> newTorrents;
QSet<int> oldIds( getIds( ) );
QSet<int> newIds;
int updatedCount = 0;
if( tr_bencIsList( torrents ) )
{
size_t i( 0 );
tr_benc * child;
while(( child = tr_bencListChild( torrents, i++ )))
{
int64_t id;
if( tr_bencDictFindInt( child, "id", &id ) )
{
newIds.insert( id );
Torrent * tor = getTorrentFromId( id );
if( tor == 0 )
{
tor = new Torrent( myPrefs, id );
tor->update( child );
newTorrents.append( tor );
connect( tor, SIGNAL(torrentChanged(int)), this, SLOT(onTorrentChanged(int)));
}
else
{
tor->update( child );
++updatedCount;
}
}
}
}
if( !newTorrents.isEmpty( ) )
{
const int oldCount( rowCount( ) );
const int newCount( oldCount + newTorrents.size( ) );
QSet<int> ids;
beginInsertRows( QModelIndex(), oldCount, newCount - 1 );
foreach( Torrent * tor, newTorrents ) {
addTorrent( tor );
ids.insert( tor->id( ) );
}
endInsertRows( );
emit torrentsAdded( ids );
}
if( isCompleteList )
{
QSet<int> removedIds( oldIds );
removedIds -= newIds;
foreach( int id, removedIds )
removeTorrent( id );
}
}
void
TorrentModel :: removeTorrent( int id )
{
const int row = myIdToRow.value( id, -1 );
if( row >= 0 )
{
Torrent * tor = myIdToTorrent.value( id, 0 );
std::cerr << "removing torrent " << id << std::endl;
beginRemoveRows( QModelIndex(), row, row );
myIdToRow.remove( id );
myIdToTorrent.remove( id );
myTorrents.remove( myTorrents.indexOf( tor ) );
endRemoveRows( );
delete tor;
}
}
Speed
TorrentModel :: getUploadSpeed( ) const
{
Speed up;
foreach( const Torrent * tor, myTorrents )
up += tor->uploadSpeed( );
return up;
}
Speed
TorrentModel :: getDownloadSpeed( ) const
{
Speed down;
foreach( const Torrent * tor, myTorrents )
down += tor->downloadSpeed( );
return down;
}
QSet<int>
TorrentModel :: getIds( ) const
{
QSet<int> ids;
foreach( const Torrent * tor, myTorrents )
ids.insert( tor->id( ) );
return ids;
}
bool
TorrentModel :: hasTorrent( const QString& hashString ) const
{
foreach( const Torrent * tor, myTorrents )
if( tor->hashString( ) == hashString )
return true;
return false;
}

78
qt/torrent-model.h Normal file
View File

@ -0,0 +1,78 @@
/*
* This file Copyright (C) 2009 Charles Kerr <charles@transmissionbt.com>
*
* This file is licensed by the GPL version 2. Works owned by the
* Transmission project are granted a special exemption to clause 2(b)
* so that the bulk of its code can remain under the MIT license.
* This exemption does not extend to derived works not owned by
* the Transmission project.
*
* $Id:$
*/
#ifndef QTR_TORRENT_MODEL_H
#define QTR_TORRENT_MODEL_H
#include <QAbstractListModel>
#include <QMap>
#include <QSet>
#include <QVector>
#include "speed.h"
#include "torrent.h"
class Prefs;
extern "C"
{
struct tr_benc;
};
class TorrentModel: public QAbstractListModel
{
Q_OBJECT
private:
typedef QMap<int,int> id_to_row_t;
typedef QMap<int,Torrent*> id_to_torrent_t;
typedef QVector<Torrent*> torrents_t;
id_to_row_t myIdToRow;
id_to_torrent_t myIdToTorrent;
torrents_t myTorrents;
Prefs& myPrefs;
public:
bool hasTorrent( const QString& hashString ) const;
virtual int rowCount( const QModelIndex& parent = QModelIndex() ) const;
virtual QVariant data( const QModelIndex& index, int role = Qt::DisplayRole ) const;
enum Role { TorrentRole = Qt::UserRole };
public:
Torrent* getTorrentFromId( int id );
const Torrent* getTorrentFromId( int id ) const;
private:
QSet<int> getIds( ) const;
void addTorrent( Torrent * );
public:
Speed getUploadSpeed( ) const;
Speed getDownloadSpeed( ) const;
signals:
void torrentsAdded( QSet<int> );
public slots:
void updateTorrents( tr_benc * torrentList, bool isCompleteList );
void removeTorrents( tr_benc * torrentList );
void removeTorrent( int id );
private slots:
void onTorrentChanged( int propertyId );
public:
TorrentModel( Prefs& prefs );
virtual ~TorrentModel( );
};
#endif

621
qt/torrent.cc Normal file
View File

@ -0,0 +1,621 @@
/*
* This file Copyright (C) 2009 Charles Kerr <charles@transmissionbt.com>
*
* This file is licensed by the GPL version 2. Works owned by the
* Transmission project are granted a special exemption to clause 2(b)
* so that the bulk of its code can remain under the MIT license.
* This exemption does not extend to derived works not owned by
* the Transmission project.
*
* $Id:$
*/
#include <cassert>
#include <iostream>
#include <QApplication>
#include <QStyle>
#include <QSet>
#include <QString>
#include <QFileInfo>
#include <QVariant>
#include <libtransmission/transmission.h>
#include <libtransmission/bencode.h>
#include <libtransmission/utils.h> /* tr_new0, tr_strdup */
#include "app.h"
#include "prefs.h"
#include "qticonloader.h"
#include "torrent.h"
#include "utils.h"
Torrent :: Torrent( Prefs& prefs, int id ):
myPrefs( prefs )
{
for( int i=0; i<PROPERTY_COUNT; ++i )
assert( myProperties[i].id == i );
setInt( ID, id );
setIcon( MIME_ICON, QApplication::style()->standardIcon( QStyle::SP_FileIcon ) );
}
Torrent :: ~Torrent( )
{
}
/***
****
***/
Torrent :: Property
Torrent :: myProperties[] =
{
{ ID, "id", QVariant::Int, INFO, },
{ UPLOAD_SPEED, "rateUpload", QVariant::Int, STAT } /* B/s */,
{ DOWNLOAD_SPEED, "rateDownload", QVariant::Int, STAT }, /* B/s */
{ SWARM_SPEED, "swarmSpeed", QVariant::Int, STAT_EXTRA },/* KB/s */
{ DOWNLOAD_DIR, "downloadDir", QVariant::String, STAT },
{ ACTIVITY, "status", QVariant::Int, STAT },
{ NAME, "name", QVariant::String, INFO },
{ ERROR, "errorString", QVariant::String, STAT },
{ SIZE_WHEN_DONE, "sizeWhenDone", QVariant::ULongLong, STAT },
{ LEFT_UNTIL_DONE, "leftUntilDone", QVariant::ULongLong, STAT },
{ HAVE_UNCHECKED, "haveUnchecked", QVariant::ULongLong, STAT },
{ HAVE_VERIFIED, "haveValid", QVariant::ULongLong, STAT },
{ TOTAL_SIZE, "totalSize", QVariant::ULongLong, INFO },
{ PIECE_SIZE, "pieceSize", QVariant::ULongLong, INFO },
{ PIECE_COUNT, "pieceCount", QVariant::Int, INFO },
{ PEERS_GETTING_FROM_US, "peersGettingFromUs", QVariant::Int, STAT },
{ PEERS_SENDING_TO_US, "peersSendingToUs", QVariant::Int, STAT },
{ WEBSEEDS_SENDING_TO_US, "webseedsSendingToUs", QVariant::Int, STAT_EXTRA },
{ PERCENT_DONE, "percentDone", QVariant::Double, STAT },
{ PERCENT_VERIFIED, "recheckProgress", QVariant::Double, STAT },
{ DATE_ACTIVITY, "activityDate", QVariant::DateTime, STAT_EXTRA },
{ DATE_ADDED, "addedDate", QVariant::DateTime, INFO },
{ DATE_STARTED, "startDate", QVariant::DateTime, STAT_EXTRA },
{ DATE_CREATED, "dateCreated", QVariant::DateTime, INFO },
{ PEERS_CONNECTED, "peersConnected", QVariant::Int, STAT },
{ ETA, "eta", QVariant::Int, STAT },
{ RATIO, "ratio", QVariant::Double, STAT },
{ DOWNLOADED_EVER, "downloadedEver", QVariant::ULongLong, STAT_EXTRA },
{ UPLOADED_EVER, "uploadedEver", QVariant::ULongLong, STAT_EXTRA },
{ FAILED_EVER, "corruptEver", QVariant::ULongLong, STAT_EXTRA },
{ TRACKERS, "trackers", QVariant::StringList, INFO },
{ MIME_ICON, "ccc", QVariant::Icon, DERIVED },
{ SEED_RATIO_LIMIT, "seedRatioLimit", QVariant::Double, STAT_EXTRA },
{ SEED_RATIO_MODE, "seedRatioMode", QVariant::Int, STAT_EXTRA },
{ DOWN_LIMIT, "downloadLimit", QVariant::Int, STAT_EXTRA }, /* KB/s */
{ DOWN_LIMITED, "downloadLimited", QVariant::Bool, STAT_EXTRA },
{ UP_LIMIT, "uploadLimit", QVariant::Int, STAT_EXTRA }, /* KB/s */
{ UP_LIMITED, "uploadLimited", QVariant::Bool, STAT_EXTRA },
{ HONORS_SESSION_LIMITS, "honorsSessionLimits", QVariant::Bool, STAT_EXTRA },
{ PEER_LIMIT, "peer-limit", QVariant::Int, STAT_EXTRA },
{ HASH_STRING, "hashString", QVariant::String, INFO },
{ IS_PRIVATE, "isPrivate", QVariant::Bool, INFO },
{ COMMENT, "comment", QVariant::String, INFO },
{ CREATOR, "creator", QVariant::String, INFO },
{ LAST_ANNOUNCE_TIME, "lastAnnounceTime", QVariant::DateTime, STAT_EXTRA },
{ LAST_SCRAPE_TIME, "lastScrapeTime", QVariant::DateTime, STAT_EXTRA },
{ MANUAL_ANNOUNCE_TIME, "manualAnnounceTime", QVariant::DateTime, STAT_EXTRA },
{ NEXT_ANNOUNCE_TIME, "nextAnnounceTime", QVariant::DateTime, STAT_EXTRA },
{ NEXT_SCRAPE_TIME, "nextScrapeTime", QVariant::DateTime, STAT_EXTRA },
{ SCRAPE_RESPONSE, "scrapeResponse", QVariant::String, STAT_EXTRA },
{ ANNOUNCE_RESPONSE, "announceResponse", QVariant::String, STAT_EXTRA },
{ ANNOUNCE_URL, "announceURL", QVariant::String, STAT_EXTRA },
{ SEEDERS, "seeders", QVariant::Int, STAT_EXTRA },
{ LEECHERS, "leechers", QVariant::Int, STAT_EXTRA },
{ TIMES_COMPLETED, "timesCompleted", QVariant::Int, STAT_EXTRA },
{ PEERS, "peers", TrTypes::PeerList, STAT_EXTRA },
{ TORRENT_FILE, "torrentFile", QVariant::String, STAT_EXTRA }
};
Torrent :: KeyList
Torrent :: buildKeyList( Group group )
{
KeyList keys;
if( keys.empty( ) )
for( int i=0; i<PROPERTY_COUNT; ++i )
if( myProperties[i].id==ID || myProperties[i].group==group )
keys << myProperties[i].key;
return keys;
}
const Torrent :: KeyList&
Torrent :: getInfoKeys( )
{
static KeyList keys;
if( keys.isEmpty( ) )
keys << buildKeyList( INFO ) << "files";
return keys;
}
const Torrent :: KeyList&
Torrent :: getStatKeys( )
{
static KeyList keys( buildKeyList( STAT ) );
return keys;
}
const Torrent :: KeyList&
Torrent :: getExtraStatKeys( )
{
static KeyList keys;
if( keys.isEmpty( ) )
keys << buildKeyList( STAT_EXTRA ) << "fileStats";
return keys;
}
bool
Torrent :: setInt( int i, int value )
{
bool changed = false;
assert( 0<=i && i<PROPERTY_COUNT );
assert( myProperties[i].type == QVariant::Int );
if( myValues[i].isNull() || myValues[i].toInt()!=value )
{
myValues[i].setValue( value );
changed = true;
}
return changed;
}
bool
Torrent :: setBool( int i, bool value )
{
bool changed = false;
assert( 0<=i && i<PROPERTY_COUNT );
assert( myProperties[i].type == QVariant::Bool );
if( myValues[i].isNull() || myValues[i].toBool()!=value )
{
myValues[i].setValue( value );
changed = true;
}
return changed;
}
bool
Torrent :: setDouble( int i, double value )
{
bool changed = false;
assert( 0<=i && i<PROPERTY_COUNT );
assert( myProperties[i].type == QVariant::Double );
if( myValues[i].isNull() || myValues[i].toDouble()!=value )
{
myValues[i].setValue( value );
changed = true;
}
return changed;
}
bool
Torrent :: setDateTime( int i, const QDateTime& value )
{
bool changed = false;
assert( 0<=i && i<PROPERTY_COUNT );
assert( myProperties[i].type == QVariant::DateTime );
if( myValues[i].isNull() || myValues[i].toDateTime()!=value )
{
myValues[i].setValue( value );
changed = true;
}
return changed;
}
bool
Torrent :: setSize( int i, qulonglong value )
{
bool changed = false;
assert( 0<=i && i<PROPERTY_COUNT );
assert( myProperties[i].type == QVariant::ULongLong );
if( myValues[i].isNull() || myValues[i].toULongLong()!=value )
{
myValues[i].setValue( value );
changed = true;
}
return changed;
}
bool
Torrent :: setString( int i, const char * value )
{
bool changed = false;
assert( 0<=i && i<PROPERTY_COUNT );
assert( myProperties[i].type == QVariant::String );
if( myValues[i].isNull() || myValues[i].toString()!=value )
{
myValues[i].setValue( QString::fromUtf8( value ) );
changed = true;
}
return changed;
}
bool
Torrent :: setIcon( int i, const QIcon& value )
{
assert( 0<=i && i<PROPERTY_COUNT );
assert( myProperties[i].type == QVariant::Icon );
myValues[i].setValue( value );
return true;
}
int
Torrent :: getInt( int i ) const
{
assert( 0<=i && i<PROPERTY_COUNT );
assert( myProperties[i].type == QVariant::Int );
return myValues[i].toInt( );
}
QDateTime
Torrent :: getDateTime( int i ) const
{
assert( 0<=i && i<PROPERTY_COUNT );
assert( myProperties[i].type == QVariant::DateTime );
return myValues[i].toDateTime( );
}
bool
Torrent :: getBool( int i ) const
{
assert( 0<=i && i<PROPERTY_COUNT );
assert( myProperties[i].type == QVariant::Bool );
return myValues[i].toBool( );
}
qulonglong
Torrent :: getSize( int i ) const
{
assert( 0<=i && i<PROPERTY_COUNT );
assert( myProperties[i].type == QVariant::ULongLong );
return myValues[i].toULongLong( );
}
double
Torrent :: getDouble( int i ) const
{
assert( 0<=i && i<PROPERTY_COUNT );
assert( myProperties[i].type == QVariant::Double );
return myValues[i].toDouble( );
}
QString
Torrent :: getString( int i ) const
{
assert( 0<=i && i<PROPERTY_COUNT );
assert( myProperties[i].type == QVariant::String );
return myValues[i].toString( );
}
QIcon
Torrent :: getIcon( int i ) const
{
assert( 0<=i && i<PROPERTY_COUNT );
assert( myProperties[i].type == QVariant::Icon );
return myValues[i].value<QIcon>();
}
/***
****
***/
bool
Torrent :: getSeedRatio( double& ratio ) const
{
bool isLimited;
switch( seedRatioMode( ) )
{
case TR_RATIOLIMIT_SINGLE:
isLimited = true;
ratio = seedRatioLimit( );
break;
case TR_RATIOLIMIT_GLOBAL:
if(( isLimited = myPrefs.getBool( Prefs :: SEED_RATIO_LIMITED )))
ratio = myPrefs.getDouble( Prefs :: SEED_RATIO_LIMIT );
break;
case TR_RATIOLIMIT_UNLIMITED:
isLimited = false;
break;
}
return isLimited;
}
bool
Torrent :: hasFileSubstring( const QString& substr ) const
{
foreach( const TrFile file, myFiles )
if( file.filename.contains( substr, Qt::CaseInsensitive ) )
return true;
return false;
}
bool
Torrent :: hasTrackerSubstring( const QString& substr ) const
{
foreach( QString s, myValues[TRACKERS].toStringList() )
if( s.contains( substr, Qt::CaseInsensitive ) )
return true;
return false;
}
int
Torrent :: compareRatio( const Torrent& that ) const
{
const double a = ratio( );
const double b = that.ratio( );
if( (int)a == TR_RATIO_INF && (int)b == TR_RATIO_INF ) return 0;
if( (int)a == TR_RATIO_INF ) return 1;
if( (int)b == TR_RATIO_INF ) return -1;
if( a < b ) return -1;
if( a > b ) return 1;
return 0;
}
int
Torrent :: compareETA( const Torrent& that ) const
{
const bool haveA( hasETA( ) );
const bool haveB( that.hasETA( ) );
if( haveA && haveB ) return getETA() - that.getETA();
if( haveA ) return -1;
if( haveB ) return 1;
return 0;
}
int
Torrent :: compareTracker( const Torrent& that ) const
{
Q_UNUSED( that );
// FIXME
return 0;
}
/***
****
***/
void
Torrent :: updateMimeIcon( )
{
const FileList& files( myFiles );
QIcon icon;
if( files.size( ) > 1 )
icon = QtIconLoader :: icon( "folder", QApplication::style()->standardIcon( QStyle::SP_DirIcon ) );
else
icon = Utils :: guessMimeIcon( files.at(0).filename );
setIcon( MIME_ICON, icon );
}
/***
****
***/
void
Torrent :: notifyComplete( ) const
{
// if someone wants to implement notification, here's the hook.
}
/***
****
***/
void
Torrent :: update( tr_benc * d )
{
bool changed = false;
for( int i=0; i<PROPERTY_COUNT; ++i )
{
tr_benc * child = tr_bencDictFind( d, myProperties[i].key );
if( !child )
continue;
switch( myProperties[i].type )
{
case QVariant :: Int: {
int64_t val;
if( tr_bencGetInt( child, &val ) )
changed |= setInt( i, val );
break;
}
case QVariant :: Bool: {
tr_bool val;
if( tr_bencGetBool( child, &val ) )
changed |= setBool( i, val );
break;
}
case QVariant :: String: {
const char * val;
if( tr_bencGetStr( child, &val ) )
changed |= setString( i, val );
break;
}
case QVariant :: ULongLong: {
int64_t val;
if( tr_bencGetInt( child, &val ) )
changed |= setSize( i, val );
break;
}
case QVariant :: Double: {
double val;
if( tr_bencGetReal( child, &val ) )
changed |= setDouble( i, val );
break;
}
case QVariant :: DateTime: {
int64_t val;
if( tr_bencGetInt( child, &val ) && val )
changed |= setDateTime( i, QDateTime :: fromTime_t( val ) );
break;
}
case QVariant :: StringList:
case TrTypes :: PeerList:
break;
default:
assert( 0 && "unhandled type" );
}
}
tr_benc * files;
if( tr_bencDictFindList( d, "files", &files ) ) {
const char * str;
int64_t intVal;
int i = 0;
myFiles.clear( );
tr_benc * child;
while(( child = tr_bencListChild( files, i ))) {
TrFile file;
file.index = i++;
if( tr_bencDictFindStr( child, "name", &str ) )
file.filename = QString::fromUtf8( str );
if( tr_bencDictFindInt( child, "length", &intVal ) )
file.size = intVal;
myFiles.append( file );
}
updateMimeIcon( );
changed = true;
}
if( tr_bencDictFindList( d, "fileStats", &files ) ) {
const int n = tr_bencListSize( files );
assert( n == myFiles.size( ) );
for( int i=0; i<n; ++i ) {
int64_t intVal;
tr_bool boolVal;
tr_benc * child = tr_bencListChild( files, i );
TrFile& file( myFiles[i] );
if( tr_bencDictFindInt( child, "bytesCompleted", &intVal ) )
file.have = intVal;
if( tr_bencDictFindBool( child, "wanted", &boolVal ) )
file.wanted = boolVal;
if( tr_bencDictFindInt( child, "priority", &intVal ) )
file.priority = intVal;
}
changed = true;
}
tr_benc * trackers;
if( tr_bencDictFindList( d, "trackers", &trackers ) ) {
const char * str;
int i = 0;
QStringList list;
tr_benc * child;
while(( child = tr_bencListChild( trackers, i++ )))
if( tr_bencDictFindStr( child, "announce", &str ))
list.append( QString::fromUtf8( str ) );
if( myValues[TRACKERS] != list ) {
myValues[TRACKERS].setValue( list );
changed = true;
}
}
tr_benc * peers;
if( tr_bencDictFindList( d, "peers", &peers ) ) {
tr_benc * child;
PeerList peerList;
int childNum = 0;
while(( child = tr_bencListChild( peers, childNum++ ))) {
double d;
tr_bool b;
int64_t i;
const char * str;
Peer peer;
if( tr_bencDictFindStr( child, "address", &str ) )
peer.address = QString::fromUtf8( str );
if( tr_bencDictFindStr( child, "clientName", &str ) )
peer.clientName = QString::fromUtf8( str );
if( tr_bencDictFindBool( child, "clientIsChoked", &b ) )
peer.clientIsChoked = b;
if( tr_bencDictFindBool( child, "clientIsInterested", &b ) )
peer.clientIsInterested = b;
if( tr_bencDictFindBool( child, "isDownloadingFrom", &b ) )
peer.isDownloadingFrom = b;
if( tr_bencDictFindBool( child, "isEncrypted", &b ) )
peer.isEncrypted = b;
if( tr_bencDictFindBool( child, "isIncoming", &b ) )
peer.isIncoming = b;
if( tr_bencDictFindBool( child, "isUploadingTo", &b ) )
peer.isUploadingTo = b;
if( tr_bencDictFindBool( child, "peerIsChoked", &b ) )
peer.peerIsChoked = b;
if( tr_bencDictFindBool( child, "peerIsInterested", &b ) )
peer.peerIsInterested = b;
if( tr_bencDictFindInt( child, "port", &i ) )
peer.port = i;
if( tr_bencDictFindReal( child, "progress", &d ) )
peer.progress = d;
if( tr_bencDictFindInt( child, "rateToClient", &i ) )
peer.rateToClient = Speed::fromBps( i );
if( tr_bencDictFindInt( child, "rateToPeer", &i ) )
peer.rateToPeer = Speed::fromBps( i );
peerList << peer;
}
myValues[PEERS].setValue( peerList );
changed = true;
}
if( changed )
emit torrentChanged( id( ) );
}
QString
Torrent :: activityString( ) const
{
QString str;
switch( getActivity( ) )
{
case TR_STATUS_CHECK_WAIT: str = tr( "Waiting to verify local data" ); break;
case TR_STATUS_CHECK: str = tr( "Verifying local data" ); break;
case TR_STATUS_DOWNLOAD: str = tr( "Downloading" ); break;
case TR_STATUS_SEED: str = tr( "Seeding" ); break;
case TR_STATUS_STOPPED: str = tr( "Paused" ); break;
}
return str;
}

296
qt/torrent.h Normal file
View File

@ -0,0 +1,296 @@
/*
* This file Copyright (C) 2009 Charles Kerr <charles@transmissionbt.com>
*
* This file is licensed by the GPL version 2. Works owned by the
* Transmission project are granted a special exemption to clause 2(b)
* so that the bulk of its code can remain under the MIT license.
* This exemption does not extend to derived works not owned by
* the Transmission project.
*
* $Id:$
*/
#ifndef QTR_TORRENT_H
#define QTR_TORRENT_H
#include <QObject>
#include <QIcon>
#include <QMetaType>
#include <QDateTime>
#include <QString>
#include <QStringList>
#include <QList>
#include <QTemporaryFile>
#include <QVariant>
#include <libtransmission/transmission.h>
#include "speed.h"
#include "types.h"
extern "C"
{
struct tr_benc;
}
class Prefs;
class QStyle;
struct Peer
{
QString address;
QString clientName;
bool clientIsChoked;
bool clientIsInterested;
bool isDownloadingFrom;
bool isEncrypted;
bool isIncoming;
bool isUploadingTo;
bool peerIsChoked;
bool peerIsInterested;
int port;
double progress;
Speed rateToClient;
Speed rateToPeer;
};
typedef QList<Peer> PeerList;
Q_DECLARE_METATYPE(Peer)
Q_DECLARE_METATYPE(PeerList)
struct TrFile
{
TrFile(): index(-1), priority(0), wanted(true), size(0), have(0) { }
int index;
int priority;
bool wanted;
uint64_t size;
uint64_t have;
QString filename;
};
typedef QList<TrFile> FileList;
Q_DECLARE_METATYPE(TrFile)
Q_DECLARE_METATYPE(FileList)
class Torrent: public QObject
{
Q_OBJECT;
public:
enum
{
ID,
UPLOAD_SPEED,
DOWNLOAD_SPEED,
SWARM_SPEED,
DOWNLOAD_DIR,
ACTIVITY,
NAME,
ERROR,
SIZE_WHEN_DONE,
LEFT_UNTIL_DONE,
HAVE_UNCHECKED,
HAVE_VERIFIED,
TOTAL_SIZE,
PIECE_SIZE,
PIECE_COUNT,
PEERS_GETTING_FROM_US,
PEERS_SENDING_TO_US,
WEBSEEDS_SENDING_TO_US,
PERCENT_DONE,
PERCENT_VERIFIED,
DATE_ACTIVITY,
DATE_ADDED,
DATE_STARTED,
DATE_CREATED,
PEERS_CONNECTED,
ETA,
RATIO,
DOWNLOADED_EVER,
UPLOADED_EVER,
FAILED_EVER,
TRACKERS,
MIME_ICON,
SEED_RATIO_LIMIT,
SEED_RATIO_MODE,
DOWN_LIMIT,
DOWN_LIMITED,
UP_LIMIT,
UP_LIMITED,
HONORS_SESSION_LIMITS,
PEER_LIMIT,
HASH_STRING,
IS_PRIVATE,
COMMENT,
CREATOR,
LAST_ANNOUNCE_TIME,
LAST_SCRAPE_TIME,
MANUAL_ANNOUNCE_TIME,
NEXT_ANNOUNCE_TIME,
NEXT_SCRAPE_TIME,
SCRAPE_RESPONSE,
ANNOUNCE_RESPONSE,
ANNOUNCE_URL,
SEEDERS,
LEECHERS,
TIMES_COMPLETED,
PEERS,
TORRENT_FILE,
PROPERTY_COUNT
};
public:
Torrent( Prefs&, int id );
virtual ~Torrent( );
signals:
void torrentChanged( int id );
private:
enum Group
{
INFO, // info fields that only need to be loaded once
STAT, // commonly-used stats that should be refreshed often
STAT_EXTRA, // rarely used; only refresh if details dialog is open
DERIVED // doesn't come from RPC
};
struct Property
{
int id;
const char * key;
int type;
int group;
};
static Property myProperties[];
public:
typedef QList<const char*> KeyList;
static const KeyList& getInfoKeys( );
static const KeyList& getStatKeys( );
static const KeyList& getExtraStatKeys( );
private:
static KeyList buildKeyList( Group group );
private:
QVariant myValues[PROPERTY_COUNT];
int getInt ( int key ) const;
bool getBool ( int key ) const;
QTime getTime ( int key ) const;
QIcon getIcon ( int key ) const;
double getDouble ( int key ) const;
qulonglong getSize ( int key ) const;
QString getString ( int key ) const;
QDateTime getDateTime ( int key ) const;
bool setInt ( int key, int value );
bool setBool ( int key, bool value );
bool setIcon ( int key, const QIcon& );
bool setDouble ( int key, double );
bool setString ( int key, const char * );
bool setSize ( int key, qulonglong );
bool setDateTime ( int key, const QDateTime& );
public:
int id( ) const { return getInt( ID ); }
QString name( ) const { return getString( NAME ); }
QString creator( ) const { return getString( CREATOR ); }
QString comment( ) const { return getString( COMMENT ); }
QString getPath( ) const { return getString( DOWNLOAD_DIR ); }
QString getError( ) const { return getString( ERROR ); }
QString hashString( ) const { return getString( HASH_STRING ); }
QString scrapeResponse( ) const { return getString( SCRAPE_RESPONSE ); }
QString announceResponse( ) const { return getString( ANNOUNCE_RESPONSE ); }
QString announceUrl( ) const { return getString( ANNOUNCE_URL ); }
QString torrentFile( ) const { return getString( TORRENT_FILE ); }
bool hasError( ) const { return !getError( ).isEmpty( ); }
bool isDone( ) const { return getSize( LEFT_UNTIL_DONE ) == 0; }
bool isSeed( ) const { return haveVerified() >= getSize( TOTAL_SIZE ); }
bool isPrivate( ) const { return getBool( IS_PRIVATE ); }
bool getSeedRatio( double& setme ) const;
uint64_t haveVerified( ) const { return getSize( HAVE_VERIFIED ); }
uint64_t haveTotal( ) const { return haveVerified( ) + getSize( HAVE_UNCHECKED ); }
uint64_t sizeWhenDone( ) const { return getSize( SIZE_WHEN_DONE ); }
uint64_t leftUntilDone( ) const { return getSize( LEFT_UNTIL_DONE ); }
uint64_t pieceSize( ) const { return getSize( PIECE_SIZE ); }
int pieceCount( ) const { return getInt( PIECE_COUNT ); }
double ratio( ) const { return getDouble( RATIO ); }
double percentDone( ) const { return getDouble( PERCENT_DONE ); }
uint64_t downloadedEver( ) const { return getSize( DOWNLOADED_EVER ); }
uint64_t uploadedEver( ) const { return getSize( UPLOADED_EVER ); }
uint64_t failedEver( ) const { return getSize( FAILED_EVER ); }
int compareTracker( const Torrent& ) const;
int compareRatio( const Torrent& ) const;
int compareETA( const Torrent& ) const;
tr_bool hasETA( ) const { return getETA( ) >= 0; }
int getETA( ) const { return getInt( ETA ); }
QDateTime lastActivity( ) const { return getDateTime( DATE_ACTIVITY ); }
QDateTime lastStarted( ) const { return getDateTime( DATE_STARTED ); }
QDateTime dateAdded( ) const { return getDateTime( DATE_ADDED ); }
QDateTime dateCreated( ) const { return getDateTime( DATE_CREATED ); }
QDateTime lastAnnounceTime( ) const { return getDateTime( LAST_ANNOUNCE_TIME ); }
QDateTime lastScrapeTime( ) const { return getDateTime( LAST_SCRAPE_TIME ); }
QDateTime manualAnnounceTime( ) const { return getDateTime( MANUAL_ANNOUNCE_TIME ); }
QDateTime nextAnnounceTime( ) const { return getDateTime( NEXT_ANNOUNCE_TIME ); }
QDateTime nextScrapeTime( ) const { return getDateTime( NEXT_SCRAPE_TIME ); }
bool canManualAnnounce( ) const { return !isPaused() && (manualAnnounceTime()<=QDateTime::currentDateTime()); }
int peersWeAreDownloadingFrom( ) const { return getInt( PEERS_SENDING_TO_US ) + getInt( WEBSEEDS_SENDING_TO_US ); }
int peersWeAreUploadingTo( ) const { return getInt( PEERS_GETTING_FROM_US ); }
bool isUploading( ) const { return peersWeAreUploadingTo( ) > 0; }
int connectedPeers( ) const { return getInt( PEERS_CONNECTED ); }
int connectedPeersAndWebseeds( ) const { return connectedPeers( ) + getInt( WEBSEEDS_SENDING_TO_US ); }
Speed downloadSpeed( ) const { return Speed::fromBps( getInt( DOWNLOAD_SPEED ) ); }
Speed uploadSpeed( ) const { return Speed::fromBps( getInt( UPLOAD_SPEED ) ); }
Speed swarmSpeed( ) const { return Speed::fromKbps( getInt( SWARM_SPEED ) ); }
double getVerifyProgress( ) const { return getDouble( PERCENT_VERIFIED ); }
bool hasFileSubstring( const QString& substr ) const;
bool hasTrackerSubstring( const QString& substr ) const;
Speed uploadLimit( ) const { return Speed::fromKbps( getInt( UP_LIMIT ) ); }
Speed downloadLimit( ) const { return Speed::fromKbps( getInt( DOWN_LIMIT ) ); }
bool uploadIsLimited( ) const { return getBool( UP_LIMITED ); }
bool downloadIsLimited( ) const { return getBool( DOWN_LIMITED ); }
bool honorsSessionLimits( ) const { return getBool( HONORS_SESSION_LIMITS ); }
int peerLimit( ) const { return getInt( PEER_LIMIT ); }
double seedRatioLimit( ) const { return getDouble( SEED_RATIO_LIMIT ); }
tr_ratiolimit seedRatioMode( ) const { return (tr_ratiolimit) getInt( SEED_RATIO_MODE ); }
int seeders() const { return getInt( SEEDERS ); }
int leechers() const { return getInt( LEECHERS ); }
int timesCompleted() const { return getInt( TIMES_COMPLETED ); }
PeerList peers( ) const{ return myValues[PEERS].value<PeerList>(); }
const FileList& files( ) const { return myFiles; }
public:
QString activityString( ) const;
tr_torrent_activity getActivity( ) const { return (tr_torrent_activity) getInt( ACTIVITY ); }
bool isPaused( ) const { return getActivity( ) == TR_STATUS_STOPPED; }
bool isChecking( ) const { return getActivity( ) == TR_STATUS_CHECK || getActivity( ) == TR_STATUS_CHECK_WAIT; }
bool isDownloading( ) const { return getActivity( ) == TR_STATUS_DOWNLOAD; }
void notifyComplete( ) const;
public:
void update( tr_benc * dict );
private:
const char * getMimeTypeString( ) const;
void updateMimeIcon( );
public:
QIcon getMimeTypeIcon( ) const { return getIcon( MIME_ICON ); }
private:
Prefs& myPrefs;
FileList myFiles;
};
Q_DECLARE_METATYPE(const Torrent*)
#endif

1814
qt/transmission_en.ts Normal file

File diff suppressed because it is too large Load Diff

29
qt/types.h Normal file
View File

@ -0,0 +1,29 @@
/*
* This file Copyright (C) 2009 Charles Kerr <charles@transmissionbt.com>
*
* This file is licensed by the GPL version 2. Works owned by the
* Transmission project are granted a special exemption to clause 2(b)
* so that the bulk of its code can remain under the MIT license.
* This exemption does not extend to derived works not owned by
* the Transmission project.
*
* $Id:$
*/
#ifndef QTR_TYPES_H
#define QTR_TYPES_H
#include <QVariant>
class TrTypes
{
public:
enum
{
PeerList = QVariant::UserType,
FileList
};
};
#endif

204
qt/utils.cc Normal file
View File

@ -0,0 +1,204 @@
/*
* This file Copyright (C) 2009 Charles Kerr <charles@transmissionbt.com>
*
* This file is licensed by the GPL version 2. Works owned by the
* Transmission project are granted a special exemption to clause 2(b)
* so that the bulk of its code can remain under the MIT license.
* This exemption does not extend to derived works not owned by
* the Transmission project.
*
* $Id:$
*/
#include <iostream>
#include <QApplication>
#include <QDataStream>
#include <QFile>
#include <QFileInfo>
#include <QObject>
#include <QSet>
#include <QStyle>
#include <libtransmission/transmission.h>
#include <libtransmission/utils.h>
#include "qticonloader.h"
#include "utils.h"
#define KILOBYTE_FACTOR 1024.0
#define MEGABYTE_FACTOR ( 1024.0 * 1024.0 )
#define GIGABYTE_FACTOR ( 1024.0 * 1024.0 * 1024.0 )
QString
Utils :: sizeToString( double size )
{
QString str;
if( !size )
{
str = tr( "None" );
}
else if( size < 1024.0 )
{
const int i = (int)size;
str = tr( "%Ln byte(s)", 0, i );
}
else
{
double displayed_size;
if( size < (int64_t)MEGABYTE_FACTOR )
{
displayed_size = (double)size / KILOBYTE_FACTOR;
str = tr( "%L1 KB" ).arg( displayed_size, 0, 'f', 1 );
}
else if( size < (int64_t)GIGABYTE_FACTOR )
{
displayed_size = (double)size / MEGABYTE_FACTOR;
str = tr( "%L1 MB" ).arg( displayed_size, 0, 'f', 1 );
}
else
{
displayed_size = (double) size / GIGABYTE_FACTOR;
str = tr( "%L1 GB" ).arg( displayed_size, 0, 'f', 1 );
}
}
return str;
}
QString
Utils :: ratioToString( double ratio )
{
QString buf;
if( (int)ratio == TR_RATIO_NA )
buf = tr( "None" );
else if( (int)ratio == TR_RATIO_INF )
buf = QString::fromUtf8( "\xE2\x88\x9E" );
else if( ratio < 10.0 )
buf.sprintf( "%'.2f", ratio );
else if( ratio < 100.0 )
buf.sprintf( "%'.1f", ratio );
else
buf.sprintf( "%'.0f", ratio );
return buf;
}
QString
Utils :: timeToString( int seconds )
{
int days, hours, minutes;
QString d, h, m, s;
QString str;
if( seconds < 0 )
seconds = 0;
days = seconds / 86400;
hours = ( seconds % 86400 ) / 3600;
minutes = ( seconds % 3600 ) / 60;
seconds %= 60;
d = tr( "%Ln day(s)", 0, days );
h = tr( "%Ln hour(s)", 0, hours );
m = tr( "%Ln minute(s)", 0, minutes );
s = tr( "%Ln seconds(s)", 0, seconds );
if( days )
{
if( days >= 4 || !hours )
str = d;
else
str = tr( "%1, %2" ).arg( d ).arg( h );
}
else if( hours )
{
if( hours >= 4 || !minutes )
str = h;
else
str = tr( "%1, %2" ).arg( h ).arg( m );
}
else if( minutes )
{
if( minutes >= 4 || !seconds )
str = m;
else
str = tr( "%1, %2" ).arg( m ).arg( s );
}
else
{
str = s;
}
return str;
}
QString
Utils :: speedToString( const Speed& speed )
{
const double kbps( speed.kbps( ) );
QString str;
if( kbps < 1000.0 ) /* 0.0 KB to 999.9 KB */
str = tr( "%L1 KB/s" ).arg( kbps, 0, 'f', 1 );
else if( kbps < 102400.0 ) /* 0.98 MB to 99.99 MB */
str = tr( "%L1 MB/s" ).arg( kbps/1024.0, 0, 'f', 2 );
else // insane speeds
str = tr( "%L1 GB/s" ).arg( kbps/(1024.0*1024.0), 0, 'f', 1 );
return str;
}
void
Utils :: toStderr( const QString& str )
{
std::cerr << str.toLatin1().constData() << std::endl;
}
const QIcon&
Utils :: guessMimeIcon( const QString& filename )
{
enum { DISK, DOCUMENT, PICTURE, VIDEO, ARCHIVE, AUDIO, APP, TYPE_COUNT };
static QIcon fallback;
static QIcon fileIcons[TYPE_COUNT];
static QSet<QString> suffixes[TYPE_COUNT];
if( fileIcons[0].isNull( ) )
{
fallback = QApplication::style()->standardIcon( QStyle :: SP_FileIcon );
fileIcons[DISK]= QtIconLoader :: icon( "media-optical", fallback );
suffixes[DISK] << "iso";
fileIcons[DOCUMENT] = QtIconLoader :: icon( "text-x-generic", fallback );
suffixes[DOCUMENT] << "txt" << "doc" << "pdf" << "rtf" << "htm" << "html";
fileIcons[PICTURE] = QtIconLoader :: icon( "image-x-generic", fallback );
suffixes[PICTURE] << "jpg" << "jpeg" << "png" << "gif" << "tiff" << "pcx";
fileIcons[VIDEO] = QtIconLoader :: icon( "video-x-generic", fallback );
suffixes[VIDEO] << "avi" << "mpeg" << "mp4" << "mkv" << "mov";
fileIcons[ARCHIVE] = QtIconLoader :: icon( "package-x-generic", fallback );
suffixes[ARCHIVE] << "rar" << "zip" << "sft" << "tar" << "7z" << "cbz";
fileIcons[AUDIO] = QtIconLoader :: icon( "audio-x-generic", fallback );
suffixes[AUDIO] << "aiff" << "au" << "m3u" << "mp2" << "wav" << "mp3" << "ape" << "mid"
<< "aac" << "ogg" << "ra" << "ram" << "flac" << "mpc" << "shn";
fileIcons[APP] = QtIconLoader :: icon( "application-x-executable", fallback );
suffixes[APP] << "exe";
}
QString suffix( QFileInfo( filename ).suffix( ).toLower( ) );
for( int i=0; i<TYPE_COUNT; ++i )
if( suffixes[i].contains( suffix ) )
return fileIcons[i];
return fallback;
}

42
qt/utils.h Normal file
View File

@ -0,0 +1,42 @@
/*
* This file Copyright (C) 2009 Charles Kerr <charles@transmissionbt.com>
*
* This file is licensed by the GPL version 2. Works owned by the
* Transmission project are granted a special exemption to clause 2(b)
* so that the bulk of its code can remain under the MIT license.
* This exemption does not extend to derived works not owned by
* the Transmission project.
*
* $Id:$
*/
#ifndef QTR_UTILS
#define QTR_UTILS
#include <QString>
#include <QObject>
#include <QIcon>
#include "speed.h"
class Utils: public QObject
{
Q_OBJECT
public:
Utils( ) { }
virtual ~Utils( ) { }
public:
static QString sizeToString( double size );
static QString speedToString( const Speed& speed );
static QString ratioToString( double ratio );
static QString timeToString( int seconds );
static const QIcon& guessMimeIcon( const QString& filename );
// meh
static void toStderr( const QString& qstr );
};
#endif

133
qt/watchdir.cc Normal file
View File

@ -0,0 +1,133 @@
/*
* This file Copyright (C) 2009 Charles Kerr <charles@transmissionbt.com>
*
* This file is licensed by the GPL version 2. Works owned by the
* Transmission project are granted a special exemption to clause 2(b)
* so that the bulk of its code can remain under the MIT license.
* This exemption does not extend to derived works not owned by
* the Transmission project.
*
* $Id:$
*/
#include <iostream>
#include <QDir>
#include <QTimer>
#include <QFileSystemWatcher>
#include <libtransmission/transmission.h>
#include "prefs.h"
#include "torrent-model.h"
#include "watchdir.h"
/***
****
***/
WatchDir :: WatchDir( const TorrentModel& model ):
myModel( model ),
myWatcher( 0 )
{
}
WatchDir :: ~WatchDir( )
{
}
/***
****
***/
int
WatchDir :: metainfoTest( const QString& filename ) const
{
int ret;
tr_info inf;
tr_ctor * ctor = tr_ctorNew( 0 );
// parse
tr_ctorSetMetainfoFromFile( ctor, filename.toUtf8().constData() );
const int err = tr_torrentParse( ctor, &inf );
if( err )
ret = ERROR;
else if( myModel.hasTorrent( inf.hashString ) )
ret = DUPLICATE;
else
ret = OK;
// cleanup
if( !err )
tr_metainfoFree( &inf );
tr_ctorFree( ctor );
return ret;
}
void
WatchDir :: onTimeout( )
{
QTimer * t = qobject_cast<QTimer*>(sender());
const QString filename = t->objectName( );
if( metainfoTest( filename ) == OK )
emit torrentFileAdded( filename );
t->deleteLater( );
}
void
WatchDir :: setPath( const QString& path, bool isEnabled )
{
// clear out any remnants of the previous watcher, if any
myWatchDirFiles.clear( );
if( myWatcher ) {
delete myWatcher;
myWatcher = 0;
}
// maybe create a new watcher
if( isEnabled ) {
myWatcher = new QFileSystemWatcher( );
myWatcher->addPath( path );
connect( myWatcher, SIGNAL(directoryChanged(const QString&)), this, SLOT(watcherActivated(const QString&)));
//std::cerr << "watching " << qPrintable(path) << " for new .torrent files" << std::endl;
watcherActivated( path ); // trigger the watchdir for .torrent files in there already
}
}
void
WatchDir :: watcherActivated( const QString& path )
{
const QDir dir(path);
// get the list of files currently in the watch directory
QSet<QString> files;
foreach( QString str, dir.entryList( QDir::Readable|QDir::Files ) )
files.insert( str );
// try to add any new files which end in .torrent
const QSet<QString> newFiles( files - myWatchDirFiles );
foreach( QString name, newFiles ) {
if( name.endsWith( ".torrent", Qt::CaseInsensitive ) ) {
const QString filename = dir.absoluteFilePath( name );
switch( metainfoTest( filename ) ) {
case OK:
emit torrentFileAdded( filename );
break;
case DUPLICATE:
break;
case ERROR: {
// give the .torrent a few seconds to finish downloading
QTimer * t = new QTimer( this );
t->setObjectName( dir.absoluteFilePath( name ) );
t->setSingleShot( true );
connect( t, SIGNAL(timeout()), this, SLOT(onTimeout()));
t->start( 5000 );
}
}
}
}
// update our file list so that we can use it
// for comparison the next time around
myWatchDirFiles = files;
}

52
qt/watchdir.h Normal file
View File

@ -0,0 +1,52 @@
/*
* This file Copyright (C) 2009 Charles Kerr <charles@transmissionbt.com>
*
* This file is licensed by the GPL version 2. Works owned by the
* Transmission project are granted a special exemption to clause 2(b)
* so that the bulk of its code can remain under the MIT license.
* This exemption does not extend to derived works not owned by
* the Transmission project.
*
* $Id:$
*/
#ifndef QTR_WATCHDIR_H
#define QTR_WATCHDIR_H
#include <QObject>
#include <QSet>
#include <QString>
class TorrentModel;
class QFileSystemWatcher;
class WatchDir: public QObject
{
Q_OBJECT
public:
WatchDir( const TorrentModel& );
~WatchDir( );
public:
void setPath( const QString& path, bool isEnabled );
private:
enum { OK, DUPLICATE, ERROR };
int metainfoTest( const QString& filename ) const;
signals:
void torrentFileAdded( QString filename );
private slots:
void watcherActivated( const QString& path );
void onTimeout( );
private:
const TorrentModel& myModel;
QSet<QString> myWatchDirFiles;
QFileSystemWatcher * myWatcher;
};
#endif