~ubuntu-branches/ubuntu/wily/qtbase-opensource-src/wily

« back to all changes in this revision

Viewing changes to examples/network/torrent/mainwindow.cpp

  • Committer: Package Import Robot
  • Author(s): Timo Jyrinki
  • Date: 2013-02-05 12:46:17 UTC
  • Revision ID: package-import@ubuntu.com-20130205124617-c8jouts182j002fx
Tags: upstream-5.0.1+dfsg
ImportĀ upstreamĀ versionĀ 5.0.1+dfsg

Show diffs side-by-side

added added

removed removed

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