1
0
mirror of https://github.com/qTox/qTox.git synced 2024-03-22 14:00:36 +08:00
qTox/src/chatlog/content/filetransferwidget.cpp
Mick Sayson c7efe320d2 refactor(filetransfer): Move file transfer progress into ToxFile
* Refactor/test ToxFileProgress to ensure that when it's moved it
  behaves well
* Notice problems with speed averaging. We were average speeds without
  keeping track of the times they were over. Adding samples of different
  lengths would result in incorrect speeds. Refactor whole class to correct
* Move ToxFileProgress to be a member of ToxFile
* Remove duplicated members between ToxFile and ToxFileProgress
* Move sample addition into CoreFile
2021-12-11 15:38:35 -08:00

560 lines
18 KiB
C++

/*
Copyright © 2014-2019 by The qTox Project Contributors
This file is part of qTox, a Qt-based graphical interface for Tox.
qTox is libre 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 3 of the License, or
(at your option) any later version.
qTox 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 qTox. If not, see <http://www.gnu.org/licenses/>.
*/
#include "filetransferwidget.h"
#include "ui_filetransferwidget.h"
#include "src/core/corefile.h"
#include "src/persistence/settings.h"
#include "src/widget/gui.h"
#include "src/widget/style.h"
#include "src/widget/widget.h"
#include "src/model/exiftransform.h"
#include <QBuffer>
#include <QDebug>
#include <QDesktopServices>
#include <QDesktopWidget>
#include <QFile>
#include <QFileDialog>
#include <QMessageBox>
#include <QMouseEvent>
#include <QPainter>
#include <QVariantAnimation>
#include <cassert>
#include <math.h>
// The leftButton is used to accept, pause, or resume a file transfer, as well as to open a
// received file.
// The rightButton is used to cancel a file transfer, or to open the directory a file was
// downloaded to.
FileTransferWidget::FileTransferWidget(QWidget* parent, CoreFile& _coreFile, ToxFile file)
: QWidget(parent)
, coreFile{_coreFile}
, ui(new Ui::FileTransferWidget)
, fileInfo(file)
, backgroundColor(Style::getColor(Style::TransferMiddle))
, buttonColor(Style::getColor(Style::TransferWait))
, buttonBackgroundColor(Style::getColor(Style::GroundBase))
, active(true)
{
ui->setupUi(this);
// hide the QWidget background (background-color: transparent doesn't seem to work)
setAttribute(Qt::WA_TranslucentBackground, true);
ui->previewButton->hide();
ui->filenameLabel->setText(file.fileName);
ui->progressBar->setValue(0);
ui->fileSizeLabel->setText(getHumanReadableSize(file.progress.getFileSize()));
ui->etaLabel->setText("");
backgroundColorAnimation = new QVariantAnimation(this);
backgroundColorAnimation->setDuration(500);
backgroundColorAnimation->setEasingCurve(QEasingCurve::OutCubic);
connect(backgroundColorAnimation, &QVariantAnimation::valueChanged, this,
[this](const QVariant& val) {
backgroundColor = val.value<QColor>();
update();
});
buttonColorAnimation = new QVariantAnimation(this);
buttonColorAnimation->setDuration(500);
buttonColorAnimation->setEasingCurve(QEasingCurve::OutCubic);
connect(buttonColorAnimation, &QVariantAnimation::valueChanged, this, [this](const QVariant& val) {
buttonColor = val.value<QColor>();
update();
});
connect(ui->leftButton, &QPushButton::clicked, this, &FileTransferWidget::onLeftButtonClicked);
connect(ui->rightButton, &QPushButton::clicked, this, &FileTransferWidget::onRightButtonClicked);
connect(ui->previewButton, &QPushButton::clicked, this,
&FileTransferWidget::onPreviewButtonClicked);
connect(&GUI::getInstance(), &GUI::themeReload, this, &FileTransferWidget::reloadTheme);
// Set lastStatus to anything but the file's current value, this forces an update
lastStatus = file.status == ToxFile::FINISHED ? ToxFile::INITIALIZING : ToxFile::FINISHED;
updateWidget(file);
setFixedHeight(64);
}
FileTransferWidget::~FileTransferWidget()
{
delete ui;
}
// TODO(sudden6): remove file IO from the UI
/**
* @brief Dangerous way to find out if a path is writable.
* @param filepath Path to file which should be deleted.
* @return True, if file writeable, false otherwise.
*/
bool FileTransferWidget::tryRemoveFile(const QString& filepath)
{
QFile tmp(filepath);
bool writable = tmp.open(QIODevice::WriteOnly);
tmp.remove();
return writable;
}
void FileTransferWidget::onFileTransferUpdate(ToxFile file)
{
updateWidget(file);
}
bool FileTransferWidget::isActive() const
{
return active;
}
void FileTransferWidget::acceptTransfer(const QString& filepath)
{
if (filepath.isEmpty()) {
return;
}
// test if writable
if (!tryRemoveFile(filepath)) {
GUI::showWarning(tr("Location not writable", "Title of permissions popup"),
tr("You do not have permission to write that location. Choose another, or "
"cancel the save dialog.",
"text of permissions popup"));
return;
}
// everything ok!
coreFile.acceptFileRecvRequest(fileInfo.friendId, fileInfo.fileNum, filepath);
}
void FileTransferWidget::setBackgroundColor(const QColor& c, bool whiteFont)
{
if (c != backgroundColor) {
backgroundColorAnimation->setStartValue(backgroundColor);
backgroundColorAnimation->setEndValue(c);
backgroundColorAnimation->start();
}
setProperty("fontColor", whiteFont ? "white" : "black");
setStyleSheet(Style::getStylesheet("fileTransferInstance/filetransferWidget.css"));
Style::repolish(this);
update();
}
void FileTransferWidget::setButtonColor(const QColor& c)
{
if (c != buttonColor) {
buttonColorAnimation->setStartValue(buttonColor);
buttonColorAnimation->setEndValue(c);
buttonColorAnimation->start();
}
}
bool FileTransferWidget::drawButtonAreaNeeded() const
{
return (ui->rightButton->isVisible() || ui->leftButton->isVisible())
&& !(ui->leftButton->isVisible() && ui->leftButton->objectName() == "ok");
}
void FileTransferWidget::paintEvent(QPaintEvent*)
{
// required by Hi-DPI support as border-image doesn't work.
QPainter painter(this);
painter.setRenderHint(QPainter::Antialiasing);
painter.setPen(Qt::NoPen);
qreal ratio = static_cast<qreal>(geometry().height()) / static_cast<qreal>(geometry().width());
const int r = 24;
const int buttonFieldWidth = 32;
const int lineWidth = 1;
// Draw the widget background:
painter.setClipRect(QRect(0, 0, width(), height()));
painter.setBrush(QBrush(backgroundColor));
painter.drawRoundedRect(geometry(), r * ratio, r, Qt::RelativeSize);
if (drawButtonAreaNeeded()) {
// Draw the button background:
QPainterPath buttonBackground;
buttonBackground.addRoundedRect(width() - 2 * buttonFieldWidth - lineWidth * 2, 0,
buttonFieldWidth, buttonFieldWidth + lineWidth, 50, 50,
Qt::RelativeSize);
buttonBackground.addRect(width() - 2 * buttonFieldWidth - lineWidth * 2, 0,
buttonFieldWidth * 2, buttonFieldWidth / 2);
buttonBackground.addRect(width() - 1.5 * buttonFieldWidth - lineWidth * 2, 0,
buttonFieldWidth * 2, buttonFieldWidth + 1);
buttonBackground.setFillRule(Qt::WindingFill);
painter.setBrush(QBrush(buttonBackgroundColor));
painter.drawPath(buttonBackground);
// Draw the left button:
QPainterPath leftButton;
leftButton.addRoundedRect(QRect(width() - 2 * buttonFieldWidth - lineWidth, 0,
buttonFieldWidth, buttonFieldWidth),
50, 50, Qt::RelativeSize);
leftButton.addRect(QRect(width() - 2 * buttonFieldWidth - lineWidth, 0,
buttonFieldWidth / 2, buttonFieldWidth / 2));
leftButton.addRect(QRect(width() - 1.5 * buttonFieldWidth - lineWidth, 0,
buttonFieldWidth / 2, buttonFieldWidth));
leftButton.setFillRule(Qt::WindingFill);
painter.setBrush(QBrush(buttonColor));
painter.drawPath(leftButton);
// Draw the right button:
painter.setBrush(QBrush(buttonColor));
painter.setClipRect(QRect(width() - buttonFieldWidth, 0, buttonFieldWidth, buttonFieldWidth));
painter.drawRoundedRect(geometry(), r * ratio, r, Qt::RelativeSize);
}
}
void FileTransferWidget::reloadTheme()
{
updateBackgroundColor(lastStatus);
}
QString FileTransferWidget::getHumanReadableSize(qint64 size)
{
static const char* suffix[] = {"B", "KiB", "MiB", "GiB", "TiB"};
int exp = 0;
if (size > 0) {
exp = std::min(static_cast<int>(log(size) / log(1024)), static_cast<int>(sizeof(suffix) / sizeof(suffix[0]) - 1));
}
return QString().setNum(size / pow(1024, exp), 'f', exp > 1 ? 2 : 0).append(suffix[exp]);
}
void FileTransferWidget::updateWidgetColor(ToxFile const& file)
{
if (lastStatus == file.status) {
return;
}
updateBackgroundColor(file.status);
}
void FileTransferWidget::updateWidgetText(ToxFile const& file)
{
if (lastStatus == file.status && file.status != ToxFile::PAUSED) {
return;
}
switch (file.status) {
case ToxFile::INITIALIZING:
if (file.direction == ToxFile::SENDING) {
ui->progressLabel->setText(tr("Waiting to send...", "file transfer widget"));
} else {
ui->progressLabel->setText(tr("Accept to receive this file", "file transfer widget"));
}
break;
case ToxFile::PAUSED:
ui->etaLabel->setText("");
if (file.pauseStatus.localPaused()) {
ui->progressLabel->setText(tr("Paused", "file transfer widget"));
} else {
ui->progressLabel->setText(tr("Remote paused", "file transfer widget"));
}
break;
case ToxFile::TRANSMITTING:
ui->etaLabel->setText("");
ui->progressLabel->setText(tr("Resuming...", "file transfer widget"));
break;
case ToxFile::BROKEN:
case ToxFile::CANCELED:
break;
case ToxFile::FINISHED:
break;
default:
qCritical() << "Invalid file status";
assert(false);
}
}
void FileTransferWidget::updatePreview(ToxFile const& file)
{
if (lastStatus == file.status) {
return;
}
switch (file.status) {
case ToxFile::INITIALIZING:
case ToxFile::PAUSED:
case ToxFile::TRANSMITTING:
case ToxFile::BROKEN:
case ToxFile::CANCELED:
if (file.direction == ToxFile::SENDING) {
showPreview(file.filePath);
}
break;
case ToxFile::FINISHED:
showPreview(file.filePath);
break;
default:
qCritical() << "Invalid file status";
assert(false);
}
}
void FileTransferWidget::updateFileProgress(ToxFile const& file)
{
switch (file.status) {
case ToxFile::INITIALIZING:
case ToxFile::PAUSED:
break;
case ToxFile::TRANSMITTING: {
auto speed = file.progress.getSpeed();
auto progress = file.progress.getProgress();
auto remainingTime = file.progress.getTimeLeftSeconds();
ui->progressBar->setValue(static_cast<int>(progress * 100.0));
// update UI
if (speed > 0) {
// ETA
QTime toGo = QTime(0, 0).addSecs(remainingTime);
QString format = toGo.hour() > 0 ? "hh:mm:ss" : "mm:ss";
ui->etaLabel->setText(toGo.toString(format));
} else {
ui->etaLabel->setText("");
}
ui->progressLabel->setText(getHumanReadableSize(speed) + "/s");
break;
}
case ToxFile::BROKEN:
case ToxFile::CANCELED:
case ToxFile::FINISHED: {
ui->progressBar->hide();
ui->progressLabel->hide();
ui->etaLabel->hide();
break;
}
default:
qCritical() << "Invalid file status";
assert(false);
}
}
void FileTransferWidget::updateSignals(ToxFile const& file)
{
if (lastStatus == file.status) {
return;
}
switch (file.status) {
case ToxFile::CANCELED:
case ToxFile::BROKEN:
case ToxFile::FINISHED:
active = false;
disconnect(&coreFile, nullptr, this, nullptr);
break;
case ToxFile::INITIALIZING:
case ToxFile::PAUSED:
case ToxFile::TRANSMITTING:
break;
default:
qCritical() << "Invalid file status";
assert(false);
}
}
void FileTransferWidget::setupButtons(ToxFile const& file)
{
if (lastStatus == file.status && file.status != ToxFile::PAUSED) {
return;
}
switch (file.status) {
case ToxFile::TRANSMITTING:
ui->leftButton->setIcon(QIcon(Style::getImagePath("fileTransferInstance/pause.svg")));
ui->leftButton->setObjectName("pause");
ui->leftButton->setToolTip(tr("Pause transfer"));
ui->rightButton->setIcon(QIcon(Style::getImagePath("fileTransferInstance/no.svg")));
ui->rightButton->setObjectName("cancel");
ui->rightButton->setToolTip(tr("Cancel transfer"));
setButtonColor(Style::getColor(Style::TransferGood));
break;
case ToxFile::PAUSED:
if (file.pauseStatus.localPaused()) {
ui->leftButton->setIcon(QIcon(Style::getImagePath("fileTransferInstance/arrow_white.svg")));
ui->leftButton->setObjectName("resume");
ui->leftButton->setToolTip(tr("Resume transfer"));
} else {
ui->leftButton->setIcon(QIcon(Style::getImagePath("fileTransferInstance/pause.svg")));
ui->leftButton->setObjectName("pause");
ui->leftButton->setToolTip(tr("Pause transfer"));
}
ui->rightButton->setIcon(QIcon(Style::getImagePath("fileTransferInstance/no.svg")));
ui->rightButton->setObjectName("cancel");
ui->rightButton->setToolTip(tr("Cancel transfer"));
setButtonColor(Style::getColor(Style::TransferMiddle));
break;
case ToxFile::INITIALIZING:
ui->rightButton->setIcon(QIcon(Style::getImagePath("fileTransferInstance/no.svg")));
ui->rightButton->setObjectName("cancel");
ui->rightButton->setToolTip(tr("Cancel transfer"));
if (file.direction == ToxFile::SENDING) {
ui->leftButton->setIcon(QIcon(Style::getImagePath("fileTransferInstance/pause.svg")));
ui->leftButton->setObjectName("pause");
ui->leftButton->setToolTip(tr("Pause transfer"));
} else {
ui->leftButton->setIcon(QIcon(Style::getImagePath("fileTransferInstance/yes.svg")));
ui->leftButton->setObjectName("accept");
ui->leftButton->setToolTip(tr("Accept transfer"));
}
break;
case ToxFile::CANCELED:
case ToxFile::BROKEN:
ui->leftButton->hide();
ui->rightButton->hide();
break;
case ToxFile::FINISHED:
ui->leftButton->setIcon(QIcon(Style::getImagePath("fileTransferInstance/yes.svg")));
ui->leftButton->setObjectName("ok");
ui->leftButton->setToolTip(tr("Open file"));
ui->leftButton->show();
ui->rightButton->setIcon(QIcon(Style::getImagePath("fileTransferInstance/dir.svg")));
ui->rightButton->setObjectName("dir");
ui->rightButton->setToolTip(tr("Open file directory"));
ui->rightButton->show();
break;
default:
qCritical() << "Invalid file status";
assert(false);
}
}
void FileTransferWidget::handleButton(QPushButton* btn)
{
if (fileInfo.direction == ToxFile::SENDING) {
if (btn->objectName() == "cancel") {
coreFile.cancelFileSend(fileInfo.friendId, fileInfo.fileNum);
} else if (btn->objectName() == "pause") {
coreFile.pauseResumeFile(fileInfo.friendId, fileInfo.fileNum);
} else if (btn->objectName() == "resume") {
coreFile.pauseResumeFile(fileInfo.friendId, fileInfo.fileNum);
}
} else // receiving or paused
{
if (btn->objectName() == "cancel") {
coreFile.cancelFileRecv(fileInfo.friendId, fileInfo.fileNum);
} else if (btn->objectName() == "pause") {
coreFile.pauseResumeFile(fileInfo.friendId, fileInfo.fileNum);
} else if (btn->objectName() == "resume") {
coreFile.pauseResumeFile(fileInfo.friendId, fileInfo.fileNum);
} else if (btn->objectName() == "accept") {
QString path =
QFileDialog::getSaveFileName(Q_NULLPTR,
tr("Save a file", "Title of the file saving dialog"),
Settings::getInstance().getGlobalAutoAcceptDir() + "/"
+ fileInfo.fileName);
acceptTransfer(path);
}
}
if (btn->objectName() == "ok" || btn->objectName() == "previewButton") {
Widget::confirmExecutableOpen(QFileInfo(fileInfo.filePath));
} else if (btn->objectName() == "dir") {
QString dirPath = QFileInfo(fileInfo.filePath).dir().path();
QDesktopServices::openUrl(QUrl::fromLocalFile(dirPath));
}
}
void FileTransferWidget::showPreview(const QString& filename)
{
ui->previewButton->setIconFromFile(filename);
ui->previewButton->show();
}
void FileTransferWidget::onLeftButtonClicked()
{
handleButton(ui->leftButton);
}
void FileTransferWidget::onRightButtonClicked()
{
handleButton(ui->rightButton);
}
void FileTransferWidget::onPreviewButtonClicked()
{
handleButton(ui->previewButton);
}
void FileTransferWidget::updateWidget(ToxFile const& file)
{
assert(file == fileInfo);
fileInfo = file;
bool shouldUpdateFileProgress = file.status != ToxFile::TRANSMITTING || lastTransmissionUpdate ==
QTime() || lastTransmissionUpdate.msecsTo(file.progress.lastSampleTime()) > 1000;
updatePreview(file);
if (shouldUpdateFileProgress)
updateFileProgress(file);
updateWidgetText(file);
updateWidgetColor(file);
setupButtons(file);
updateSignals(file);
lastStatus = file.status;
if (shouldUpdateFileProgress) {
lastTransmissionUpdate = QTime::currentTime();
update();
}
}
void FileTransferWidget::updateBackgroundColor(const ToxFile::FileStatus status)
{
switch (status) {
case ToxFile::INITIALIZING:
case ToxFile::PAUSED:
case ToxFile::TRANSMITTING:
setBackgroundColor(Style::getColor(Style::TransferMiddle), false);
break;
case ToxFile::BROKEN:
case ToxFile::CANCELED:
setBackgroundColor(Style::getColor(Style::TransferBad), true);
break;
case ToxFile::FINISHED:
setBackgroundColor(Style::getColor(Style::TransferGood), true);
break;
default:
qCritical() << "Invalid file status";
assert(false);
}
}