feature: paste image in chat #51

Merged
blue merged 3 commits from shunf4/squawk:feat/paste_img into messageFeed 2021-10-16 15:34:01 +00:00
7 changed files with 86 additions and 7 deletions

View File

@ -378,7 +378,32 @@ void Core::NetworkAccess::onUploadFinished()
qDebug() << "upload success for" << url; qDebug() << "upload success for" << url;
storage.addFile(upl->messages, upl->url, upl->path); storage.addFile(upl->messages, upl->url, upl->path);
emit uploadFileComplete(upl->messages, upl->url); emit uploadFileComplete(upl->messages, upl->url, upl->path);
// Copy file to Download folder if it is a temp file. See Conversation::onImagePasted.
if (upl->path.startsWith(QDir::tempPath() + QStringLiteral("/squawk_img_attach_")) && upl->path.endsWith(".png")) {
QString err = "";
QString downloadDirPath = prepareDirectory(upl->messages.front().jid);
if (downloadDirPath.size() > 0) {
QString newPath = downloadDirPath + "/" + upl->path.mid(QDir::tempPath().length() + 1);
// Copy {TEMPDIR}/squawk_img_attach_XXXXXX.png to Download folder
bool copyResult = QFile::copy(upl->path, newPath);
if (copyResult) {
// Change storage
storage.setPath(upl->url, newPath);
} else {
err = "copying to " + newPath + " failed";
}
} else {
err = "Couldn't prepare a directory for file";
}
if (err.size() != 0) {
qDebug() << "failed to copy temporary upload file " << upl->path << " to download folder:" << err;
}
}
} }
upl->reply->deleteLater(); upl->reply->deleteLater();

View File

@ -58,7 +58,7 @@ public:
signals: signals:
void loadFileProgress(const std::list<Shared::MessageInfo>& msgs, qreal value, bool up); void loadFileProgress(const std::list<Shared::MessageInfo>& msgs, qreal value, bool up);
void loadFileError(const std::list<Shared::MessageInfo>& msgs, const QString& text, bool up); void loadFileError(const std::list<Shared::MessageInfo>& msgs, const QString& text, bool up);
void uploadFileComplete(const std::list<Shared::MessageInfo>& msgs, const QString& url); void uploadFileComplete(const std::list<Shared::MessageInfo>& msgs, const QString& url, const QString& path);
void downloadFileComplete(const std::list<Shared::MessageInfo>& msgs, const QString& path); void downloadFileComplete(const std::list<Shared::MessageInfo>& msgs, const QString& path);
public slots: public slots:

View File

@ -82,7 +82,7 @@ signals:
void fileError(const std::list<Shared::MessageInfo> msgs, const QString& error, bool up); void fileError(const std::list<Shared::MessageInfo> msgs, const QString& error, bool up);
void fileProgress(const std::list<Shared::MessageInfo> msgs, qreal value, bool up); void fileProgress(const std::list<Shared::MessageInfo> msgs, qreal value, bool up);
void fileDownloadComplete(const std::list<Shared::MessageInfo> msgs, const QString& path); void fileDownloadComplete(const std::list<Shared::MessageInfo> msgs, const QString& path);
void fileUploadComplete(const std::list<Shared::MessageInfo> msgs, const QString& path); void fileUploadComplete(const std::list<Shared::MessageInfo> msgs, const QString& url, const QString& path);
void responseVCard(const QString& jid, const Shared::VCard& card); void responseVCard(const QString& jid, const Shared::VCard& card);
void changeMessage(const QString& account, const QString& jid, const QString& id, const QMap<QString, QVariant>& data); void changeMessage(const QString& account, const QString& jid, const QString& id, const QMap<QString, QVariant>& data);

View File

@ -405,7 +405,7 @@ void Squawk::fileError(const std::list<Shared::MessageInfo> msgs, const QString&
rosterModel.fileError(msgs, error, up); rosterModel.fileError(msgs, error, up);
} }
void Squawk::fileUploadComplete(const std::list<Shared::MessageInfo> msgs, const QString& path) void Squawk::fileUploadComplete(const std::list<Shared::MessageInfo> msgs, const QString& url, const QString& path)
{ {
rosterModel.fileComplete(msgs, true); rosterModel.fileComplete(msgs, true);
} }

View File

@ -107,7 +107,7 @@ public slots:
void fileError(const std::list<Shared::MessageInfo> msgs, const QString& error, bool up); void fileError(const std::list<Shared::MessageInfo> msgs, const QString& error, bool up);
void fileProgress(const std::list<Shared::MessageInfo> msgs, qreal value, bool up); void fileProgress(const std::list<Shared::MessageInfo> msgs, qreal value, bool up);
void fileDownloadComplete(const std::list<Shared::MessageInfo> msgs, const QString& path); void fileDownloadComplete(const std::list<Shared::MessageInfo> msgs, const QString& path);
void fileUploadComplete(const std::list<Shared::MessageInfo> msgs, const QString& path); void fileUploadComplete(const std::list<Shared::MessageInfo> msgs, const QString& url, const QString& path);
void responseVCard(const QString& jid, const Shared::VCard& card); void responseVCard(const QString& jid, const Shared::VCard& card);
void changeMessage(const QString& account, const QString& jid, const QString& id, const QMap<QString, QVariant>& data); void changeMessage(const QString& account, const QString& jid, const QString& id, const QMap<QString, QVariant>& data);
void requestPassword(const QString& account); void requestPassword(const QString& account);

