examples/network/torrent/mainwindow.cpp
changeset 0 1918ee327afb
child 3 41300fa6a67c
equal deleted inserted replaced
-1:000000000000 0:1918ee327afb
       
     1 /****************************************************************************
       
     2 **
       
     3 ** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies).
       
     4 ** All rights reserved.
       
     5 ** Contact: Nokia Corporation (qt-info@nokia.com)
       
     6 **
       
     7 ** This file is part of the examples of the Qt Toolkit.
       
     8 **
       
     9 ** $QT_BEGIN_LICENSE:LGPL$
       
    10 ** No Commercial Usage
       
    11 ** This file contains pre-release code and may not be distributed.
       
    12 ** You may use this file in accordance with the terms and conditions
       
    13 ** contained in the Technology Preview License Agreement accompanying
       
    14 ** this package.
       
    15 **
       
    16 ** GNU Lesser General Public License Usage
       
    17 ** Alternatively, this file may be used under the terms of the GNU Lesser
       
    18 ** General Public License version 2.1 as published by the Free Software
       
    19 ** Foundation and appearing in the file LICENSE.LGPL included in the
       
    20 ** packaging of this file.  Please review the following information to
       
    21 ** ensure the GNU Lesser General Public License version 2.1 requirements
       
    22 ** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
       
    23 **
       
    24 ** In addition, as a special exception, Nokia gives you certain additional
       
    25 ** rights.  These rights are described in the Nokia Qt LGPL Exception
       
    26 ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
       
    27 **
       
    28 ** If you have questions regarding the use of this file, please contact
       
    29 ** Nokia at qt-info@nokia.com.
       
    30 **
       
    31 **
       
    32 **
       
    33 **
       
    34 **
       
    35 **
       
    36 **
       
    37 **
       
    38 ** $QT_END_LICENSE$
       
    39 **
       
    40 ****************************************************************************/
       
    41 
       
    42 #include <QtGui>
       
    43 
       
    44 #include "addtorrentdialog.h"
       
    45 #include "mainwindow.h"
       
    46 #include "ratecontroller.h"
       
    47 #include "torrentclient.h"
       
    48 
       
    49 // TorrentView extends QTreeWidget to allow drag and drop.
       
    50 class TorrentView : public QTreeWidget
       
    51 {
       
    52     Q_OBJECT
       
    53 public:
       
    54     TorrentView(QWidget *parent = 0);
       
    55 
       
    56 signals:
       
    57     void fileDropped(const QString &fileName);
       
    58 
       
    59 protected:
       
    60     void dragMoveEvent(QDragMoveEvent *event);
       
    61     void dropEvent(QDropEvent *event);
       
    62 };
       
    63 
       
    64 // TorrentViewDelegate is used to draw the progress bars.
       
    65 class TorrentViewDelegate : public QItemDelegate
       
    66 {
       
    67     Q_OBJECT
       
    68 public:
       
    69     inline TorrentViewDelegate(MainWindow *mainWindow) : QItemDelegate(mainWindow) {}
       
    70 
       
    71     inline void paint(QPainter *painter, const QStyleOptionViewItem &option,
       
    72                       const QModelIndex &index ) const
       
    73     {
       
    74         if (index.column() != 2) {
       
    75             QItemDelegate::paint(painter, option, index);
       
    76             return;
       
    77         }
       
    78 
       
    79         // Set up a QStyleOptionProgressBar to precisely mimic the
       
    80         // environment of a progress bar.
       
    81         QStyleOptionProgressBar progressBarOption;
       
    82         progressBarOption.state = QStyle::State_Enabled;
       
    83         progressBarOption.direction = QApplication::layoutDirection();
       
    84         progressBarOption.rect = option.rect;
       
    85         progressBarOption.fontMetrics = QApplication::fontMetrics();
       
    86         progressBarOption.minimum = 0;
       
    87         progressBarOption.maximum = 100;
       
    88         progressBarOption.textAlignment = Qt::AlignCenter;
       
    89         progressBarOption.textVisible = true;
       
    90 
       
    91         // Set the progress and text values of the style option.
       
    92         int progress = qobject_cast<MainWindow *>(parent())->clientForRow(index.row())->progress();
       
    93         progressBarOption.progress = progress < 0 ? 0 : progress;
       
    94         progressBarOption.text = QString().sprintf("%d%%", progressBarOption.progress);
       
    95 
       
    96         // Draw the progress bar onto the view.
       
    97         QApplication::style()->drawControl(QStyle::CE_ProgressBar, &progressBarOption, painter);
       
    98     }
       
    99 };
       
   100 
       
   101 MainWindow::MainWindow(QWidget *parent)
       
   102     : QMainWindow(parent), quitDialog(0), saveChanges(false)
       
   103 {
       
   104     // Initialize some static strings
       
   105     QStringList headers;
       
   106     headers << tr("Torrent") << tr("Peers/Seeds") << tr("Progress")
       
   107             << tr("Down rate") << tr("Up rate") << tr("Status");
       
   108 
       
   109     // Main torrent list
       
   110     torrentView = new TorrentView(this);
       
   111     torrentView->setItemDelegate(new TorrentViewDelegate(this));
       
   112     torrentView->setHeaderLabels(headers);
       
   113     torrentView->setSelectionBehavior(QAbstractItemView::SelectRows);
       
   114     torrentView->setAlternatingRowColors(true);
       
   115     torrentView->setRootIsDecorated(false);
       
   116     setCentralWidget(torrentView);
       
   117 
       
   118     // Set header resize modes and initial section sizes
       
   119     QFontMetrics fm = fontMetrics();
       
   120     QHeaderView *header = torrentView->header();
       
   121     header->resizeSection(0, fm.width("typical-name-for-a-torrent.torrent"));
       
   122     header->resizeSection(1, fm.width(headers.at(1) + "  "));
       
   123     header->resizeSection(2, fm.width(headers.at(2) + "  "));
       
   124     header->resizeSection(3, qMax(fm.width(headers.at(3) + "  "), fm.width(" 1234.0 KB/s ")));
       
   125     header->resizeSection(4, qMax(fm.width(headers.at(4) + "  "), fm.width(" 1234.0 KB/s ")));
       
   126     header->resizeSection(5, qMax(fm.width(headers.at(5) + "  "), fm.width(tr("Downloading") + "  ")));
       
   127 
       
   128     // Create common actions
       
   129     QAction *newTorrentAction = new QAction(QIcon(":/icons/bottom.png"), tr("Add &new torrent"), this);
       
   130     pauseTorrentAction = new QAction(QIcon(":/icons/player_pause.png"), tr("&Pause torrent"), this);
       
   131     removeTorrentAction = new QAction(QIcon(":/icons/player_stop.png"), tr("&Remove torrent"), this);
       
   132     
       
   133     // File menu
       
   134     QMenu *fileMenu = menuBar()->addMenu(tr("&File"));
       
   135     fileMenu->addAction(newTorrentAction);
       
   136     fileMenu->addAction(pauseTorrentAction);
       
   137     fileMenu->addAction(removeTorrentAction);
       
   138     fileMenu->addSeparator();
       
   139     fileMenu->addAction(QIcon(":/icons/exit.png"), tr("E&xit"), this, SLOT(close()));
       
   140 
       
   141     // Help menu
       
   142     QMenu *helpMenu = menuBar()->addMenu(tr("&Help"));
       
   143     helpMenu->addAction(tr("&About"), this, SLOT(about()));
       
   144     helpMenu->addAction(tr("About &Qt"), qApp, SLOT(aboutQt()));
       
   145 
       
   146     // Top toolbar
       
   147     QToolBar *topBar = new QToolBar(tr("Tools"));
       
   148     addToolBar(Qt::TopToolBarArea, topBar);
       
   149     topBar->setMovable(false);
       
   150     topBar->addAction(newTorrentAction);
       
   151     topBar->addAction(removeTorrentAction);
       
   152     topBar->addAction(pauseTorrentAction);
       
   153     topBar->addSeparator();
       
   154     downActionTool = topBar->addAction(QIcon(tr(":/icons/1downarrow.png")), tr("Move down"));
       
   155     upActionTool = topBar->addAction(QIcon(tr(":/icons/1uparrow.png")), tr("Move up"));
       
   156 
       
   157     // Bottom toolbar
       
   158     QToolBar *bottomBar = new QToolBar(tr("Rate control"));
       
   159     addToolBar(Qt::BottomToolBarArea, bottomBar);
       
   160     bottomBar->setMovable(false);
       
   161     downloadLimitSlider = new QSlider(Qt::Horizontal);
       
   162     downloadLimitSlider->setRange(0, 1000);
       
   163     bottomBar->addWidget(new QLabel(tr("Max download:")));
       
   164     bottomBar->addWidget(downloadLimitSlider);
       
   165     bottomBar->addWidget((downloadLimitLabel = new QLabel(tr("0 KB/s"))));
       
   166     downloadLimitLabel->setFixedSize(QSize(fm.width(tr("99999 KB/s")), fm.lineSpacing()));
       
   167     bottomBar->addSeparator();
       
   168     uploadLimitSlider = new QSlider(Qt::Horizontal);
       
   169     uploadLimitSlider->setRange(0, 1000);
       
   170     bottomBar->addWidget(new QLabel(tr("Max upload:")));
       
   171     bottomBar->addWidget(uploadLimitSlider);
       
   172     bottomBar->addWidget((uploadLimitLabel = new QLabel(tr("0 KB/s"))));
       
   173     uploadLimitLabel->setFixedSize(QSize(fm.width(tr("99999 KB/s")), fm.lineSpacing()));
       
   174 
       
   175     // Set up connections
       
   176     connect(torrentView, SIGNAL(itemSelectionChanged()),
       
   177             this, SLOT(setActionsEnabled()));
       
   178     connect(torrentView, SIGNAL(fileDropped(const QString &)),
       
   179             this, SLOT(acceptFileDrop(const QString &)));
       
   180     connect(uploadLimitSlider, SIGNAL(valueChanged(int)),
       
   181             this, SLOT(setUploadLimit(int)));
       
   182     connect(downloadLimitSlider, SIGNAL(valueChanged(int)),
       
   183             this, SLOT(setDownloadLimit(int)));
       
   184     connect(newTorrentAction, SIGNAL(triggered()),
       
   185             this, SLOT(addTorrent()));
       
   186     connect(pauseTorrentAction, SIGNAL(triggered()),
       
   187             this, SLOT(pauseTorrent()));
       
   188     connect(removeTorrentAction, SIGNAL(triggered()),
       
   189             this, SLOT(removeTorrent()));
       
   190     connect(upActionTool, SIGNAL(triggered(bool)),
       
   191             this, SLOT(moveTorrentUp()));
       
   192     connect(downActionTool, SIGNAL(triggered(bool)),
       
   193             this, SLOT(moveTorrentDown()));
       
   194 
       
   195     // Load settings and start
       
   196     setWindowTitle(tr("Torrent Client"));
       
   197     setActionsEnabled();
       
   198     QMetaObject::invokeMethod(this, "loadSettings", Qt::QueuedConnection);
       
   199 }
       
   200 
       
   201 QSize MainWindow::sizeHint() const
       
   202 {
       
   203     const QHeaderView *header = torrentView->header();
       
   204 
       
   205     // Add up the sizes of all header sections. The last section is
       
   206     // stretched, so its size is relative to the size of the width;
       
   207     // instead of counting it, we count the size of its largest value.
       
   208     int width = fontMetrics().width(tr("Downloading") + "  ");
       
   209     for (int i = 0; i < header->count() - 1; ++i)
       
   210         width += header->sectionSize(i);
       
   211 
       
   212     return QSize(width, QMainWindow::sizeHint().height())
       
   213         .expandedTo(QApplication::globalStrut());
       
   214 }
       
   215 
       
   216 const TorrentClient *MainWindow::clientForRow(int row) const
       
   217 {
       
   218     // Return the client at the given row.
       
   219     return jobs.at(row).client;
       
   220 }
       
   221 
       
   222 int MainWindow::rowOfClient(TorrentClient *client) const
       
   223 {
       
   224     // Return the row that displays this client's status, or -1 if the
       
   225     // client is not known.
       
   226     int row = 0;
       
   227     foreach (Job job, jobs) {
       
   228         if (job.client == client)
       
   229             return row;
       
   230         ++row;
       
   231     }
       
   232     return -1;
       
   233 }
       
   234 
       
   235 void MainWindow::loadSettings()
       
   236 {
       
   237     // Load base settings (last working directory, upload/download limits).
       
   238     QSettings settings("Trolltech", "Torrent");
       
   239     lastDirectory = settings.value("LastDirectory").toString();
       
   240     if (lastDirectory.isEmpty())
       
   241         lastDirectory = QDir::currentPath();
       
   242     int up = settings.value("UploadLimit").toInt();
       
   243     int down = settings.value("DownloadLimit").toInt();
       
   244     uploadLimitSlider->setValue(up ? up : 170);
       
   245     downloadLimitSlider->setValue(down ? down : 550);
       
   246 
       
   247     // Resume all previous downloads.
       
   248     int size = settings.beginReadArray("Torrents");
       
   249     for (int i = 0; i < size; ++i) {
       
   250         settings.setArrayIndex(i);
       
   251         QByteArray resumeState = settings.value("resumeState").toByteArray();
       
   252         QString fileName = settings.value("sourceFileName").toString();
       
   253         QString dest = settings.value("destinationFolder").toString();
       
   254 
       
   255         if (addTorrent(fileName, dest, resumeState)) {
       
   256             TorrentClient *client = jobs.last().client;
       
   257             client->setDownloadedBytes(settings.value("downloadedBytes").toLongLong());
       
   258             client->setUploadedBytes(settings.value("uploadedBytes").toLongLong());
       
   259         }
       
   260     }
       
   261 }
       
   262 
       
   263 bool MainWindow::addTorrent()
       
   264 {
       
   265     // Show the file dialog, let the user select what torrent to start downloading.
       
   266     QString fileName = QFileDialog::getOpenFileName(this, tr("Choose a torrent file"),
       
   267                                                     lastDirectory,
       
   268                                                     tr("Torrents (*.torrent);;"
       
   269                                                        " All files (*.*)"));
       
   270     if (fileName.isEmpty())
       
   271         return false;
       
   272     lastDirectory = QFileInfo(fileName).absolutePath();
       
   273 
       
   274     // Show the "Add Torrent" dialog.
       
   275     AddTorrentDialog *addTorrentDialog = new AddTorrentDialog(this);
       
   276     addTorrentDialog->setTorrent(fileName);
       
   277     addTorrentDialog->deleteLater();
       
   278     if (!addTorrentDialog->exec())
       
   279         return false;
       
   280 
       
   281     // Add the torrent to our list of downloads
       
   282     addTorrent(fileName, addTorrentDialog->destinationFolder());
       
   283     if (!saveChanges) {
       
   284         saveChanges = true;
       
   285         QTimer::singleShot(1000, this, SLOT(saveSettings()));
       
   286     }
       
   287     return true;
       
   288 }
       
   289 
       
   290 void MainWindow::removeTorrent()
       
   291 {
       
   292     // Find the row of the current item, and find the torrent client
       
   293     // for that row.
       
   294     int row = torrentView->indexOfTopLevelItem(torrentView->currentItem());
       
   295     TorrentClient *client = jobs.at(row).client;
       
   296 
       
   297     // Stop the client.
       
   298     client->disconnect();
       
   299     connect(client, SIGNAL(stopped()), this, SLOT(torrentStopped()));
       
   300     client->stop();
       
   301 
       
   302     // Remove the row from the view.
       
   303     delete torrentView->takeTopLevelItem(row);
       
   304     jobs.removeAt(row);
       
   305     setActionsEnabled();
       
   306 
       
   307     saveChanges = true;
       
   308     saveSettings();
       
   309 }
       
   310 
       
   311 void MainWindow::torrentStopped()
       
   312 {
       
   313     // Schedule the client for deletion.
       
   314     TorrentClient *client = qobject_cast<TorrentClient *>(sender());
       
   315     client->deleteLater();
       
   316 
       
   317     // If the quit dialog is shown, update its progress.
       
   318     if (quitDialog) {
       
   319         if (++jobsStopped == jobsToStop)
       
   320             quitDialog->close();
       
   321     }
       
   322 }
       
   323 
       
   324 void MainWindow::torrentError(TorrentClient::Error)
       
   325 {
       
   326     // Delete the client.
       
   327     TorrentClient *client = qobject_cast<TorrentClient *>(sender());
       
   328     int row = rowOfClient(client);
       
   329     QString fileName = jobs.at(row).torrentFileName;
       
   330     jobs.removeAt(row);
       
   331 
       
   332     // Display the warning.
       
   333     QMessageBox::warning(this, tr("Error"),
       
   334                          tr("An error occurred while downloading %0: %1")
       
   335                          .arg(fileName)
       
   336                          .arg(client->errorString()));
       
   337 
       
   338     delete torrentView->takeTopLevelItem(row);
       
   339     client->deleteLater();
       
   340 }
       
   341 
       
   342 bool MainWindow::addTorrent(const QString &fileName, const QString &destinationFolder,
       
   343                             const QByteArray &resumeState)
       
   344 {
       
   345     // Check if the torrent is already being downloaded.
       
   346     foreach (Job job, jobs) {
       
   347         if (job.torrentFileName == fileName && job.destinationDirectory == destinationFolder) {
       
   348             QMessageBox::warning(this, tr("Already downloading"),
       
   349                                  tr("The torrent file %1 is "
       
   350                                     "already being downloaded.").arg(fileName));
       
   351             return false;
       
   352         }
       
   353     }
       
   354 
       
   355     // Create a new torrent client and attempt to parse the torrent data.
       
   356     TorrentClient *client = new TorrentClient(this);
       
   357     if (!client->setTorrent(fileName)) {
       
   358         QMessageBox::warning(this, tr("Error"),
       
   359                              tr("The torrent file %1 cannot not be opened/resumed.").arg(fileName));
       
   360         delete client;
       
   361         return false;
       
   362     }
       
   363     client->setDestinationFolder(destinationFolder);
       
   364     client->setDumpedState(resumeState);
       
   365 
       
   366     // Setup the client connections.
       
   367     connect(client, SIGNAL(stateChanged(TorrentClient::State)), this, SLOT(updateState(TorrentClient::State)));
       
   368     connect(client, SIGNAL(peerInfoUpdated()), this, SLOT(updatePeerInfo()));
       
   369     connect(client, SIGNAL(progressUpdated(int)), this, SLOT(updateProgress(int)));
       
   370     connect(client, SIGNAL(downloadRateUpdated(int)), this, SLOT(updateDownloadRate(int)));
       
   371     connect(client, SIGNAL(uploadRateUpdated(int)), this, SLOT(updateUploadRate(int)));
       
   372     connect(client, SIGNAL(stopped()), this, SLOT(torrentStopped()));
       
   373     connect(client, SIGNAL(error(TorrentClient::Error)), this, SLOT(torrentError(TorrentClient::Error)));
       
   374 
       
   375     // Add the client to the list of downloading jobs.
       
   376     Job job;
       
   377     job.client = client;
       
   378     job.torrentFileName = fileName;
       
   379     job.destinationDirectory = destinationFolder;
       
   380     jobs << job;
       
   381 
       
   382     // Create and add a row in the torrent view for this download.
       
   383     QTreeWidgetItem *item = new QTreeWidgetItem(torrentView);
       
   384 
       
   385     QString baseFileName = QFileInfo(fileName).fileName();
       
   386     if (baseFileName.toLower().endsWith(".torrent"))
       
   387         baseFileName.remove(baseFileName.size() - 8);
       
   388 
       
   389     item->setText(0, baseFileName);
       
   390     item->setToolTip(0, tr("Torrent: %1<br>Destination: %2")
       
   391                      .arg(baseFileName).arg(destinationFolder));
       
   392     item->setText(1, tr("0/0"));
       
   393     item->setText(2, "0");
       
   394     item->setText(3, "0.0 KB/s");
       
   395     item->setText(4, "0.0 KB/s");
       
   396     item->setText(5, tr("Idle"));
       
   397     item->setFlags(item->flags() & ~Qt::ItemIsEditable);
       
   398     item->setTextAlignment(1, Qt::AlignHCenter);
       
   399 
       
   400     if (!saveChanges) {
       
   401         saveChanges = true;
       
   402         QTimer::singleShot(5000, this, SLOT(saveSettings()));
       
   403     }
       
   404     client->start();
       
   405     return true;
       
   406 }
       
   407 
       
   408 void MainWindow::saveSettings()
       
   409 {
       
   410     if (!saveChanges)
       
   411       return;
       
   412     saveChanges = false;
       
   413 
       
   414     // Prepare and reset the settings
       
   415     QSettings settings("Trolltech", "Torrent");
       
   416     settings.clear();
       
   417 
       
   418     settings.setValue("LastDirectory", lastDirectory);
       
   419     settings.setValue("UploadLimit", uploadLimitSlider->value());
       
   420     settings.setValue("DownloadLimit", downloadLimitSlider->value());
       
   421 
       
   422     // Store data on all known torrents
       
   423     settings.beginWriteArray("Torrents");
       
   424     for (int i = 0; i < jobs.size(); ++i) {
       
   425         settings.setArrayIndex(i);
       
   426         settings.setValue("sourceFileName", jobs.at(i).torrentFileName);
       
   427         settings.setValue("destinationFolder", jobs.at(i).destinationDirectory);
       
   428         settings.setValue("uploadedBytes", jobs.at(i).client->uploadedBytes());
       
   429         settings.setValue("downloadedBytes", jobs.at(i).client->downloadedBytes());
       
   430         settings.setValue("resumeState", jobs.at(i).client->dumpedState());
       
   431     }
       
   432     settings.endArray();
       
   433     settings.sync();
       
   434 }
       
   435 
       
   436 void MainWindow::updateState(TorrentClient::State)
       
   437 {
       
   438     // Update the state string whenever the client's state changes.
       
   439     TorrentClient *client = qobject_cast<TorrentClient *>(sender());
       
   440     int row = rowOfClient(client);
       
   441     QTreeWidgetItem *item = torrentView->topLevelItem(row);
       
   442     if (item) {
       
   443         item->setToolTip(0, tr("Torrent: %1<br>Destination: %2<br>State: %3")
       
   444                          .arg(jobs.at(row).torrentFileName)
       
   445                          .arg(jobs.at(row).destinationDirectory)
       
   446                          .arg(client->stateString()));
       
   447 
       
   448         item->setText(5, client->stateString());
       
   449     }
       
   450     setActionsEnabled();
       
   451 }
       
   452 
       
   453 void MainWindow::updatePeerInfo()
       
   454 {
       
   455     // Update the number of connected, visited, seed and leecher peers.
       
   456     TorrentClient *client = qobject_cast<TorrentClient *>(sender());
       
   457     int row = rowOfClient(client);
       
   458 
       
   459     QTreeWidgetItem *item = torrentView->topLevelItem(row);
       
   460     item->setText(1, tr("%1/%2").arg(client->connectedPeerCount())
       
   461                   .arg(client->seedCount()));
       
   462 }
       
   463 
       
   464 void MainWindow::updateProgress(int percent)
       
   465 {
       
   466     TorrentClient *client = qobject_cast<TorrentClient *>(sender());
       
   467     int row = rowOfClient(client);
       
   468 
       
   469     // Update the progressbar.
       
   470     QTreeWidgetItem *item = torrentView->topLevelItem(row);
       
   471     if (item)
       
   472         item->setText(2, QString::number(percent));
       
   473 }
       
   474 
       
   475 void MainWindow::setActionsEnabled()
       
   476 {
       
   477     // Find the view item and client for the current row, and update
       
   478     // the states of the actions.
       
   479     QTreeWidgetItem *item = 0;
       
   480     if (!torrentView->selectedItems().isEmpty())
       
   481         item = torrentView->selectedItems().first();
       
   482     TorrentClient *client = item ? jobs.at(torrentView->indexOfTopLevelItem(item)).client : 0;
       
   483     bool pauseEnabled = client && ((client->state() == TorrentClient::Paused)
       
   484                                        ||  (client->state() > TorrentClient::Preparing));
       
   485 
       
   486     removeTorrentAction->setEnabled(item != 0);
       
   487     pauseTorrentAction->setEnabled(item != 0 && pauseEnabled);
       
   488 
       
   489     if (client && client->state() == TorrentClient::Paused) {
       
   490         pauseTorrentAction->setIcon(QIcon(":/icons/player_play.png"));
       
   491         pauseTorrentAction->setText(tr("Resume torrent"));
       
   492     } else {
       
   493         pauseTorrentAction->setIcon(QIcon(":/icons/player_pause.png"));
       
   494         pauseTorrentAction->setText(tr("Pause torrent"));
       
   495     }
       
   496 
       
   497     int row = torrentView->indexOfTopLevelItem(item);
       
   498     upActionTool->setEnabled(item && row != 0);
       
   499     downActionTool->setEnabled(item && row != jobs.size() - 1);
       
   500 }
       
   501 
       
   502 void MainWindow::updateDownloadRate(int bytesPerSecond)
       
   503 {
       
   504     // Update the download rate.
       
   505     TorrentClient *client = qobject_cast<TorrentClient *>(sender());
       
   506     int row = rowOfClient(client);
       
   507     QString num;
       
   508     num.sprintf("%.1f KB/s", bytesPerSecond / 1024.0);
       
   509     torrentView->topLevelItem(row)->setText(3, num);
       
   510 
       
   511     if (!saveChanges) {
       
   512         saveChanges = true;
       
   513         QTimer::singleShot(5000, this, SLOT(saveSettings()));
       
   514     }
       
   515 }
       
   516 
       
   517 void MainWindow::updateUploadRate(int bytesPerSecond)
       
   518 {
       
   519     // Update the upload rate.
       
   520     TorrentClient *client = qobject_cast<TorrentClient *>(sender());
       
   521     int row = rowOfClient(client);
       
   522     QString num;
       
   523     num.sprintf("%.1f KB/s", bytesPerSecond / 1024.0);
       
   524     torrentView->topLevelItem(row)->setText(4, num);
       
   525 
       
   526     if (!saveChanges) {
       
   527         saveChanges = true;
       
   528         QTimer::singleShot(5000, this, SLOT(saveSettings()));
       
   529     }
       
   530 }
       
   531 
       
   532 void MainWindow::pauseTorrent()
       
   533 {
       
   534     // Pause or unpause the current torrent.
       
   535     int row = torrentView->indexOfTopLevelItem(torrentView->currentItem());
       
   536     TorrentClient *client = jobs.at(row).client;
       
   537     client->setPaused(client->state() != TorrentClient::Paused);
       
   538     setActionsEnabled();
       
   539 }
       
   540 
       
   541 void MainWindow::moveTorrentUp()
       
   542 {
       
   543     QTreeWidgetItem *item = torrentView->currentItem();
       
   544     int row = torrentView->indexOfTopLevelItem(item);
       
   545     if (row == 0)
       
   546         return;
       
   547 
       
   548     Job tmp = jobs.at(row - 1);
       
   549     jobs[row - 1] = jobs[row];
       
   550     jobs[row] = tmp;
       
   551 
       
   552     QTreeWidgetItem *itemAbove = torrentView->takeTopLevelItem(row - 1);
       
   553     torrentView->insertTopLevelItem(row, itemAbove);
       
   554     setActionsEnabled();
       
   555 }
       
   556 
       
   557 void MainWindow::moveTorrentDown()
       
   558 {
       
   559     QTreeWidgetItem *item = torrentView->currentItem();
       
   560     int row = torrentView->indexOfTopLevelItem(item);
       
   561     if (row == jobs.size() - 1)
       
   562         return;
       
   563 
       
   564     Job tmp = jobs.at(row + 1);
       
   565     jobs[row + 1] = jobs[row];
       
   566     jobs[row] = tmp;
       
   567 
       
   568     QTreeWidgetItem *itemAbove = torrentView->takeTopLevelItem(row + 1);
       
   569     torrentView->insertTopLevelItem(row, itemAbove);
       
   570     setActionsEnabled();
       
   571 }
       
   572 
       
   573 static int rateFromValue(int value)
       
   574 {
       
   575     int rate = 0;
       
   576     if (value >= 0 && value < 250) {
       
   577         rate = 1 + int(value * 0.124);
       
   578     } else if (value < 500) {
       
   579         rate = 32 + int((value - 250) * 0.384);
       
   580     } else if (value < 750) {
       
   581         rate = 128 + int((value - 500) * 1.536);
       
   582     } else {
       
   583         rate = 512 + int((value - 750) * 6.1445);
       
   584     }
       
   585     return rate;
       
   586 }
       
   587 
       
   588 void MainWindow::setUploadLimit(int value)
       
   589 {
       
   590     int rate = rateFromValue(value);
       
   591     uploadLimitLabel->setText(tr("%1 KB/s").arg(QString().sprintf("%4d", rate)));
       
   592     RateController::instance()->setUploadLimit(rate * 1024);
       
   593 }
       
   594 
       
   595 void MainWindow::setDownloadLimit(int value)
       
   596 {
       
   597     int rate = rateFromValue(value);
       
   598     downloadLimitLabel->setText(tr("%1 KB/s").arg(QString().sprintf("%4d", rate)));
       
   599     RateController::instance()->setDownloadLimit(rate * 1024);
       
   600 }
       
   601 
       
   602 void MainWindow::about()
       
   603 {
       
   604     QLabel *icon = new QLabel;
       
   605     icon->setPixmap(QPixmap(":/icons/peertopeer.png"));
       
   606 
       
   607     QLabel *text = new QLabel;
       
   608     text->setWordWrap(true);
       
   609     text->setText("<p>The <b>Torrent Client</b> example demonstrates how to"
       
   610                   " write a complete peer-to-peer file sharing"
       
   611                   " application using Qt's network and thread classes.</p>"
       
   612                   "<p>This feature complete client implementation of"
       
   613                   " the BitTorrent protocol can efficiently"
       
   614                   " maintain several hundred network connections"
       
   615                   " simultaneously.</p>");
       
   616 
       
   617     QPushButton *quitButton = new QPushButton("OK");
       
   618 
       
   619     QHBoxLayout *topLayout = new QHBoxLayout;
       
   620     topLayout->setMargin(10);
       
   621     topLayout->setSpacing(10);
       
   622     topLayout->addWidget(icon);
       
   623     topLayout->addWidget(text);
       
   624 
       
   625     QHBoxLayout *bottomLayout = new QHBoxLayout;
       
   626     bottomLayout->addStretch();
       
   627     bottomLayout->addWidget(quitButton);
       
   628     bottomLayout->addStretch();
       
   629 
       
   630     QVBoxLayout *mainLayout = new QVBoxLayout;
       
   631     mainLayout->addLayout(topLayout);
       
   632     mainLayout->addLayout(bottomLayout);
       
   633 
       
   634     QDialog about(this);
       
   635     about.setModal(true);
       
   636     about.setWindowTitle(tr("About Torrent Client"));
       
   637     about.setLayout(mainLayout);
       
   638 
       
   639     connect(quitButton, SIGNAL(clicked()), &about, SLOT(close()));
       
   640 
       
   641     about.exec();
       
   642 }
       
   643 
       
   644 void MainWindow::acceptFileDrop(const QString &fileName)
       
   645 {
       
   646     // Create and show the "Add Torrent" dialog.
       
   647     AddTorrentDialog *addTorrentDialog = new AddTorrentDialog;
       
   648     lastDirectory = QFileInfo(fileName).absolutePath();
       
   649     addTorrentDialog->setTorrent(fileName);
       
   650     addTorrentDialog->deleteLater();
       
   651     if (!addTorrentDialog->exec())
       
   652         return;
       
   653 
       
   654     // Add the torrent to our list of downloads.
       
   655     addTorrent(fileName, addTorrentDialog->destinationFolder());
       
   656     saveSettings();
       
   657 }
       
   658 
       
   659 void MainWindow::closeEvent(QCloseEvent *)
       
   660 {
       
   661     if (jobs.isEmpty())
       
   662         return;
       
   663 
       
   664     // Save upload / download numbers.
       
   665     saveSettings();
       
   666     saveChanges = false;
       
   667 
       
   668     quitDialog = new QProgressDialog(tr("Disconnecting from trackers"), tr("Abort"), 0, jobsToStop, this);
       
   669 
       
   670     // Stop all clients, remove the rows from the view and wait for
       
   671     // them to signal that they have stopped.
       
   672     jobsToStop = 0;
       
   673     jobsStopped = 0;
       
   674     foreach (Job job, jobs) {
       
   675         ++jobsToStop;
       
   676         TorrentClient *client = job.client;
       
   677         client->disconnect();
       
   678         connect(client, SIGNAL(stopped()), this, SLOT(torrentStopped()));
       
   679         client->stop();
       
   680         delete torrentView->takeTopLevelItem(0);
       
   681     }
       
   682 
       
   683     if (jobsToStop > jobsStopped)
       
   684         quitDialog->exec();
       
   685     quitDialog->deleteLater();
       
   686     quitDialog = 0;
       
   687 }
       
   688 
       
   689 TorrentView::TorrentView(QWidget *parent)
       
   690     : QTreeWidget(parent)
       
   691 {
       
   692     setAcceptDrops(true);
       
   693 }
       
   694 
       
   695 void TorrentView::dragMoveEvent(QDragMoveEvent *event)
       
   696 {
       
   697     // Accept file actions with a '.torrent' extension.
       
   698     QUrl url(event->mimeData()->text());
       
   699     if (url.isValid() && url.scheme().toLower() == "file"
       
   700             && url.path().toLower().endsWith(".torrent"))
       
   701         event->acceptProposedAction();
       
   702 }
       
   703 
       
   704 void TorrentView::dropEvent(QDropEvent *event)
       
   705 {
       
   706     // Accept drops if the file has a '.torrent' extension and it
       
   707     // exists.
       
   708     QString fileName = QUrl(event->mimeData()->text()).path();
       
   709     if (QFile::exists(fileName) && fileName.toLower().endsWith(".torrent"))
       
   710         emit fileDropped(fileName);
       
   711 }
       
   712 
       
   713 #include "mainwindow.moc"