qBittorrent/src/propertieswidget.cpp

596 lines
20 KiB
C++
Raw Normal View History

2009-11-08 17:20:58 +03:00
/*
* Bittorrent Client using Qt4 and libtorrent.
* Copyright (C) 2006 Christophe Dumez
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* In addition, as a special exception, the copyright holders give permission to
* link this program with the OpenSSL project's "OpenSSL" library (or with
* modified versions of it that use the same license as the "OpenSSL" library),
* and distribute the linked executables. You must obey the GNU General Public
* License in all respects for all of the code used other than "OpenSSL". If you
* modify file(s), you may extend this exception to your version of the file(s),
* but you are not obligated to do so. If you do not wish to do so, delete this
* exception statement from your version.
*
* Contact : chris@qbittorrent.org
*/
#include <QTimer>
#include <QListWidgetItem>
#include <QVBoxLayout>
#include <QStackedWidget>
#include <QSplitter>
#include <QAction>
#include <QMessageBox>
#include <QMenu>
#include <QFileDialog>
#include <QInputDialog>
2009-11-08 17:20:58 +03:00
#include "propertieswidget.h"
#include "TransferListWidget.h"
#include "torrentPersistentData.h"
#include "realprogressbar.h"
#include "realprogressbarthread.h"
#include "bittorrent.h"
#include "PropListDelegate.h"
#include "TorrentFilesModel.h"
2009-11-14 13:37:45 +03:00
#include "peerlistwidget.h"
#include "trackerlist.h"
2009-11-08 17:20:58 +03:00
#ifdef Q_WS_MAC
#define DEFAULT_BUTTON_CSS ""
#define SELECTED_BUTTON_CSS "background-color: rgb(255, 208, 105);}"
#else
#define DEFAULT_BUTTON_CSS "QPushButton {border: 1px solid rgb(85, 81, 91);border-radius: 3px;padding: 2px; margin-left: 3px; margin-right: 3px;}"
#define SELECTED_BUTTON_CSS "QPushButton {border: 1px solid rgb(85, 81, 91);border-radius: 3px;padding: 2px;background-color: rgb(255, 208, 105); margin-left: 3px; margin-right: 3px;}"
#endif
2009-11-08 19:39:17 +03:00
PropertiesWidget::PropertiesWidget(QWidget *parent, TransferListWidget *transferList, bittorrent* BTSession): QWidget(parent), transferList(transferList), BTSession(BTSession) {
2009-11-08 17:20:58 +03:00
setupUi(this);
state = VISIBLE;
// Buttons stylesheet
trackers_button->setStyleSheet(DEFAULT_BUTTON_CSS);
peers_button->setStyleSheet(DEFAULT_BUTTON_CSS);
url_seeds_button->setStyleSheet(DEFAULT_BUTTON_CSS);
files_button->setStyleSheet(DEFAULT_BUTTON_CSS);
main_infos_button->setStyleSheet(DEFAULT_BUTTON_CSS);
main_infos_button->setShortcut(QKeySequence(QString::fromUtf8("Alt+P")));
// Set Properties list model
PropListModel = new TorrentFilesModel();
filesList->setModel(PropListModel);
PropDelegate = new PropListDelegate(0);
filesList->setItemDelegate(PropDelegate);
// QActions
actionIgnored = new QAction(tr("Ignored"), this);
actionNormal = new QAction(tr("Normal"), this);
actionMaximum = new QAction(tr("Maximum"), this);
actionHigh = new QAction(tr("High"), this);
// SIGNAL/SLOTS
connect(filesList, SIGNAL(clicked(const QModelIndex&)), filesList, SLOT(edit(const QModelIndex&)));
connect(collapseAllButton, SIGNAL(clicked()), filesList, SLOT(collapseAll()));
connect(expandAllButton, SIGNAL(clicked()), filesList, SLOT(expandAll()));
connect(filesList, SIGNAL(customContextMenuRequested(const QPoint&)), this, SLOT(displayFilesListMenu(const QPoint&)));
connect(actionIgnored, SIGNAL(triggered()), this, SLOT(ignoreSelection()));
connect(actionNormal, SIGNAL(triggered()), this, SLOT(normalSelection()));
connect(actionHigh, SIGNAL(triggered()), this, SLOT(highSelection()));
connect(actionMaximum, SIGNAL(triggered()), this, SLOT(maximumSelection()));
connect(addWS_button, SIGNAL(clicked()), this, SLOT(askWebSeed()));
connect(deleteWS_button, SIGNAL(clicked()), this, SLOT(deleteSelectedUrlSeeds()));
connect(transferList, SIGNAL(currentTorrentChanged(QTorrentHandle&)), this, SLOT(loadTorrentInfos(QTorrentHandle &)));
connect(PropDelegate, SIGNAL(filteredFilesChanged()), this, SLOT(filteredFilesChanged()));
connect(stackedProperties, SIGNAL(currentChanged(int)), this, SLOT(loadDynamicData()));
// Downloaded pieces progress bar
progressBar = new RealProgressBar(this);
progressBar->setForegroundColor(Qt::blue);
ProgressHLayout->insertWidget(1, progressBar);
// Tracker list
trackerList = new TrackerList(this);
verticalLayout_trackers->addWidget(trackerList);
2009-11-14 13:37:45 +03:00
// Peers list
peersList = new PeerListWidget(this);
2009-11-14 13:37:45 +03:00
peerpage_layout->addWidget(peersList);
// Pointers init
progressBarUpdater = 0;
// Dynamic data refresher
refreshTimer = new QTimer(this);
connect(refreshTimer, SIGNAL(timeout()), this, SLOT(loadDynamicData()));
refreshTimer->start(3000); // 3sec
2009-11-08 17:20:58 +03:00
}
PropertiesWidget::~PropertiesWidget() {
2009-11-09 22:30:13 +03:00
saveSettings();
delete refreshTimer;
if(progressBarUpdater)
delete progressBarUpdater;
delete trackerList;
2009-11-14 13:37:45 +03:00
delete peersList;
delete progressBar;
delete PropListModel;
delete PropDelegate;
// Delete QActions
delete actionIgnored;
delete actionNormal;
delete actionMaximum;
delete actionHigh;
}
void PropertiesWidget::reduce() {
if(state == VISIBLE) {
QSplitter *hSplitter = static_cast<QSplitter*>(parentWidget());
slideSizes = hSplitter->sizes();
stackedProperties->setVisible(false);
QList<int> sizes;
sizes << hSplitter->geometry().height()-30 << 30;
hSplitter->setSizes(sizes);
hSplitter->handle(1)->setVisible(false);
hSplitter->handle(1)->setDisabled(true);
state = REDUCED;
}
}
void PropertiesWidget::slide() {
if(state == REDUCED) {
stackedProperties->setVisible(true);
QSplitter *hSplitter = static_cast<QSplitter*>(parentWidget());
hSplitter->handle(1)->setDisabled(false);
hSplitter->handle(1)->setVisible(true);
hSplitter->setSizes(slideSizes);
state = VISIBLE;
// Force refresh
loadDynamicData();
}
}
void PropertiesWidget::clear() {
qDebug("Clearing torrent properties");
save_path->clear();
lbl_creationDate->clear();
hash_lbl->clear();
comment_text->clear();
trackerList->clear();
progressBar->setProgress(QRealArray());
wasted->clear();
upTotal->clear();
dlTotal->clear();
peersList->clear();
lbl_uplimit->clear();
lbl_dllimit->clear();
lbl_elapsed->clear();
lbl_connections->clear();
shareRatio->clear();
listWebSeeds->clear();
PropListModel->clear();
setEnabled(false);
}
const QTorrentHandle& PropertiesWidget::getCurrentTorrent() const {
return h;
}
bittorrent* PropertiesWidget::getBTSession() const {
return BTSession;
}
void PropertiesWidget::loadTorrentInfos(QTorrentHandle &_h) {
h = _h;
if(!h.is_valid()) {
clear();
return;
}
setEnabled(true);
if(progressBarUpdater) {
delete progressBarUpdater;
progressBarUpdater = 0;
}
try {
// Save path
save_path->setText(TorrentPersistentData::getSavePath(h.hash()));
// Creation date
lbl_creationDate->setText(h.creation_date());
// Hash
hash_lbl->setText(h.hash());
// Comment
comment_text->setHtml(h.comment());
// URL seeds
loadUrlSeeds();
// downloaded pieces updater
progressBarUpdater = new RealProgressBarThread(progressBar, h);
progressBarUpdater->start();
// List files in torrent
PropListModel->clear();
PropListModel->setupModelData(h.get_torrent_info());
std::vector<int> files_priority = loadFilesPriorities();
PropListModel->updateFilesPriorities(files_priority);
// Expand first item if possible
2009-11-11 15:30:46 +03:00
filesList->expand(PropListModel->index(0, 0));
} catch(invalid_handle e) {
}
// Load dynamic data
loadDynamicData();
}
2009-11-08 17:20:58 +03:00
2009-11-09 22:30:13 +03:00
void PropertiesWidget::readSettings() {
QSettings settings(QString::fromUtf8("qBittorrent"), QString::fromUtf8("qBittorrent"));
QVariantList contentColsWidths = settings.value(QString::fromUtf8("TorrentProperties/filesColsWidth"), QVariantList()).toList();
if(contentColsWidths.empty()) {
filesList->header()->resizeSection(0, filesList->width()/2.);
} else {
for(int i=0; i<contentColsWidths.size(); ++i) {
filesList->setColumnWidth(i, contentColsWidths.at(i).toInt());
}
}
// Restore splitter sizes
QStringList sizes_str = settings.value(QString::fromUtf8("TorrentProperties/SplitterSizes"), QString()).toString().split(",");
if(sizes_str.size() == 2) {
slideSizes << sizes_str.first().toInt();
slideSizes << sizes_str.last().toInt();
QSplitter *hSplitter = static_cast<QSplitter*>(parentWidget());
hSplitter->setSizes(slideSizes);
}
if(!settings.value("TorrentProperties/Visible", false).toBool()) {
reduce();
} else {
main_infos_button->setStyleSheet(SELECTED_BUTTON_CSS);
//setEnabled(false);
}
2009-11-09 22:30:13 +03:00
}
void PropertiesWidget::saveSettings() {
QSettings settings(QString::fromUtf8("qBittorrent"), QString::fromUtf8("qBittorrent"));
settings.setValue("TorrentProperties/Visible", state==VISIBLE);
QVariantList contentColsWidths;
for(int i=0; i<PropListModel->columnCount(); ++i) {
2009-11-09 22:30:13 +03:00
contentColsWidths.append(filesList->columnWidth(i));
}
settings.setValue(QString::fromUtf8("TorrentProperties/filesColsWidth"), contentColsWidths);
// Splitter sizes
QSplitter *hSplitter = static_cast<QSplitter*>(parentWidget());
QList<int> sizes;
if(state == VISIBLE)
sizes = hSplitter->sizes();
else
sizes = slideSizes;
if(state == VISIBLE)
qDebug("Visible");
qDebug("Sizes: %d", sizes.size());
if(sizes.size() == 2) {
settings.setValue(QString::fromUtf8("TorrentProperties/SplitterSizes"), QString::number(sizes.first())+','+QString::number(sizes.last()));
}
2009-11-09 22:30:13 +03:00
}
void PropertiesWidget::reloadPreferences() {
// Take program preferences into consideration
peersList->updatePeerHostNameResolutionState();
peersList->updatePeerCountryResolutionState();
2009-11-12 22:44:47 +03:00
}
void PropertiesWidget::loadDynamicData() {
if(!h.is_valid()) return;
if(state != VISIBLE) return;
try {
// Transfer infos
if(stackedProperties->currentIndex() == MAIN_TAB) {
wasted->setText(misc::friendlyUnit(h.total_failed_bytes()+h.total_redundant_bytes()));
upTotal->setText(misc::friendlyUnit(h.all_time_upload()) + " ("+misc::friendlyUnit(h.total_payload_upload())+" "+tr("this session")+")");
dlTotal->setText(misc::friendlyUnit(h.all_time_download()) + " ("+misc::friendlyUnit(h.total_payload_download())+" "+tr("this session")+")");
lbl_uplimit->setText(misc::friendlyUnit(h.upload_limit()));
lbl_dllimit->setText(misc::friendlyUnit(h.download_limit()));
QString elapsed_txt = misc::userFriendlyDuration(h.active_time());
if(h.is_seed()) {
elapsed_txt += " ("+tr("Seeding for %1", "e.g. Seeding for 3m10s").arg(misc::userFriendlyDuration(h.seeding_time()))+")";
}
lbl_elapsed->setText(elapsed_txt);
lbl_connections->setText(QString::number(h.num_connections())+" ("+tr("%1 max", "e.g. 10 max").arg(QString::number(h.connections_limit()))+")");
// Update ratio info
float ratio;
if(h.total_payload_download() == 0){
if(h.total_payload_upload() == 0)
ratio = 1.;
else
ratio = 10.; // Max ratio
}else{
ratio = (double)h.total_payload_upload()/(double)h.total_payload_download();
if(ratio > 10.){
ratio = 10.;
}
}
shareRatio->setText(QString(QByteArray::number(ratio, 'f', 1)));
// Downloaded pieces
if(progressBarUpdater)
progressBarUpdater->refresh();
// Progress
progress_lbl->setText(QString::number(h.progress()*100., 'f', 1)+"%");
return;
}
if(stackedProperties->currentIndex() == TRACKERS_TAB) {
// Trackers
trackerList->loadTrackers();
return;
}
if(stackedProperties->currentIndex() == PEERS_TAB) {
// Load peers
peersList->loadPeers(h);
return;
}
if(stackedProperties->currentIndex() == FILES_TAB) {
// Files progress
std::vector<size_type> fp;
h.file_progress(fp);
PropListModel->updateFilesProgress(fp);
}
} catch(invalid_handle e) {}
2009-11-08 17:20:58 +03:00
}
void PropertiesWidget::loadUrlSeeds(){
QStringList already_added;
listWebSeeds->clear();
QVariantList url_seeds = TorrentPersistentData::getUrlSeeds(h.hash());
foreach(const QVariant &var_url_seed, url_seeds){
QString url_seed = var_url_seed.toString();
if(!url_seed.isEmpty()) {
new QListWidgetItem(url_seed, listWebSeeds);
already_added << url_seed;
}
}
// Load the hard-coded url seeds
QStringList hc_seeds = h.url_seeds();
// Add hard coded url seeds
foreach(const QString &hc_seed, hc_seeds){
if(!already_added.contains(hc_seed)){
new QListWidgetItem(hc_seed, listWebSeeds);
}
}
}
/* Tab buttons */
2009-11-08 19:39:17 +03:00
QPushButton* PropertiesWidget::getButtonFromIndex(int index) {
switch(index) {
2009-11-08 19:48:38 +03:00
case TRACKERS_TAB:
2009-11-08 19:39:17 +03:00
return trackers_button;
2009-11-12 22:44:47 +03:00
case PEERS_TAB:
return peers_button;
2009-11-08 19:48:38 +03:00
case URLSEEDS_TAB:
return url_seeds_button;
case FILES_TAB:
return files_button;
2009-11-08 19:39:17 +03:00
default:
return main_infos_button;
}
}
void PropertiesWidget::on_main_infos_button_clicked() {
if(state == VISIBLE && stackedProperties->currentIndex() == MAIN_TAB) {
reduce();
} else {
slide();
getButtonFromIndex(stackedProperties->currentIndex())->setStyleSheet(DEFAULT_BUTTON_CSS);
stackedProperties->setCurrentIndex(MAIN_TAB);
main_infos_button->setStyleSheet(SELECTED_BUTTON_CSS);
}
}
void PropertiesWidget::on_trackers_button_clicked() {
if(state == VISIBLE && stackedProperties->currentIndex() == TRACKERS_TAB) {
reduce();
} else {
slide();
getButtonFromIndex(stackedProperties->currentIndex())->setStyleSheet(DEFAULT_BUTTON_CSS);
stackedProperties->setCurrentIndex(TRACKERS_TAB);
trackers_button->setStyleSheet(SELECTED_BUTTON_CSS);
}
}
2009-11-08 19:48:38 +03:00
2009-11-12 22:44:47 +03:00
void PropertiesWidget::on_peers_button_clicked() {
if(state == VISIBLE && stackedProperties->currentIndex() == PEERS_TAB) {
reduce();
} else {
slide();
getButtonFromIndex(stackedProperties->currentIndex())->setStyleSheet(DEFAULT_BUTTON_CSS);
stackedProperties->setCurrentIndex(PEERS_TAB);
peers_button->setStyleSheet(SELECTED_BUTTON_CSS);
}
}
2009-11-08 19:48:38 +03:00
void PropertiesWidget::on_url_seeds_button_clicked() {
if(state == VISIBLE && stackedProperties->currentIndex() == URLSEEDS_TAB) {
reduce();
} else {
slide();
getButtonFromIndex(stackedProperties->currentIndex())->setStyleSheet(DEFAULT_BUTTON_CSS);
stackedProperties->setCurrentIndex(URLSEEDS_TAB);
url_seeds_button->setStyleSheet(SELECTED_BUTTON_CSS);
}
2009-11-08 19:48:38 +03:00
}
void PropertiesWidget::on_files_button_clicked() {
if(state == VISIBLE && stackedProperties->currentIndex() == FILES_TAB) {
reduce();
} else {
slide();
getButtonFromIndex(stackedProperties->currentIndex())->setStyleSheet(DEFAULT_BUTTON_CSS);
stackedProperties->setCurrentIndex(FILES_TAB);
files_button->setStyleSheet(SELECTED_BUTTON_CSS);
}
2009-11-08 19:48:38 +03:00
}
std::vector<int> PropertiesWidget::loadFilesPriorities(){
std::vector<int> fp;
QVariantList files_priority = TorrentPersistentData::getFilesPriority(h.hash());
if(files_priority.empty()) {
for(int i=0; i<h.num_files(); ++i) {
fp.push_back(1);
}
} else {
foreach(const QVariant &var_prio, files_priority) {
int priority = var_prio.toInt();
if( priority < 0 || priority > 7){
// Normal priority as default
priority = 1;
}
fp.push_back(priority);
}
}
return fp;
}
void PropertiesWidget::displayFilesListMenu(const QPoint&){
//if(h.get_torrent_info().num_files() == 1) return;
QMenu myFilesLlistMenu(this);
//QModelIndex index;
// Enable/disable pause/start action given the DL state
//QModelIndexList selectedIndexes = filesList->selectionModel()->selectedIndexes();
myFilesLlistMenu.setTitle(tr("Priority"));
myFilesLlistMenu.addAction(actionIgnored);
myFilesLlistMenu.addAction(actionNormal);
myFilesLlistMenu.addAction(actionHigh);
myFilesLlistMenu.addAction(actionMaximum);
// Call menu
myFilesLlistMenu.exec(QCursor::pos());
}
void PropertiesWidget::ignoreSelection(){
QModelIndexList selectedIndexes = filesList->selectionModel()->selectedIndexes();
foreach(const QModelIndex &index, selectedIndexes){
if(index.column() == PRIORITY){
if(PropListModel->data(index) != QVariant(IGNORED)){
PropListModel->setData(index, QVariant(IGNORED));
2009-11-09 21:01:54 +03:00
filteredFilesChanged();
}
}
}
}
void PropertiesWidget::normalSelection(){
QModelIndexList selectedIndexes = filesList->selectionModel()->selectedIndexes();
foreach(const QModelIndex &index, selectedIndexes){
if(index.column() == PRIORITY){
if(PropListModel->data(index) != QVariant(NORMAL)){
PropListModel->setData(index, QVariant(NORMAL));
2009-11-09 21:01:54 +03:00
filteredFilesChanged();
}
}
}
}
void PropertiesWidget::highSelection(){
QModelIndexList selectedIndexes = filesList->selectionModel()->selectedIndexes();
foreach(const QModelIndex &index, selectedIndexes){
if(index.column() == PRIORITY){
if(PropListModel->data(index) != QVariant(HIGH)){
PropListModel->setData(index, QVariant(HIGH));
2009-11-09 21:01:54 +03:00
filteredFilesChanged();
}
}
}
}
void PropertiesWidget::maximumSelection(){
QModelIndexList selectedIndexes = filesList->selectionModel()->selectedIndexes();
foreach(const QModelIndex &index, selectedIndexes){
if(index.column() == PRIORITY){
if(PropListModel->data(index) != QVariant(MAXIMUM)){
PropListModel->setData(index, QVariant(MAXIMUM));
2009-11-09 21:01:54 +03:00
filteredFilesChanged();
}
}
}
}
void PropertiesWidget::askWebSeed(){
bool ok;
// Ask user for a new url seed
QString url_seed = QInputDialog::getText(this, tr("New url seed", "New HTTP source"),
tr("New url seed:"), QLineEdit::Normal,
QString::fromUtf8("http://www."), &ok);
if(!ok) return;
qDebug("Adding %s web seed", url_seed.toLocal8Bit().data());
if(!listWebSeeds->findItems(url_seed, Qt::MatchFixedString).empty()) {
QMessageBox::warning(this, tr("qBittorrent"),
tr("This url seed is already in the list."),
QMessageBox::Ok);
return;
}
h.add_url_seed(url_seed);
TorrentPersistentData::saveUrlSeeds(h);
// Refresh the seeds list
loadUrlSeeds();
}
void PropertiesWidget::deleteSelectedUrlSeeds(){
QList<QListWidgetItem *> selectedItems = listWebSeeds->selectedItems();
bool change = false;
foreach(QListWidgetItem *item, selectedItems){
QString url_seed = item->text();
h.remove_url_seed(url_seed);
change = true;
}
if(change){
// Save them to disk
TorrentPersistentData::saveUrlSeeds(h);
// Refresh list
loadUrlSeeds();
}
}
2009-11-09 21:01:54 +03:00
bool PropertiesWidget::savePiecesPriorities() {
qDebug("Saving pieces priorities");
std::vector<int> priorities = PropListModel->getFilesPriorities(h.get_torrent_info().num_files());
h.prioritize_files(priorities);
2009-11-09 21:01:54 +03:00
TorrentPersistentData::saveFilesPriority(h);
return true;
}
void PropertiesWidget::on_changeSavePathButton_clicked() {
QString dir;
QDir saveDir(h.save_path());
if(saveDir.exists()){
dir = QFileDialog::getExistingDirectory(this, tr("Choose save path"), h.save_path());
}else{
dir = QFileDialog::getExistingDirectory(this, tr("Choose save path"), QDir::homePath());
}
if(!dir.isNull()){
// Check if savePath exists
QDir savePath(dir);
if(!savePath.exists()){
if(!savePath.mkpath(savePath.path())){
QMessageBox::critical(0, tr("Save path creation error"), tr("Could not create the save path"));
return;
}
}
// Save savepath
TorrentPersistentData::saveSavePath(h.hash(), savePath.path());
// Actually move storage
if(!BTSession->useTemporaryFolder() || h.is_seed())
h.move_storage(savePath.path());
// Update save_path in dialog
save_path->setText(savePath.path());
}
}
void PropertiesWidget::filteredFilesChanged() {
2009-11-09 21:01:54 +03:00
if(h.is_valid()) {
savePiecesPriorities();
transferList->updateTorrentSizeAndProgress(h.hash());
2009-11-09 21:01:54 +03:00
}
}