View File

@ -20,6 +20,7 @@
#include "ui_conversation.h" #include "ui_conversation.h"
#include <QDebug> #include <QDebug>
#include <QClipboard>
#include <QScrollBar> #include <QScrollBar>
#include <QTimer> #include <QTimer>
#include <QFileDialog> #include <QFileDialog>
@ -27,6 +28,9 @@
#include <unistd.h> #include <unistd.h>
#include <QAbstractTextDocumentLayout> #include <QAbstractTextDocumentLayout>
#include <QCoreApplication> #include <QCoreApplication>
#include <QTemporaryFile>
#include <QDir>
#include <QMenu>
Conversation::Conversation(bool muc, Models::Account* acc, Models::Element* el, const QString pJid, const QString pRes, QWidget* parent): Conversation::Conversation(bool muc, Models::Account* acc, Models::Element* el, const QString pJid, const QString pRes, QWidget* parent):
QWidget(parent), QWidget(parent),
@ -47,6 +51,7 @@ Conversation::Conversation(bool muc, Models::Account* acc, Models::Element* el,
delegate(new MessageDelegate(this)), delegate(new MessageDelegate(this)),
manualSliderChange(false), manualSliderChange(false),
tsb(QApplication::style()->styleHint(QStyle::SH_ScrollBar_Transient) == 1), tsb(QApplication::style()->styleHint(QStyle::SH_ScrollBar_Transient) == 1),
pasteImageAction(new QAction(tr("Paste Image"), this)),
shadow(10, 1, Qt::black, this), shadow(10, 1, Qt::black, this),
contextMenu(new QMenu()) contextMenu(new QMenu())
{ {
@ -75,6 +80,7 @@ Conversation::Conversation(bool muc, Models::Account* acc, Models::Element* el,
statusLabel = m_ui->statusLabel; statusLabel = m_ui->statusLabel;
connect(&ker, &KeyEnterReceiver::enterPressed, this, &Conversation::onEnterPressed); connect(&ker, &KeyEnterReceiver::enterPressed, this, &Conversation::onEnterPressed);
connect(&ker, &KeyEnterReceiver::imagePasted, this, &Conversation::onImagePasted);
connect(m_ui->sendButton, &QPushButton::clicked, this, &Conversation::onEnterPressed); connect(m_ui->sendButton, &QPushButton::clicked, this, &Conversation::onEnterPressed);
connect(m_ui->attachButton, &QPushButton::clicked, this, &Conversation::onAttach); connect(m_ui->attachButton, &QPushButton::clicked, this, &Conversation::onAttach);
connect(m_ui->clearButton, &QPushButton::clicked, this, &Conversation::onClearButton); connect(m_ui->clearButton, &QPushButton::clicked, this, &Conversation::onClearButton);
@ -82,8 +88,11 @@ Conversation::Conversation(bool muc, Models::Account* acc, Models::Element* el,
this, &Conversation::onTextEditDocSizeChanged); this, &Conversation::onTextEditDocSizeChanged);
m_ui->messageEditor->installEventFilter(&ker); m_ui->messageEditor->installEventFilter(&ker);
m_ui->messageEditor->setContextMenuPolicy(Qt::CustomContextMenu);
blue marked this conversation as resolved
Review

Do you mind if we better move it to initialization block? Cuz, since you declare here that variable again as QAction* it might be even considered like another variable rather than that one declared as a property...

Do you mind if we better move it to initialization block? Cuz, since you declare here that variable again as QAction* it might be even considered like another variable rather than that one declared as a property...
Review

How about this change? 52551c1ce0

How about this change? https://git.macaw.me/blue/squawk/commit/52551c1ce0be0a223e388d339389b7a55b159b6c
Review

That's good, thank you!

That's good, thank you!
connect(m_ui->messageEditor, &QTextEdit::customContextMenuRequested, this, &Conversation::onMessageEditorContext);
connect(pasteImageAction, &QAction::triggered, this, &Conversation::onImagePasted);
//line->setAutoFillBackground(false); //line->setAutoFillBackground(false);
//if (testAttribute(Qt::WA_TranslucentBackground)) { //if (testAttribute(Qt::WA_TranslucentBackground)) {
//m_ui->scrollArea->setAutoFillBackground(false); //m_ui->scrollArea->setAutoFillBackground(false);
@ -183,10 +192,20 @@ bool KeyEnterReceiver::eventFilter(QObject* obj, QEvent* event)
} }
} }
} }
if (k == Qt::Key_V && key->modifiers() & Qt::CTRL) {
if (Conversation::checkClipboardImage()) {
emit imagePasted();
return true;
}
}
} }
return QObject::eventFilter(obj, event); return QObject::eventFilter(obj, event);
} }
bool Conversation::checkClipboardImage() {
return !QApplication::clipboard()->image().isNull();
}
QString Conversation::getPalResource() const QString Conversation::getPalResource() const
{ {
return activePalResource; return activePalResource;
@ -218,6 +237,24 @@ void Conversation::onEnterPressed()
} }
} }
void Conversation::onImagePasted()
{
QImage image = QApplication::clipboard()->image();
if (image.isNull()) {
return;
}
QTemporaryFile *tempFile = new QTemporaryFile(QDir::tempPath() + QStringLiteral("/squawk_img_attach_XXXXXX.png"), QApplication::instance());
tempFile->open();
image.save(tempFile, "PNG");
tempFile->close();
qDebug() << "image on paste temp file: " << tempFile->fileName();
addAttachedFile(tempFile->fileName());
// The file, if successfully uploaded, will be copied to Download folder.
// On application closing, this temporary file will be automatically removed by Qt.
// See Core::NetworkAccess::onUploadFinished.
}
Outdated
Review

