diff --git a/CMakeLists.txt b/CMakeLists.txt
index 8dfc0ccee..a0ef510a8 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -373,6 +373,8 @@ set(${PROJECT_NAME}_SOURCES
src/widget/extensionstatus.h
src/widget/flowlayout.cpp
src/widget/flowlayout.h
+ src/widget/imagepreviewwidget.h
+ src/widget/imagepreviewwidget.cpp
src/widget/searchform.cpp
src/widget/searchform.h
src/widget/searchtypes.h
diff --git a/src/chatlog/content/filetransferwidget.cpp b/src/chatlog/content/filetransferwidget.cpp
index 6e62d5a59..8716d3634 100644
--- a/src/chatlog/content/filetransferwidget.cpp
+++ b/src/chatlog/content/filetransferwidget.cpp
@@ -500,49 +500,8 @@ void FileTransferWidget::handleButton(QPushButton* btn)
void FileTransferWidget::showPreview(const QString& filename)
{
- static const QStringList previewExtensions = {"png", "jpeg", "jpg", "gif", "svg",
- "PNG", "JPEG", "JPG", "GIF", "SVG"};
-
- if (previewExtensions.contains(QFileInfo(filename).suffix())) {
- // Subtract to make border visible
- const int size = qMax(ui->previewButton->width(), ui->previewButton->height()) - 4;
-
- QFile imageFile(filename);
- if (!imageFile.open(QIODevice::ReadOnly)) {
- return;
- }
-
- const QByteArray imageFileData = imageFile.readAll();
- QImage image = QImage::fromData(imageFileData);
- auto orientation = ExifTransform::getOrientation(imageFileData);
- image = ExifTransform::applyTransformation(image, orientation);
-
- const QPixmap iconPixmap = scaleCropIntoSquare(QPixmap::fromImage(image), size);
-
- ui->previewButton->setIcon(QIcon(iconPixmap));
- ui->previewButton->setIconSize(iconPixmap.size());
- ui->previewButton->show();
- // Show mouseover preview, but make sure it's not larger than 50% of the screen
- // width/height
- const QRect desktopSize = QApplication::desktop()->geometry();
- const int maxPreviewWidth{desktopSize.width() / 2};
- const int maxPreviewHeight{desktopSize.height() / 2};
- const QImage previewImage = [&image, maxPreviewWidth, maxPreviewHeight]() {
- if (image.width() > maxPreviewWidth || image.height() > maxPreviewHeight) {
- return image.scaled(maxPreviewWidth, maxPreviewHeight, Qt::KeepAspectRatio,
- Qt::SmoothTransformation);
- } else {
- return image;
- }
- }();
-
- QByteArray imageData;
- QBuffer buffer(&imageData);
- buffer.open(QIODevice::WriteOnly);
- previewImage.save(&buffer, "PNG");
- buffer.close();
- ui->previewButton->setToolTip("");
- }
+ ui->previewButton->setIconFromFile(filename);
+ ui->previewButton->show();
}
void FileTransferWidget::onLeftButtonClicked()
@@ -560,31 +519,6 @@ void FileTransferWidget::onPreviewButtonClicked()
handleButton(ui->previewButton);
}
-QPixmap FileTransferWidget::scaleCropIntoSquare(const QPixmap& source, const int targetSize)
-{
- QPixmap result;
-
- // Make sure smaller-than-icon images (at least one dimension is smaller) will not be
- // upscaled
- if (source.width() < targetSize || source.height() < targetSize) {
- result = source;
- } else {
- result = source.scaled(targetSize, targetSize, Qt::KeepAspectRatioByExpanding,
- Qt::SmoothTransformation);
- }
-
- // Then, image has to be cropped (if needed) so it will not overflow rectangle
- // Only one dimension will be bigger after Qt::KeepAspectRatioByExpanding
- if (result.width() > targetSize) {
- return result.copy((result.width() - targetSize) / 2, 0, targetSize, targetSize);
- } else if (result.height() > targetSize) {
- return result.copy(0, (result.height() - targetSize) / 2, targetSize, targetSize);
- }
-
- // Picture was rectangle in the first place, no cropping
- return result;
-}
-
void FileTransferWidget::updateWidget(ToxFile const& file)
{
assert(file == fileInfo);
diff --git a/src/chatlog/content/filetransferwidget.h b/src/chatlog/content/filetransferwidget.h
index 88face59d..39c39d35c 100644
--- a/src/chatlog/content/filetransferwidget.h
+++ b/src/chatlog/content/filetransferwidget.h
@@ -73,9 +73,6 @@ private slots:
void onPreviewButtonClicked();
private:
- static QPixmap scaleCropIntoSquare(const QPixmap& source, int targetSize);
- static int getExifOrientation(const char* data, const int size);
- static void applyTransformation(const int oritentation, QImage& image);
static bool tryRemoveFile(const QString &filepath);
void updateWidget(ToxFile const& file);
diff --git a/src/chatlog/content/filetransferwidget.ui b/src/chatlog/content/filetransferwidget.ui
index eca5d8480..053ef9ebd 100644
--- a/src/chatlog/content/filetransferwidget.ui
+++ b/src/chatlog/content/filetransferwidget.ui
@@ -270,7 +270,7 @@
-
-
+
0
@@ -296,7 +296,7 @@
QPushButton{ border: 2px solid white }
-
+
:themes/default/fileTransferInstance/no.svg:themes/default/fileTransferInstance/no.svg
@@ -382,7 +382,7 @@
-
+
:themes/default/fileTransferInstance/no.svg:themes/default/fileTransferInstance/no.svg
@@ -420,7 +420,7 @@
-
+
:themes/default/fileTransferInstance/no.svg:themes/default/fileTransferInstance/no.svg
@@ -445,6 +445,11 @@
QLabel
src/widget/tool/croppinglabel.h
+
+ ImagePreviewButton
+ QPushButton
+ src/widget/imagepreviewwidget.h
+
previewButton
diff --git a/src/widget/form/chatform.cpp b/src/widget/form/chatform.cpp
index d0b4f1942..e31d9fd4b 100644
--- a/src/widget/form/chatform.cpp
+++ b/src/widget/form/chatform.cpp
@@ -37,6 +37,7 @@
#include "src/widget/chatformheader.h"
#include "src/widget/contentdialogmanager.h"
#include "src/widget/form/loadhistorydialog.h"
+#include "src/widget/imagepreviewwidget.h"
#include "src/widget/maskablepixmapwidget.h"
#include "src/widget/searchform.h"
#include "src/widget/style.h"
@@ -134,6 +135,23 @@ ChatForm::ChatForm(Profile& profile, Friend* chatFriend, IChatLog& chatLog, IMes
headWidget->addWidget(callDuration, 1, Qt::AlignCenter);
callDuration->hide();
+ imagePreview = new ImagePreviewButton(this);
+ imagePreview->setFixedSize(100, 100);
+ imagePreview->setFlat(true);
+ imagePreview->setStyleSheet("QPushButton { border: 0px }");
+ imagePreview->hide();
+
+ auto cancelIcon = QIcon(Style::getImagePath("rejectCall/rejectCall.svg"));
+ QPushButton* cancelButton = new QPushButton(imagePreview);
+ cancelButton->setFixedSize(20, 20);
+ cancelButton->move(QPoint(80, 0));
+ cancelButton->setIcon(cancelIcon);
+ cancelButton->setFlat(true);
+
+ connect(cancelButton, &QPushButton::pressed, this, &ChatForm::cancelImagePreview);
+
+ contentLayout->insertWidget(3, imagePreview);
+
copyStatusAction = statusMessageMenu.addAction(QString(), this, SLOT(onCopyStatusMessage()));
const CoreFile* coreFile = core.getCoreFile();
@@ -155,9 +173,12 @@ ChatForm::ChatForm(Profile& profile, Friend* chatFriend, IChatLog& chatLog, IMes
connect(headWidget, &ChatFormHeader::micMuteToggle, this, &ChatForm::onMicMuteToggle);
connect(headWidget, &ChatFormHeader::volMuteToggle, this, &ChatForm::onVolMuteToggle);
connect(sendButton, &QPushButton::pressed, this, &ChatForm::callUpdateFriendActivity);
+ connect(sendButton, &QPushButton::pressed, this, &ChatForm::sendImageFromPreview);
connect(msgEdit, &ChatTextEdit::enterPressed, this, &ChatForm::callUpdateFriendActivity);
connect(msgEdit, &ChatTextEdit::textChanged, this, &ChatForm::onTextEditChanged);
- connect(msgEdit, &ChatTextEdit::pasteImage, this, &ChatForm::sendImage);
+ connect(msgEdit, &ChatTextEdit::pasteImage, this, &ChatForm::previewImage);
+ connect(msgEdit, &ChatTextEdit::enterPressed, this, &ChatForm::sendImageFromPreview);
+ connect(msgEdit, &ChatTextEdit::escapePressed, this, &ChatForm::cancelImagePreview);
connect(statusMessageLabel, &CroppingLabel::customContextMenuRequested, this,
[&](const QPoint& pos) {
if (!statusMessageLabel->text().isEmpty()) {
@@ -561,12 +582,29 @@ void ChatForm::doScreenshot()
{
// note: grabber is self-managed and will destroy itself when done
ScreenshotGrabber* grabber = new ScreenshotGrabber;
- connect(grabber, &ScreenshotGrabber::screenshotTaken, this, &ChatForm::sendImage);
+ connect(grabber, &ScreenshotGrabber::screenshotTaken, this, &ChatForm::previewImage);
grabber->showGrabber();
}
-void ChatForm::sendImage(const QPixmap& pixmap)
+void ChatForm::previewImage(const QPixmap& pixmap)
{
+ imagePreviewSource = pixmap;
+ imagePreview->setIconFromPixmap(pixmap);
+ imagePreview->show();
+}
+
+void ChatForm::cancelImagePreview()
+{
+ imagePreviewSource = QPixmap();
+ imagePreview->hide();
+}
+
+void ChatForm::sendImageFromPreview()
+{
+ if (!imagePreview->isVisible()) {
+ return;
+ }
+
QDir(Settings::getInstance().getPaths().getAppDataDirPath()).mkpath("images");
// use ~ISO 8601 for screenshot timestamp, considering FS limitations
@@ -580,8 +618,10 @@ void ChatForm::sendImage(const QPixmap& pixmap)
QFile file(filepath);
if (file.open(QFile::ReadWrite)) {
- pixmap.save(&file, "PNG");
+ imagePreviewSource.save(&file, "PNG");
qint64 filesize = file.size();
+ imagePreview->hide();
+ imagePreviewSource = QPixmap();
file.close();
QFileInfo fi(file);
CoreFile* coreFile = core.getCoreFile();
diff --git a/src/widget/form/chatform.h b/src/widget/form/chatform.h
index bb940e5ad..b371f385e 100644
--- a/src/widget/form/chatform.h
+++ b/src/widget/form/chatform.h
@@ -41,6 +41,7 @@ class OfflineMsgEngine;
class QPixmap;
class QHideEvent;
class QMoveEvent;
+class ImagePreviewButton;
class ChatForm : public GenericChatForm
{
@@ -96,7 +97,9 @@ private slots:
void onFriendNameChanged(const QString& name);
void onStatusMessage(const QString& message);
void onUpdateTime();
- void sendImage(const QPixmap& pixmap);
+ void previewImage(const QPixmap& pixmap);
+ void cancelImagePreview();
+ void sendImageFromPreview();
void doScreenshot();
void onCopyStatusMessage();
@@ -131,6 +134,8 @@ private:
QTimer typingTimer;
QElapsedTimer timeElapsed;
QAction* copyStatusAction;
+ QPixmap imagePreviewSource;
+ ImagePreviewButton* imagePreview;
bool isTyping;
bool lastCallIsVideo;
std::unique_ptr netcam;
diff --git a/src/widget/form/genericchatform.cpp b/src/widget/form/genericchatform.cpp
index c66a80c22..83299c260 100644
--- a/src/widget/form/genericchatform.cpp
+++ b/src/widget/form/genericchatform.cpp
@@ -309,7 +309,7 @@ GenericChatForm::GenericChatForm(const Core& _core, const Contact* contact, ICha
mainFootLayout->addWidget(sendButton);
mainFootLayout->setSpacing(0);
- QVBoxLayout* contentLayout = new QVBoxLayout(contentWidget);
+ contentLayout = new QVBoxLayout(contentWidget);
contentLayout->addWidget(searchForm);
contentLayout->addWidget(dateInfo);
contentLayout->addWidget(chatWidget);
diff --git a/src/widget/form/genericchatform.h b/src/widget/form/genericchatform.h
index 359b69644..9853eac4d 100644
--- a/src/widget/form/genericchatform.h
+++ b/src/widget/form/genericchatform.h
@@ -168,6 +168,7 @@ protected:
QMenu menu;
+ QVBoxLayout* contentLayout;
QPushButton* emoteButton;
QPushButton* fileButton;
QPushButton* screenshotButton;
diff --git a/src/widget/imagepreviewwidget.cpp b/src/widget/imagepreviewwidget.cpp
new file mode 100644
index 000000000..0a14a0fee
--- /dev/null
+++ b/src/widget/imagepreviewwidget.cpp
@@ -0,0 +1,125 @@
+/*
+ Copyright © 2020 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 .
+*/
+
+#include "imagepreviewwidget.h"
+#include "src/model/exiftransform.h"
+
+#include
+#include
+#include
+#include
+#include
+#include
+
+namespace
+{
+QPixmap pixmapFromFile(const QString& filename)
+{
+ static const QStringList previewExtensions = {"png", "jpeg", "jpg", "gif", "svg",
+ "PNG", "JPEG", "JPG", "GIF", "SVG"};
+
+ if (!previewExtensions.contains(QFileInfo(filename).suffix())) {
+ return QPixmap();
+ }
+
+ QFile imageFile(filename);
+ if (!imageFile.open(QIODevice::ReadOnly)) {
+ return QPixmap();
+ }
+
+ const QByteArray imageFileData = imageFile.readAll();
+ QImage image = QImage::fromData(imageFileData);
+ auto orientation = ExifTransform::getOrientation(imageFileData);
+ image = ExifTransform::applyTransformation(image, orientation);
+
+ return QPixmap::fromImage(image);
+}
+
+QPixmap scaleCropIntoSquare(const QPixmap& source, const int targetSize)
+{
+ QPixmap result;
+
+ // Make sure smaller-than-icon images (at least one dimension is smaller) will not be
+ // upscaled
+ if (source.width() < targetSize || source.height() < targetSize) {
+ result = source;
+ } else {
+ result = source.scaled(targetSize, targetSize, Qt::KeepAspectRatioByExpanding,
+ Qt::SmoothTransformation);
+ }
+
+ // Then, image has to be cropped (if needed) so it will not overflow rectangle
+ // Only one dimension will be bigger after Qt::KeepAspectRatioByExpanding
+ if (result.width() > targetSize) {
+ return result.copy((result.width() - targetSize) / 2, 0, targetSize, targetSize);
+ } else if (result.height() > targetSize) {
+ return result.copy(0, (result.height() - targetSize) / 2, targetSize, targetSize);
+ }
+
+ // Picture was rectangle in the first place, no cropping
+ return result;
+}
+
+QString getToolTipDisplayingImage(const QPixmap& image)
+{
+ // Show mouseover preview, but make sure it's not larger than 50% of the screen
+ // width/height
+ const QRect desktopSize = QApplication::desktop()->geometry();
+ const int maxPreviewWidth{desktopSize.width() / 2};
+ const int maxPreviewHeight{desktopSize.height() / 2};
+ const QPixmap previewImage = [&image, maxPreviewWidth, maxPreviewHeight]() {
+ if (image.width() > maxPreviewWidth || image.height() > maxPreviewHeight) {
+ return image.scaled(maxPreviewWidth, maxPreviewHeight, Qt::KeepAspectRatio,
+ Qt::SmoothTransformation);
+ } else {
+ return image;
+ }
+ }();
+
+ QByteArray imageData;
+ QBuffer buffer(&imageData);
+ buffer.open(QIODevice::WriteOnly);
+ previewImage.save(&buffer, "PNG");
+ buffer.close();
+
+ return "";
+}
+
+} // namespace
+
+void ImagePreviewButton::initialize(const QPixmap& image)
+{
+ auto desiredSize = qMin(width(), height()); // Assume widget is a square
+ desiredSize = qMax(desiredSize, 4) - 4; // Leave some room for a border
+
+ auto croppedImage = scaleCropIntoSquare(image, desiredSize);
+ setIcon(QIcon(croppedImage));
+ setIconSize(croppedImage.size());
+ setToolTip(getToolTipDisplayingImage(image));
+}
+
+void ImagePreviewButton::setIconFromFile(const QString& filename)
+{
+ initialize(pixmapFromFile(filename));
+}
+
+void ImagePreviewButton::setIconFromPixmap(const QPixmap& pixmap)
+{
+ initialize(pixmap);
+}
diff --git a/src/widget/imagepreviewwidget.h b/src/widget/imagepreviewwidget.h
new file mode 100644
index 000000000..2eb51527b
--- /dev/null
+++ b/src/widget/imagepreviewwidget.h
@@ -0,0 +1,37 @@
+/*
+ Copyright © 2020 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 .
+*/
+
+#pragma once
+
+#include
+#include
+#include
+
+class ImagePreviewButton : public QPushButton
+{
+public:
+ ImagePreviewButton(QWidget* parent = nullptr)
+ : QPushButton(parent)
+ {}
+
+ void setIconFromFile(const QString& filename);
+ void setIconFromPixmap(const QPixmap& image);
+private:
+ void initialize(const QPixmap& image);
+};
diff --git a/src/widget/tool/chattextedit.cpp b/src/widget/tool/chattextedit.cpp
index 3de982405..e67f85f29 100644
--- a/src/widget/tool/chattextedit.cpp
+++ b/src/widget/tool/chattextedit.cpp
@@ -48,6 +48,10 @@ void ChatTextEdit::keyPressEvent(QKeyEvent* event)
emit enterPressed();
return;
}
+ if (key == Qt::Key_Escape) {
+ emit escapePressed();
+ return;
+ }
if (key == Qt::Key_Tab) {
if (event->modifiers())
event->ignore();
diff --git a/src/widget/tool/chattextedit.h b/src/widget/tool/chattextedit.h
index 7b3a8ccf9..efa00ff4c 100644
--- a/src/widget/tool/chattextedit.h
+++ b/src/widget/tool/chattextedit.h
@@ -32,6 +32,7 @@ public:
signals:
void enterPressed();
+ void escapePressed();
void tabPressed();
void keyPressed();
void pasteImage(const QPixmap& pixmap);