2 * Bittorrent Client using Qt and libtorrent.
3 * Copyright (C) 2024 Vladimir Golovnev <glassez@yandex.ru>
4 * Copyright (C) 2016 Eugene Shalygin
5 * Copyright (C) 2006 Christophe Dumez
7 * This program is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU General Public License
9 * as published by the Free Software Foundation; either version 2
10 * of the License, or (at your option) any later version.
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
17 * You should have received a copy of the GNU General Public License
18 * along with this program; if not, write to the Free Software
19 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
21 * In addition, as a special exception, the copyright holders give permission to
22 * link this program with the OpenSSL project's "OpenSSL" library (or with
23 * modified versions of it that use the same license as the "OpenSSL" library),
24 * and distribute the linked executables. You must obey the GNU General Public
25 * License in all respects for all of the code used other than "OpenSSL". If you
26 * modify file(s), you may extend this exception to your version of the file(s),
27 * but you are not obligated to do so. If you do not wish to do so, delete this
28 * exception statement from your version.
31 #include "piecesbar.h"
33 #include <QApplication>
37 #include <QPainterPath>
40 #include "base/bittorrent/torrent.h"
41 #include "base/bittorrent/torrentinfo.h"
42 #include "base/indexrange.h"
43 #include "base/path.h"
44 #include "base/utils/misc.h"
45 #include "gui/uithememanager.h"
49 using ImageRange
= IndexRange
<int>;
51 // Computes approximate mapping from image scale (measured in pixels) onto the torrent contents scale (in pieces)
52 // However, taking the size of a screen to be ~ 1000 px and the torrent size larger than 10 MiB, the pointing error
53 // is well below 0.5 px and thus is negligible.
54 class PieceIndexToImagePos
57 PieceIndexToImagePos(const BitTorrent::TorrentInfo
&torrentInfo
, const QImage
&image
)
58 : m_bytesPerPixel
{((image
.width() > 0) && (torrentInfo
.totalSize() >= image
.width()))
59 ? torrentInfo
.totalSize() / image
.width() : -1}
60 , m_torrentInfo
{torrentInfo
}
62 if ((m_bytesPerPixel
> 0) && (m_bytesPerPixel
< 10))
63 qDebug() << "PieceIndexToImagePos: torrent size is too small for correct computaions."
64 << "Torrent size =" << torrentInfo
.totalSize() << "Image width = " << image
.width();
67 ImageRange
imagePos(const BitTorrent::TorrentInfo::PieceRange
&pieces
) const
69 if (m_bytesPerPixel
< 0)
72 // the type conversion is used to prevent integer overflow with torrents of 2+ GiB size
73 const qlonglong pieceLength
= m_torrentInfo
.pieceLength();
74 return makeInterval
<ImageRange::IndexType
>(
75 (pieces
.first() * pieceLength
) / m_bytesPerPixel
,
76 (pieces
.last() * pieceLength
+ m_torrentInfo
.pieceLength(pieces
.last()) - 1) / m_bytesPerPixel
);
79 int pieceIndex(int imagePos
) const
81 return m_bytesPerPixel
< 0 ? 0 : (imagePos
* m_bytesPerPixel
+ m_bytesPerPixel
/ 2) / m_torrentInfo
.pieceLength();
85 const qlonglong m_bytesPerPixel
; // how many bytes of the torrent are squeezed into a bar's pixel
86 const BitTorrent::TorrentInfo m_torrentInfo
;
89 class DetailedTooltipRenderer
92 DetailedTooltipRenderer(QString
&string
, const QString
&header
)
96 + uR
"(<table style="width
:100%; padding
: 3px
; vertical
-align
: middle
;">)";
99 ~DetailedTooltipRenderer()
101 m_string
+= u
"</table>";
104 void operator()(const QString
&size
, const Path
&path
)
106 m_string
+= uR
"(<tr><td style="white
-space
:nowrap
">)"
118 PiecesBar::PiecesBar(QWidget
*parent
)
121 setMouseTracking(true);
124 connect(UIThemeManager::instance(), &UIThemeManager::themeChanged
, this, [this]
131 void PiecesBar::setTorrent(const BitTorrent::Torrent
*torrent
)
138 void PiecesBar::clear()
144 bool PiecesBar::event(QEvent
*e
)
146 if (e
->type() == QEvent::ToolTip
)
148 showToolTip(static_cast<QHelpEvent
*>(e
));
152 return base::event(e
);
155 void PiecesBar::enterEvent(QEnterEvent
*e
)
161 void PiecesBar::leaveEvent(QEvent
*e
)
164 m_highlightedRegion
= {};
169 void PiecesBar::mouseMoveEvent(QMouseEvent
*e
)
171 // if user pointed to a piece which is a part of a single large file,
172 // we highlight the space, occupied by this file
173 highlightFile(e
->pos().x() - borderWidth
);
174 base::mouseMoveEvent(e
);
177 void PiecesBar::paintEvent(QPaintEvent
*)
179 QPainter
painter(this);
180 QRect
imageRect(borderWidth
, borderWidth
, width() - 2 * borderWidth
, height() - 2 * borderWidth
);
181 if (m_image
.isNull())
183 painter
.setBrush(backgroundColor());
184 painter
.drawRect(imageRect
);
188 if (m_image
.width() != imageRect
.width())
190 if (const QImage image
= renderImage(); !image
.isNull())
193 painter
.drawImage(imageRect
, m_image
);
196 if (!m_highlightedRegion
.isNull())
198 QRect targetHighlightRect
{m_highlightedRegion
.adjusted(borderWidth
, borderWidth
, borderWidth
, height() - 2 * borderWidth
)};
199 painter
.fillRect(targetHighlightRect
, highlightedPieceColor());
203 border
.addRect(0, 0, width(), height());
204 painter
.setPen(borderColor());
205 painter
.drawPath(border
);
208 void PiecesBar::redraw()
210 if (const QImage image
= renderImage(); !image
.isNull())
217 QColor
PiecesBar::backgroundColor() const
219 return palette().color(QPalette::Active
, QPalette::Base
);
222 QColor
PiecesBar::borderColor() const
224 return palette().color(QPalette::Active
, QPalette::Dark
);
227 QColor
PiecesBar::pieceColor() const
229 return palette().color(QPalette::Active
, QPalette::Highlight
);
232 QColor
PiecesBar::highlightedPieceColor() const
234 QColor col
= palette().color(QPalette::Highlight
).darker();
239 QColor
PiecesBar::colorBoxBorderColor() const
241 return palette().color(QPalette::Active
, QPalette::ToolTipText
);
244 const QList
<QRgb
> &PiecesBar::pieceColors() const
246 return m_pieceColors
;
249 QRgb
PiecesBar::mixTwoColors(QRgb rgb1
, QRgb rgb2
, float ratio
)
252 int g1
= qGreen(rgb1
);
253 int b1
= qBlue(rgb1
);
256 int g2
= qGreen(rgb2
);
257 int b2
= qBlue(rgb2
);
259 float ratioN
= 1.0f
- ratio
;
260 int r
= (r1
* ratioN
) + (r2
* ratio
);
261 int g
= (g1
* ratioN
) + (g2
* ratio
);
262 int b
= (b1
* ratioN
) + (b2
* ratio
);
264 return qRgb(r
, g
, b
);
267 void PiecesBar::showToolTip(const QHelpEvent
*e
)
274 const bool showDetailedInformation
= QApplication::keyboardModifiers().testFlag(Qt::ShiftModifier
);
275 if (showDetailedInformation
&& m_torrent
->hasMetadata())
277 const BitTorrent::TorrentInfo torrentInfo
= m_torrent
->info();
278 const int imagePos
= e
->pos().x() - borderWidth
;
279 if ((imagePos
>= 0) && (imagePos
< m_image
.width()))
281 const PieceIndexToImagePos transform
{torrentInfo
, m_image
};
282 const int pieceIndex
= transform
.pieceIndex(imagePos
);
283 const QList
<int> fileIndexes
= torrentInfo
.fileIndicesForPiece(pieceIndex
);
285 QString tooltipTitle
;
286 if (fileIndexes
.count() > 1)
287 tooltipTitle
= tr("Files in this piece:");
288 else if (torrentInfo
.fileSize(fileIndexes
.front()) == torrentInfo
.pieceLength(pieceIndex
))
289 tooltipTitle
= tr("File in this piece:");
291 tooltipTitle
= tr("File in these pieces:");
293 toolTipText
.reserve(fileIndexes
.size() * 128);
294 toolTipText
+= u
"<html><body>";
296 DetailedTooltipRenderer renderer
{toolTipText
, tooltipTitle
};
298 for (const int index
: fileIndexes
)
300 const Path filePath
= m_torrent
->filePath(index
);
301 renderer(Utils::Misc::friendlyUnit(torrentInfo
.fileSize(index
)), filePath
);
303 toolTipText
+= u
"</body></html>";
308 toolTipText
+= simpleToolTipText();
309 if (showDetailedInformation
) // metadata are not available at this point
310 toolTipText
+= u
'\n' + tr("Wait until metadata become available to see detailed information");
312 toolTipText
+= u
'\n' + tr("Hold Shift key for detailed information");
315 QToolTip::showText(e
->globalPos(), toolTipText
, this);
318 void PiecesBar::highlightFile(int imagePos
)
320 if (!m_torrent
|| !m_torrent
->hasMetadata() || (imagePos
< 0) || (imagePos
>= m_image
.width()))
323 const BitTorrent::TorrentInfo torrentInfo
= m_torrent
->info();
324 PieceIndexToImagePos transform
{torrentInfo
, m_image
};
326 int pieceIndex
= transform
.pieceIndex(imagePos
);
327 QList
<int> fileIndices
{torrentInfo
.fileIndicesForPiece(pieceIndex
)};
328 if (fileIndices
.count() == 1)
330 BitTorrent::TorrentInfo::PieceRange filePieces
= torrentInfo
.filePieces(fileIndices
.first());
332 ImageRange imageRange
= transform
.imagePos(filePieces
);
333 QRect newHighlightedRegion
{imageRange
.first(), 0, imageRange
.size(), m_image
.height()};
334 if (newHighlightedRegion
!= m_highlightedRegion
)
336 m_highlightedRegion
= newHighlightedRegion
;
340 else if (!m_highlightedRegion
.isEmpty())
342 m_highlightedRegion
= {};
347 void PiecesBar::updateColors()
352 void PiecesBar::updateColorsImpl()
354 m_pieceColors
= QList
<QRgb
>(256);
355 for (int i
= 0; i
< 256; ++i
)
357 const float ratio
= (i
/ 255.0);
358 m_pieceColors
[i
] = mixTwoColors(backgroundColor().rgb(), pieceColor().rgb(), ratio
);