mainwindow.cpp Example File

torrent/mainwindow.cpp

  /****************************************************************************
  **
  ** Copyright (C) 2016 The Qt Company Ltd.
  ** Contact: https://www.qt.io/licensing/
  **
  ** This file is part of the examples of the Qt Toolkit.
  **
  ** $QT_BEGIN_LICENSE:BSD$
  ** Commercial License Usage
  ** Licensees holding valid commercial Qt licenses may use this file in
  ** accordance with the commercial license agreement provided with the
  ** Software or, alternatively, in accordance with the terms contained in
  ** a written agreement between you and The Qt Company. For licensing terms
  ** and conditions see https://www.qt.io/terms-conditions. For further
  ** information use the contact form at https://www.qt.io/contact-us.
  **
  ** BSD License Usage
  ** Alternatively, you may use this file under the terms of the BSD license
  ** as follows:
  **
  ** "Redistribution and use in source and binary forms, with or without
  ** modification, are permitted provided that the following conditions are
  ** met:
  **   * Redistributions of source code must retain the above copyright
  **     notice, this list of conditions and the following disclaimer.
  **   * Redistributions in binary form must reproduce the above copyright
  **     notice, this list of conditions and the following disclaimer in
  **     the documentation and/or other materials provided with the
  **     distribution.
  **   * Neither the name of The Qt Company Ltd nor the names of its
  **     contributors may be used to endorse or promote products derived
  **     from this software without specific prior written permission.
  **
  **
  ** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
  ** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
  ** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
  ** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
  ** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
  ** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
  ** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
  ** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
  ** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
  ** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
  ** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
  **
  ** $QT_END_LICENSE$
  **
  ****************************************************************************/

  #include <QtWidgets>

  #include "addtorrentdialog.h"
  #include "mainwindow.h"
  #include "ratecontroller.h"
  #include "torrentclient.h"

  // TorrentView extends QTreeWidget to allow drag and drop.
  class TorrentView : public QTreeWidget
  {
      Q_OBJECT
  public:
      TorrentView(QWidget *parent = 0);

  #ifndef QT_NO_DRAGANDDROP
  signals:
      void fileDropped(const QString &fileName);

  protected:
      void dragMoveEvent(QDragMoveEvent *event) override;
      void dropEvent(QDropEvent *event) override;
  #endif
  };

  // TorrentViewDelegate is used to draw the progress bars.
  class TorrentViewDelegate : public QItemDelegate
  {
      Q_OBJECT
  public:
      inline TorrentViewDelegate(MainWindow *mainWindow) : QItemDelegate(mainWindow) {}

      void paint(QPainter *painter, const QStyleOptionViewItem &option,
                 const QModelIndex &index ) const override
      {
          if (index.column() != 2) {
              QItemDelegate::paint(painter, option, index);
              return;
          }

          // Set up a QStyleOptionProgressBar to precisely mimic the
          // environment of a progress bar.
          QStyleOptionProgressBar progressBarOption;
          progressBarOption.state = QStyle::State_Enabled;
          progressBarOption.direction = QApplication::layoutDirection();
          progressBarOption.rect = option.rect;
          progressBarOption.fontMetrics = QApplication::fontMetrics();
          progressBarOption.minimum = 0;
          progressBarOption.maximum = 100;
          progressBarOption.textAlignment = Qt::AlignCenter;
          progressBarOption.textVisible = true;

          // Set the progress and text values of the style option.
          int progress = qobject_cast<MainWindow *>(parent())->clientForRow(index.row())->progress();
          progressBarOption.progress = progress < 0 ? 0 : progress;
          progressBarOption.text = QString::asprintf("%d%%", progressBarOption.progress);

          // Draw the progress bar onto the view.
          QApplication::style()->drawControl(QStyle::CE_ProgressBar, &progressBarOption, painter);
      }
  };

  MainWindow::MainWindow(QWidget *parent)
      : QMainWindow(parent), quitDialog(0), saveChanges(false)
  {
      // Initialize some static strings
      QStringList headers;
      headers << tr("Torrent") << tr("Peers/Seeds") << tr("Progress")
              << tr("Down rate") << tr("Up rate") << tr("Status");

      // Main torrent list
      torrentView = new TorrentView(this);
      torrentView->setItemDelegate(new TorrentViewDelegate(this));
      torrentView->setHeaderLabels(headers);
      torrentView->setSelectionBehavior(QAbstractItemView::SelectRows);
      torrentView->setAlternatingRowColors(true);
      torrentView->setRootIsDecorated(false);
      setCentralWidget(torrentView);

      // Set header resize modes and initial section sizes
      QFontMetrics fm = fontMetrics();
      QHeaderView *header = torrentView->header();
      header->resizeSection(0, fm.width("typical-name-for-a-torrent.torrent"));
      header->resizeSection(1, fm.width(headers.at(1) + "  "));
      header->resizeSection(2, fm.width(headers.at(2) + "  "));
      header->resizeSection(3, qMax(fm.width(headers.at(3) + "  "), fm.width(" 1234.0 KB/s ")));
      header->resizeSection(4, qMax(fm.width(headers.at(4) + "  "), fm.width(" 1234.0 KB/s ")));
      header->resizeSection(5, qMax(fm.width(headers.at(5) + "  "), fm.width(tr("Downloading") + "  ")));

      // Create common actions
      QAction *newTorrentAction = new QAction(QIcon(":/icons/bottom.png"), tr("Add &new torrent"), this);
      pauseTorrentAction = new QAction(QIcon(":/icons/player_pause.png"), tr("&Pause torrent"), this);
      removeTorrentAction = new QAction(QIcon(":/icons/player_stop.png"), tr("&Remove torrent"), this);

      // File menu
      QMenu *fileMenu = menuBar()->addMenu(tr("&File"));
      fileMenu->addAction(newTorrentAction);
      fileMenu->addAction(pauseTorrentAction);
      fileMenu->addAction(removeTorrentAction);
      fileMenu->addSeparator();
      fileMenu->addAction(QIcon(":/icons/exit.png"), tr("E&xit"), this, SLOT(close()));

      // Help menu
      QMenu *helpMenu = menuBar()->addMenu(tr("&Help"));
      helpMenu->addAction(tr("&About"), this, SLOT(about()));
      helpMenu->addAction(tr("About &Qt"), qApp, SLOT(aboutQt()));

      // Top toolbar
      QToolBar *topBar = new QToolBar(tr("Tools"));
      addToolBar(Qt::TopToolBarArea, topBar);
      topBar->setMovable(false);
      topBar->addAction(newTorrentAction);
      topBar->addAction(removeTorrentAction);
      topBar->addAction(pauseTorrentAction);
      topBar->addSeparator();
      downActionTool = topBar->addAction(QIcon(tr(":/icons/1downarrow.png")), tr("Move down"));
      upActionTool = topBar->addAction(QIcon(tr(":/icons/1uparrow.png")), tr("Move up"));

      // Bottom toolbar
      QToolBar *bottomBar = new QToolBar(tr("Rate control"));
      addToolBar(Qt::BottomToolBarArea, bottomBar);
      bottomBar->setMovable(false);
      downloadLimitSlider = new QSlider(Qt::Horizontal);
      downloadLimitSlider->setRange(0, 1000);
      bottomBar->addWidget(new QLabel(tr("Max download:")));
      bottomBar->addWidget(downloadLimitSlider);
      bottomBar->addWidget((downloadLimitLabel = new QLabel(tr("0 KB/s"))));
      downloadLimitLabel->setFixedSize(QSize(fm.width(tr("99999 KB/s")), fm.lineSpacing()));
      bottomBar->addSeparator();
      uploadLimitSlider = new QSlider(Qt::Horizontal);
      uploadLimitSlider->setRange(0, 1000);
      bottomBar->addWidget(new QLabel(tr("Max upload:")));
      bottomBar->addWidget(uploadLimitSlider);
      bottomBar->addWidget((uploadLimitLabel = new QLabel(tr("0 KB/s"))));
      uploadLimitLabel->setFixedSize(QSize(fm.width(tr("99999 KB/s")), fm.lineSpacing()));

  #ifdef Q_OS_OSX
      setUnifiedTitleAndToolBarOnMac(true);
  #endif

      // Set up connections
      connect(torrentView, SIGNAL(itemSelectionChanged()),
              this, SLOT(setActionsEnabled()));
      connect(torrentView, SIGNAL(fileDropped(QString)),
              this, SLOT(acceptFileDrop(QString)));
      connect(uploadLimitSlider, SIGNAL(valueChanged(int)),
              this, SLOT(setUploadLimit(int)));
      connect(downloadLimitSlider, SIGNAL(valueChanged(int)),
              this, SLOT(setDownloadLimit(int)));
      connect(newTorrentAction, SIGNAL(triggered()),
              this, SLOT(addTorrent()));
      connect(pauseTorrentAction, SIGNAL(triggered()),
              this, SLOT(pauseTorrent()));
      connect(removeTorrentAction, SIGNAL(triggered()),
              this, SLOT(removeTorrent()));
      connect(upActionTool, SIGNAL(triggered(bool)),
              this, SLOT(moveTorrentUp()));
      connect(downActionTool, SIGNAL(triggered(bool)),
              this, SLOT(moveTorrentDown()));

      // Load settings and start
      setWindowTitle(tr("Torrent Client"));
      setActionsEnabled();
      QMetaObject::invokeMethod(this, "loadSettings", Qt::QueuedConnection);
  }

  QSize MainWindow::sizeHint() const
  {
      const QHeaderView *header = torrentView->header();

      // Add up the sizes of all header sections. The last section is
      // stretched, so its size is relative to the size of the width;
      // instead of counting it, we count the size of its largest value.
      int width = fontMetrics().width(tr("Downloading") + "  ");
      for (int i = 0; i < header->count() - 1; ++i)
          width += header->sectionSize(i);

      return QSize(width, QMainWindow::sizeHint().height())
          .expandedTo(QApplication::globalStrut());
  }

  const TorrentClient *MainWindow::clientForRow(int row) const
  {
      // Return the client at the given row.
      return jobs.at(row).client;
  }

  int MainWindow::rowOfClient(TorrentClient *client) const
  {
      // Return the row that displays this client's status, or -1 if the
      // client is not known.
      int row = 0;
      foreach (Job job, jobs) {
          if (job.client == client)
              return row;
          ++row;
      }
      return -1;
  }

  void MainWindow::loadSettings()
  {
      // Load base settings (last working directory, upload/download limits).
      QSettings settings("QtProject", "Torrent");
      lastDirectory = settings.value("LastDirectory").toString();
      if (lastDirectory.isEmpty())
          lastDirectory = QDir::currentPath();
      int up = settings.value("UploadLimit").toInt();
      int down = settings.value("DownloadLimit").toInt();
      uploadLimitSlider->setValue(up ? up : 170);
      downloadLimitSlider->setValue(down ? down : 550);

      // Resume all previous downloads.
      int size = settings.beginReadArray("Torrents");
      for (int i = 0; i < size; ++i) {
          settings.setArrayIndex(i);
          QByteArray resumeState = settings.value("resumeState").toByteArray();
          QString fileName = settings.value("sourceFileName").toString();
          QString dest = settings.value("destinationFolder").toString();

          if (addTorrent(fileName, dest, resumeState)) {
              TorrentClient *client = jobs.last().client;
              client->setDownloadedBytes(settings.value("downloadedBytes").toLongLong());
              client->setUploadedBytes(settings.value("uploadedBytes").toLongLong());
          }
      }
  }

  bool MainWindow::addTorrent()
  {
      // Show the file dialog, let the user select what torrent to start downloading.
      QString fileName = QFileDialog::getOpenFileName(this, tr("Choose a torrent file"),
                                                      lastDirectory,
                                                      tr("Torrents (*.torrent);;"
                                                         " All files (*.*)"));
      if (fileName.isEmpty())
          return false;
      lastDirectory = QFileInfo(fileName).absolutePath();

      // Show the "Add Torrent" dialog.
      AddTorrentDialog *addTorrentDialog = new AddTorrentDialog(this);
      addTorrentDialog->setTorrent(fileName);
      addTorrentDialog->deleteLater();
      if (!addTorrentDialog->exec())
          return false;

      // Add the torrent to our list of downloads
      addTorrent(fileName, addTorrentDialog->destinationFolder());
      if (!saveChanges) {
          saveChanges = true;
          QTimer::singleShot(1000, this, SLOT(saveSettings()));
      }
      return true;
  }

  void MainWindow::removeTorrent()
  {
      // Find the row of the current item, and find the torrent client
      // for that row.
      int row = torrentView->indexOfTopLevelItem(torrentView->currentItem());
      TorrentClient *client = jobs.at(row).client;

      // Stop the client.
      client->disconnect();
      connect(client, SIGNAL(stopped()), this, SLOT(torrentStopped()));
      client->stop();

      // Remove the row from the view.
      delete torrentView->takeTopLevelItem(row);
      jobs.removeAt(row);
      setActionsEnabled();

      saveChanges = true;
      saveSettings();
  }

  void MainWindow::torrentStopped()
  {
      // Schedule the client for deletion.
      TorrentClient *client = qobject_cast<TorrentClient *>(sender());
      client->deleteLater();

      // If the quit dialog is shown, update its progress.
      if (quitDialog) {
          if (++jobsStopped == jobsToStop)
              quitDialog->close();
      }
  }

  void MainWindow::torrentError(TorrentClient::Error)
  {
      // Delete the client.
      TorrentClient *client = qobject_cast<TorrentClient *>(sender());
      int row = rowOfClient(client);
      QString fileName = jobs.at(row).torrentFileName;
      jobs.removeAt(row);

      // Display the warning.
      QMessageBox::warning(this, tr("Error"),
                           tr("An error occurred while downloading %0: %1")
                           .arg(fileName)
                           .arg(client->errorString()));

      delete torrentView->takeTopLevelItem(row);
      client->deleteLater();
  }

  bool MainWindow::addTorrent(const QString &fileName, const QString &destinationFolder,
                              const QByteArray &resumeState)
  {
      // Check if the torrent is already being downloaded.
      foreach (Job job, jobs) {
          if (job.torrentFileName == fileName && job.destinationDirectory == destinationFolder) {
              QMessageBox::warning(this, tr("Already downloading"),
                                   tr("The torrent file %1 is "
                                      "already being downloaded.").arg(fileName));
              return false;
          }
      }

      // Create a new torrent client and attempt to parse the torrent data.
      TorrentClient *client = new TorrentClient(this);
      if (!client->setTorrent(fileName)) {
          QMessageBox::warning(this, tr("Error"),
                               tr("The torrent file %1 cannot not be opened/resumed.").arg(fileName));
          delete client;
          return false;
      }
      client->setDestinationFolder(destinationFolder);
      client->setDumpedState(resumeState);

      // Setup the client connections.
      connect(client, SIGNAL(stateChanged(TorrentClient::State)), this, SLOT(updateState(TorrentClient::State)));
      connect(client, SIGNAL(peerInfoUpdated()), this, SLOT(updatePeerInfo()));
      connect(client, SIGNAL(progressUpdated(int)), this, SLOT(updateProgress(int)));
      connect(client, SIGNAL(downloadRateUpdated(int)), this, SLOT(updateDownloadRate(int)));
      connect(client, SIGNAL(uploadRateUpdated(int)), this, SLOT(updateUploadRate(int)));
      connect(client, SIGNAL(stopped()), this, SLOT(torrentStopped()));
      connect(client, SIGNAL(error(TorrentClient::Error)), this, SLOT(torrentError(TorrentClient::Error)));

      // Add the client to the list of downloading jobs.
      Job job;
      job.client = client;
      job.torrentFileName = fileName;
      job.destinationDirectory = destinationFolder;
      jobs << job;

      // Create and add a row in the torrent view for this download.
      QTreeWidgetItem *item = new QTreeWidgetItem(torrentView);

      QString baseFileName = QFileInfo(fileName).fileName();
      if (baseFileName.toLower().endsWith(".torrent"))
          baseFileName.remove(baseFileName.size() - 8);

      item->setText(0, baseFileName);
      item->setToolTip(0, tr("Torrent: %1<br>Destination: %2")
                       .arg(baseFileName).arg(destinationFolder));
      item->setText(1, tr("0/0"));
      item->setText(2, "0");
      item->setText(3, "0.0 KB/s");
      item->setText(4, "0.0 KB/s");
      item->setText(5, tr("Idle"));
      item->setFlags(item->flags() & ~Qt::ItemIsEditable);
      item->setTextAlignment(1, Qt::AlignHCenter);

      if (!saveChanges) {
          saveChanges = true;
          QTimer::singleShot(5000, this, SLOT(saveSettings()));
      }
      client->start();
      return true;
  }

  void MainWindow::saveSettings()
  {
      if (!saveChanges)
        return;
      saveChanges = false;

      // Prepare and reset the settings
      QSettings settings("QtProject", "Torrent");
      settings.clear();

      settings.setValue("LastDirectory", lastDirectory);
      settings.setValue("UploadLimit", uploadLimitSlider->value());
      settings.setValue("DownloadLimit", downloadLimitSlider->value());

      // Store data on all known torrents
      settings.beginWriteArray("Torrents");
      for (int i = 0; i < jobs.size(); ++i) {
          settings.setArrayIndex(i);
          settings.setValue("sourceFileName", jobs.at(i).torrentFileName);
          settings.setValue("destinationFolder", jobs.at(i).destinationDirectory);
          settings.setValue("uploadedBytes", jobs.at(i).client->uploadedBytes());
          settings.setValue("downloadedBytes", jobs.at(i).client->downloadedBytes());
          settings.setValue("resumeState", jobs.at(i).client->dumpedState());
      }
      settings.endArray();
      settings.sync();
  }

  void MainWindow::updateState(TorrentClient::State)
  {
      // Update the state string whenever the client's state changes.
      TorrentClient *client = qobject_cast<TorrentClient *>(sender());
      int row = rowOfClient(client);
      QTreeWidgetItem *item = torrentView->topLevelItem(row);
      if (item) {
          item->setToolTip(0, tr("Torrent: %1<br>Destination: %2<br>State: %3")
                           .arg(jobs.at(row).torrentFileName)
                           .arg(jobs.at(row).destinationDirectory)
                           .arg(client->stateString()));

          item->setText(5, client->stateString());
      }
      setActionsEnabled();
  }

  void MainWindow::updatePeerInfo()
  {
      // Update the number of connected, visited, seed and leecher peers.
      TorrentClient *client = qobject_cast<TorrentClient *>(sender());
      int row = rowOfClient(client);

      QTreeWidgetItem *item = torrentView->topLevelItem(row);
      item->setText(1, tr("%1/%2").arg(client->connectedPeerCount())
                    .arg(client->seedCount()));
  }

  void MainWindow::updateProgress(int percent)
  {
      TorrentClient *client = qobject_cast<TorrentClient *>(sender());
      int row = rowOfClient(client);

      // Update the progressbar.
      QTreeWidgetItem *item = torrentView->topLevelItem(row);
      if (item)
          item->setText(2, QString::number(percent));
  }

  void MainWindow::setActionsEnabled()
  {
      // Find the view item and client for the current row, and update
      // the states of the actions.
      QTreeWidgetItem *item = 0;
      if (!torrentView->selectedItems().isEmpty())
          item = torrentView->selectedItems().first();
      TorrentClient *client = item ? jobs.at(torrentView->indexOfTopLevelItem(item)).client : 0;
      bool pauseEnabled = client && ((client->state() == TorrentClient::Paused)
                                         ||  (client->state() > TorrentClient::Preparing));

      removeTorrentAction->setEnabled(item != 0);
      pauseTorrentAction->setEnabled(item != 0 && pauseEnabled);

      if (client && client->state() == TorrentClient::Paused) {
          pauseTorrentAction->setIcon(QIcon(":/icons/player_play.png"));
          pauseTorrentAction->setText(tr("Resume torrent"));
      } else {
          pauseTorrentAction->setIcon(QIcon(":/icons/player_pause.png"));
          pauseTorrentAction->setText(tr("Pause torrent"));
      }

      int row = torrentView->indexOfTopLevelItem(item);
      upActionTool->setEnabled(item && row != 0);
      downActionTool->setEnabled(item && row != jobs.size() - 1);
  }

  void MainWindow::updateDownloadRate(int bytesPerSecond)
  {
      // Update the download rate.
      TorrentClient *client = qobject_cast<TorrentClient *>(sender());
      int row = rowOfClient(client);
      const QString num = QString::asprintf("%.1f KB/s", bytesPerSecond / 1024.0);
      torrentView->topLevelItem(row)->setText(3, num);

      if (!saveChanges) {
          saveChanges = true;
          QTimer::singleShot(5000, this, SLOT(saveSettings()));
      }
  }

  void MainWindow::updateUploadRate(int bytesPerSecond)
  {
      // Update the upload rate.
      TorrentClient *client = qobject_cast<TorrentClient *>(sender());
      int row = rowOfClient(client);
      const QString num = QString::asprintf("%.1f KB/s", bytesPerSecond / 1024.0);
      torrentView->topLevelItem(row)->setText(4, num);

      if (!saveChanges) {
          saveChanges = true;
          QTimer::singleShot(5000, this, SLOT(saveSettings()));
      }
  }

  void MainWindow::pauseTorrent()
  {
      // Pause or unpause the current torrent.
      int row = torrentView->indexOfTopLevelItem(torrentView->currentItem());
      TorrentClient *client = jobs.at(row).client;
      client->setPaused(client->state() != TorrentClient::Paused);
      setActionsEnabled();
  }

  void MainWindow::moveTorrentUp()
  {
      QTreeWidgetItem *item = torrentView->currentItem();
      int row = torrentView->indexOfTopLevelItem(item);
      if (row == 0)
          return;

      Job tmp = jobs.at(row - 1);
      jobs[row - 1] = jobs[row];
      jobs[row] = tmp;

      QTreeWidgetItem *itemAbove = torrentView->takeTopLevelItem(row - 1);
      torrentView->insertTopLevelItem(row, itemAbove);
      setActionsEnabled();
  }

  void MainWindow::moveTorrentDown()
  {
      QTreeWidgetItem *item = torrentView->currentItem();
      int row = torrentView->indexOfTopLevelItem(item);
      if (row == jobs.size() - 1)
          return;

      Job tmp = jobs.at(row + 1);
      jobs[row + 1] = jobs[row];
      jobs[row] = tmp;

      QTreeWidgetItem *itemAbove = torrentView->takeTopLevelItem(row + 1);
      torrentView->insertTopLevelItem(row, itemAbove);
      setActionsEnabled();
  }

  static int rateFromValue(int value)
  {
      int rate = 0;
      if (value >= 0 && value < 250) {
          rate = 1 + int(value * 0.124);
      } else if (value < 500) {
          rate = 32 + int((value - 250) * 0.384);
      } else if (value < 750) {
          rate = 128 + int((value - 500) * 1.536);
      } else {
          rate = 512 + int((value - 750) * 6.1445);
      }
      return rate;
  }

  void MainWindow::setUploadLimit(int value)
  {
      int rate = rateFromValue(value);
      uploadLimitLabel->setText(tr("%1 KB/s").arg(QString::asprintf("%4d", rate)));
      RateController::instance()->setUploadLimit(rate * 1024);
  }

  void MainWindow::setDownloadLimit(int value)
  {
      int rate = rateFromValue(value);
      downloadLimitLabel->setText(tr("%1 KB/s").arg(QString::asprintf("%4d", rate)));
      RateController::instance()->setDownloadLimit(rate * 1024);
  }

  void MainWindow::about()
  {
      QLabel *icon = new QLabel;
      icon->setPixmap(QPixmap(":/icons/peertopeer.png"));

      QLabel *text = new QLabel;
      text->setWordWrap(true);
      text->setText("<p>The <b>Torrent Client</b> example demonstrates how to"
                    " write a complete peer-to-peer file sharing"
                    " application using Qt's network and thread classes.</p>"
                    "<p>This feature complete client implementation of"
                    " the BitTorrent protocol can efficiently"
                    " maintain several hundred network connections"
                    " simultaneously.</p>");

      QPushButton *quitButton = new QPushButton("OK");

      QHBoxLayout *topLayout = new QHBoxLayout;
      topLayout->setMargin(10);
      topLayout->setSpacing(10);
      topLayout->addWidget(icon);
      topLayout->addWidget(text);

      QHBoxLayout *bottomLayout = new QHBoxLayout;
      bottomLayout->addStretch();
      bottomLayout->addWidget(quitButton);
      bottomLayout->addStretch();

      QVBoxLayout *mainLayout = new QVBoxLayout;
      mainLayout->addLayout(topLayout);
      mainLayout->addLayout(bottomLayout);

      QDialog about(this);
      about.setModal(true);
      about.setWindowTitle(tr("About Torrent Client"));
      about.setLayout(mainLayout);

      connect(quitButton, SIGNAL(clicked()), &about, SLOT(close()));

      about.exec();
  }

  void MainWindow::acceptFileDrop(const QString &fileName)
  {
      // Create and show the "Add Torrent" dialog.
      AddTorrentDialog *addTorrentDialog = new AddTorrentDialog;
      lastDirectory = QFileInfo(fileName).absolutePath();
      addTorrentDialog->setTorrent(fileName);
      addTorrentDialog->deleteLater();
      if (!addTorrentDialog->exec())
          return;

      // Add the torrent to our list of downloads.
      addTorrent(fileName, addTorrentDialog->destinationFolder());
      saveSettings();
  }

  void MainWindow::closeEvent(QCloseEvent *)
  {
      if (jobs.isEmpty())
          return;

      // Save upload / download numbers.
      saveSettings();
      saveChanges = false;

      quitDialog = new QProgressDialog(tr("Disconnecting from trackers"), tr("Abort"), 0, jobsToStop, this);

      // Stop all clients, remove the rows from the view and wait for
      // them to signal that they have stopped.
      jobsToStop = 0;
      jobsStopped = 0;
      foreach (Job job, jobs) {
          ++jobsToStop;
          TorrentClient *client = job.client;
          client->disconnect();
          connect(client, SIGNAL(stopped()), this, SLOT(torrentStopped()));
          client->stop();
          delete torrentView->takeTopLevelItem(0);
      }

      if (jobsToStop > jobsStopped)
          quitDialog->exec();
      quitDialog->deleteLater();
      quitDialog = 0;
  }

  TorrentView::TorrentView(QWidget *parent)
      : QTreeWidget(parent)
  {
  #ifndef QT_NO_DRAGANDDROP
      setAcceptDrops(true);
  #endif
  }

  #ifndef QT_NO_DRAGANDDROP
  void TorrentView::dragMoveEvent(QDragMoveEvent *event)
  {
      // Accept file actions with a '.torrent' extension.
      QUrl url(event->mimeData()->text());
      if (url.isValid() && url.scheme() == "file"
              && url.path().toLower().endsWith(".torrent"))
          event->acceptProposedAction();
  }

  void TorrentView::dropEvent(QDropEvent *event)
  {
      // Accept drops if the file has a '.torrent' extension and it
      // exists.
      QString fileName = QUrl(event->mimeData()->text()).path();
      if (QFile::exists(fileName) && fileName.toLower().endsWith(".torrent"))
          emit fileDropped(fileName);
  }
  #endif

  #include "mainwindow.moc"