Interestring solution...
I actually was thinking of it, but was more into may be saving it into downloads folder, for the next time. Like this way at somepoint the image is not going to be accesible, so, we're going to be forced to download it to see it.
Dunno, i really have mixed feelings about my way, what do you think?

Interestring solution... I actually was thinking of it, but was more into may be saving it into downloads folder, for the next time. Like this way at somepoint the image is not going to be accesible, so, we're going to be forced to download it to see it. Dunno, i really have mixed feelings about my way, what do you think?

I agree with you. Placing the pasted image into downloads folder is better.

Or putting it to temp folder, then moving it to downloads folder if it is sent successfully?

I agree with you. Placing the pasted image into downloads folder is better. Or putting it to temp folder, then moving it to downloads folder if it is sent successfully?
Outdated
Review

Yeah, that would do! But if you will move the image on successfull send you're going to need to update the path to the image in databases after the image is moved, I hope I've done that handler...

Yeah, that would do! But if you will move the image on successfull send you're going to need to update the path to the image in databases after the image is moved, I hope I've done that handler...

Done in 39f2f3d975 .

The file is copied (rather than moved) because it might still be used by QImageReader in message preview widget.

Done in https://git.macaw.me/blue/squawk/commit/39f2f3d975a1ce38144ef1992506416a934e5005 . The file is copied (rather than moved) because it might still be used by `QImageReader` in message preview widget.
void Conversation::onAttach() void Conversation::onAttach()
{ {
QFileDialog* d = new QFileDialog(this, tr("Chose a file to send")); QFileDialog* d = new QFileDialog(this, tr("Chose a file to send"));
@ -443,3 +480,14 @@ void Conversation::onFeedContext(const QPoint& pos)
} }
} }
} }
void Conversation::onMessageEditorContext(const QPoint& pos)
{
pasteImageAction->setEnabled(Conversation::checkClipboardImage());
QMenu *editorMenu = m_ui->messageEditor->createStandardContextMenu();
editorMenu->addSeparator();
editorMenu->addAction(pasteImageAction);
editorMenu->exec(this->m_ui->messageEditor->mapToGlobal(pos));
}

View File

@ -60,6 +60,7 @@ protected:
signals: signals:
void enterPressed(); void enterPressed();
void imagePasted();
}; };
class Conversation : public QWidget class Conversation : public QWidget
@ -77,6 +78,7 @@ public:
void setPalResource(const QString& res); void setPalResource(const QString& res);
virtual void setAvatar(const QString& path); virtual void setAvatar(const QString& path);
void setFeedFrames(bool top, bool right, bool bottom, bool left); void setFeedFrames(bool top, bool right, bool bottom, bool left);
static bool checkClipboardImage();
signals: signals:
void sendMessage(const Shared::Message& message); void sendMessage(const Shared::Message& message);
@ -102,6 +104,7 @@ protected:
protected slots: protected slots:
void onEnterPressed(); void onEnterPressed();
void onImagePasted();
void onAttach(); void onAttach();
void onFileSelected(); void onFileSelected();
void onBadgeClose(); void onBadgeClose();
@ -111,6 +114,7 @@ protected slots:
void onFeedMessage(const Shared::Message& msg); void onFeedMessage(const Shared::Message& msg);
void positionShadow(); void positionShadow();
void onFeedContext(const QPoint &pos); void onFeedContext(const QPoint &pos);
void onMessageEditorContext(const QPoint &pos);
public: public:
const bool isMuc; const bool isMuc;
@ -133,6 +137,8 @@ protected:
bool manualSliderChange; bool manualSliderChange;
bool tsb; //transient scroll bars bool tsb; //transient scroll bars
QAction* pasteImageAction;
ShadowOverlay shadow; ShadowOverlay shadow;
QMenu* contextMenu; QMenu* contextMenu;
}; };