Some work about adaptive color scheme for Web UI (PR #19901) http://[316:c51a:62a3:8b9::4]/d4708/qBittorrent/src/branch/adaptive-webui
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

335 lines
10 KiB

/*
* 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 <QApplication>
#include <QDebug>
#include <QHelpEvent>
#include <QPainter>
#include <QTextStream>
#include <QToolTip>
#include "base/bittorrent/torrenthandle.h"
#include "base/utils/misc.h"
namespace
{
using ImageRange = IndexRange<int>;
// 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<ImageRange::IndexType>(
(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"(<table style="width:100%; padding: 3px; vertical-align: middle;">)";
}
~DetailedTooltipRenderer()
{
m_stream << "</table>";
}
void operator()(const QString &size, const QString &path)
{
m_stream << R"(<tr><td style="white-space:nowrap">)" << size << "</td><td>" << path << "</td></tr>";
}
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<QHelpEvent *>(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<QRgb> &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) {
const int imagePos = e->pos().x() - borderWidth;
if ((imagePos >=0) && (imagePos < m_image.width())) {
stream << "<html><body>";
PieceIndexToImagePos transform {m_torrent->info(), m_image};
int pieceIndex = transform.pieceIndex(imagePos);
QVector<int> 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 << "</body></html>";
}
}
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<int> 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<QRgb>(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;
}