1 /*
2     Copyright © 2014-2019 by The qTox Project Contributors
3 
4     This file is part of qTox, a Qt-based graphical interface for Tox.
5 
6     qTox is libre software: you can redistribute it and/or modify
7     it under the terms of the GNU General Public License as published by
8     the Free Software Foundation, either version 3 of the License, or
9     (at your option) any later version.
10 
11     qTox is distributed in the hope that it will be useful,
12     but WITHOUT ANY WARRANTY; without even the implied warranty of
13     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14     GNU General Public License for more details.
15 
16     You should have received a copy of the GNU General Public License
17     along with qTox.  If not, see <http://www.gnu.org/licenses/>.
18 */
19 
20 #include "filetransferwidget.h"
21 #include "ui_filetransferwidget.h"
22 
23 #include "src/core/core.h"
24 #include "src/core/corefile.h"
25 #include "src/persistence/settings.h"
26 #include "src/widget/gui.h"
27 #include "src/widget/style.h"
28 #include "src/widget/widget.h"
29 
30 #include <libexif/exif-loader.h>
31 
32 #include <QBuffer>
33 #include <QDebug>
34 #include <QDesktopServices>
35 #include <QDesktopWidget>
36 #include <QFile>
37 #include <QFileDialog>
38 #include <QMessageBox>
39 #include <QMouseEvent>
40 #include <QPainter>
41 #include <QVariantAnimation>
42 
43 #include <cassert>
44 #include <math.h>
45 
46 
47 // The leftButton is used to accept, pause, or resume a file transfer, as well as to open a
48 // received file.
49 // The rightButton is used to cancel a file transfer, or to open the directory a file was
50 // downloaded to.
51 
FileTransferWidget(QWidget * parent,ToxFile file)52 FileTransferWidget::FileTransferWidget(QWidget* parent, ToxFile file)
53     : QWidget(parent)
54     , ui(new Ui::FileTransferWidget)
55     , fileInfo(file)
56     , backgroundColor(Style::getColor(Style::TransferMiddle))
57     , buttonColor(Style::getColor(Style::TransferWait))
58     , buttonBackgroundColor(Style::getColor(Style::GroundBase))
59     , active(true)
60 {
61     ui->setupUi(this);
62 
63     // hide the QWidget background (background-color: transparent doesn't seem to work)
64     setAttribute(Qt::WA_TranslucentBackground, true);
65 
66     ui->previewButton->hide();
67     ui->filenameLabel->setText(file.fileName);
68     ui->progressBar->setValue(0);
69     ui->fileSizeLabel->setText(getHumanReadableSize(file.filesize));
70     ui->etaLabel->setText("");
71 
72     backgroundColorAnimation = new QVariantAnimation(this);
73     backgroundColorAnimation->setDuration(500);
74     backgroundColorAnimation->setEasingCurve(QEasingCurve::OutCubic);
75     connect(backgroundColorAnimation, &QVariantAnimation::valueChanged, this,
76             [this](const QVariant& val) {
77                 backgroundColor = val.value<QColor>();
78                 update();
79             });
80 
81     buttonColorAnimation = new QVariantAnimation(this);
82     buttonColorAnimation->setDuration(500);
83     buttonColorAnimation->setEasingCurve(QEasingCurve::OutCubic);
84     connect(buttonColorAnimation, &QVariantAnimation::valueChanged, this, [this](const QVariant& val) {
85         buttonColor = val.value<QColor>();
86         update();
87     });
88 
89     CoreFile* coreFile = Core::getInstance()->getCoreFile();
90 
91     connect(ui->leftButton, &QPushButton::clicked, this, &FileTransferWidget::onLeftButtonClicked);
92     connect(ui->rightButton, &QPushButton::clicked, this, &FileTransferWidget::onRightButtonClicked);
93     connect(ui->previewButton, &QPushButton::clicked, this,
94             &FileTransferWidget::onPreviewButtonClicked);
95 
96     // Set lastStatus to anything but the file's current value, this forces an update
97     lastStatus = file.status == ToxFile::FINISHED ? ToxFile::INITIALIZING : ToxFile::FINISHED;
98     updateWidget(file);
99 
100     setFixedHeight(64);
101 }
102 
~FileTransferWidget()103 FileTransferWidget::~FileTransferWidget()
104 {
105     delete ui;
106 }
107 
108 // TODO(sudden6): remove file IO from the UI
109 /**
110  * @brief Dangerous way to find out if a path is writable.
111  * @param filepath Path to file which should be deleted.
112  * @return True, if file writeable, false otherwise.
113  */
tryRemoveFile(const QString & filepath)114 bool FileTransferWidget::tryRemoveFile(const QString& filepath)
115 {
116     QFile tmp(filepath);
117     bool writable = tmp.open(QIODevice::WriteOnly);
118     tmp.remove();
119     return writable;
120 }
121 
onFileTransferUpdate(ToxFile file)122 void FileTransferWidget::onFileTransferUpdate(ToxFile file)
123 {
124     updateWidget(file);
125 }
126 
isActive() const127 bool FileTransferWidget::isActive() const
128 {
129     return active;
130 }
131 
acceptTransfer(const QString & filepath)132 void FileTransferWidget::acceptTransfer(const QString& filepath)
133 {
134     if (filepath.isEmpty()) {
135         return;
136     }
137 
138     // test if writable
139     if (!tryRemoveFile(filepath)) {
140         GUI::showWarning(tr("Location not writable", "Title of permissions popup"),
141                          tr("You do not have permission to write that location. Choose another, or "
142                             "cancel the save dialog.",
143                             "text of permissions popup"));
144         return;
145     }
146 
147     // everything ok!
148     CoreFile* coreFile = Core::getInstance()->getCoreFile();
149     coreFile->acceptFileRecvRequest(fileInfo.friendId, fileInfo.fileNum, filepath);
150 }
151 
setBackgroundColor(const QColor & c,bool whiteFont)152 void FileTransferWidget::setBackgroundColor(const QColor& c, bool whiteFont)
153 {
154     if (c != backgroundColor) {
155         backgroundColorAnimation->setStartValue(backgroundColor);
156         backgroundColorAnimation->setEndValue(c);
157         backgroundColorAnimation->start();
158     }
159 
160     setProperty("fontColor", whiteFont ? "white" : "black");
161 
162     setStyleSheet(Style::getStylesheet("fileTransferInstance/filetransferWidget.css"));
163     Style::repolish(this);
164 
165     update();
166 }
167 
setButtonColor(const QColor & c)168 void FileTransferWidget::setButtonColor(const QColor& c)
169 {
170     if (c != buttonColor) {
171         buttonColorAnimation->setStartValue(buttonColor);
172         buttonColorAnimation->setEndValue(c);
173         buttonColorAnimation->start();
174     }
175 }
176 
drawButtonAreaNeeded() const177 bool FileTransferWidget::drawButtonAreaNeeded() const
178 {
179     return (ui->rightButton->isVisible() || ui->leftButton->isVisible())
180            && !(ui->leftButton->isVisible() && ui->leftButton->objectName() == "ok");
181 }
182 
paintEvent(QPaintEvent *)183 void FileTransferWidget::paintEvent(QPaintEvent*)
184 {
185     // required by Hi-DPI support as border-image doesn't work.
186     QPainter painter(this);
187     painter.setRenderHint(QPainter::Antialiasing);
188     painter.setPen(Qt::NoPen);
189 
190     qreal ratio = static_cast<qreal>(geometry().height()) / static_cast<qreal>(geometry().width());
191     const int r = 24;
192     const int buttonFieldWidth = 32;
193     const int lineWidth = 1;
194 
195     // Draw the widget background:
196     painter.setClipRect(QRect(0, 0, width(), height()));
197     painter.setBrush(QBrush(backgroundColor));
198     painter.drawRoundedRect(geometry(), r * ratio, r, Qt::RelativeSize);
199 
200     if (drawButtonAreaNeeded()) {
201         // Draw the button background:
202         QPainterPath buttonBackground;
203         buttonBackground.addRoundedRect(width() - 2 * buttonFieldWidth - lineWidth * 2, 0,
204                                       buttonFieldWidth, buttonFieldWidth + lineWidth, 50, 50,
205                                       Qt::RelativeSize);
206         buttonBackground.addRect(width() - 2 * buttonFieldWidth - lineWidth * 2, 0,
207                                  buttonFieldWidth * 2, buttonFieldWidth / 2);
208         buttonBackground.addRect(width() - 1.5 * buttonFieldWidth - lineWidth * 2, 0,
209                                  buttonFieldWidth * 2, buttonFieldWidth + 1);
210         buttonBackground.setFillRule(Qt::WindingFill);
211         painter.setBrush(QBrush(buttonBackgroundColor));
212         painter.drawPath(buttonBackground);
213 
214         // Draw the left button:
215         QPainterPath leftButton;
216         leftButton.addRoundedRect(QRect(width() - 2 * buttonFieldWidth - lineWidth, 0,
217                                       buttonFieldWidth, buttonFieldWidth),
218                                 50, 50, Qt::RelativeSize);
219         leftButton.addRect(QRect(width() - 2 * buttonFieldWidth - lineWidth, 0,
220                                  buttonFieldWidth / 2, buttonFieldWidth / 2));
221         leftButton.addRect(QRect(width() - 1.5 * buttonFieldWidth - lineWidth, 0,
222                                  buttonFieldWidth / 2, buttonFieldWidth));
223         leftButton.setFillRule(Qt::WindingFill);
224         painter.setBrush(QBrush(buttonColor));
225         painter.drawPath(leftButton);
226 
227         // Draw the right button:
228         painter.setBrush(QBrush(buttonColor));
229         painter.setClipRect(QRect(width() - buttonFieldWidth, 0, buttonFieldWidth, buttonFieldWidth));
230         painter.drawRoundedRect(geometry(), r * ratio, r, Qt::RelativeSize);
231     }
232 }
233 
getHumanReadableSize(qint64 size)234 QString FileTransferWidget::getHumanReadableSize(qint64 size)
235 {
236     static const char* suffix[] = {"B", "kiB", "MiB", "GiB", "TiB"};
237     int exp = 0;
238 
239     if (size > 0) {
240         exp = std::min((int)(log(size) / log(1024)), (int)(sizeof(suffix) / sizeof(suffix[0]) - 1));
241     }
242 
243     return QString().setNum(size / pow(1024, exp), 'f', exp > 1 ? 2 : 0).append(suffix[exp]);
244 }
245 
updateWidgetColor(ToxFile const & file)246 void FileTransferWidget::updateWidgetColor(ToxFile const& file)
247 {
248     if (lastStatus == file.status) {
249         return;
250     }
251 
252     switch (file.status) {
253     case ToxFile::INITIALIZING:
254     case ToxFile::PAUSED:
255     case ToxFile::TRANSMITTING:
256         setBackgroundColor(Style::getColor(Style::TransferMiddle), false);
257         break;
258     case ToxFile::BROKEN:
259     case ToxFile::CANCELED:
260         setBackgroundColor(Style::getColor(Style::TransferBad), true);
261         break;
262     case ToxFile::FINISHED:
263         setBackgroundColor(Style::getColor(Style::TransferGood), true);
264         break;
265     default:
266         qCritical() << "Invalid file status";
267         assert(false);
268     }
269 }
270 
updateWidgetText(ToxFile const & file)271 void FileTransferWidget::updateWidgetText(ToxFile const& file)
272 {
273     if (lastStatus == file.status && file.status != ToxFile::PAUSED) {
274         return;
275     }
276 
277     switch (file.status) {
278     case ToxFile::INITIALIZING:
279         if (file.direction == ToxFile::SENDING) {
280             ui->progressLabel->setText(tr("Waiting to send...", "file transfer widget"));
281         } else {
282             ui->progressLabel->setText(tr("Accept to receive this file", "file transfer widget"));
283         }
284         break;
285     case ToxFile::PAUSED:
286         ui->etaLabel->setText("");
287         if (file.pauseStatus.localPaused()) {
288             ui->progressLabel->setText(tr("Paused", "file transfer widget"));
289         } else {
290             ui->progressLabel->setText(tr("Remote Paused", "file transfer widget"));
291         }
292         break;
293     case ToxFile::TRANSMITTING:
294         ui->etaLabel->setText("");
295         ui->progressLabel->setText(tr("Resuming...", "file transfer widget"));
296         break;
297     case ToxFile::BROKEN:
298     case ToxFile::CANCELED:
299         break;
300     case ToxFile::FINISHED:
301         break;
302     default:
303         qCritical() << "Invalid file status";
304         assert(false);
305     }
306 }
307 
updatePreview(ToxFile const & file)308 void FileTransferWidget::updatePreview(ToxFile const& file)
309 {
310     if (lastStatus == file.status) {
311         return;
312     }
313 
314     switch (file.status) {
315     case ToxFile::INITIALIZING:
316     case ToxFile::PAUSED:
317     case ToxFile::TRANSMITTING:
318     case ToxFile::BROKEN:
319     case ToxFile::CANCELED:
320         if (file.direction == ToxFile::SENDING) {
321             showPreview(file.filePath);
322         }
323         break;
324     case ToxFile::FINISHED:
325         showPreview(file.filePath);
326         break;
327     default:
328         qCritical() << "Invalid file status";
329         assert(false);
330     }
331 }
332 
updateFileProgress(ToxFile const & file)333 void FileTransferWidget::updateFileProgress(ToxFile const& file)
334 {
335     switch (file.status) {
336     case ToxFile::INITIALIZING:
337         break;
338     case ToxFile::PAUSED:
339         fileProgress.resetSpeed();
340         break;
341     case ToxFile::TRANSMITTING: {
342         if (!fileProgress.needsUpdate()) {
343             break;
344         }
345 
346         fileProgress.addSample(file);
347         auto speed = fileProgress.getSpeed();
348         auto progress = fileProgress.getProgress();
349         auto remainingTime = fileProgress.getTimeLeftSeconds();
350 
351         ui->progressBar->setValue(static_cast<int>(progress * 100.0));
352 
353         // update UI
354         if (speed > 0) {
355             // ETA
356             QTime toGo = QTime(0, 0).addSecs(remainingTime);
357             QString format = toGo.hour() > 0 ? "hh:mm:ss" : "mm:ss";
358             ui->etaLabel->setText(toGo.toString(format));
359         } else {
360             ui->etaLabel->setText("");
361         }
362 
363         ui->progressLabel->setText(getHumanReadableSize(speed) + "/s");
364         break;
365     }
366     case ToxFile::BROKEN:
367     case ToxFile::CANCELED:
368     case ToxFile::FINISHED: {
369         ui->progressBar->hide();
370         ui->progressLabel->hide();
371         ui->etaLabel->hide();
372         break;
373     }
374     default:
375         qCritical() << "Invalid file status";
376         assert(false);
377     }
378 }
379 
updateSignals(ToxFile const & file)380 void FileTransferWidget::updateSignals(ToxFile const& file)
381 {
382     if (lastStatus == file.status) {
383         return;
384     }
385 
386     switch (file.status) {
387     case ToxFile::CANCELED:
388     case ToxFile::BROKEN:
389     case ToxFile::FINISHED:
390         active = false;
391         disconnect(Core::getInstance()->getCoreFile(), nullptr, this, nullptr);
392         break;
393     case ToxFile::INITIALIZING:
394     case ToxFile::PAUSED:
395     case ToxFile::TRANSMITTING:
396         break;
397     default:
398         qCritical() << "Invalid file status";
399         assert(false);
400     }
401 }
402 
setupButtons(ToxFile const & file)403 void FileTransferWidget::setupButtons(ToxFile const& file)
404 {
405     if (lastStatus == file.status && file.status != ToxFile::PAUSED) {
406         return;
407     }
408 
409     switch (file.status) {
410     case ToxFile::TRANSMITTING:
411         ui->leftButton->setIcon(QIcon(Style::getImagePath("fileTransferInstance/pause.svg")));
412         ui->leftButton->setObjectName("pause");
413         ui->leftButton->setToolTip(tr("Pause transfer"));
414 
415         ui->rightButton->setIcon(QIcon(Style::getImagePath("fileTransferInstance/no.svg")));
416         ui->rightButton->setObjectName("cancel");
417         ui->rightButton->setToolTip(tr("Cancel transfer"));
418 
419         setButtonColor(Style::getColor(Style::TransferGood));
420         break;
421 
422     case ToxFile::PAUSED:
423         if (file.pauseStatus.localPaused()) {
424             ui->leftButton->setIcon(QIcon(Style::getImagePath("fileTransferInstance/arrow_white.svg")));
425             ui->leftButton->setObjectName("resume");
426             ui->leftButton->setToolTip(tr("Resume transfer"));
427         } else {
428             ui->leftButton->setIcon(QIcon(Style::getImagePath("fileTransferInstance/pause.svg")));
429             ui->leftButton->setObjectName("pause");
430             ui->leftButton->setToolTip(tr("Pause transfer"));
431         }
432 
433         ui->rightButton->setIcon(QIcon(Style::getImagePath("fileTransferInstance/no.svg")));
434         ui->rightButton->setObjectName("cancel");
435         ui->rightButton->setToolTip(tr("Cancel transfer"));
436 
437         setButtonColor(Style::getColor(Style::TransferMiddle));
438         break;
439 
440     case ToxFile::INITIALIZING:
441         ui->rightButton->setIcon(QIcon(Style::getImagePath("fileTransferInstance/no.svg")));
442         ui->rightButton->setObjectName("cancel");
443         ui->rightButton->setToolTip(tr("Cancel transfer"));
444 
445         if (file.direction == ToxFile::SENDING) {
446             ui->leftButton->setIcon(QIcon(Style::getImagePath("fileTransferInstance/pause.svg")));
447             ui->leftButton->setObjectName("pause");
448             ui->leftButton->setToolTip(tr("Pause transfer"));
449         } else {
450             ui->leftButton->setIcon(QIcon(Style::getImagePath("fileTransferInstance/yes.svg")));
451             ui->leftButton->setObjectName("accept");
452             ui->leftButton->setToolTip(tr("Accept transfer"));
453         }
454         break;
455     case ToxFile::CANCELED:
456     case ToxFile::BROKEN:
457         ui->leftButton->hide();
458         ui->rightButton->hide();
459         break;
460     case ToxFile::FINISHED:
461         ui->leftButton->setIcon(QIcon(Style::getImagePath("fileTransferInstance/yes.svg")));
462         ui->leftButton->setObjectName("ok");
463         ui->leftButton->setToolTip(tr("Open file"));
464         ui->leftButton->show();
465 
466         ui->rightButton->setIcon(QIcon(Style::getImagePath("fileTransferInstance/dir.svg")));
467         ui->rightButton->setObjectName("dir");
468         ui->rightButton->setToolTip(tr("Open file directory"));
469         ui->rightButton->show();
470 
471         break;
472     default:
473         qCritical() << "Invalid file status";
474         assert(false);
475     }
476 }
477 
handleButton(QPushButton * btn)478 void FileTransferWidget::handleButton(QPushButton* btn)
479 {
480     CoreFile* coreFile = Core::getInstance()->getCoreFile();
481     if (fileInfo.direction == ToxFile::SENDING) {
482         if (btn->objectName() == "cancel") {
483             coreFile->cancelFileSend(fileInfo.friendId, fileInfo.fileNum);
484         } else if (btn->objectName() == "pause") {
485             coreFile->pauseResumeFile(fileInfo.friendId, fileInfo.fileNum);
486         } else if (btn->objectName() == "resume") {
487             coreFile->pauseResumeFile(fileInfo.friendId, fileInfo.fileNum);
488         }
489     } else // receiving or paused
490     {
491         if (btn->objectName() == "cancel") {
492             coreFile->cancelFileRecv(fileInfo.friendId, fileInfo.fileNum);
493         } else if (btn->objectName() == "pause") {
494             coreFile->pauseResumeFile(fileInfo.friendId, fileInfo.fileNum);
495         } else if (btn->objectName() == "resume") {
496             coreFile->pauseResumeFile(fileInfo.friendId, fileInfo.fileNum);
497         } else if (btn->objectName() == "accept") {
498             QString path =
499                 QFileDialog::getSaveFileName(Q_NULLPTR,
500                                              tr("Save a file", "Title of the file saving dialog"),
501                                              Settings::getInstance().getGlobalAutoAcceptDir() + "/"
502                                                  + fileInfo.fileName);
503             acceptTransfer(path);
504         }
505     }
506 
507     if (btn->objectName() == "ok" || btn->objectName() == "previewButton") {
508         Widget::confirmExecutableOpen(QFileInfo(fileInfo.filePath));
509     } else if (btn->objectName() == "dir") {
510         QString dirPath = QFileInfo(fileInfo.filePath).dir().path();
511         QDesktopServices::openUrl(QUrl::fromLocalFile(dirPath));
512     }
513 }
514 
showPreview(const QString & filename)515 void FileTransferWidget::showPreview(const QString& filename)
516 {
517     static const QStringList previewExtensions = {"png", "jpeg", "jpg", "gif", "svg",
518                                                   "PNG", "JPEG", "JPG", "GIF", "SVG"};
519 
520     if (previewExtensions.contains(QFileInfo(filename).suffix())) {
521         // Subtract to make border visible
522         const int size = qMax(ui->previewButton->width(), ui->previewButton->height()) - 4;
523 
524         QFile imageFile(filename);
525         if (!imageFile.open(QIODevice::ReadOnly)) {
526             return;
527         }
528         const QByteArray imageFileData = imageFile.readAll();
529         QImage image = QImage::fromData(imageFileData);
530         const int exifOrientation =
531             getExifOrientation(imageFileData.constData(), imageFileData.size());
532         if (exifOrientation) {
533             applyTransformation(exifOrientation, image);
534         }
535 
536         const QPixmap iconPixmap = scaleCropIntoSquare(QPixmap::fromImage(image), size);
537 
538         ui->previewButton->setIcon(QIcon(iconPixmap));
539         ui->previewButton->setIconSize(iconPixmap.size());
540         ui->previewButton->show();
541         // Show mouseover preview, but make sure it's not larger than 50% of the screen
542         // width/height
543         const QRect desktopSize = QApplication::desktop()->geometry();
544         const int maxPreviewWidth{desktopSize.width() / 2};
545         const int maxPreviewHeight{desktopSize.height() / 2};
546         const QImage previewImage = [&image, maxPreviewWidth, maxPreviewHeight]() {
547             if (image.width() > maxPreviewWidth || image.height() > maxPreviewHeight) {
548                 return image.scaled(maxPreviewWidth, maxPreviewHeight, Qt::KeepAspectRatio,
549                                     Qt::SmoothTransformation);
550             } else {
551                 return image;
552             }
553         }();
554 
555         QByteArray imageData;
556         QBuffer buffer(&imageData);
557         buffer.open(QIODevice::WriteOnly);
558         previewImage.save(&buffer, "PNG");
559         buffer.close();
560         ui->previewButton->setToolTip("<img src=data:image/png;base64," + imageData.toBase64() + "/>");
561     }
562 }
563 
onLeftButtonClicked()564 void FileTransferWidget::onLeftButtonClicked()
565 {
566     handleButton(ui->leftButton);
567 }
568 
onRightButtonClicked()569 void FileTransferWidget::onRightButtonClicked()
570 {
571     handleButton(ui->rightButton);
572 }
573 
onPreviewButtonClicked()574 void FileTransferWidget::onPreviewButtonClicked()
575 {
576     handleButton(ui->previewButton);
577 }
578 
scaleCropIntoSquare(const QPixmap & source,const int targetSize)579 QPixmap FileTransferWidget::scaleCropIntoSquare(const QPixmap& source, const int targetSize)
580 {
581     QPixmap result;
582 
583     // Make sure smaller-than-icon images (at least one dimension is smaller) will not be
584     // upscaled
585     if (source.width() < targetSize || source.height() < targetSize) {
586         result = source;
587     } else {
588         result = source.scaled(targetSize, targetSize, Qt::KeepAspectRatioByExpanding,
589                                Qt::SmoothTransformation);
590     }
591 
592     // Then, image has to be cropped (if needed) so it will not overflow rectangle
593     // Only one dimension will be bigger after Qt::KeepAspectRatioByExpanding
594     if (result.width() > targetSize) {
595         return result.copy((result.width() - targetSize) / 2, 0, targetSize, targetSize);
596     } else if (result.height() > targetSize) {
597         return result.copy(0, (result.height() - targetSize) / 2, targetSize, targetSize);
598     }
599 
600     // Picture was rectangle in the first place, no cropping
601     return result;
602 }
603 
getExifOrientation(const char * data,const int size)604 int FileTransferWidget::getExifOrientation(const char* data, const int size)
605 {
606     ExifData* exifData = exif_data_new_from_data(reinterpret_cast<const unsigned char*>(data), size);
607 
608     if (!exifData) {
609         return 0;
610     }
611 
612     int orientation = 0;
613     const ExifByteOrder byteOrder = exif_data_get_byte_order(exifData);
614     const ExifEntry* const exifEntry = exif_data_get_entry(exifData, EXIF_TAG_ORIENTATION);
615     if (exifEntry) {
616         orientation = exif_get_short(exifEntry->data, byteOrder);
617     }
618     exif_data_free(exifData);
619     return orientation;
620 }
621 
applyTransformation(const int orientation,QImage & image)622 void FileTransferWidget::applyTransformation(const int orientation, QImage& image)
623 {
624     QTransform exifTransform;
625     switch (static_cast<ExifOrientation>(orientation)) {
626     case ExifOrientation::TopLeft:
627         break;
628     case ExifOrientation::TopRight:
629         image = image.mirrored(1, 0);
630         break;
631     case ExifOrientation::BottomRight:
632         exifTransform.rotate(180);
633         break;
634     case ExifOrientation::BottomLeft:
635         image = image.mirrored(0, 1);
636         break;
637     case ExifOrientation::LeftTop:
638         exifTransform.rotate(90);
639         image = image.mirrored(0, 1);
640         break;
641     case ExifOrientation::RightTop:
642         exifTransform.rotate(-90);
643         break;
644     case ExifOrientation::RightBottom:
645         exifTransform.rotate(-90);
646         image = image.mirrored(0, 1);
647         break;
648     case ExifOrientation::LeftBottom:
649         exifTransform.rotate(90);
650         break;
651     default:
652         qWarning() << "Invalid exif orientation passed to applyTransformation!";
653     }
654     image = image.transformed(exifTransform);
655 }
656 
updateWidget(ToxFile const & file)657 void FileTransferWidget::updateWidget(ToxFile const& file)
658 {
659     assert(file == fileInfo);
660 
661     fileInfo = file;
662 
663     // If we repainted on every packet our gui would be *very* slow
664     bool bTransmitNeedsUpdate = fileProgress.needsUpdate();
665 
666     updatePreview(file);
667     updateFileProgress(file);
668     updateWidgetText(file);
669     updateWidgetColor(file);
670     setupButtons(file);
671     updateSignals(file);
672 
673     lastStatus = file.status;
674 
675     // trigger repaint
676     switch (file.status) {
677     case ToxFile::TRANSMITTING:
678         if (!bTransmitNeedsUpdate) {
679             break;
680         }
681     // fallthrough
682     default:
683         update();
684     }
685 }
686