/* * 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 #include #include #include #include #include #include #include #include #include #include "propertieswidget.h" #include "TransferListWidget.h" #include "torrentPersistentData.h" #include "realprogressbar.h" #include "realprogressbarthread.h" #include "bittorrent.h" #include "PropListDelegate.h" #include "TrackersAdditionDlg.h" #include "TorrentFilesModel.h" #include "peerlistwidget.h" #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;}" PropertiesWidget::PropertiesWidget(QWidget *parent, TransferListWidget *transferList, bittorrent* BTSession): QWidget(parent), transferList(transferList), BTSession(BTSession) { setupUi(this); state = VISIBLE; // 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(addTracker_button, SIGNAL(clicked()), this, SLOT(askForTracker())); connect(removeTracker_button, SIGNAL(clicked()), this, SLOT(deleteSelectedTrackers())); connect(riseTracker_button, SIGNAL(clicked()), this, SLOT(riseSelectedTracker())); connect(lowerTracker_button, SIGNAL(clicked()), this, SLOT(lowerSelectedTracker())); 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(incrementalDownload, SIGNAL(stateChanged(int)), this, SLOT(setIncrementalDownload(int))); 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); // Peers list peersList = new PeerListWidget(this); peerpage_layout->addWidget(peersList); // Pointers init progressBarUpdater = 0; // Dynamic data refresher refreshTimer = new QTimer(this); connect(refreshTimer, SIGNAL(timeout()), this, SLOT(loadDynamicData())); refreshTimer->start(10000); // 10sec } PropertiesWidget::~PropertiesWidget() { saveSettings(); delete refreshTimer; if(progressBarUpdater) delete progressBarUpdater; 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(parentWidget()); slideSizes = hSplitter->sizes(); stackedProperties->setVisible(false); QList 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(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_lbl->clear(); incrementalDownload->setChecked(false); trackersURLS->clear(); trackerURL->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; } 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_lbl->setText(h.comment()); // Sequential download incrementalDownload->setChecked(TorrentPersistentData::isSequentialDownload(h.hash())); // Trackers loadTrackers(); // 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 files_priority = loadFilesPriorities(); PropListModel->updateFilesPriorities(files_priority); // Expand first item if possible filesList->expand(PropListModel->index(0, 0)); } catch(invalid_handle e) { } // Load dynamic data loadDynamicData(); } 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; isetColumnWidth(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(parentWidget()); hSplitter->setSizes(slideSizes); } if(!settings.value("TorrentProperties/Visible", false).toBool()) { reduce(); } else { main_infos_button->setStyleSheet(SELECTED_BUTTON_CSS); //setEnabled(false); } } void PropertiesWidget::saveSettings() { QSettings settings(QString::fromUtf8("qBittorrent"), QString::fromUtf8("qBittorrent")); settings.setValue("TorrentProperties/Visible", state==VISIBLE); QVariantList contentColsWidths; for(int i=0; icolumnCount(); ++i) { contentColsWidths.append(filesList->columnWidth(i)); } settings.setValue(QString::fromUtf8("TorrentProperties/filesColsWidth"), contentColsWidths); // Splitter sizes QSplitter *hSplitter = static_cast(parentWidget()); QList 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())); } } void PropertiesWidget::reloadPreferences() { // Take program preferences into consideration peersList->updatePeerHostNameResolutionState(); peersList->updatePeerCountryResolutionState(); } 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() == PEERS_TAB) { // Load peers peersList->loadPeers(h); return; } if(stackedProperties->currentIndex() == FILES_TAB) { // Files progress std::vector fp; h.file_progress(fp); PropListModel->updateFilesProgress(fp); } } catch(invalid_handle e) {} } void PropertiesWidget::setIncrementalDownload(int checkboxState) { if(!h.is_valid()) return; h.set_sequential_download(checkboxState == Qt::Checked); TorrentPersistentData::saveSequentialStatus(h); } void PropertiesWidget::loadTrackers() { if(!h.is_valid()) return; //Trackers std::vector trackers = h.trackers(); trackersURLS->clear(); QHash errors = BTSession->getTrackersErrors(h.hash()); unsigned int nbTrackers = trackers.size(); for(unsigned int i=0; isetForeground(QBrush(QColor("red"))); // Set tooltip QString msg=""; unsigned int i=0; foreach(QString word, errors[current_tracker].split(" ")) { if(i > 0 && i%5!=1) msg += " "; msg += word; if(i> 0 && i%5==0) msg += "\n"; ++i; } item->setToolTip(msg); } else { item->setForeground(QBrush(QColor("green"))); } } QString tracker = h.current_tracker().trimmed(); if(!tracker.isEmpty()){ trackerURL->setText(tracker); }else{ trackerURL->setText(tr("None - Unreachable?")); } } 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 */ QPushButton* PropertiesWidget::getButtonFromIndex(int index) { switch(index) { case TRACKERS_TAB: return trackers_button; case PEERS_TAB: return peers_button; case URLSEEDS_TAB: return url_seeds_button; case FILES_TAB: return files_button; 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); } } 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); } } 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); } } 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); } } std::vector PropertiesWidget::loadFilesPriorities(){ std::vector fp; QVariantList files_priority = TorrentPersistentData::getFilesPriority(h.hash()); if(files_priority.empty()) { for(int i=0; i 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)); 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)); 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)); 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)); 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(); } // Ask the user for a new tracker // and add it to the download list // if it is not already in it void PropertiesWidget::askForTracker(){ TrackersAddDlg *dlg = new TrackersAddDlg(this); connect(dlg, SIGNAL(TrackersToAdd(QStringList)), this, SLOT(addTrackerList(QStringList))); } void PropertiesWidget::addTrackerList(QStringList myTrackers) { // Add the trackers to the list std::vector trackers = h.trackers(); foreach(const QString& tracker, myTrackers) { announce_entry new_tracker(misc::toString(tracker.trimmed().toLocal8Bit().data())); new_tracker.tier = 0; // Will be fixed a bit later trackers.push_back(new_tracker); misc::fixTrackersTiers(trackers); } h.replace_trackers(trackers); h.force_reannounce(); // Reload Trackers loadTrackers(); BTSession->saveTrackerFile(h.hash()); } void PropertiesWidget::deleteSelectedUrlSeeds(){ QList 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(); } } void PropertiesWidget::deleteSelectedTrackers(){ QList selectedItems = trackersURLS->selectedItems(); if(!selectedItems.size()) return; std::vector trackers = h.trackers(); unsigned int nbTrackers = trackers.size(); if(nbTrackers == (unsigned int) selectedItems.size()){ QMessageBox::warning(this, tr("qBittorrent"), tr("Trackers list can't be empty."), QMessageBox::Ok); return; } foreach(QListWidgetItem *item, selectedItems){ QString url = item->text(); for(unsigned int i=0; isaveTrackerFile(h.hash()); } void PropertiesWidget::riseSelectedTracker(){ unsigned int i = 0; std::vector trackers = h.trackers(); QList selectedItems = trackersURLS->selectedItems(); bool change = false; unsigned int nbTrackers = trackers.size(); foreach(QListWidgetItem *item, selectedItems){ QString url = item->text(); for(i=0; i 0){ announce_entry tmp = trackers[i]; trackers[i] = trackers[i-1]; trackers[i-1] = tmp; change = true; } break; } } } if(change){ misc::fixTrackersTiers(trackers); h.replace_trackers(trackers); h.force_reannounce(); // Reload Trackers loadTrackers(); trackersURLS->item(i-1)->setSelected(true); BTSession->saveTrackerFile(h.hash()); } } void PropertiesWidget::lowerSelectedTracker(){ unsigned int i = 0; std::vector trackers = h.trackers(); QList selectedItems = trackersURLS->selectedItems(); bool change = false; unsigned int nbTrackers = trackers.size(); foreach(QListWidgetItem *item, selectedItems){ QString url = item->text(); for(i=0; iitem(i+1)->setSelected(true); BTSession->saveTrackerFile(h.hash()); } } bool PropertiesWidget::savePiecesPriorities() { qDebug("Saving pieces priorities"); std::vector priorities = PropListModel->getFilesPriorities(h.get_torrent_info().num_files()); h.prioritize_files(priorities); 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() { if(h.is_valid()) { savePiecesPriorities(); transferList->updateTorrentSizeAndProgress(h.hash()); } }