diff --git a/src/base/CMakeLists.txt b/src/base/CMakeLists.txt index 6f57029ba..551a7697f 100644 --- a/src/base/CMakeLists.txt +++ b/src/base/CMakeLists.txt @@ -46,6 +46,7 @@ utils/misc.h utils/string.h filesystemwatcher.h iconprovider.h +indexrange.h logger.h preferences.h qinisettings.h diff --git a/src/base/base.pri b/src/base/base.pri index 725ece8c5..0c4c4f303 100644 --- a/src/base/base.pri +++ b/src/base/base.pri @@ -6,6 +6,7 @@ HEADERS += \ $$PWD/logger.h \ $$PWD/settingsstorage.h \ $$PWD/preferences.h \ + $$PWD/indexrange.h \ $$PWD/iconprovider.h \ $$PWD/http/irequesthandler.h \ $$PWD/http/connection.h \ diff --git a/src/base/bittorrent/torrentinfo.cpp b/src/base/bittorrent/torrentinfo.cpp index d0ee54ce3..bfe89d014 100644 --- a/src/base/bittorrent/torrentinfo.cpp +++ b/src/base/bittorrent/torrentinfo.cpp @@ -26,6 +26,7 @@ * exception statement from your version. */ +#include #include #include #include @@ -138,6 +139,12 @@ int TorrentInfo::pieceLength() const return m_nativeInfo->piece_length(); } +int TorrentInfo::pieceLength(int index) const +{ + if (!isValid()) return -1; + return m_nativeInfo->piece_size(index); +} + int TorrentInfo::piecesCount() const { if (!isValid()) return -1; @@ -178,7 +185,7 @@ qlonglong TorrentInfo::fileSize(int index) const qlonglong TorrentInfo::fileOffset(int index) const { - if (!isValid()) return -1; + if (!isValid()) return -1; return m_nativeInfo->file_at(index).offset; } @@ -213,24 +220,79 @@ QByteArray TorrentInfo::metadata() const QStringList TorrentInfo::filesForPiece(int pieceIndex) const { - if (pieceIndex < 0) - return QStringList(); + // no checks here because fileIndicesForPiece() will return an empty list + QVector fileIndices = fileIndicesForPiece(pieceIndex); - std::vector files( - nativeInfo()->map_block(pieceIndex, 0, nativeInfo()->piece_size(pieceIndex))); QStringList res; - for (const libtorrent::file_slice& s: files) { - res.append(filePath(s.file_index)); - } + res.reserve(fileIndices.size()); + std::transform(fileIndices.begin(), fileIndices.end(), std::back_inserter(res), + [this](int i) { return filePath(i); }); + return res; } +QVector TorrentInfo::fileIndicesForPiece(int pieceIndex) const +{ + if (!isValid() || (pieceIndex < 0) || (pieceIndex >= piecesCount())) + return QVector(); + + std::vector files( + nativeInfo()->map_block(pieceIndex, 0, nativeInfo()->piece_size(pieceIndex))); + QVector res; + res.reserve(files.size()); + std::transform(files.begin(), files.end(), std::back_inserter(res), + [](const libt::file_slice &s) { return s.file_index; }); + + return res; +} + +TorrentInfo::PieceRange TorrentInfo::filePieces(const QString& file) const +{ + if (!isValid()) // if we do not check here the debug message will be printed, which would be not correct + return {}; + + int index = fileIndex(file); + if (index == -1) { + qDebug() << "Filename" << file << "was not found in torrent" << name(); + return {}; + } + return filePieces(index); +} + +TorrentInfo::PieceRange TorrentInfo::filePieces(int fileIndex) const +{ + if (!isValid()) + return {}; + + if ((fileIndex < 0) || (fileIndex >= filesCount())) { + qDebug() << "File index (" << fileIndex << ") is out of range for torrent" << name(); + return {}; + } + + const libt::file_storage &files = nativeInfo()->files(); + const auto fileSize = files.file_size(fileIndex); + const auto firstOffset = files.file_offset(fileIndex); + return makeInterval(static_cast(firstOffset / pieceLength()), + static_cast((firstOffset + fileSize - 1) / pieceLength())); +} + void TorrentInfo::renameFile(uint index, const QString &newPath) { if (!isValid()) return; nativeInfo()->rename_file(index, Utils::String::toStdString(newPath)); } +int BitTorrent::TorrentInfo::fileIndex(const QString& fileName) const +{ + // the check whether the object valid is not needed here + // because filesCount() returns -1 in that case and the loop exits immediately + for (int i = 0; i < filesCount(); ++i) + if (fileName == filePath(i)) + return i; + + return -1; +} + TorrentInfo::NativePtr TorrentInfo::nativeInfo() const { return m_nativeInfo; diff --git a/src/base/bittorrent/torrentinfo.h b/src/base/bittorrent/torrentinfo.h index 53a592531..9a148add0 100644 --- a/src/base/bittorrent/torrentinfo.h +++ b/src/base/bittorrent/torrentinfo.h @@ -34,12 +34,15 @@ #include #include +#include "base/indexrange.h" + class QString; class QUrl; class QDateTime; class QStringList; class QByteArray; template class QList; +template class QVector; namespace BitTorrent { @@ -75,6 +78,7 @@ namespace BitTorrent qlonglong totalSize() const; int filesCount() const; int pieceLength() const; + int pieceLength(int index) const; int piecesCount() const; QString filePath(int index) const; QStringList filePaths() const; @@ -86,12 +90,21 @@ namespace BitTorrent QList urlSeeds() const; QByteArray metadata() const; QStringList filesForPiece(int pieceIndex) const; + QVector fileIndicesForPiece(int pieceIndex) const; + + using PieceRange = IndexRange; + // returns pair of the first and the last pieces into which + // the given file extends (maybe partially). + PieceRange filePieces(const QString &file) const; + PieceRange filePieces(int fileIndex) const; void renameFile(uint index, const QString &newPath); NativePtr nativeInfo() const; private: + // returns file index or -1 if fileName is not found + int fileIndex(const QString &fileName) const; NativePtr m_nativeInfo; }; } diff --git a/src/base/indexrange.h b/src/base/indexrange.h new file mode 100644 index 000000000..77384d89f --- /dev/null +++ b/src/base/indexrange.h @@ -0,0 +1,130 @@ +/* + * Bittorrent Client using Qt and libtorrent. + * Copyright (C) 2016 Eugene Shalygin + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * In addition, as a special exception, the copyright holders give permission to + * link this program with the OpenSSL project's "OpenSSL" library (or with + * modified versions of it that use the same license as the "OpenSSL" library), + * and distribute the linked executables. You must obey the GNU General Public + * License in all respects for all of the code used other than "OpenSSL". If you + * modify file(s), you may extend this exception to your version of the file(s), + * but you are not obligated to do so. If you do not wish to do so, delete this + * exception statement from your version. + */ + +#ifndef QBT_INDEXRANGE_H +#define QBT_INDEXRANGE_H + +#include + +// Interval is defined via [first;last] +template +class IndexInterval +{ +public: + using IndexType = Index; + + IndexInterval(IndexType first, IndexType last) + : m_first {first} + , m_last {last} + { + Q_ASSERT(first <= last); + } + + IndexType first() const + { + return m_first; + } + + IndexType last() const + { + return m_last; + } + +private: + IndexType m_first; + IndexType m_last; +}; + +template +inline IndexInterval makeInterval(T first, T last) +{ + return {first, last}; +} + +// range is defined via first index and size +template +class IndexRange +{ +public: + using IndexType = Index; + using IndexDiffType = IndexDiff; + + constexpr IndexRange() + : m_first {0} + , m_size {0} + { + } + + constexpr IndexRange(IndexType first, IndexDiffType size) + : m_first {first} + , m_size {size} + { + } + + constexpr IndexRange(const IndexInterval &interval) + : m_first {interval.first()} + , m_size {interval.last() - interval.first() + 1} + { + } + + constexpr IndexType begin() const + { + return m_first; + } + + constexpr IndexType end() const + { + return m_first + m_size; + } + + constexpr IndexDiffType size() const + { + return m_size; + } + + constexpr IndexType first() const + { + return m_first; + } + + constexpr IndexType last() const + { + return m_first + m_size - 1; + } + + constexpr bool isEmpty() const + { + return m_size == 0; + } + +private: + IndexType m_first; + IndexDiffType m_size; +}; + +#endif // QBT_INDEXRANGE_H diff --git a/src/gui/properties/CMakeLists.txt b/src/gui/properties/CMakeLists.txt index 59c4ac427..c172ee54f 100644 --- a/src/gui/properties/CMakeLists.txt +++ b/src/gui/properties/CMakeLists.txt @@ -16,6 +16,7 @@ peerlistwidget.h proplistdelegate.h trackerlist.h downloadedpiecesbar.h +piecesbar.h peerlistdelegate.h peerlistsortmodel.h peersadditiondlg.h @@ -33,6 +34,7 @@ peerlistwidget.cpp trackerlist.cpp peersadditiondlg.cpp downloadedpiecesbar.cpp +piecesbar.cpp trackersadditiondlg.cpp pieceavailabilitybar.cpp proptabbar.cpp diff --git a/src/gui/properties/downloadedpiecesbar.cpp b/src/gui/properties/downloadedpiecesbar.cpp index ca79449a4..4ecdb82f9 100644 --- a/src/gui/properties/downloadedpiecesbar.cpp +++ b/src/gui/properties/downloadedpiecesbar.cpp @@ -28,20 +28,16 @@ * Contact : chris@qbittorrent.org */ -#include -#include #include "downloadedpiecesbar.h" -DownloadedPiecesBar::DownloadedPiecesBar(QWidget *parent) : QWidget(parent) -{ - setToolTip(QString("%1\n%2\n%3").arg(tr("White: Missing pieces")).arg(tr("Green: Partial pieces")).arg(tr("Blue: Completed pieces"))); +#include - m_bgColor = 0xffffff; - m_borderColor = palette().color(QPalette::Dark).rgb(); - m_pieceColor = 0x0000ff; - m_dlPieceColor = 0x00d000; +#include - updatePieceColors(); +DownloadedPiecesBar::DownloadedPiecesBar(QWidget *parent) + : base {parent} + , m_dlPieceColor {0, 0xd0, 0} +{ } QVector DownloadedPiecesBar::bitfieldToFloatVector(const QBitArray &vecin, int reqSize) @@ -49,7 +45,7 @@ QVector DownloadedPiecesBar::bitfieldToFloatVector(const QBitArray &vecin QVector result(reqSize, 0.0); if (vecin.isEmpty()) return result; - const float ratio = vecin.size() / (float)reqSize; + const float ratio = vecin.size() / static_cast(reqSize); // simple linear transformation algorithm // for example: @@ -62,7 +58,7 @@ QVector DownloadedPiecesBar::bitfieldToFloatVector(const QBitArray &vecin const float toR = (x + 1) * ratio; // C - integer - int fromC = fromR;// std::floor not needed + int fromC = fromR; // std::floor not needed int toC = std::ceil(toR); if (toC > vecin.size()) --toC; @@ -108,7 +104,7 @@ QVector DownloadedPiecesBar::bitfieldToFloatVector(const QBitArray &vecin value /= ratio; // float precision sometimes gives > 1, because in not possible to store irrational numbers - value = qMin(value, (float)1.0); + value = qMin(value, 1.0f); result[x] = value; } @@ -116,38 +112,19 @@ QVector DownloadedPiecesBar::bitfieldToFloatVector(const QBitArray &vecin return result; } -int DownloadedPiecesBar::mixTwoColors(int &rgb1, int &rgb2, float ratio) -{ - int r1 = qRed(rgb1); - int g1 = qGreen(rgb1); - int b1 = qBlue(rgb1); - - int r2 = qRed(rgb2); - int g2 = qGreen(rgb2); - int b2 = qBlue(rgb2); - - float ratio_n = 1.0 - ratio; - int r = (r1 * ratio_n) + (r2 * ratio); - int g = (g1 * ratio_n) + (g2 * ratio); - int b = (b1 * ratio_n) + (b2 * ratio); - - return qRgb(r, g, b); -} - -void DownloadedPiecesBar::updateImage() +bool DownloadedPiecesBar::updateImage(QImage &image) { // qDebug() << "updateImage"; - QImage image2(width() - 2, 1, QImage::Format_RGB888); + QImage image2(width() - 2 * borderWidth, 1, QImage::Format_RGB888); if (image2.isNull()) { qDebug() << "QImage image2() allocation failed, width():" << width(); - return; + return false; } if (m_pieces.isEmpty()) { - image2.fill(0xffffff); - m_image = image2; - update(); - return; + image2.fill(Qt::white); + image = image2; + return true; } QVector scaled_pieces = bitfieldToFloatVector(m_pieces, image2.width()); @@ -161,16 +138,17 @@ void DownloadedPiecesBar::updateImage() float fill_ratio = pieces2_val + pieces2_val_dl; float ratio = pieces2_val_dl / fill_ratio; - int mixedColor = mixTwoColors(m_pieceColor, m_dlPieceColor, ratio); - mixedColor = mixTwoColors(m_bgColor, mixedColor, fill_ratio); + QRgb mixedColor = mixTwoColors(pieceColor().rgb(), m_dlPieceColor.rgb(), ratio); + mixedColor = mixTwoColors(backgroundColor().rgb(), mixedColor, fill_ratio); image2.setPixel(x, 0, mixedColor); } else { - image2.setPixel(x, 0, m_pieceColors[pieces2_val * 255]); + image2.setPixel(x, 0, pieceColors()[pieces2_val * 255]); } } - m_image = image2; + image = image2; + return true; } void DownloadedPiecesBar::setProgress(const QBitArray &pieces, const QBitArray &downloadedPieces) @@ -178,53 +156,25 @@ void DownloadedPiecesBar::setProgress(const QBitArray &pieces, const QBitArray & m_pieces = pieces; m_downloadedPieces = downloadedPieces; - updateImage(); - update(); + requestImageUpdate(); } -void DownloadedPiecesBar::updatePieceColors() +void DownloadedPiecesBar::setColors(const QColor &background, const QColor &border, const QColor &complete, const QColor &incomplete) { - m_pieceColors = QVector(256); - for (int i = 0; i < 256; ++i) { - float ratio = (i / 255.0); - m_pieceColors[i] = mixTwoColors(m_bgColor, m_pieceColor, ratio); - } + m_dlPieceColor = incomplete; + base::setColors(background, border, complete); } void DownloadedPiecesBar::clear() { - m_image = QImage(); - update(); + m_pieces.clear(); + m_downloadedPieces.clear(); + base::clear(); } -void DownloadedPiecesBar::paintEvent(QPaintEvent*) +QString DownloadedPiecesBar::simpleToolTipText() const { - QPainter painter(this); - QRect imageRect(1, 1, width() - 2, height() - 2); - if (m_image.isNull()) { - painter.setBrush(Qt::white); - painter.drawRect(imageRect); - } - else { - if (m_image.width() != imageRect.width()) - updateImage(); - painter.drawImage(imageRect, m_image); - } - QPainterPath border; - border.addRect(0, 0, width() - 1, height() - 1); - - painter.setPen(m_borderColor); - painter.drawPath(border); -} - -void DownloadedPiecesBar::setColors(int background, int border, int complete, int incomplete) -{ - m_bgColor = background; - m_borderColor = border; - m_pieceColor = complete; - m_dlPieceColor = incomplete; - - updatePieceColors(); - updateImage(); - update(); + return tr("White: Missing pieces") + '\n' + + tr("Green: Partial pieces") + '\n' + + tr("Blue: Completed pieces") + '\n'; } diff --git a/src/gui/properties/downloadedpiecesbar.h b/src/gui/properties/downloadedpiecesbar.h index 1a637a734..137c0723d 100644 --- a/src/gui/properties/downloadedpiecesbar.h +++ b/src/gui/properties/downloadedpiecesbar.h @@ -32,54 +32,39 @@ #define DOWNLOADEDPIECESBAR_H #include -#include -#include #include #include -class DownloadedPiecesBar: public QWidget { +#include "piecesbar.h" + +class DownloadedPiecesBar: public PiecesBar +{ + using base = PiecesBar; Q_OBJECT Q_DISABLE_COPY(DownloadedPiecesBar) -private: - QImage m_image; +public: + DownloadedPiecesBar(QWidget *parent); - // I used values, because it should be possible to change colors in runtime + void setProgress(const QBitArray &pieces, const QBitArray &downloadedPieces); - // background color - int m_bgColor; - // border color - int m_borderColor; - // complete piece color - int m_pieceColor; - // incomplete piece color - int m_dlPieceColor; - // buffered 256 levels gradient from bg_color to piece_color - QVector m_pieceColors; + void setColors(const QColor &background, const QColor &border, const QColor &complete, const QColor &incomplete); - // last used bitfields, uses to better resize redraw - // TODO: make a diff pieces to new pieces and update only changed pixels, speedup when update > 20x faster - QBitArray m_pieces; - QBitArray m_downloadedPieces; + // PiecesBar interface + void clear() override; +private: // scale bitfield vector to float vector QVector bitfieldToFloatVector(const QBitArray &vecin, int reqSize); - // mix two colors by light model, ratio <0, 1> - int mixTwoColors(int &rgb1, int &rgb2, float ratio); - // draw new image and replace actual image - void updateImage(); + virtual bool updateImage(QImage &image) override; + QString simpleToolTipText() const override; -public: - DownloadedPiecesBar(QWidget *parent); - - void setProgress(const QBitArray &m_pieces, const QBitArray &downloadedPieces); - void updatePieceColors(); - void clear(); - - void setColors(int background, int border, int complete, int incomplete); - -protected: - void paintEvent(QPaintEvent *); + // incomplete piece color + QColor m_dlPieceColor; + // last used bitfields, uses to better resize redraw + // TODO: make a diff pieces to new pieces and update only changed pixels, speedup when update > 20x faster + QBitArray m_pieces; + QBitArray m_downloadedPieces; }; #endif // DOWNLOADEDPIECESBAR_H diff --git a/src/gui/properties/pieceavailabilitybar.cpp b/src/gui/properties/pieceavailabilitybar.cpp index 4b303a670..fcbf44bb2 100644 --- a/src/gui/properties/pieceavailabilitybar.cpp +++ b/src/gui/properties/pieceavailabilitybar.cpp @@ -28,20 +28,15 @@ * Contact : chris@qbittorrent.org */ +#include "pieceavailabilitybar.h" + #include + #include -#include "pieceavailabilitybar.h" PieceAvailabilityBar::PieceAvailabilityBar(QWidget *parent) - : QWidget(parent) + : base {parent} { - setToolTip(QString("%1\n%2").arg(tr("White: Unavailable pieces")).arg(tr("Blue: Available pieces"))); - - m_bgColor = 0xffffff; - m_borderColor = palette().color(QPalette::Dark).rgb(); - m_pieceColor = 0x0000ff; - - updatePieceColors(); } QVector PieceAvailabilityBar::intToFloatVector(const QVector &vecin, int reqSize) @@ -125,37 +120,18 @@ QVector PieceAvailabilityBar::intToFloatVector(const QVector &vecin, return result; } -int PieceAvailabilityBar::mixTwoColors(int &rgb1, int &rgb2, float ratio) -{ - int r1 = qRed(rgb1); - int g1 = qGreen(rgb1); - int b1 = qBlue(rgb1); - - int r2 = qRed(rgb2); - int g2 = qGreen(rgb2); - int b2 = qBlue(rgb2); - - float ratio_n = 1.0 - ratio; - int r = (r1 * ratio_n) + (r2 * ratio); - int g = (g1 * ratio_n) + (g2 * ratio); - int b = (b1 * ratio_n) + (b2 * ratio); - - return qRgb(r, g, b); -} - -void PieceAvailabilityBar::updateImage() +bool PieceAvailabilityBar::updateImage(QImage &image) { - QImage image2(width() - 2, 1, QImage::Format_RGB888); + QImage image2(width() - 2 * borderWidth, 1, QImage::Format_RGB888); if (image2.isNull()) { qDebug() << "QImage image2() allocation failed, width():" << width(); - return; + return false; } if (m_pieces.empty()) { - image2.fill(0xffffff); - m_image = image2; - update(); - return; + image2.fill(Qt::white); + image = image2; + return true; } QVector scaled_pieces = intToFloatVector(m_pieces, image2.width()); @@ -163,61 +139,32 @@ void PieceAvailabilityBar::updateImage() // filling image for (int x = 0; x < scaled_pieces.size(); ++x) { float pieces2_val = scaled_pieces.at(x); - image2.setPixel(x, 0, m_pieceColors[pieces2_val * 255]); + image2.setPixel(x, 0, pieceColors()[pieces2_val * 255]); } - m_image = image2; + image = image2; + return true; } void PieceAvailabilityBar::setAvailability(const QVector &avail) { m_pieces = avail; - updateImage(); - update(); -} - -void PieceAvailabilityBar::updatePieceColors() -{ - m_pieceColors = QVector(256); - for (int i = 0; i < 256; ++i) { - float ratio = (i / 255.0); - m_pieceColors[i] = mixTwoColors(m_bgColor, m_pieceColor, ratio); - } + requestImageUpdate(); } void PieceAvailabilityBar::clear() { - m_image = QImage(); - update(); + m_pieces.clear(); + base::clear(); } -void PieceAvailabilityBar::paintEvent(QPaintEvent*) +QString PieceAvailabilityBar::simpleToolTipText() const { - QPainter painter(this); - QRect imageRect(1, 1, width() - 2, height() - 2); - if (m_image.isNull()) { - painter.setBrush(Qt::white); - painter.drawRect(imageRect); - } - else { - if (m_image.width() != imageRect.width()) - updateImage(); - painter.drawImage(imageRect, m_image); - } - QPainterPath border; - border.addRect(0, 0, width() - 1, height() - 1); - - painter.setPen(m_borderColor); - painter.drawPath(border); + return tr("White: Unavailable pieces") + '\n' + + tr("Blue: Available pieces") + '\n'; } -void PieceAvailabilityBar::setColors(int background, int border, int available) +bool PieceAvailabilityBar::isFileNameCorrectionNeeded() const { - m_bgColor = background; - m_borderColor = border; - m_pieceColor = available; - - updatePieceColors(); - updateImage(); - update(); + return true; } diff --git a/src/gui/properties/pieceavailabilitybar.h b/src/gui/properties/pieceavailabilitybar.h index 90a0164af..98d98a31a 100644 --- a/src/gui/properties/pieceavailabilitybar.h +++ b/src/gui/properties/pieceavailabilitybar.h @@ -31,28 +31,26 @@ #ifndef PIECEAVAILABILITYBAR_H #define PIECEAVAILABILITYBAR_H -#include -#include -#include +#include "piecesbar.h" -class PieceAvailabilityBar: public QWidget +class PieceAvailabilityBar: public PiecesBar { + using base = PiecesBar; Q_OBJECT Q_DISABLE_COPY(PieceAvailabilityBar) -private: - QImage m_image; +public: + PieceAvailabilityBar(QWidget *parent); - // I used values, because it should be possible to change colors in runtime + void setAvailability(const QVector &avail); - // background color - int m_bgColor; - // border color - int m_borderColor; - // complete piece color - int m_pieceColor; - // buffered 256 levels gradient from bg_color to piece_color - QVector m_pieceColors; + // PiecesBar interface + void clear() override; + +private: + bool updateImage(QImage &image) override; + QString simpleToolTipText() const override; + bool isFileNameCorrectionNeeded() const override; // last used int vector, uses to better resize redraw // TODO: make a diff pieces to new pieces and update only changed pixels, speedup when update > 20x faster @@ -60,23 +58,6 @@ private: // scale int vector to float vector QVector intToFloatVector(const QVector &vecin, int reqSize); - - // mix two colors by light model, ratio <0, 1> - int mixTwoColors(int &rgb1, int &rgb2, float ratio); - // draw new image and replace actual image - void updateImage(); - -public: - PieceAvailabilityBar(QWidget *parent); - - void setAvailability(const QVector &avail); - void updatePieceColors(); - void clear(); - - void setColors(int background, int border, int available); - -protected: - void paintEvent(QPaintEvent*); }; #endif // PIECEAVAILABILITYBAR_H diff --git a/src/gui/properties/piecesbar.cpp b/src/gui/properties/piecesbar.cpp new file mode 100644 index 000000000..843c1dd62 --- /dev/null +++ b/src/gui/properties/piecesbar.cpp @@ -0,0 +1,334 @@ +/* + * Bittorrent Client using Qt and libtorrent. + * Copyright (C) 2016 Eugene Shalygin + * Copyright (C) 2006 Christophe Dumez + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * In addition, as a special exception, the copyright holders give permission to + * link this program with the OpenSSL project's "OpenSSL" library (or with + * modified versions of it that use the same license as the "OpenSSL" library), + * and distribute the linked executables. You must obey the GNU General Public + * License in all respects for all of the code used other than "OpenSSL". If you + * modify file(s), you may extend this exception to your version of the file(s), + * but you are not obligated to do so. If you do not wish to do so, delete this + * exception statement from your version. + */ + +#include "piecesbar.h" + +#include +#include +#include +#include +#include +#include + +#include "base/bittorrent/torrenthandle.h" +#include "base/utils/misc.h" + +namespace +{ + using ImageRange = IndexRange; + + // Computes approximate mapping from image scale (measured in pixels) onto the torrent contents scale (in pieces) + // However, taking the size of a screen to be ~ 1000 px and the torrent size larger than 10 MiB, the pointing error + // is well below 0.5 px and thus is negligible. + class PieceIndexToImagePos + { + public: + PieceIndexToImagePos(const BitTorrent::TorrentInfo &torrentInfo, const QImage &image) + : m_bytesPerPixel {image.width() > 0 ? torrentInfo.totalSize() / image.width() : -1} + , m_torrentInfo {torrentInfo} + { + if ((m_bytesPerPixel > 0) && (m_bytesPerPixel < 10)) + qDebug() << "PieceIndexToImagePos: torrent size is too small for correct computaions." + << "Torrent size =" << torrentInfo.totalSize() << "Image width = " << image.width(); + } + + ImageRange imagePos(const BitTorrent::TorrentInfo::PieceRange &pieces) const + { + if (m_bytesPerPixel < 0) + return {0, 0}; + + // the type conversion is used to prevent integer overflow with torrents of 2+ GiB size + const qlonglong pieceLength = m_torrentInfo.pieceLength(); + return makeInterval( + (pieces.first() * pieceLength) / m_bytesPerPixel, + (pieces.last() * pieceLength + m_torrentInfo.pieceLength(pieces.last()) - 1) / m_bytesPerPixel); + } + + int pieceIndex(int imagePos) const + { + return m_bytesPerPixel < 0 ? 0 : (imagePos * m_bytesPerPixel + m_bytesPerPixel / 2) / m_torrentInfo.pieceLength(); + } + + private: + const qlonglong m_bytesPerPixel; // how many bytes of the torrent are squeezed into a bar's pixel + const BitTorrent::TorrentInfo m_torrentInfo; + }; + + class DetailedTooltipRenderer + { + public: + DetailedTooltipRenderer(QTextStream &stream, const QString &header) + : m_stream(stream) + { + m_stream << header + << R"()"; + } + + ~DetailedTooltipRenderer() + { + m_stream << "
"; + } + + void operator()(const QString &size, const QString &path) + { + m_stream << R"()" << size << "" << path << ""; + } + + private: + QTextStream &m_stream; + }; +} + +PiecesBar::PiecesBar(QWidget *parent) + : QWidget {parent} + , m_torrent {nullptr} + , m_borderColor {palette().color(QPalette::Dark)} + , m_bgColor {Qt::white} + , m_pieceColor {Qt::blue} + , m_hovered {false} +{ + updatePieceColors(); + setMouseTracking(true); +} + +void PiecesBar::setTorrent(BitTorrent::TorrentHandle *torrent) +{ + m_torrent = torrent; + if (!m_torrent) + clear(); +} + +void PiecesBar::clear() +{ + m_image = QImage(); + update(); +} + +void PiecesBar::setColors(const QColor &background, const QColor &border, const QColor &complete) +{ + m_bgColor = background; + m_borderColor = border; + m_pieceColor = complete; + + updatePieceColors(); + requestImageUpdate(); +} + +bool PiecesBar::event(QEvent *e) +{ + if (e->type() == QEvent::ToolTip) { + showToolTip(static_cast(e)); + return true; + } + else { + return base::event(e); + } +} + +void PiecesBar::enterEvent(QEvent *e) +{ + m_hovered = true; + base::enterEvent(e); +} + +void PiecesBar::leaveEvent(QEvent *e) +{ + m_hovered = false; + m_highlitedRegion = QRect(); + requestImageUpdate(); + base::leaveEvent(e); +} + +void PiecesBar::mouseMoveEvent(QMouseEvent *e) +{ + // if user pointed to a piece which is a part of a single large file, + // we highlight the space, occupied by this file + highlightFile(e->pos().x() - borderWidth); + base::mouseMoveEvent(e); +} + +void PiecesBar::paintEvent(QPaintEvent *) +{ + QPainter painter(this); + QRect imageRect(borderWidth, borderWidth, width() - 2 * borderWidth, height() - 2 * borderWidth); + if (m_image.isNull()) { + painter.setBrush(Qt::white); + painter.drawRect(imageRect); + } + else { + if (m_image.width() != imageRect.width()) + updateImage(m_image); + painter.drawImage(imageRect, m_image); + } + + if (!m_highlitedRegion.isNull()) { + QColor highlightColor {this->palette().color(QPalette::Active, QPalette::Highlight)}; + highlightColor.setAlphaF(0.35); + QRect targetHighlightRect {m_highlitedRegion.adjusted(borderWidth, borderWidth, borderWidth, height() - 2 * borderWidth)}; + painter.fillRect(targetHighlightRect, highlightColor); + } + + QPainterPath border; + border.addRect(0, 0, width(), height()); + painter.setPen(m_borderColor); + painter.drawPath(border); +} + +void PiecesBar::requestImageUpdate() +{ + if (updateImage(m_image)) + update(); +} + +QColor PiecesBar::backgroundColor() const +{ + return m_bgColor; +} + +QColor PiecesBar::borderColor() const +{ + return m_borderColor; +} + +QColor PiecesBar::pieceColor() const +{ + return m_pieceColor; +} + +const QVector &PiecesBar::pieceColors() const +{ + return m_pieceColors; +} + +QRgb PiecesBar::mixTwoColors(QRgb rgb1, QRgb rgb2, float ratio) +{ + int r1 = qRed(rgb1); + int g1 = qGreen(rgb1); + int b1 = qBlue(rgb1); + + int r2 = qRed(rgb2); + int g2 = qGreen(rgb2); + int b2 = qBlue(rgb2); + + float ratioN = 1.0f - ratio; + int r = (r1 * ratioN) + (r2 * ratio); + int g = (g1 * ratioN) + (g2 * ratio); + int b = (b1 * ratioN) + (b2 * ratio); + + return qRgb(r, g, b); +} + +void PiecesBar::showToolTip(const QHelpEvent *e) +{ + if (!m_torrent) + return; + + QString toolTipText; + QTextStream stream(&toolTipText, QIODevice::WriteOnly); + bool showDetailedInformation = QApplication::keyboardModifiers().testFlag(Qt::ShiftModifier); + if (showDetailedInformation) { + stream << ""; + const int imagePos = e->pos().x() - borderWidth; + if ((imagePos >=0) && (imagePos < m_image.width())) { + PieceIndexToImagePos transform {m_torrent->info(), m_image}; + int pieceIndex = transform.pieceIndex(e->pos().x() - borderWidth); + QVector files {m_torrent->info().fileIndicesForPiece(pieceIndex)}; + + QString tooltipTitle; + if (files.count() > 1) { + tooltipTitle = tr("Files in this piece:"); + } + else { + if (m_torrent->info().fileSize(files.front()) == m_torrent->info().pieceLength(pieceIndex)) + tooltipTitle = tr("File in this piece"); + else + tooltipTitle = tr("File in these pieces"); + } + + DetailedTooltipRenderer renderer(stream, tooltipTitle); + + const bool isFileNameCorrectionNeeded = this->isFileNameCorrectionNeeded(); + for (int f: files) { + QString filePath {m_torrent->info().filePath(f)}; + if (isFileNameCorrectionNeeded) + filePath.replace(QLatin1String("/.unwanted"), QString()); + + renderer(Utils::Misc::friendlyUnit(m_torrent->info().fileSize(f)), filePath); + } + } + stream << ""; + } + else { + stream << simpleToolTipText(); + stream << '\n' << tr("Hold Shift key for detailed information"); + } + + stream.flush(); + + QToolTip::showText(e->globalPos(), toolTipText, this); +} + +void PiecesBar::highlightFile(int imagePos) +{ + if (!m_torrent || (imagePos < 0) || (imagePos >= m_image.width())) + return; + + PieceIndexToImagePos transform {m_torrent->info(), m_image}; + + int pieceIndex = transform.pieceIndex(imagePos); + QVector fileIndices {m_torrent->info().fileIndicesForPiece(pieceIndex)}; + if (fileIndices.count() == 1) { + BitTorrent::TorrentInfo::PieceRange filePieces = m_torrent->info().filePieces(fileIndices.first()); + + ImageRange imageRange = transform.imagePos(filePieces); + QRect newHighlitedRegion {imageRange.first(), 0, imageRange.size(), m_image.height()}; + if (newHighlitedRegion != m_highlitedRegion) { + m_highlitedRegion = newHighlitedRegion; + update(); + } + } + else if (!m_highlitedRegion.isEmpty()) { + m_highlitedRegion = QRect(); + update(); + } +} + +void PiecesBar::updatePieceColors() +{ + m_pieceColors = QVector(256); + for (int i = 0; i < 256; ++i) { + float ratio = (i / 255.0); + m_pieceColors[i] = mixTwoColors(backgroundColor().rgb(), m_pieceColor.rgb(), ratio); + } +} + +bool PiecesBar::isFileNameCorrectionNeeded() const +{ + return false; +} diff --git a/src/gui/properties/piecesbar.h b/src/gui/properties/piecesbar.h new file mode 100644 index 000000000..92f81a80e --- /dev/null +++ b/src/gui/properties/piecesbar.h @@ -0,0 +1,109 @@ +/* + * Bittorrent Client using Qt and libtorrent. + * Copyright (C) 2016 Eugene Shalygin + * Copyright (C) 2006 Christophe Dumez + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * In addition, as a special exception, the copyright holders give permission to + * link this program with the OpenSSL project's "OpenSSL" library (or with + * modified versions of it that use the same license as the "OpenSSL" library), + * and distribute the linked executables. You must obey the GNU General Public + * License in all respects for all of the code used other than "OpenSSL". If you + * modify file(s), you may extend this exception to your version of the file(s), + * but you are not obligated to do so. If you do not wish to do so, delete this + * exception statement from your version. + */ + +#ifndef PIECESBAR_H +#define PIECESBAR_H + +#include +#include +#include + +class QHelpEvent; + +namespace BitTorrent +{ + class TorrentHandle; +} + +class PiecesBar: public QWidget +{ + using base = QWidget; + Q_OBJECT + Q_DISABLE_COPY(PiecesBar) + +public: + explicit PiecesBar(QWidget *parent = nullptr); + + void setTorrent(BitTorrent::TorrentHandle *torrent); + void setColors(const QColor &background, const QColor &border, const QColor &complete); + + virtual void clear(); + + // QObject interface + virtual bool event(QEvent*) override; + +protected: + // QWidget interface + void enterEvent(QEvent*) override; + void leaveEvent(QEvent*) override; + void mouseMoveEvent(QMouseEvent*) override; + + void paintEvent(QPaintEvent*) override; + void requestImageUpdate(); + + QColor backgroundColor() const; + QColor borderColor() const; + QColor pieceColor() const; + const QVector &pieceColors() const; + + // mix two colors by light model, ratio <0, 1> + static QRgb mixTwoColors(QRgb rgb1, QRgb rgb2, float ratio); + + static constexpr int borderWidth = 1; + +private: + void showToolTip(const QHelpEvent*); + void highlightFile(int imagePos); + + virtual QString simpleToolTipText() const = 0; + + /// whether to perform removing of ".unwanted" directory from paths + virtual bool isFileNameCorrectionNeeded() const; + + // draw new image to replace the actual image + // returns true if image was successfully updated + virtual bool updateImage(QImage &image) = 0; + void updatePieceColors(); + + const BitTorrent::TorrentHandle *m_torrent; + QImage m_image; + // I used values, because it should be possible to change colors at run time + // border color + QColor m_borderColor; + // background color + QColor m_bgColor; + // complete piece color + QColor m_pieceColor; + // buffered 256 levels gradient from bg_color to piece_color + QVector m_pieceColors; + bool m_hovered; + QRect m_highlitedRegion; //!< part of the bar can be highlighted; this rectangle is in the same frame as m_image +}; + +#endif // PIECESBAR_H diff --git a/src/gui/properties/properties.pri b/src/gui/properties/properties.pri index 0535e4634..da380922a 100644 --- a/src/gui/properties/properties.pri +++ b/src/gui/properties/properties.pri @@ -16,7 +16,8 @@ HEADERS += $$PWD/propertieswidget.h \ $$PWD/pieceavailabilitybar.h \ $$PWD/proptabbar.h \ $$PWD/speedwidget.h \ - $$PWD/speedplotview.h + $$PWD/speedplotview.h \ + $$PWD/piecesbar.h SOURCES += $$PWD/propertieswidget.cpp \ $$PWD/proplistdelegate.cpp \ @@ -28,4 +29,5 @@ SOURCES += $$PWD/propertieswidget.cpp \ $$PWD/pieceavailabilitybar.cpp \ $$PWD/proptabbar.cpp \ $$PWD/speedwidget.cpp \ - $$PWD/speedplotview.cpp + $$PWD/speedplotview.cpp \ + $$PWD/piecesbar.cpp diff --git a/src/gui/properties/propertieswidget.cpp b/src/gui/properties/propertieswidget.cpp index 7c66b2d7f..483ebf69a 100644 --- a/src/gui/properties/propertieswidget.cpp +++ b/src/gui/properties/propertieswidget.cpp @@ -298,6 +298,8 @@ void PropertiesWidget::loadTorrentInfos(BitTorrent::TorrentHandle *const torrent { clear(); m_torrent = torrent; + downloaded_pieces->setTorrent(m_torrent); + pieces_availability->setTorrent(m_torrent); if (!m_torrent) return; // Save path