Enable customizing the save statistics time interval
[qBittorrent.git] / src / gui / properties / piecesbar.cpp
blob780d63db535cf99b131a949f22a3b895a2b3b8d9
1 /*
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>
34 #include <QDebug>
35 #include <QHelpEvent>
36 #include <QPainter>
37 #include <QPainterPath>
38 #include <QToolTip>
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"
47 namespace
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
56 public:
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)
70 return {0, 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();
84 private:
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
91 public:
92 DetailedTooltipRenderer(QString &string, const QString &header)
93 : m_string(string)
95 m_string += 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">)"
107 + size
108 + u"</td><td>"
109 + path.toString()
110 + u"</td></tr>";
113 private:
114 QString &m_string;
118 PiecesBar::PiecesBar(QWidget *parent)
119 : QWidget(parent)
121 setMouseTracking(true);
123 updateColorsImpl();
124 connect(UIThemeManager::instance(), &UIThemeManager::themeChanged, this, [this]
126 updateColors();
127 redraw();
131 void PiecesBar::setTorrent(const BitTorrent::Torrent *torrent)
133 m_torrent = torrent;
134 if (!m_torrent)
135 clear();
138 void PiecesBar::clear()
140 m_image = QImage();
141 update();
144 bool PiecesBar::event(QEvent *e)
146 if (e->type() == QEvent::ToolTip)
148 showToolTip(static_cast<QHelpEvent *>(e));
149 return true;
152 return base::event(e);
155 void PiecesBar::enterEvent(QEnterEvent *e)
157 m_hovered = true;
158 base::enterEvent(e);
161 void PiecesBar::leaveEvent(QEvent *e)
163 m_hovered = false;
164 m_highlightedRegion = {};
165 redraw();
166 base::leaveEvent(e);
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);
186 else
188 if (m_image.width() != imageRect.width())
190 if (const QImage image = renderImage(); !image.isNull())
191 m_image = image;
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());
202 QPainterPath border;
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())
212 m_image = image;
213 update();
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();
235 col.setAlphaF(0.35);
236 return col;
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)
251 int r1 = qRed(rgb1);
252 int g1 = qGreen(rgb1);
253 int b1 = qBlue(rgb1);
255 int r2 = qRed(rgb2);
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)
269 if (!m_torrent)
270 return;
272 QString toolTipText;
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:");
290 else
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>";
306 else
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");
311 else
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()))
321 return;
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;
337 update();
340 else if (!m_highlightedRegion.isEmpty())
342 m_highlightedRegion = {};
343 update();
347 void PiecesBar::updateColors()
349 updateColorsImpl();
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);