mirror of
https://github.com/qTox/qTox.git
synced 2024-03-22 14:00:36 +08:00
c7efe320d2
* 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
560 lines
18 KiB
C++
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);
|
|
}
|
|
}
|