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/torrenthandle.cpp b/src/base/bittorrent/torrenthandle.cpp index f8211278b..fce7c2359 100644 --- a/src/base/bittorrent/torrenthandle.cpp +++ b/src/base/bittorrent/torrenthandle.cpp @@ -597,25 +597,6 @@ QStringList TorrentHandle::absoluteFilePathsUnwanted() const return res; } -QPair TorrentHandle::fileExtremityPieces(int index) const -{ - if (!hasMetadata()) return qMakePair(-1, -1); - - const int numPieces = piecesCount(); - const qlonglong pieceSize = pieceLength(); - - // Determine the first and last piece of the file - int firstPiece = floor((m_torrentInfo.fileOffset(index) + 1) / (float) pieceSize); - Q_ASSERT((firstPiece >= 0) && (firstPiece < numPieces)); - - int numPiecesInFile = ceil(fileSize(index) / (float) pieceSize); - int lastPiece = firstPiece + numPiecesInFile - 1; - Q_ASSERT((lastPiece >= 0) && (lastPiece < numPieces)); - - Q_UNUSED(numPieces) - return qMakePair(firstPiece, lastPiece); -} - QVector TorrentHandle::filePriorities() const { std::vector fp; @@ -733,13 +714,13 @@ bool TorrentHandle::hasFirstLastPiecePriority() const std::vector fp; SAFE_GET(fp, file_priorities); - QPair extremities; + TorrentInfo::PieceRange extremities; bool found = false; int count = static_cast(fp.size()); for (int i = 0; i < count; ++i) { const QString ext = Utils::Fs::fileExtension(filePath(i)); if (Utils::Misc::isPreviewable(ext) && (fp[i] > 0)) { - extremities = fileExtremityPieces(i); + extremities = info().filePieces(i); found = true; break; } @@ -749,8 +730,8 @@ bool TorrentHandle::hasFirstLastPiecePriority() const int first = 0; int last = 0; - SAFE_GET(first, piece_priority, extremities.first); - SAFE_GET(last, piece_priority, extremities.second); + SAFE_GET(first, piece_priority, extremities.first()); + SAFE_GET(last, piece_priority, extremities.last()); return ((first == 7) && (last == 7)); } @@ -1245,13 +1226,13 @@ void TorrentHandle::setFirstLastPiecePriority(bool b) // Determine the priority to set int prio = b ? 7 : fp[index]; - QPair extremities = fileExtremityPieces(index); + TorrentInfo::PieceRange extremities = info().filePieces(index); // worst case: AVI index = 1% of total file size (at the end of the file) int nNumPieces = ceil(fileSize(index) * 0.01 / pieceLength()); for (int i = 0; i < nNumPieces; ++i) { - pp[extremities.first + i] = prio; - pp[extremities.second - i] = prio; + pp[extremities.first() + i] = prio; + pp[extremities.last() - i] = prio; } } } diff --git a/src/base/bittorrent/torrenthandle.h b/src/base/bittorrent/torrenthandle.h index e2c3f1e5e..aad66f001 100644 --- a/src/base/bittorrent/torrenthandle.h +++ b/src/base/bittorrent/torrenthandle.h @@ -247,7 +247,6 @@ namespace BitTorrent qlonglong fileSize(int index) const; QStringList absoluteFilePaths() const; QStringList absoluteFilePathsUnwanted() const; - QPair fileExtremityPieces(int index) const; QVector filePriorities() const; TorrentInfo info() const; 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 4d1aee7f1..4ecdb82f9 100644 --- a/src/gui/properties/downloadedpiecesbar.cpp +++ b/src/gui/properties/downloadedpiecesbar.cpp @@ -28,216 +28,153 @@ * 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) { - QVector result(reqSize, 0.0); - if (vecin.isEmpty()) return result; - - const float ratio = vecin.size() / (float)reqSize; - - // simple linear transformation algorithm - // for example: - // image.x(0) = pieces.x(0.0 >= x < 1.7) - // image.x(1) = pieces.x(1.7 >= x < 3.4) - - for (int x = 0; x < reqSize; ++x) { - // R - real - const float fromR = x * ratio; - const float toR = (x + 1) * ratio; - - // C - integer - int fromC = fromR;// std::floor not needed - int toC = std::ceil(toR); - if (toC > vecin.size()) - --toC; - - // position in pieces table - int x2 = fromC; - - // little speed up for really big pieces table, 10K+ size - const int toCMinusOne = toC - 1; - - // value in returned vector - float value = 0; - - // case when calculated range is (15.2 >= x < 15.7) - if (x2 == toCMinusOne) { - if (vecin[x2]) { - value += ratio; - } - ++x2; - } - // case when (15.2 >= x < 17.8) - else { - // subcase (15.2 >= x < 16) - if (x2 != fromR) { - if (vecin[x2]) { - value += 1.0 - (fromR - fromC); - } - ++x2; - } + QVector result(reqSize, 0.0); + if (vecin.isEmpty()) return result; - // subcase (16 >= x < 17) - for (; x2 < toCMinusOne; ++x2) { - if (vecin[x2]) { - value += 1.0; - } - } + const float ratio = vecin.size() / static_cast(reqSize); - // subcase (17 >= x < 17.8) - if (x2 == toCMinusOne) { - if (vecin[x2]) { - value += 1.0 - (toC - toR); - } - ++x2; - } - } + // simple linear transformation algorithm + // for example: + // image.x(0) = pieces.x(0.0 >= x < 1.7) + // image.x(1) = pieces.x(1.7 >= x < 3.4) - // normalization <0, 1> - value /= ratio; + for (int x = 0; x < reqSize; ++x) { + // R - real + const float fromR = x * ratio; + const float toR = (x + 1) * ratio; - // float precision sometimes gives > 1, because in not possible to store irrational numbers - value = qMin(value, (float)1.0); + // C - integer + int fromC = fromR; // std::floor not needed + int toC = std::ceil(toR); + if (toC > vecin.size()) + --toC; - result[x] = value; - } + // position in pieces table + int x2 = fromC; - return result; -} + // little speed up for really big pieces table, 10K+ size + const int toCMinusOne = toC - 1; + // value in returned vector + float value = 0; -int DownloadedPiecesBar::mixTwoColors(int &rgb1, int &rgb2, float ratio) -{ - int r1 = qRed(rgb1); - int g1 = qGreen(rgb1); - int b1 = qBlue(rgb1); + // case when calculated range is (15.2 >= x < 15.7) + if (x2 == toCMinusOne) { + if (vecin[x2]) + value += ratio; + ++x2; + } + // case when (15.2 >= x < 17.8) + else { + // subcase (15.2 >= x < 16) + if (x2 != fromR) { + if (vecin[x2]) + value += 1.0 - (fromR - fromC); + ++x2; + } + + // subcase (16 >= x < 17) + for (; x2 < toCMinusOne; ++x2) + if (vecin[x2]) + value += 1.0; + + // subcase (17 >= x < 17.8) + if (x2 == toCMinusOne) { + if (vecin[x2]) + value += 1.0 - (toC - toR); + ++x2; + } + } - int r2 = qRed(rgb2); - int g2 = qGreen(rgb2); - int b2 = qBlue(rgb2); + // normalization <0, 1> + value /= ratio; - 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); + // float precision sometimes gives > 1, because in not possible to store irrational numbers + value = qMin(value, 1.0f); - return qRgb(r, g, b); + result[x] = value; + } + + return result; } -void DownloadedPiecesBar::updateImage() +bool DownloadedPiecesBar::updateImage(QImage &image) { - // qDebug() << "updateImage"; - QImage image2(width() - 2, 1, QImage::Format_RGB888); - if (image2.isNull()) { - qDebug() << "QImage image2() allocation failed, width():" << width(); - return; - } - - if (m_pieces.isEmpty()) { - image2.fill(0xffffff); - m_image = image2; - update(); - return; - } - - QVector scaled_pieces = bitfieldToFloatVector(m_pieces, image2.width()); - QVector scaled_pieces_dl = bitfieldToFloatVector(m_downloadedPieces, image2.width()); - - // filling image - for (int x = 0; x < scaled_pieces.size(); ++x) - { - float pieces2_val = scaled_pieces.at(x); - float pieces2_val_dl = scaled_pieces_dl.at(x); - if (pieces2_val_dl != 0) - { - 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); - - image2.setPixel(x, 0, mixedColor); + // qDebug() << "updateImage"; + QImage image2(width() - 2 * borderWidth, 1, QImage::Format_RGB888); + if (image2.isNull()) { + qDebug() << "QImage image2() allocation failed, width():" << width(); + return false; } - else - { - image2.setPixel(x, 0, m_pieceColors[pieces2_val * 255]); + + if (m_pieces.isEmpty()) { + image2.fill(Qt::white); + image = image2; + return true; } - } - m_image = image2; + + QVector scaled_pieces = bitfieldToFloatVector(m_pieces, image2.width()); + QVector scaled_pieces_dl = bitfieldToFloatVector(m_downloadedPieces, image2.width()); + + // filling image + for (int x = 0; x < scaled_pieces.size(); ++x) { + float pieces2_val = scaled_pieces.at(x); + float pieces2_val_dl = scaled_pieces_dl.at(x); + if (pieces2_val_dl != 0) { + float fill_ratio = pieces2_val + pieces2_val_dl; + float ratio = pieces2_val_dl / 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, pieceColors()[pieces2_val * 255]); + } + } + image = image2; + return true; } void DownloadedPiecesBar::setProgress(const QBitArray &pieces, const QBitArray &downloadedPieces) { - m_pieces = pieces; - m_downloadedPieces = downloadedPieces; + 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); + return tr("White: Missing pieces") + '\n' + + tr("Green: Partial pieces") + '\n' + + tr("Blue: Completed pieces") + '\n'; } - -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(); -} - - diff --git a/src/gui/properties/downloadedpiecesbar.h b/src/gui/properties/downloadedpiecesbar.h index 74ce1a5cf..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 { - Q_OBJECT - Q_DISABLE_COPY(DownloadedPiecesBar) +#include "piecesbar.h" -private: - QImage m_image; - - // I used values, because it should be possible to change colors in runtime - - // 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; +class DownloadedPiecesBar: public PiecesBar +{ + using base = PiecesBar; + Q_OBJECT + Q_DISABLE_COPY(DownloadedPiecesBar) - // 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; +public: + DownloadedPiecesBar(QWidget *parent); - // 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(); + void setProgress(const QBitArray &pieces, const QBitArray &downloadedPieces); -public: - DownloadedPiecesBar(QWidget *parent); + void setColors(const QColor &background, const QColor &border, const QColor &complete, const QColor &incomplete); - void setProgress(const QBitArray &m_pieces, const QBitArray &downloadedPieces); - void updatePieceColors(); - void clear(); + // PiecesBar interface + void clear() override; - void setColors(int background, int border, int complete, int incomplete); +private: + // scale bitfield vector to float vector + QVector bitfieldToFloatVector(const QBitArray &vecin, int reqSize); + virtual bool updateImage(QImage &image) override; + QString simpleToolTipText() const override; -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 07dadf0bc..fcbf44bb2 100644 --- a/src/gui/properties/pieceavailabilitybar.cpp +++ b/src/gui/properties/pieceavailabilitybar.cpp @@ -28,21 +28,15 @@ * Contact : chris@qbittorrent.org */ -#include -#include #include "pieceavailabilitybar.h" +#include + +#include 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) @@ -126,37 +120,18 @@ QVector PieceAvailabilityBar::intToFloatVector(const QVector &vecin, return result; } -int PieceAvailabilityBar::mixTwoColors(int &rgb1, int &rgb2, float ratio) +bool PieceAvailabilityBar::updateImage(QImage &image) { - 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() -{ - 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()); @@ -164,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 6d8e2142b..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 8948a020f..483ebf69a 100644 --- a/src/gui/properties/propertieswidget.cpp +++ b/src/gui/properties/propertieswidget.cpp @@ -28,6 +28,8 @@ * Contact : chris@qbittorrent.org */ +#include "propertieswidget.h" + #include #include #include @@ -61,219 +63,223 @@ #include "lineedit.h" #include "transferlistwidget.h" #include "autoexpandabledialog.h" -#include "propertieswidget.h" -PropertiesWidget::PropertiesWidget(QWidget *parent, MainWindow* main_window, TransferListWidget *transferList): - QWidget(parent), transferList(transferList), main_window(main_window), m_torrent(0) { - setupUi(this); - setAutoFillBackground(true); - - state = VISIBLE; - - // Set Properties list model - PropListModel = new TorrentContentFilterModel(); - filesList->setModel(PropListModel); - PropDelegate = new PropListDelegate(this); - filesList->setItemDelegate(PropDelegate); - filesList->setSortingEnabled(true); - // Torrent content filtering - m_contentFilterLine = new LineEdit(this); - m_contentFilterLine->setPlaceholderText(tr("Filter files...")); - m_contentFilterLine->setMaximumSize(300, m_contentFilterLine->size().height()); - connect(m_contentFilterLine, SIGNAL(textChanged(QString)), this, SLOT(filterText(QString))); - contentFilterLayout->insertWidget(3, m_contentFilterLine); - - // SIGNAL/SLOTS - connect(filesList, SIGNAL(clicked(const QModelIndex&)), filesList, SLOT(edit(const QModelIndex&))); - connect(selectAllButton, SIGNAL(clicked()), PropListModel, SLOT(selectAll())); - connect(selectNoneButton, SIGNAL(clicked()), PropListModel, SLOT(selectNone())); - connect(filesList, SIGNAL(customContextMenuRequested(const QPoint&)), this, SLOT(displayFilesListMenu(const QPoint&))); - connect(filesList, SIGNAL(doubleClicked(const QModelIndex &)), this, SLOT(openDoubleClickedFile(const QModelIndex &))); - connect(PropListModel, SIGNAL(filteredFilesChanged()), this, SLOT(filteredFilesChanged())); - connect(listWebSeeds, SIGNAL(customContextMenuRequested(const QPoint&)), this, SLOT(displayWebSeedListMenu(const QPoint&))); - connect(transferList, SIGNAL(currentTorrentChanged(BitTorrent::TorrentHandle *const)), this, SLOT(loadTorrentInfos(BitTorrent::TorrentHandle *const))); - connect(PropDelegate, SIGNAL(filteredFilesChanged()), this, SLOT(filteredFilesChanged())); - connect(stackedProperties, SIGNAL(currentChanged(int)), this, SLOT(loadDynamicData())); - connect(BitTorrent::Session::instance(), SIGNAL(torrentSavePathChanged(BitTorrent::TorrentHandle *const)), this, SLOT(updateSavePath(BitTorrent::TorrentHandle *const))); - connect(BitTorrent::Session::instance(), SIGNAL(torrentMetadataLoaded(BitTorrent::TorrentHandle *const)), this, SLOT(updateTorrentInfos(BitTorrent::TorrentHandle *const))); - connect(filesList->header(), SIGNAL(sectionMoved(int, int, int)), this, SLOT(saveSettings())); - connect(filesList->header(), SIGNAL(sectionResized(int, int, int)), this, SLOT(saveSettings())); - connect(filesList->header(), SIGNAL(sortIndicatorChanged(int, Qt::SortOrder)), this, SLOT(saveSettings())); +PropertiesWidget::PropertiesWidget(QWidget *parent, MainWindow *main_window, TransferListWidget *transferList) + : QWidget(parent), transferList(transferList), main_window(main_window), m_torrent(0) +{ + setupUi(this); + setAutoFillBackground(true); + + state = VISIBLE; + + // Set Properties list model + PropListModel = new TorrentContentFilterModel(); + filesList->setModel(PropListModel); + PropDelegate = new PropListDelegate(this); + filesList->setItemDelegate(PropDelegate); + filesList->setSortingEnabled(true); + // Torrent content filtering + m_contentFilterLine = new LineEdit(this); + m_contentFilterLine->setPlaceholderText(tr("Filter files...")); + m_contentFilterLine->setMaximumSize(300, m_contentFilterLine->size().height()); + connect(m_contentFilterLine, SIGNAL(textChanged(QString)), this, SLOT(filterText(QString))); + contentFilterLayout->insertWidget(3, m_contentFilterLine); + + // SIGNAL/SLOTS + connect(filesList, SIGNAL(clicked(const QModelIndex&)), filesList, SLOT(edit(const QModelIndex&))); + connect(selectAllButton, SIGNAL(clicked()), PropListModel, SLOT(selectAll())); + connect(selectNoneButton, SIGNAL(clicked()), PropListModel, SLOT(selectNone())); + connect(filesList, SIGNAL(customContextMenuRequested(const QPoint&)), this, SLOT(displayFilesListMenu(const QPoint&))); + connect(filesList, SIGNAL(doubleClicked(const QModelIndex&)), this, SLOT(openDoubleClickedFile(const QModelIndex&))); + connect(PropListModel, SIGNAL(filteredFilesChanged()), this, SLOT(filteredFilesChanged())); + connect(listWebSeeds, SIGNAL(customContextMenuRequested(const QPoint&)), this, SLOT(displayWebSeedListMenu(const QPoint&))); + connect(transferList, SIGNAL(currentTorrentChanged(BitTorrent::TorrentHandle * const)), this, SLOT(loadTorrentInfos(BitTorrent::TorrentHandle * const))); + connect(PropDelegate, SIGNAL(filteredFilesChanged()), this, SLOT(filteredFilesChanged())); + connect(stackedProperties, SIGNAL(currentChanged(int)), this, SLOT(loadDynamicData())); + connect(BitTorrent::Session::instance(), SIGNAL(torrentSavePathChanged(BitTorrent::TorrentHandle * const)), this, SLOT(updateSavePath(BitTorrent::TorrentHandle * const))); + connect(BitTorrent::Session::instance(), SIGNAL(torrentMetadataLoaded(BitTorrent::TorrentHandle * const)), this, SLOT(updateTorrentInfos(BitTorrent::TorrentHandle * const))); + connect(filesList->header(), SIGNAL(sectionMoved(int,int,int)), this, SLOT(saveSettings())); + connect(filesList->header(), SIGNAL(sectionResized(int,int,int)), this, SLOT(saveSettings())); + connect(filesList->header(), SIGNAL(sortIndicatorChanged(int,Qt::SortOrder)), this, SLOT(saveSettings())); #ifdef QBT_USES_QT5 - // set bar height relative to screen dpi - int barHeight = devicePixelRatio() * 18; + // set bar height relative to screen dpi + int barHeight = devicePixelRatio() * 18; #else - // set bar height relative to font height - QFont defFont; - QFontMetrics fMetrics(defFont, 0); // need to be device-dependent - int barHeight = fMetrics.height() * 5 / 4; + // set bar height relative to font height + QFont defFont; + QFontMetrics fMetrics(defFont, 0); // need to be device-dependent + int barHeight = fMetrics.height() * 5 / 4; #endif - // Downloaded pieces progress bar - tempProgressBarArea->setVisible(false); - downloaded_pieces = new DownloadedPiecesBar(this); - groupBarLayout->addWidget(downloaded_pieces, 0, 1); - downloaded_pieces->setFixedHeight(barHeight); - downloaded_pieces->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed); - - // Pieces availability bar - tempAvailabilityBarArea->setVisible(false); - pieces_availability = new PieceAvailabilityBar(this); - groupBarLayout->addWidget(pieces_availability, 1, 1); - pieces_availability->setFixedHeight(barHeight); - pieces_availability->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed); - - // Tracker list - trackerList = new TrackerList(this); - trackerUpButton->setIcon(GuiIconProvider::instance()->getIcon("go-up")); - trackerUpButton->setIconSize(Utils::Misc::smallIconSize()); - trackerDownButton->setIcon(GuiIconProvider::instance()->getIcon("go-down")); - trackerDownButton->setIconSize(Utils::Misc::smallIconSize()); - connect(trackerUpButton, SIGNAL(clicked()), trackerList, SLOT(moveSelectionUp())); - connect(trackerDownButton, SIGNAL(clicked()), trackerList, SLOT(moveSelectionDown())); - horizontalLayout_trackers->insertWidget(0, trackerList); - connect(trackerList->header(), SIGNAL(sectionMoved(int, int, int)), trackerList, SLOT(saveSettings())); - connect(trackerList->header(), SIGNAL(sectionResized(int, int, int)), trackerList, SLOT(saveSettings())); - connect(trackerList->header(), SIGNAL(sortIndicatorChanged(int, Qt::SortOrder)), trackerList, SLOT(saveSettings())); - // Peers list - peersList = new PeerListWidget(this); - peerpage_layout->addWidget(peersList); - connect(peersList->header(), SIGNAL(sectionMoved(int, int, int)), peersList, SLOT(saveSettings())); - connect(peersList->header(), SIGNAL(sectionResized(int, int, int)), peersList, SLOT(saveSettings())); - connect(peersList->header(), SIGNAL(sortIndicatorChanged(int, Qt::SortOrder)), peersList, SLOT(saveSettings())); - // Speed widget - speedWidget = new SpeedWidget(this); - speed_layout->addWidget(speedWidget); - // Tab bar - m_tabBar = new PropTabBar(); - m_tabBar->setContentsMargins(0, 5, 0, 0); - verticalLayout->addLayout(m_tabBar); - connect(m_tabBar, SIGNAL(tabChanged(int)), stackedProperties, SLOT(setCurrentIndex(int))); - connect(m_tabBar, SIGNAL(tabChanged(int)), this, SLOT(saveSettings())); - connect(m_tabBar, SIGNAL(visibilityToggled(bool)), SLOT(setVisibility(bool))); - connect(m_tabBar, SIGNAL(visibilityToggled(bool)), this, SLOT(saveSettings())); - // Dynamic data refresher - refreshTimer = new QTimer(this); - connect(refreshTimer, SIGNAL(timeout()), this, SLOT(loadDynamicData())); - refreshTimer->start(3000); // 3sec - editHotkeyFile = new QShortcut(QKeySequence("F2"), filesList, 0, 0, Qt::WidgetShortcut); - connect(editHotkeyFile, SIGNAL(activated()), SLOT(renameSelectedFile())); - editHotkeyWeb = new QShortcut(QKeySequence("F2"), listWebSeeds, 0, 0, Qt::WidgetShortcut); - connect(editHotkeyWeb, SIGNAL(activated()), SLOT(editWebSeed())); - connect(listWebSeeds, SIGNAL(doubleClicked(QModelIndex)), SLOT(editWebSeed())); - deleteHotkeyWeb = new QShortcut(QKeySequence(QKeySequence::Delete), listWebSeeds, 0, 0, Qt::WidgetShortcut); - connect(deleteHotkeyWeb, SIGNAL(activated()), SLOT(deleteSelectedUrlSeeds())); - openHotkeyFile = new QShortcut(QKeySequence("Return"), filesList, 0, 0, Qt::WidgetShortcut); - connect(openHotkeyFile, SIGNAL(activated()), SLOT(openSelectedFile())); + // Downloaded pieces progress bar + tempProgressBarArea->setVisible(false); + downloaded_pieces = new DownloadedPiecesBar(this); + groupBarLayout->addWidget(downloaded_pieces, 0, 1); + downloaded_pieces->setFixedHeight(barHeight); + downloaded_pieces->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed); + + // Pieces availability bar + tempAvailabilityBarArea->setVisible(false); + pieces_availability = new PieceAvailabilityBar(this); + groupBarLayout->addWidget(pieces_availability, 1, 1); + pieces_availability->setFixedHeight(barHeight); + pieces_availability->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed); + + // Tracker list + trackerList = new TrackerList(this); + trackerUpButton->setIcon(GuiIconProvider::instance()->getIcon("go-up")); + trackerUpButton->setIconSize(Utils::Misc::smallIconSize()); + trackerDownButton->setIcon(GuiIconProvider::instance()->getIcon("go-down")); + trackerDownButton->setIconSize(Utils::Misc::smallIconSize()); + connect(trackerUpButton, SIGNAL(clicked()), trackerList, SLOT(moveSelectionUp())); + connect(trackerDownButton, SIGNAL(clicked()), trackerList, SLOT(moveSelectionDown())); + horizontalLayout_trackers->insertWidget(0, trackerList); + connect(trackerList->header(), SIGNAL(sectionMoved(int,int,int)), trackerList, SLOT(saveSettings())); + connect(trackerList->header(), SIGNAL(sectionResized(int,int,int)), trackerList, SLOT(saveSettings())); + connect(trackerList->header(), SIGNAL(sortIndicatorChanged(int,Qt::SortOrder)), trackerList, SLOT(saveSettings())); + // Peers list + peersList = new PeerListWidget(this); + peerpage_layout->addWidget(peersList); + connect(peersList->header(), SIGNAL(sectionMoved(int,int,int)), peersList, SLOT(saveSettings())); + connect(peersList->header(), SIGNAL(sectionResized(int,int,int)), peersList, SLOT(saveSettings())); + connect(peersList->header(), SIGNAL(sortIndicatorChanged(int,Qt::SortOrder)), peersList, SLOT(saveSettings())); + // Speed widget + speedWidget = new SpeedWidget(this); + speed_layout->addWidget(speedWidget); + // Tab bar + m_tabBar = new PropTabBar(); + m_tabBar->setContentsMargins(0, 5, 0, 0); + verticalLayout->addLayout(m_tabBar); + connect(m_tabBar, SIGNAL(tabChanged(int)), stackedProperties, SLOT(setCurrentIndex(int))); + connect(m_tabBar, SIGNAL(tabChanged(int)), this, SLOT(saveSettings())); + connect(m_tabBar, SIGNAL(visibilityToggled(bool)), SLOT(setVisibility(bool))); + connect(m_tabBar, SIGNAL(visibilityToggled(bool)), this, SLOT(saveSettings())); + // Dynamic data refresher + refreshTimer = new QTimer(this); + connect(refreshTimer, SIGNAL(timeout()), this, SLOT(loadDynamicData())); + refreshTimer->start(3000); // 3sec + editHotkeyFile = new QShortcut(QKeySequence("F2"), filesList, 0, 0, Qt::WidgetShortcut); + connect(editHotkeyFile, SIGNAL(activated()), SLOT(renameSelectedFile())); + editHotkeyWeb = new QShortcut(QKeySequence("F2"), listWebSeeds, 0, 0, Qt::WidgetShortcut); + connect(editHotkeyWeb, SIGNAL(activated()), SLOT(editWebSeed())); + connect(listWebSeeds, SIGNAL(doubleClicked(QModelIndex)), SLOT(editWebSeed())); + deleteHotkeyWeb = new QShortcut(QKeySequence(QKeySequence::Delete), listWebSeeds, 0, 0, Qt::WidgetShortcut); + connect(deleteHotkeyWeb, SIGNAL(activated()), SLOT(deleteSelectedUrlSeeds())); + openHotkeyFile = new QShortcut(QKeySequence("Return"), filesList, 0, 0, Qt::WidgetShortcut); + connect(openHotkeyFile, SIGNAL(activated()), SLOT(openSelectedFile())); } -PropertiesWidget::~PropertiesWidget() { - qDebug() << Q_FUNC_INFO << "ENTER"; - delete refreshTimer; - delete trackerList; - delete peersList; - delete speedWidget; - delete downloaded_pieces; - delete pieces_availability; - delete PropListModel; - delete PropDelegate; - delete m_tabBar; - delete editHotkeyFile; - delete editHotkeyWeb; - delete deleteHotkeyWeb; - delete openHotkeyFile; - qDebug() << Q_FUNC_INFO << "EXIT"; +PropertiesWidget::~PropertiesWidget() +{ + qDebug() << Q_FUNC_INFO << "ENTER"; + delete refreshTimer; + delete trackerList; + delete peersList; + delete speedWidget; + delete downloaded_pieces; + delete pieces_availability; + delete PropListModel; + delete PropDelegate; + delete m_tabBar; + delete editHotkeyFile; + delete editHotkeyWeb; + delete deleteHotkeyWeb; + delete openHotkeyFile; + qDebug() << Q_FUNC_INFO << "EXIT"; } -void PropertiesWidget::showPiecesAvailability(bool show) { - avail_pieces_lbl->setVisible(show); - pieces_availability->setVisible(show); - avail_average_lbl->setVisible(show); - if (show || (!show && !downloaded_pieces->isVisible())) - line_2->setVisible(show); +void PropertiesWidget::showPiecesAvailability(bool show) +{ + avail_pieces_lbl->setVisible(show); + pieces_availability->setVisible(show); + avail_average_lbl->setVisible(show); + if (show || (!show && !downloaded_pieces->isVisible())) + line_2->setVisible(show); } -void PropertiesWidget::showPiecesDownloaded(bool show) { - downloaded_pieces_lbl->setVisible(show); - downloaded_pieces->setVisible(show); - progress_lbl->setVisible(show); - if (show || (!show && !pieces_availability->isVisible())) - line_2->setVisible(show); +void PropertiesWidget::showPiecesDownloaded(bool show) +{ + downloaded_pieces_lbl->setVisible(show); + downloaded_pieces->setVisible(show); + progress_lbl->setVisible(show); + if (show || (!show && !pieces_availability->isVisible())) + line_2->setVisible(show); } -void PropertiesWidget::setVisibility(bool visible) { - if (!visible && state == VISIBLE) { - QSplitter *hSplitter = static_cast(parentWidget()); - stackedProperties->setVisible(false); - slideSizes = hSplitter->sizes(); - hSplitter->handle(1)->setVisible(false); - hSplitter->handle(1)->setDisabled(true); - QList sizes = QList() << hSplitter->geometry().height()-30 << 30; - hSplitter->setSizes(sizes); - state = REDUCED; - return; - } - - if (visible && state == REDUCED) { - stackedProperties->setVisible(true); - QSplitter *hSplitter = static_cast(parentWidget()); - hSplitter->handle(1)->setDisabled(false); - hSplitter->handle(1)->setVisible(true); - hSplitter->setSizes(slideSizes); - state = VISIBLE; - // Force refresh - loadDynamicData(); - } +void PropertiesWidget::setVisibility(bool visible) +{ + if (!visible && ( state == VISIBLE) ) { + QSplitter *hSplitter = static_cast(parentWidget()); + stackedProperties->setVisible(false); + slideSizes = hSplitter->sizes(); + hSplitter->handle(1)->setVisible(false); + hSplitter->handle(1)->setDisabled(true); + QList sizes = QList() << hSplitter->geometry().height() - 30 << 30; + hSplitter->setSizes(sizes); + state = REDUCED; + return; + } + + if (visible && ( state == REDUCED) ) { + stackedProperties->setVisible(true); + QSplitter *hSplitter = static_cast(parentWidget()); + hSplitter->handle(1)->setDisabled(false); + hSplitter->handle(1)->setVisible(true); + hSplitter->setSizes(slideSizes); + state = VISIBLE; + // Force refresh + loadDynamicData(); + } } -void PropertiesWidget::clear() { - qDebug("Clearing torrent properties"); - save_path->clear(); - lbl_creationDate->clear(); - label_total_pieces_val->clear(); - hash_lbl->clear(); - comment_text->clear(); - progress_lbl->clear(); - trackerList->clear(); - downloaded_pieces->clear(); - pieces_availability->clear(); - avail_average_lbl->clear(); - wasted->clear(); - upTotal->clear(); - dlTotal->clear(); - peersList->clear(); - lbl_uplimit->clear(); - lbl_dllimit->clear(); - lbl_elapsed->clear(); - lbl_connections->clear(); - reannounce_lbl->clear(); - shareRatio->clear(); - listWebSeeds->clear(); - m_contentFilterLine->clear(); - PropListModel->model()->clear(); - label_eta_val->clear(); - label_seeds_val->clear(); - label_peers_val->clear(); - label_dl_speed_val->clear(); - label_upload_speed_val->clear(); - label_total_size_val->clear(); - label_completed_on_val->clear(); - label_last_complete_val->clear(); - label_created_by_val->clear(); - label_added_on_val->clear(); +void PropertiesWidget::clear() +{ + qDebug("Clearing torrent properties"); + save_path->clear(); + lbl_creationDate->clear(); + label_total_pieces_val->clear(); + hash_lbl->clear(); + comment_text->clear(); + progress_lbl->clear(); + trackerList->clear(); + downloaded_pieces->clear(); + pieces_availability->clear(); + avail_average_lbl->clear(); + wasted->clear(); + upTotal->clear(); + dlTotal->clear(); + peersList->clear(); + lbl_uplimit->clear(); + lbl_dllimit->clear(); + lbl_elapsed->clear(); + lbl_connections->clear(); + reannounce_lbl->clear(); + shareRatio->clear(); + listWebSeeds->clear(); + m_contentFilterLine->clear(); + PropListModel->model()->clear(); + label_eta_val->clear(); + label_seeds_val->clear(); + label_peers_val->clear(); + label_dl_speed_val->clear(); + label_upload_speed_val->clear(); + label_total_size_val->clear(); + label_completed_on_val->clear(); + label_last_complete_val->clear(); + label_created_by_val->clear(); + label_added_on_val->clear(); } BitTorrent::TorrentHandle *PropertiesWidget::getCurrentTorrent() const { - return m_torrent; + return m_torrent; } void PropertiesWidget::updateSavePath(BitTorrent::TorrentHandle *const torrent) { - if (m_torrent == torrent) { - save_path->setText(Utils::Fs::toNativePath(m_torrent->savePath())); - } + if (m_torrent == torrent) + save_path->setText(Utils::Fs::toNativePath(m_torrent->savePath())); } void PropertiesWidget::loadTrackers(BitTorrent::TorrentHandle *const torrent) @@ -284,15 +290,17 @@ void PropertiesWidget::loadTrackers(BitTorrent::TorrentHandle *const torrent) void PropertiesWidget::updateTorrentInfos(BitTorrent::TorrentHandle *const torrent) { - if (m_torrent == torrent) - loadTorrentInfos(m_torrent); + if (m_torrent == torrent) + loadTorrentInfos(m_torrent); } void PropertiesWidget::loadTorrentInfos(BitTorrent::TorrentHandle *const torrent) { - clear(); - m_torrent = torrent; - if (!m_torrent) return; + clear(); + m_torrent = torrent; + downloaded_pieces->setTorrent(m_torrent); + pieces_availability->setTorrent(m_torrent); + if (!m_torrent) return; // Save path updateSavePath(m_torrent); @@ -300,91 +308,92 @@ void PropertiesWidget::loadTorrentInfos(BitTorrent::TorrentHandle *const torrent hash_lbl->setText(m_torrent->hash()); PropListModel->model()->clear(); if (m_torrent->hasMetadata()) { - // Creation date - lbl_creationDate->setText(m_torrent->creationDate().toString(Qt::DefaultLocaleShortDate)); + // Creation date + lbl_creationDate->setText(m_torrent->creationDate().toString(Qt::DefaultLocaleShortDate)); - label_total_size_val->setText(Utils::Misc::friendlyUnit(m_torrent->totalSize())); + label_total_size_val->setText(Utils::Misc::friendlyUnit(m_torrent->totalSize())); - // Comment - comment_text->setText(Utils::Misc::parseHtmlLinks(m_torrent->comment())); + // Comment + comment_text->setText(Utils::Misc::parseHtmlLinks(m_torrent->comment())); - // URL seeds - loadUrlSeeds(); + // URL seeds + loadUrlSeeds(); - label_created_by_val->setText(m_torrent->creator()); + label_created_by_val->setText(m_torrent->creator()); - // List files in torrent - PropListModel->model()->setupModelData(m_torrent->info()); - filesList->setExpanded(PropListModel->index(0, 0), true); + // List files in torrent + PropListModel->model()->setupModelData(m_torrent->info()); + filesList->setExpanded(PropListModel->index(0, 0), true); - // Load file priorities - PropListModel->model()->updateFilesPriorities(m_torrent->filePriorities()); + // Load file priorities + PropListModel->model()->updateFilesPriorities(m_torrent->filePriorities()); } - // Load dynamic data - loadDynamicData(); + // Load dynamic data + loadDynamicData(); } -void PropertiesWidget::readSettings() { - const Preferences* const pref = Preferences::instance(); - // Restore splitter sizes - QStringList sizes_str = pref->getPropSplitterSizes().split(","); - if (sizes_str.size() == 2) { - slideSizes << sizes_str.first().toInt(); - slideSizes << sizes_str.last().toInt(); - QSplitter *hSplitter = static_cast(parentWidget()); - hSplitter->setSizes(slideSizes); - } - const int current_tab = pref->getPropCurTab(); - const bool visible = pref->getPropVisible(); - // the following will call saveSettings but shouldn't change any state - if (!filesList->header()->restoreState(pref->getPropFileListState())) { - filesList->header()->resizeSection(0, 400); //Default - } - m_tabBar->setCurrentIndex(current_tab); - if (!visible) { - setVisibility(false); - } +void PropertiesWidget::readSettings() +{ + const Preferences *const pref = Preferences::instance(); + // Restore splitter sizes + QStringList sizes_str = pref->getPropSplitterSizes().split(","); + if (sizes_str.size() == 2) { + slideSizes << sizes_str.first().toInt(); + slideSizes << sizes_str.last().toInt(); + QSplitter *hSplitter = static_cast(parentWidget()); + hSplitter->setSizes(slideSizes); + } + const int current_tab = pref->getPropCurTab(); + const bool visible = pref->getPropVisible(); + // the following will call saveSettings but shouldn't change any state + if (!filesList->header()->restoreState(pref->getPropFileListState())) + filesList->header()->resizeSection(0, 400); // Default + m_tabBar->setCurrentIndex(current_tab); + if (!visible) + setVisibility(false); } -void PropertiesWidget::saveSettings() { - Preferences* const pref = Preferences::instance(); - pref->setPropVisible(state==VISIBLE); - // Splitter sizes - QSplitter *hSplitter = static_cast(parentWidget()); - QList sizes; - if (state == VISIBLE) - sizes = hSplitter->sizes(); - else - sizes = slideSizes; - qDebug("Sizes: %d", sizes.size()); - if (sizes.size() == 2) { - pref->setPropSplitterSizes(QString::number(sizes.first()) + ',' + QString::number(sizes.last())); - } - pref->setPropFileListState(filesList->header()->saveState()); - // Remember current tab - pref->setPropCurTab(m_tabBar->currentIndex()); +void PropertiesWidget::saveSettings() +{ + Preferences *const pref = Preferences::instance(); + pref->setPropVisible(state==VISIBLE); + // Splitter sizes + QSplitter *hSplitter = static_cast(parentWidget()); + QList sizes; + if (state == VISIBLE) + sizes = hSplitter->sizes(); + else + sizes = slideSizes; + qDebug("Sizes: %d", sizes.size()); + if (sizes.size() == 2) + pref->setPropSplitterSizes(QString::number(sizes.first()) + ',' + QString::number(sizes.last())); + pref->setPropFileListState(filesList->header()->saveState()); + // Remember current tab + pref->setPropCurTab(m_tabBar->currentIndex()); } -void PropertiesWidget::reloadPreferences() { - // Take program preferences into consideration - peersList->updatePeerHostNameResolutionState(); - peersList->updatePeerCountryResolutionState(); +void PropertiesWidget::reloadPreferences() +{ + // Take program preferences into consideration + peersList->updatePeerHostNameResolutionState(); + peersList->updatePeerCountryResolutionState(); } -void PropertiesWidget::loadDynamicData() { +void PropertiesWidget::loadDynamicData() +{ // Refresh only if the torrent handle is valid and if visible if (!m_torrent || (main_window->currentTabWidget() != transferList) || (state != VISIBLE)) return; // Transfer infos - switch(stackedProperties->currentIndex()) { + switch (stackedProperties->currentIndex()) { case PropTabBar::MAIN_TAB: { wasted->setText(Utils::Misc::friendlyUnit(m_torrent->wastedSize())); upTotal->setText(tr("%1 (%2 this session)").arg(Utils::Misc::friendlyUnit(m_torrent->totalUpload())) - .arg(Utils::Misc::friendlyUnit(m_torrent->totalPayloadUpload()))); + .arg(Utils::Misc::friendlyUnit(m_torrent->totalPayloadUpload()))); dlTotal->setText(tr("%1 (%2 this session)").arg(Utils::Misc::friendlyUnit(m_torrent->totalDownload())) - .arg(Utils::Misc::friendlyUnit(m_torrent->totalPayloadDownload()))); + .arg(Utils::Misc::friendlyUnit(m_torrent->totalPayloadDownload()))); lbl_uplimit->setText(m_torrent->uploadLimit() <= 0 ? QString::fromUtf8(C_INFINITY) : Utils::Misc::friendlyUnit(m_torrent->uploadLimit(), true)); @@ -393,15 +402,15 @@ void PropertiesWidget::loadDynamicData() { QString elapsed_txt; if (m_torrent->isSeed()) elapsed_txt = tr("%1 (seeded for %2)", "e.g. 4m39s (seeded for 3m10s)") - .arg(Utils::Misc::userFriendlyDuration(m_torrent->activeTime())) - .arg(Utils::Misc::userFriendlyDuration(m_torrent->seedingTime())); + .arg(Utils::Misc::userFriendlyDuration(m_torrent->activeTime())) + .arg(Utils::Misc::userFriendlyDuration(m_torrent->seedingTime())); else elapsed_txt = Utils::Misc::userFriendlyDuration(m_torrent->activeTime()); lbl_elapsed->setText(elapsed_txt); lbl_connections->setText(tr("%1 (%2 max)", "%1 and %2 are numbers, e.g. 3 (10 max)") - .arg(m_torrent->connectionsCount()) - .arg(m_torrent->connectionsLimit() < 0 ? QString::fromUtf8(C_INFINITY) : QString::number(m_torrent->connectionsLimit()))); + .arg(m_torrent->connectionsCount()) + .arg(m_torrent->connectionsLimit() < 0 ? QString::fromUtf8(C_INFINITY) : QString::number(m_torrent->connectionsLimit()))); label_eta_val->setText(Utils::Misc::userFriendlyDuration(m_torrent->eta())); @@ -413,16 +422,16 @@ void PropertiesWidget::loadDynamicData() { shareRatio->setText(ratio > BitTorrent::TorrentHandle::MAX_RATIO ? QString::fromUtf8(C_INFINITY) : Utils::String::fromDouble(ratio, 2)); label_seeds_val->setText(tr("%1 (%2 total)", "%1 and %2 are numbers, e.g. 3 (10 total)") - .arg(QString::number(m_torrent->seedsCount())) - .arg(QString::number(m_torrent->totalSeedsCount()))); + .arg(QString::number(m_torrent->seedsCount())) + .arg(QString::number(m_torrent->totalSeedsCount()))); label_peers_val->setText(tr("%1 (%2 total)", "%1 and %2 are numbers, e.g. 3 (10 total)") - .arg(QString::number(m_torrent->leechsCount())) - .arg(QString::number(m_torrent->totalLeechersCount()))); + .arg(QString::number(m_torrent->leechsCount())) + .arg(QString::number(m_torrent->totalLeechersCount()))); label_dl_speed_val->setText(tr("%1 (%2 avg.)", "%1 and %2 are speed rates, e.g. 200KiB/s (100KiB/s avg.)") - .arg(Utils::Misc::friendlyUnit(m_torrent->downloadPayloadRate(), true)) - .arg(Utils::Misc::friendlyUnit(m_torrent->totalDownload() / (1 + m_torrent->activeTime() - m_torrent->finishedTime()), true))); + .arg(Utils::Misc::friendlyUnit(m_torrent->downloadPayloadRate(), true)) + .arg(Utils::Misc::friendlyUnit(m_torrent->totalDownload() / (1 + m_torrent->activeTime() - m_torrent->finishedTime()), true))); label_upload_speed_val->setText(tr("%1 (%2 avg.)", "%1 and %2 are speed rates, e.g. 200KiB/s (100KiB/s avg.)") .arg(Utils::Misc::friendlyUnit(m_torrent->uploadPayloadRate(), true)) @@ -449,7 +458,7 @@ void PropertiesWidget::loadDynamicData() { // Progress qreal progress = m_torrent->progress() * 100.; - progress_lbl->setText(Utils::String::fromDouble(progress, 1)+"%"); + progress_lbl->setText(Utils::String::fromDouble(progress, 1) + "%"); downloaded_pieces->setProgress(m_torrent->pieces(), m_torrent->downloadingPieces()); } else { @@ -490,377 +499,395 @@ void PropertiesWidget::loadDynamicData() { } } -void PropertiesWidget::loadUrlSeeds() { - listWebSeeds->clear(); - qDebug("Loading URL seeds"); - const QList hc_seeds = m_torrent->urlSeeds(); - // Add url seeds - foreach (const QUrl &hc_seed, hc_seeds) { - qDebug("Loading URL seed: %s", qPrintable(hc_seed.toString())); - new QListWidgetItem(hc_seed.toString(), listWebSeeds); - } -} - -void PropertiesWidget::openDoubleClickedFile(const QModelIndex &index) { - if (!index.isValid()) return; - if (!m_torrent || !m_torrent->hasMetadata()) return; - if (PropListModel->itemType(index) == TorrentContentModelItem::FileType) - openFile(index); - else - openFolder(index, false); +void PropertiesWidget::loadUrlSeeds() +{ + listWebSeeds->clear(); + qDebug("Loading URL seeds"); + const QList hc_seeds = m_torrent->urlSeeds(); + // Add url seeds + foreach (const QUrl &hc_seed, hc_seeds) { + qDebug("Loading URL seed: %s", qPrintable(hc_seed.toString())); + new QListWidgetItem(hc_seed.toString(), listWebSeeds); + } } -void PropertiesWidget::openFile(const QModelIndex &index) { - int i = PropListModel->getFileIndex(index); - const QDir saveDir(m_torrent->savePath(true)); - const QString filename = m_torrent->filePath(i); - const QString file_path = Utils::Fs::expandPath(saveDir.absoluteFilePath(filename)); - qDebug("Trying to open file at %s", qPrintable(file_path)); - // Flush data - m_torrent->flushCache(); - Utils::Misc::openPath(file_path); +void PropertiesWidget::openDoubleClickedFile(const QModelIndex &index) +{ + if (!index.isValid()) return; + if (!m_torrent || !m_torrent->hasMetadata()) return; + if (PropListModel->itemType(index) == TorrentContentModelItem::FileType) + openFile(index); + else + openFolder(index, false); } -void PropertiesWidget::openFolder(const QModelIndex &index, bool containing_folder) { - QString absolute_path; - // FOLDER - if (PropListModel->itemType(index) == TorrentContentModelItem::FolderType) { - // Generate relative path to selected folder - QStringList path_items; - path_items << index.data().toString(); - QModelIndex parent = PropListModel->parent(index); - while(parent.isValid()) { - path_items.prepend(parent.data().toString()); - parent = PropListModel->parent(parent); - } - if (path_items.isEmpty()) - return; - const QDir saveDir(m_torrent->savePath(true)); - const QString relative_path = path_items.join("/"); - absolute_path = Utils::Fs::expandPath(saveDir.absoluteFilePath(relative_path)); - } - else { +void PropertiesWidget::openFile(const QModelIndex &index) +{ int i = PropListModel->getFileIndex(index); const QDir saveDir(m_torrent->savePath(true)); - const QString relative_path = m_torrent->filePath(i); - absolute_path = Utils::Fs::expandPath(saveDir.absoluteFilePath(relative_path)); - } - - // Flush data - m_torrent->flushCache(); - if (containing_folder) - Utils::Misc::openFolderSelect(absolute_path); - else - Utils::Misc::openPath(absolute_path); + const QString filename = m_torrent->filePath(i); + const QString file_path = Utils::Fs::expandPath(saveDir.absoluteFilePath(filename)); + qDebug("Trying to open file at %s", qPrintable(file_path)); + // Flush data + m_torrent->flushCache(); + Utils::Misc::openPath(file_path); } -void PropertiesWidget::displayFilesListMenu(const QPoint&) { - if (!m_torrent) return; - - QModelIndexList selectedRows = filesList->selectionModel()->selectedRows(0); - if (selectedRows.empty()) - return; - QMenu myFilesLlistMenu; - QAction *actOpen = 0; - QAction *actOpenContainingFolder = 0; - QAction *actRename = 0; - if (selectedRows.size() == 1) { - actOpen = myFilesLlistMenu.addAction(GuiIconProvider::instance()->getIcon("folder-documents"), tr("Open")); - actOpenContainingFolder = myFilesLlistMenu.addAction(GuiIconProvider::instance()->getIcon("inode-directory"), tr("Open Containing Folder")); - actRename = myFilesLlistMenu.addAction(GuiIconProvider::instance()->getIcon("edit-rename"), tr("Rename...")); - myFilesLlistMenu.addSeparator(); - } - QMenu subMenu; - if (!m_torrent->isSeed()) { - subMenu.setTitle(tr("Priority")); - subMenu.addAction(actionNot_downloaded); - subMenu.addAction(actionNormal); - subMenu.addAction(actionHigh); - subMenu.addAction(actionMaximum); - myFilesLlistMenu.addMenu(&subMenu); - } - // Call menu - const QAction *act = myFilesLlistMenu.exec(QCursor::pos()); - // The selected torrent might have disappeared during exec() - // from the current view thus leaving invalid indices. - const QModelIndex index = *(selectedRows.begin()); - if (!index.isValid()) - return; - if (act) { - if (act == actOpen) - openDoubleClickedFile(index); - else if (act == actOpenContainingFolder) - openFolder(index, true); - else if (act == actRename) - renameSelectedFile(); +void PropertiesWidget::openFolder(const QModelIndex &index, bool containing_folder) +{ + QString absolute_path; + // FOLDER + if (PropListModel->itemType(index) == TorrentContentModelItem::FolderType) { + // Generate relative path to selected folder + QStringList path_items; + path_items << index.data().toString(); + QModelIndex parent = PropListModel->parent(index); + while (parent.isValid()) { + path_items.prepend(parent.data().toString()); + parent = PropListModel->parent(parent); + } + if (path_items.isEmpty()) + return; + const QDir saveDir(m_torrent->savePath(true)); + const QString relative_path = path_items.join("/"); + absolute_path = Utils::Fs::expandPath(saveDir.absoluteFilePath(relative_path)); + } else { - int prio = prio::NORMAL; - if (act == actionHigh) - prio = prio::HIGH; - else if (act == actionMaximum) - prio = prio::MAXIMUM; - else if (act == actionNot_downloaded) - prio = prio::IGNORED; - - qDebug("Setting files priority"); - foreach (QModelIndex index, selectedRows) { - qDebug("Setting priority(%d) for file at row %d", prio, index.row()); - PropListModel->setData(PropListModel->index(index.row(), PRIORITY, index.parent()), prio); - } - // Save changes - filteredFilesChanged(); + int i = PropListModel->getFileIndex(index); + const QDir saveDir(m_torrent->savePath(true)); + const QString relative_path = m_torrent->filePath(i); + absolute_path = Utils::Fs::expandPath(saveDir.absoluteFilePath(relative_path)); } - } + + // Flush data + m_torrent->flushCache(); + if (containing_folder) + Utils::Misc::openFolderSelect(absolute_path); + else + Utils::Misc::openPath(absolute_path); } -void PropertiesWidget::displayWebSeedListMenu(const QPoint&) { - if (!m_torrent) return; - - QMenu seedMenu; - QModelIndexList rows = listWebSeeds->selectionModel()->selectedRows(); - QAction *actAdd = seedMenu.addAction(GuiIconProvider::instance()->getIcon("list-add"), tr("New Web seed")); - QAction *actDel = 0; - QAction *actCpy = 0; - QAction *actEdit = 0; - - if (rows.size()) { - actDel = seedMenu.addAction(GuiIconProvider::instance()->getIcon("list-remove"), tr("Remove Web seed")); - seedMenu.addSeparator(); - actCpy = seedMenu.addAction(GuiIconProvider::instance()->getIcon("edit-copy"), tr("Copy Web seed URL")); - actEdit = seedMenu.addAction(GuiIconProvider::instance()->getIcon("edit-rename"), tr("Edit Web seed URL")); - } - - const QAction *act = seedMenu.exec(QCursor::pos()); - if (act) { - if (act == actAdd) - askWebSeed(); - else if (act == actDel) - deleteSelectedUrlSeeds(); - else if (act == actCpy) - copySelectedWebSeedsToClipboard(); - else if (act == actEdit) - editWebSeed(); - } +void PropertiesWidget::displayFilesListMenu(const QPoint &) +{ + if (!m_torrent) return; + + QModelIndexList selectedRows = filesList->selectionModel()->selectedRows(0); + if (selectedRows.empty()) + return; + QMenu myFilesLlistMenu; + QAction *actOpen = 0; + QAction *actOpenContainingFolder = 0; + QAction *actRename = 0; + if (selectedRows.size() == 1) { + actOpen = myFilesLlistMenu.addAction(GuiIconProvider::instance()->getIcon("folder-documents"), tr("Open")); + actOpenContainingFolder = myFilesLlistMenu.addAction(GuiIconProvider::instance()->getIcon("inode-directory"), tr("Open Containing Folder")); + actRename = myFilesLlistMenu.addAction(GuiIconProvider::instance()->getIcon("edit-rename"), tr("Rename...")); + myFilesLlistMenu.addSeparator(); + } + QMenu subMenu; + if (!m_torrent->isSeed()) { + subMenu.setTitle(tr("Priority")); + subMenu.addAction(actionNot_downloaded); + subMenu.addAction(actionNormal); + subMenu.addAction(actionHigh); + subMenu.addAction(actionMaximum); + myFilesLlistMenu.addMenu(&subMenu); + } + // Call menu + const QAction *act = myFilesLlistMenu.exec(QCursor::pos()); + // The selected torrent might have disappeared during exec() + // from the current view thus leaving invalid indices. + const QModelIndex index = *(selectedRows.begin()); + if (!index.isValid()) + return; + if (act) { + if (act == actOpen) { + openDoubleClickedFile(index); + } + else if (act == actOpenContainingFolder) { + openFolder(index, true); + } + else if (act == actRename) { + renameSelectedFile(); + } + else { + int prio = prio::NORMAL; + if (act == actionHigh) + prio = prio::HIGH; + else if (act == actionMaximum) + prio = prio::MAXIMUM; + else if (act == actionNot_downloaded) + prio = prio::IGNORED; + + qDebug("Setting files priority"); + foreach (QModelIndex index, selectedRows) { + qDebug("Setting priority(%d) for file at row %d", prio, index.row()); + PropListModel->setData(PropListModel->index(index.row(), PRIORITY, index.parent()), prio); + } + // Save changes + filteredFilesChanged(); + } + } } -void PropertiesWidget::renameSelectedFile() { - const QModelIndexList selectedIndexes = filesList->selectionModel()->selectedRows(0); - if (selectedIndexes.size() != 1) - return; - const QModelIndex index = selectedIndexes.first(); - if (!index.isValid()) - return; - // Ask for new name - bool ok; - QString new_name_last = AutoExpandableDialog::getText(this, tr("Rename the file"), - tr("New name:"), QLineEdit::Normal, - index.data().toString(), &ok).trimmed(); - if (ok && !new_name_last.isEmpty()) { - if (!Utils::Fs::isValidFileSystemName(new_name_last)) { - MessageBoxRaised::warning(this, tr("The file could not be renamed"), - tr("This file name contains forbidden characters, please choose a different one."), - QMessageBox::Ok); - return; +void PropertiesWidget::displayWebSeedListMenu(const QPoint &) +{ + if (!m_torrent) return; + + QMenu seedMenu; + QModelIndexList rows = listWebSeeds->selectionModel()->selectedRows(); + QAction *actAdd = seedMenu.addAction(GuiIconProvider::instance()->getIcon("list-add"), tr("New Web seed")); + QAction *actDel = 0; + QAction *actCpy = 0; + QAction *actEdit = 0; + + if (rows.size()) { + actDel = seedMenu.addAction(GuiIconProvider::instance()->getIcon("list-remove"), tr("Remove Web seed")); + seedMenu.addSeparator(); + actCpy = seedMenu.addAction(GuiIconProvider::instance()->getIcon("edit-copy"), tr("Copy Web seed URL")); + actEdit = seedMenu.addAction(GuiIconProvider::instance()->getIcon("edit-rename"), tr("Edit Web seed URL")); } - if (PropListModel->itemType(index) == TorrentContentModelItem::FileType) { - // File renaming - const int file_index = PropListModel->getFileIndex(index); - if (!m_torrent || !m_torrent->hasMetadata()) return; - QString old_name = m_torrent->filePath(file_index); - if (old_name.endsWith(".!qB") && !new_name_last.endsWith(".!qB")) { - new_name_last += ".!qB"; - } - QStringList path_items = old_name.split("/"); - path_items.removeLast(); - path_items << new_name_last; - QString new_name = path_items.join("/"); - if (Utils::Fs::sameFileNames(old_name, new_name)) { - qDebug("Name did not change"); - return; - } - new_name = Utils::Fs::expandPath(new_name); - qDebug("New name: %s", qPrintable(new_name)); - // Check if that name is already used - for (int i = 0; i < m_torrent->filesCount(); ++i) { - if (i == file_index) continue; - if (Utils::Fs::sameFileNames(m_torrent->filePath(i), new_name)) { - // Display error message - MessageBoxRaised::warning(this, tr("The file could not be renamed"), - tr("This name is already in use in this folder. Please use a different name."), - QMessageBox::Ok); - return; - } - } - const bool force_recheck = QFile::exists(m_torrent->savePath(true) + "/" + new_name); - qDebug("Renaming %s to %s", qPrintable(old_name), qPrintable(new_name)); - m_torrent->renameFile(file_index, new_name); - // Force recheck - if (force_recheck) m_torrent->forceRecheck(); - // Rename if torrent files model too - if (new_name_last.endsWith(".!qB")) - new_name_last.chop(4); - PropListModel->setData(index, new_name_last); + + const QAction *act = seedMenu.exec(QCursor::pos()); + if (act) { + if (act == actAdd) + askWebSeed(); + else if (act == actDel) + deleteSelectedUrlSeeds(); + else if (act == actCpy) + copySelectedWebSeedsToClipboard(); + else if (act == actEdit) + editWebSeed(); } - else { - // Folder renaming - QStringList path_items; - path_items << index.data().toString(); - QModelIndex parent = PropListModel->parent(index); - while(parent.isValid()) { - path_items.prepend(parent.data().toString()); - parent = PropListModel->parent(parent); - } - const QString old_path = path_items.join("/"); - path_items.removeLast(); - path_items << new_name_last; - QString new_path = path_items.join("/"); - if (Utils::Fs::sameFileNames(old_path, new_path)) { - qDebug("Name did not change"); - return; - } - if (!new_path.endsWith("/")) new_path += "/"; - // Check for overwriting - for (int i = 0; i < m_torrent->filesCount(); ++i) { - const QString ¤t_name = m_torrent->filePath(i); +} + +void PropertiesWidget::renameSelectedFile() +{ + const QModelIndexList selectedIndexes = filesList->selectionModel()->selectedRows(0); + if (selectedIndexes.size() != 1) + return; + const QModelIndex index = selectedIndexes.first(); + if (!index.isValid()) + return; + // Ask for new name + bool ok; + QString new_name_last = AutoExpandableDialog::getText(this, tr("Rename the file"), + tr("New name:"), QLineEdit::Normal, + index.data().toString(), &ok).trimmed(); + if (ok && !new_name_last.isEmpty()) { + if (!Utils::Fs::isValidFileSystemName(new_name_last)) { + MessageBoxRaised::warning(this, tr("The file could not be renamed"), + tr("This file name contains forbidden characters, please choose a different one."), + QMessageBox::Ok); + return; + } + if (PropListModel->itemType(index) == TorrentContentModelItem::FileType) { + // File renaming + const int file_index = PropListModel->getFileIndex(index); + if (!m_torrent || !m_torrent->hasMetadata()) return; + QString old_name = m_torrent->filePath(file_index); + if (old_name.endsWith(".!qB") && !new_name_last.endsWith(".!qB")) + new_name_last += ".!qB"; + QStringList path_items = old_name.split("/"); + path_items.removeLast(); + path_items << new_name_last; + QString new_name = path_items.join("/"); + if (Utils::Fs::sameFileNames(old_name, new_name)) { + qDebug("Name did not change"); + return; + } + new_name = Utils::Fs::expandPath(new_name); + qDebug("New name: %s", qPrintable(new_name)); + // Check if that name is already used + for (int i = 0; i < m_torrent->filesCount(); ++i) { + if (i == file_index) continue; + if (Utils::Fs::sameFileNames(m_torrent->filePath(i), new_name)) { + // Display error message + MessageBoxRaised::warning(this, tr("The file could not be renamed"), + tr("This name is already in use in this folder. Please use a different name."), + QMessageBox::Ok); + return; + } + } + const bool force_recheck = QFile::exists(m_torrent->savePath(true) + "/" + new_name); + qDebug("Renaming %s to %s", qPrintable(old_name), qPrintable(new_name)); + m_torrent->renameFile(file_index, new_name); + // Force recheck + if (force_recheck) m_torrent->forceRecheck(); + // Rename if torrent files model too + if (new_name_last.endsWith(".!qB")) + new_name_last.chop(4); + PropListModel->setData(index, new_name_last); + } + else { + // Folder renaming + QStringList path_items; + path_items << index.data().toString(); + QModelIndex parent = PropListModel->parent(index); + while (parent.isValid()) { + path_items.prepend(parent.data().toString()); + parent = PropListModel->parent(parent); + } + const QString old_path = path_items.join("/"); + path_items.removeLast(); + path_items << new_name_last; + QString new_path = path_items.join("/"); + if (Utils::Fs::sameFileNames(old_path, new_path)) { + qDebug("Name did not change"); + return; + } + if (!new_path.endsWith("/")) new_path += "/"; + // Check for overwriting + for (int i = 0; i < m_torrent->filesCount(); ++i) { + const QString ¤t_name = m_torrent->filePath(i); #if defined(Q_OS_UNIX) || defined(Q_WS_QWS) - if (current_name.startsWith(new_path, Qt::CaseSensitive)) { + if (current_name.startsWith(new_path, Qt::CaseSensitive)) { #else - if (current_name.startsWith(new_path, Qt::CaseInsensitive)) { + if (current_name.startsWith(new_path, Qt::CaseInsensitive)) { #endif - QMessageBox::warning(this, tr("The folder could not be renamed"), - tr("This name is already in use in this folder. Please use a different name."), - QMessageBox::Ok); - return; - } - } - bool force_recheck = false; - // Replace path in all files - for (int i = 0; i < m_torrent->filesCount(); ++i) { - const QString current_name = m_torrent->filePath(i); - if (current_name.startsWith(old_path)) { - QString new_name = current_name; - new_name.replace(0, old_path.length(), new_path); - if (!force_recheck && QDir(m_torrent->savePath(true)).exists(new_name)) - force_recheck = true; - new_name = Utils::Fs::expandPath(new_name); - qDebug("Rename %s to %s", qPrintable(current_name), qPrintable(new_name)); - m_torrent->renameFile(i, new_name); + QMessageBox::warning(this, tr("The folder could not be renamed"), + tr("This name is already in use in this folder. Please use a different name."), + QMessageBox::Ok); + return; + } + } + bool force_recheck = false; + // Replace path in all files + for (int i = 0; i < m_torrent->filesCount(); ++i) { + const QString current_name = m_torrent->filePath(i); + if (current_name.startsWith(old_path)) { + QString new_name = current_name; + new_name.replace(0, old_path.length(), new_path); + if (!force_recheck && QDir(m_torrent->savePath(true)).exists(new_name)) + force_recheck = true; + new_name = Utils::Fs::expandPath(new_name); + qDebug("Rename %s to %s", qPrintable(current_name), qPrintable(new_name)); + m_torrent->renameFile(i, new_name); + } + } + // Force recheck + if (force_recheck) m_torrent->forceRecheck(); + // Rename folder in torrent files model too + PropListModel->setData(index, new_name_last); + // Remove old folder + const QDir old_folder(m_torrent->savePath(true) + "/" + old_path); + int timeout = 10; + while (!QDir().rmpath(old_folder.absolutePath()) && timeout > 0) { + // FIXME: We should not sleep here (freezes the UI for 1 second) + Utils::Misc::msleep(100); + --timeout; + } } - } - // Force recheck - if (force_recheck) m_torrent->forceRecheck(); - // Rename folder in torrent files model too - PropListModel->setData(index, new_name_last); - // Remove old folder - const QDir old_folder(m_torrent->savePath(true) + "/" + old_path); - int timeout = 10; - while(!QDir().rmpath(old_folder.absolutePath()) && timeout > 0) { - // FIXME: We should not sleep here (freezes the UI for 1 second) - Utils::Misc::msleep(100); - --timeout; - } } - } } -void PropertiesWidget::openSelectedFile() { - const QModelIndexList selectedIndexes = filesList->selectionModel()->selectedRows(0); - if (selectedIndexes.size() != 1) - return; - openDoubleClickedFile(selectedIndexes.first()); +void PropertiesWidget::openSelectedFile() +{ + const QModelIndexList selectedIndexes = filesList->selectionModel()->selectedRows(0); + if (selectedIndexes.size() != 1) + return; + openDoubleClickedFile(selectedIndexes.first()); } -void PropertiesWidget::askWebSeed() { - bool ok; - // Ask user for a new url seed - const QString url_seed = AutoExpandableDialog::getText(this, tr("New URL seed", "New HTTP source"), - tr("New URL seed:"), QLineEdit::Normal, - QString::fromUtf8("http://www."), &ok); - if (!ok) return; - qDebug("Adding %s web seed", qPrintable(url_seed)); - if (!listWebSeeds->findItems(url_seed, Qt::MatchFixedString).empty()) { - QMessageBox::warning(this, "qBittorrent", - tr("This URL seed is already in the list."), - QMessageBox::Ok); - return; - } - if (m_torrent) - m_torrent->addUrlSeeds(QList() << url_seed); - // Refresh the seeds list - loadUrlSeeds(); +void PropertiesWidget::askWebSeed() +{ + bool ok; + // Ask user for a new url seed + const QString url_seed = AutoExpandableDialog::getText(this, tr("New URL seed", "New HTTP source"), + tr("New URL seed:"), QLineEdit::Normal, + QString::fromUtf8("http://www."), &ok); + if (!ok) return; + qDebug("Adding %s web seed", qPrintable(url_seed)); + if (!listWebSeeds->findItems(url_seed, Qt::MatchFixedString).empty()) { + QMessageBox::warning(this, "qBittorrent", + tr("This URL seed is already in the list."), + QMessageBox::Ok); + return; + } + if (m_torrent) + m_torrent->addUrlSeeds(QList() << url_seed); + // Refresh the seeds list + loadUrlSeeds(); } -void PropertiesWidget::deleteSelectedUrlSeeds() { - const QList selectedItems = listWebSeeds->selectedItems(); - if (selectedItems.isEmpty()) return; +void PropertiesWidget::deleteSelectedUrlSeeds() +{ + const QList selectedItems = listWebSeeds->selectedItems(); + if (selectedItems.isEmpty()) return; - QList urlSeeds; - foreach (const QListWidgetItem *item, selectedItems) - urlSeeds << item->text(); + QList urlSeeds; + foreach (const QListWidgetItem *item, selectedItems) + urlSeeds << item->text(); - m_torrent->removeUrlSeeds(urlSeeds); - // Refresh list - loadUrlSeeds(); + m_torrent->removeUrlSeeds(urlSeeds); + // Refresh list + loadUrlSeeds(); } -void PropertiesWidget::copySelectedWebSeedsToClipboard() const { - const QList selected_items = listWebSeeds->selectedItems(); - if (selected_items.isEmpty()) - return; +void PropertiesWidget::copySelectedWebSeedsToClipboard() const +{ + const QList selected_items = listWebSeeds->selectedItems(); + if (selected_items.isEmpty()) + return; - QStringList urls_to_copy; - foreach (QListWidgetItem *item, selected_items) - urls_to_copy << item->text(); + QStringList urls_to_copy; + foreach (QListWidgetItem *item, selected_items) + urls_to_copy << item->text(); - QApplication::clipboard()->setText(urls_to_copy.join("\n")); + QApplication::clipboard()->setText(urls_to_copy.join("\n")); } -void PropertiesWidget::editWebSeed() { - const QList selected_items = listWebSeeds->selectedItems(); - if (selected_items.size() != 1) - return; - - const QListWidgetItem *selected_item = selected_items.last(); - const QString old_seed = selected_item->text(); - bool result; - const QString new_seed = AutoExpandableDialog::getText(this, tr("Web seed editing"), - tr("Web seed URL:"), QLineEdit::Normal, - old_seed, &result); - if (!result) - return; - - if (!listWebSeeds->findItems(new_seed, Qt::MatchFixedString).empty()) { - QMessageBox::warning(this, tr("qBittorrent"), - tr("This URL seed is already in the list."), - QMessageBox::Ok); - return; - } - - m_torrent->removeUrlSeeds(QList() << old_seed); - m_torrent->addUrlSeeds(QList() << new_seed); - loadUrlSeeds(); +void PropertiesWidget::editWebSeed() +{ + const QList selected_items = listWebSeeds->selectedItems(); + if (selected_items.size() != 1) + return; + + const QListWidgetItem *selected_item = selected_items.last(); + const QString old_seed = selected_item->text(); + bool result; + const QString new_seed = AutoExpandableDialog::getText(this, tr("Web seed editing"), + tr("Web seed URL:"), QLineEdit::Normal, + old_seed, &result); + if (!result) + return; + + if (!listWebSeeds->findItems(new_seed, Qt::MatchFixedString).empty()) { + QMessageBox::warning(this, tr("qBittorrent"), + tr("This URL seed is already in the list."), + QMessageBox::Ok); + return; + } + + m_torrent->removeUrlSeeds(QList() << old_seed); + m_torrent->addUrlSeeds(QList() << new_seed); + loadUrlSeeds(); } -bool PropertiesWidget::applyPriorities() { - qDebug("Saving files priorities"); - const QVector priorities = PropListModel->model()->getFilePriorities(); - // Prioritize the files - qDebug("prioritize files: %d", priorities[0]); - m_torrent->prioritizeFiles(priorities); - return true; +bool PropertiesWidget::applyPriorities() +{ + qDebug("Saving files priorities"); + const QVector priorities = PropListModel->model()->getFilePriorities(); + // Prioritize the files + qDebug("prioritize files: %d", priorities[0]); + m_torrent->prioritizeFiles(priorities); + return true; } -void PropertiesWidget::filteredFilesChanged() { - if (m_torrent) - applyPriorities(); +void PropertiesWidget::filteredFilesChanged() +{ + if (m_torrent) + applyPriorities(); } -void PropertiesWidget::filterText(const QString& filter) { - PropListModel->setFilterRegExp(QRegExp(filter, Qt::CaseInsensitive, QRegExp::WildcardUnix)); - if (filter.isEmpty()) { - filesList->collapseAll(); - filesList->expand(PropListModel->index(0, 0)); - } - else - filesList->expandAll(); +void PropertiesWidget::filterText(const QString &filter) +{ + PropListModel->setFilterRegExp(QRegExp(filter, Qt::CaseInsensitive, QRegExp::WildcardUnix)); + if (filter.isEmpty()) { + filesList->collapseAll(); + filesList->expand(PropListModel->index(0, 0)); + } + else { + filesList->expandAll(); + } } diff --git a/src/gui/properties/propertieswidget.h b/src/gui/properties/propertieswidget.h index 854a99483..8f6410fc0 100644 --- a/src/gui/properties/propertieswidget.h +++ b/src/gui/properties/propertieswidget.h @@ -55,80 +55,81 @@ class QAction; class QTimer; QT_END_NAMESPACE -class PropertiesWidget : public QWidget, private Ui::PropertiesWidget { - Q_OBJECT - Q_DISABLE_COPY(PropertiesWidget) +class PropertiesWidget: public QWidget, private Ui::PropertiesWidget +{ + Q_OBJECT + Q_DISABLE_COPY(PropertiesWidget) public: - enum SlideState {REDUCED, VISIBLE}; + enum SlideState {REDUCED, VISIBLE}; public: - PropertiesWidget(QWidget *parent, MainWindow* main_window, TransferListWidget *transferList); - ~PropertiesWidget(); - BitTorrent::TorrentHandle *getCurrentTorrent() const; - TrackerList* getTrackerList() const { return trackerList; } - PeerListWidget* getPeerList() const { return peersList; } - QTreeView* getFilesList() const { return filesList; } - SpeedWidget* getSpeedWidget() const { return speedWidget; } + PropertiesWidget(QWidget *parent, MainWindow *main_window, TransferListWidget *transferList); + ~PropertiesWidget(); + BitTorrent::TorrentHandle *getCurrentTorrent() const; + TrackerList *getTrackerList() const { return trackerList; } + PeerListWidget *getPeerList() const { return peersList; } + QTreeView *getFilesList() const { return filesList; } + SpeedWidget *getSpeedWidget() const { return speedWidget; } protected: - QPushButton* getButtonFromIndex(int index); - bool applyPriorities(); + QPushButton *getButtonFromIndex(int index); + bool applyPriorities(); protected slots: - void loadTorrentInfos(BitTorrent::TorrentHandle *const torrent); - void updateTorrentInfos(BitTorrent::TorrentHandle *const torrent); - void loadUrlSeeds(); - void askWebSeed(); - void deleteSelectedUrlSeeds(); - void copySelectedWebSeedsToClipboard() const; - void editWebSeed(); - void displayFilesListMenu(const QPoint& pos); - void displayWebSeedListMenu(const QPoint& pos); - void filteredFilesChanged(); - void showPiecesDownloaded(bool show); - void showPiecesAvailability(bool show); - void renameSelectedFile(); - void openSelectedFile(); + void loadTorrentInfos(BitTorrent::TorrentHandle *const torrent); + void updateTorrentInfos(BitTorrent::TorrentHandle *const torrent); + void loadUrlSeeds(); + void askWebSeed(); + void deleteSelectedUrlSeeds(); + void copySelectedWebSeedsToClipboard() const; + void editWebSeed(); + void displayFilesListMenu(const QPoint &pos); + void displayWebSeedListMenu(const QPoint &pos); + void filteredFilesChanged(); + void showPiecesDownloaded(bool show); + void showPiecesAvailability(bool show); + void renameSelectedFile(); + void openSelectedFile(); public slots: - void setVisibility(bool visible); - void loadDynamicData(); - void clear(); - void readSettings(); - void saveSettings(); - void reloadPreferences(); - void openDoubleClickedFile(const QModelIndex &); - void loadTrackers(BitTorrent::TorrentHandle *const torrent); + void setVisibility(bool visible); + void loadDynamicData(); + void clear(); + void readSettings(); + void saveSettings(); + void reloadPreferences(); + void openDoubleClickedFile(const QModelIndex &); + void loadTrackers(BitTorrent::TorrentHandle *const torrent); private: - void openFile(const QModelIndex &index); - void openFolder(const QModelIndex &index, bool containing_folder); + void openFile(const QModelIndex &index); + void openFolder(const QModelIndex &index, bool containing_folder); private: - TransferListWidget *transferList; - MainWindow *main_window; - BitTorrent::TorrentHandle *m_torrent; - QTimer *refreshTimer; - SlideState state; - TorrentContentFilterModel *PropListModel; - PropListDelegate *PropDelegate; - PeerListWidget *peersList; - TrackerList *trackerList; - SpeedWidget *speedWidget; - QList slideSizes; - DownloadedPiecesBar *downloaded_pieces; - PieceAvailabilityBar *pieces_availability; - PropTabBar *m_tabBar; - LineEdit *m_contentFilterLine; - QShortcut *editHotkeyFile; - QShortcut *editHotkeyWeb; - QShortcut *deleteHotkeyWeb; - QShortcut *openHotkeyFile; + TransferListWidget *transferList; + MainWindow *main_window; + BitTorrent::TorrentHandle *m_torrent; + QTimer *refreshTimer; + SlideState state; + TorrentContentFilterModel *PropListModel; + PropListDelegate *PropDelegate; + PeerListWidget *peersList; + TrackerList *trackerList; + SpeedWidget *speedWidget; + QList slideSizes; + DownloadedPiecesBar *downloaded_pieces; + PieceAvailabilityBar *pieces_availability; + PropTabBar *m_tabBar; + LineEdit *m_contentFilterLine; + QShortcut *editHotkeyFile; + QShortcut *editHotkeyWeb; + QShortcut *deleteHotkeyWeb; + QShortcut *openHotkeyFile; private slots: - void filterText(const QString& filter); - void updateSavePath(BitTorrent::TorrentHandle *const torrent); + void filterText(const QString &filter); + void updateSavePath(BitTorrent::TorrentHandle *const torrent); }; #endif // PROPERTIESWIDGET_H