(trunk) add the Qt beta into svn
|
@ -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"
|
||||
|
|
@ -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( ) );
|
||||
}
|
|
@ -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
|
|
@ -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><a href="http://www.transmissionbt.com">http://www.transmissionbt.com/</a></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>
|
|
@ -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( );
|
||||
}
|
|
@ -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
|
|
@ -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>
|
|
@ -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 ®ardless 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( ) );
|
||||
}
|
|
@ -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
|
|
@ -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( );
|
||||
}
|
|
@ -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
|
|
@ -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;
|
||||
}
|
|
@ -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
|
After Width: | Height: | Size: 34 KiB |
After Width: | Height: | Size: 841 B |
After Width: | Height: | Size: 1.2 KiB |
After Width: | Height: | Size: 1.2 KiB |
After Width: | Height: | Size: 2.0 KiB |
After Width: | Height: | Size: 2.9 KiB |
After Width: | Height: | Size: 515 B |
After Width: | Height: | Size: 2.9 KiB |
After Width: | Height: | Size: 398 B |
After Width: | Height: | Size: 456 B |
|
@ -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( );
|
||||
}
|
|
@ -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
|
|
@ -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&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>&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>&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>&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>&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>&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>&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>&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>&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>&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>&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>&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>&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>&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 &More Peers</string>
|
||||
</property>
|
||||
<property name="toolTip" >
|
||||
<string>Ask tracker for more peers</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="action_Pause" >
|
||||
<property name="text" >
|
||||
<string>&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>&Verify Local Data</string>
|
||||
</property>
|
||||
<property name="toolTip" >
|
||||
<string>Verify local data</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="action_Remove" >
|
||||
<property name="text" >
|
||||
<string>&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>&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>&Start All</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="action_PauseAll" >
|
||||
<property name="text" >
|
||||
<string>&Pause All</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="action_Quit" >
|
||||
<property name="text" >
|
||||
<string>&Quit</string>
|
||||
</property>
|
||||
<property name="shortcut" >
|
||||
<string>Ctrl+Q</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="action_SelectAll" >
|
||||
<property name="text" >
|
||||
<string>&Select All</string>
|
||||
</property>
|
||||
<property name="shortcut" >
|
||||
<string>Ctrl+A</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="action_DeselectAll" >
|
||||
<property name="text" >
|
||||
<string>&Deselect All</string>
|
||||
</property>
|
||||
<property name="shortcut" >
|
||||
<string>Ctrl+Shift+A</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="action_Preferences" >
|
||||
<property name="text" >
|
||||
<string>&Preferences</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="action_MinimalView" >
|
||||
<property name="checkable" >
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="text" >
|
||||
<string>&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>&Toolbar</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="action_Filterbar" >
|
||||
<property name="checkable" >
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="text" >
|
||||
<string>&Filterbar</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="action_Statusbar" >
|
||||
<property name="checkable" >
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="text" >
|
||||
<string>&Statusbar</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="action_SortByActivity" >
|
||||
<property name="checkable" >
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="text" >
|
||||
<string>Sort by &Activity</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="action_SortByAge" >
|
||||
<property name="checkable" >
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="text" >
|
||||
<string>Sort by A&ge</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="action_SortByETA" >
|
||||
<property name="checkable" >
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="text" >
|
||||
<string>Sort by &ETA</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="action_SortByName" >
|
||||
<property name="checkable" >
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="text" >
|
||||
<string>Sort by &Name</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="action_SortByProgress" >
|
||||
<property name="checkable" >
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="text" >
|
||||
<string>Sort by &Progress</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="action_SortByRatio" >
|
||||
<property name="checkable" >
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="text" >
|
||||
<string>Sort by &Ratio</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="action_SortBySize" >
|
||||
<property name="checkable" >
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="text" >
|
||||
<string>Sort by Si&ze</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="action_SortByState" >
|
||||
<property name="checkable" >
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="text" >
|
||||
<string>Sort by &State</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="action_SortByTracker" >
|
||||
<property name="checkable" >
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="text" >
|
||||
<string>Sort by &Tracker</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="action_ShowMessageLog" >
|
||||
<property name="checkable" >
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="text" >
|
||||
<string>Message &Log</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="action_Statistics" >
|
||||
<property name="checkable" >
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="text" >
|
||||
<string>&Statistics</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="action_Contents" >
|
||||
<property name="text" >
|
||||
<string>&Contents</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="action_About" >
|
||||
<property name="text" >
|
||||
<string>&About</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="action_ReverseSortOrder" >
|
||||
<property name="checkable" >
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="text" >
|
||||
<string>&Reverse Sort Order</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="action_FilterByName" >
|
||||
<property name="checkable" >
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="text" >
|
||||
<string>&Name</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="action_FilterByFiles" >
|
||||
<property name="checkable" >
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="text" >
|
||||
<string>&Files</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="action_FilterByTracker" >
|
||||
<property name="checkable" >
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="text" >
|
||||
<string>&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>&Main Window</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="action_TrayIcon" >
|
||||
<property name="checkable" >
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="text" >
|
||||
<string>Tray &Icon</string>
|
||||
</property>
|
||||
</action>
|
||||
</widget>
|
||||
<resources>
|
||||
<include location="application.qrc" />
|
||||
</resources>
|
||||
<connections/>
|
||||
</ui>
|
|
@ -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( );
|
||||
}
|
|
@ -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
|
|
@ -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/
|
|
@ -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( );
|
||||
}
|
|
@ -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
|
|
@ -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;
|
||||
}
|
|
@ -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
|
|
@ -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 ) );
|
||||
}
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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 );
|
||||
}
|
|
@ -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
|
||||
|
|
@ -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
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
@ -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 ) );
|
||||
}
|
|
@ -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
|
|
@ -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( );
|
||||
}
|
|
@ -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
|
|
@ -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( );
|
||||
}
|
|
@ -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
|
|
@ -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( );
|
||||
}
|
||||
|
|
@ -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
|
|
@ -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;
|
||||
}
|
|
@ -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
|
|
@ -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;
|
||||
}
|
|
@ -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
|
||||
|
|
@ -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
|
|
@ -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;
|
||||
}
|
|
@ -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
|
|
@ -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;
|
||||
}
|
|
@ -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
|