2 * Bittorrent Client using Qt and libtorrent.
3 * Copyright (C) 2016 Eugene Shalygin
4 * Copyright (C) 2006 Christophe Dumez
6 * This program is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU General Public License
8 * as published by the Free Software Foundation; either version 2
9 * of the License, or (at your option) any later version.
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
16 * You should have received a copy of the GNU General Public License
17 * along with this program; if not, write to the Free Software
18 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
20 * In addition, as a special exception, the copyright holders give permission to
21 * link this program with the OpenSSL project's "OpenSSL" library (or with
22 * modified versions of it that use the same license as the "OpenSSL" library),
23 * and distribute the linked executables. You must obey the GNU General Public
24 * License in all respects for all of the code used other than "OpenSSL". If you
25 * modify file(s), you may extend this exception to your version of the file(s),
26 * but you are not obligated to do so. If you do not wish to do so, delete this
27 * exception statement from your version.
30 #include "piecesbar.h"
32 #include <QApplication>
36 #include <QTextStream>
39 #include "base/bittorrent/torrenthandle.h"
40 #include "base/utils/misc.h"
44 using ImageRange
= IndexRange
<int>;
46 // Computes approximate mapping from image scale (measured in pixels) onto the torrent contents scale (in pieces)
47 // However, taking the size of a screen to be ~ 1000 px and the torrent size larger than 10 MiB, the pointing error
48 // is well below 0.5 px and thus is negligible.
49 class PieceIndexToImagePos
52 PieceIndexToImagePos(const BitTorrent::TorrentInfo
&torrentInfo
, const QImage
&image
)
53 : m_bytesPerPixel
{((image
.width() > 0) && (torrentInfo
.totalSize() >= image
.width()))
54 ? torrentInfo
.totalSize() / image
.width() : -1}
55 , m_torrentInfo
{torrentInfo
}
57 if ((m_bytesPerPixel
> 0) && (m_bytesPerPixel
< 10))
58 qDebug() << "PieceIndexToImagePos: torrent size is too small for correct computaions."
59 << "Torrent size =" << torrentInfo
.totalSize() << "Image width = " << image
.width();
62 ImageRange
imagePos(const BitTorrent::TorrentInfo::PieceRange
&pieces
) const
64 if (m_bytesPerPixel
< 0)
67 // the type conversion is used to prevent integer overflow with torrents of 2+ GiB size
68 const qlonglong pieceLength
= m_torrentInfo
.pieceLength();
69 return makeInterval
<ImageRange::IndexType
>(
70 (pieces
.first() * pieceLength
) / m_bytesPerPixel
,
71 (pieces
.last() * pieceLength
+ m_torrentInfo
.pieceLength(pieces
.last()) - 1) / m_bytesPerPixel
);
74 int pieceIndex(int imagePos
) const
76 return m_bytesPerPixel
< 0 ? 0 : (imagePos
* m_bytesPerPixel
+ m_bytesPerPixel
/ 2) / m_torrentInfo
.pieceLength();
80 const qlonglong m_bytesPerPixel
; // how many bytes of the torrent are squeezed into a bar's pixel
81 const BitTorrent::TorrentInfo m_torrentInfo
;
84 class DetailedTooltipRenderer
87 DetailedTooltipRenderer(QTextStream
&stream
, const QString
&header
)
91 << R
"(<table style="width
:100%; padding
: 3px
; vertical
-align
: middle
;">)";
94 ~DetailedTooltipRenderer()
96 m_stream
<< "</table>";
99 void operator()(const QString
&size
, const QString
&path
)
101 m_stream
<< R
"(<tr><td style="white
-space
:nowrap
">)" << size
<< "</td><td>" << path
<< "</td></tr>";
105 QTextStream
&m_stream
;
109 PiecesBar::PiecesBar(QWidget
*parent
)
111 , m_torrent
{nullptr}
112 , m_borderColor
{palette().color(QPalette::Dark
)}
113 , m_bgColor
{Qt::white
}
114 , m_pieceColor
{Qt::blue
}
118 setMouseTracking(true);
121 void PiecesBar::setTorrent(BitTorrent::TorrentHandle
*torrent
)
128 void PiecesBar::clear()
134 void PiecesBar::setColors(const QColor
&background
, const QColor
&border
, const QColor
&complete
)
136 m_bgColor
= background
;
137 m_borderColor
= border
;
138 m_pieceColor
= complete
;
141 requestImageUpdate();
144 bool PiecesBar::event(QEvent
*e
)
146 if (e
->type() == QEvent::ToolTip
) {
147 showToolTip(static_cast<QHelpEvent
*>(e
));
151 return base::event(e
);
154 void PiecesBar::enterEvent(QEvent
*e
)
160 void PiecesBar::leaveEvent(QEvent
*e
)
163 m_highlitedRegion
= QRect();
164 requestImageUpdate();
168 void PiecesBar::mouseMoveEvent(QMouseEvent
*e
)
170 // if user pointed to a piece which is a part of a single large file,
171 // we highlight the space, occupied by this file
172 highlightFile(e
->pos().x() - borderWidth
);
173 base::mouseMoveEvent(e
);
176 void PiecesBar::paintEvent(QPaintEvent
*)
178 QPainter
painter(this);
179 QRect
imageRect(borderWidth
, borderWidth
, width() - 2 * borderWidth
, height() - 2 * borderWidth
);
180 if (m_image
.isNull()) {
181 painter
.setBrush(Qt::white
);
182 painter
.drawRect(imageRect
);
185 if (m_image
.width() != imageRect
.width())
186 updateImage(m_image
);
187 painter
.drawImage(imageRect
, m_image
);
190 if (!m_highlitedRegion
.isNull()) {
191 QColor highlightColor
{this->palette().color(QPalette::Active
, QPalette::Highlight
)};
192 highlightColor
.setAlphaF(0.35);
193 QRect targetHighlightRect
{m_highlitedRegion
.adjusted(borderWidth
, borderWidth
, borderWidth
, height() - 2 * borderWidth
)};
194 painter
.fillRect(targetHighlightRect
, highlightColor
);
198 border
.addRect(0, 0, width(), height());
199 painter
.setPen(m_borderColor
);
200 painter
.drawPath(border
);
203 void PiecesBar::requestImageUpdate()
205 if (updateImage(m_image
))
209 QColor
PiecesBar::backgroundColor() const
214 QColor
PiecesBar::borderColor() const
216 return m_borderColor
;
219 QColor
PiecesBar::pieceColor() const
224 const QVector
<QRgb
> &PiecesBar::pieceColors() const
226 return m_pieceColors
;
229 QRgb
PiecesBar::mixTwoColors(QRgb rgb1
, QRgb rgb2
, float ratio
)
232 int g1
= qGreen(rgb1
);
233 int b1
= qBlue(rgb1
);
236 int g2
= qGreen(rgb2
);
237 int b2
= qBlue(rgb2
);
239 float ratioN
= 1.0f
- ratio
;
240 int r
= (r1
* ratioN
) + (r2
* ratio
);
241 int g
= (g1
* ratioN
) + (g2
* ratio
);
242 int b
= (b1
* ratioN
) + (b2
* ratio
);
244 return qRgb(r
, g
, b
);
247 void PiecesBar::showToolTip(const QHelpEvent
*e
)
253 QTextStream
stream(&toolTipText
, QIODevice::WriteOnly
);
254 const bool showDetailedInformation
= QApplication::keyboardModifiers().testFlag(Qt::ShiftModifier
);
255 if (showDetailedInformation
&& m_torrent
->hasMetadata()) {
256 const int imagePos
= e
->pos().x() - borderWidth
;
257 if ((imagePos
>=0) && (imagePos
< m_image
.width())) {
258 stream
<< "<html><body>";
259 PieceIndexToImagePos transform
{m_torrent
->info(), m_image
};
260 int pieceIndex
= transform
.pieceIndex(imagePos
);
261 const QVector
<int> files
{m_torrent
->info().fileIndicesForPiece(pieceIndex
)};
263 QString tooltipTitle
;
264 if (files
.count() > 1) {
265 tooltipTitle
= tr("Files in this piece:");
268 if (m_torrent
->info().fileSize(files
.front()) == m_torrent
->info().pieceLength(pieceIndex
))
269 tooltipTitle
= tr("File in this piece");
271 tooltipTitle
= tr("File in these pieces");
274 DetailedTooltipRenderer
renderer(stream
, tooltipTitle
);
276 const bool isFileNameCorrectionNeeded
= this->isFileNameCorrectionNeeded();
277 for (int f
: files
) {
278 QString filePath
{m_torrent
->info().filePath(f
)};
279 if (isFileNameCorrectionNeeded
)
280 filePath
.replace(QLatin1String("/.unwanted"), QString());
282 renderer(Utils::Misc::friendlyUnit(m_torrent
->info().fileSize(f
)), filePath
);
284 stream
<< "</body></html>";
288 stream
<< simpleToolTipText();
289 if (showDetailedInformation
) // metadata are not available at this point
290 stream
<< '\n' << tr("Wait until metadata become available to see detailed information");
292 stream
<< '\n' << tr("Hold Shift key for detailed information");
297 QToolTip::showText(e
->globalPos(), toolTipText
, this);
300 void PiecesBar::highlightFile(int imagePos
)
302 if (!m_torrent
|| !m_torrent
->hasMetadata() || (imagePos
< 0) || (imagePos
>= m_image
.width()))
305 PieceIndexToImagePos transform
{m_torrent
->info(), m_image
};
307 int pieceIndex
= transform
.pieceIndex(imagePos
);
308 QVector
<int> fileIndices
{m_torrent
->info().fileIndicesForPiece(pieceIndex
)};
309 if (fileIndices
.count() == 1) {
310 BitTorrent::TorrentInfo::PieceRange filePieces
= m_torrent
->info().filePieces(fileIndices
.first());
312 ImageRange imageRange
= transform
.imagePos(filePieces
);
313 QRect newHighlitedRegion
{imageRange
.first(), 0, imageRange
.size(), m_image
.height()};
314 if (newHighlitedRegion
!= m_highlitedRegion
) {
315 m_highlitedRegion
= newHighlitedRegion
;
319 else if (!m_highlitedRegion
.isEmpty()) {
320 m_highlitedRegion
= QRect();
325 void PiecesBar::updatePieceColors()
327 m_pieceColors
= QVector
<QRgb
>(256);
328 for (int i
= 0; i
< 256; ++i
) {
329 float ratio
= (i
/ 255.0);
330 m_pieceColors
[i
] = mixTwoColors(backgroundColor().rgb(), m_pieceColor
.rgb(), ratio
);
334 bool PiecesBar::isFileNameCorrectionNeeded() const