VInsertImageDialog: fetch image from clipboard

Support `Ctrl+'` to insert image.
This commit is contained in:
Le Tan 2017-12-11 20:49:42 +08:00
parent 1dcd65e7dd
commit 8d6fc6cb4f
5 changed files with 234 additions and 85 deletions

View File

@ -2,44 +2,76 @@
#include <QValidator> #include <QValidator>
#include <QRegExp> #include <QRegExp>
#include <QDebug> #include <QDebug>
#include <QTimer>
#include "vinsertimagedialog.h" #include "vinsertimagedialog.h"
#include "utils/vutils.h" #include "utils/vutils.h"
#include "vlineedit.h" #include "vlineedit.h"
#include "vdownloader.h"
VInsertImageDialog::VInsertImageDialog(const QString &title, const QString &defaultImageTitle, VInsertImageDialog::VInsertImageDialog(const QString &p_title,
const QString &defaultPath, QWidget *parent) const QString &p_imageTitle,
: QDialog(parent), title(title), defaultImageTitle(defaultImageTitle), defaultPath(defaultPath), const QString &p_imagePath,
image(NULL) bool p_browsable,
QWidget *p_parent)
: QDialog(p_parent),
m_image(NULL),
m_browsable(p_browsable),
m_timer(NULL)
{ {
setupUI(); setupUI(p_title, p_imageTitle, p_imagePath);
connect(m_imageTitleEdit, &QLineEdit::textChanged, connect(m_imageTitleEdit, &QLineEdit::textChanged,
this, &VInsertImageDialog::handleInputChanged); this, &VInsertImageDialog::handleInputChanged);
connect(pathEdit, &QLineEdit::textChanged,
this, &VInsertImageDialog::handleInputChanged); if (m_browsable) {
connect(browseBtn, &QPushButton::clicked, m_timer = new QTimer(this);
this, &VInsertImageDialog::handleBrowseBtnClicked); m_timer->setSingleShot(true);
m_timer->setInterval(500 /* ms */);
connect(m_timer, &QTimer::timeout,
this, &VInsertImageDialog::handlePathEditChanged);
connect(m_pathEdit, &QLineEdit::textChanged,
this, [this]() {
m_timer->stop();
setImage(QImage());
if (m_pathEdit->text().isEmpty()) {
return;
}
m_timer->start();
});
connect(browseBtn, &QPushButton::clicked,
this, &VInsertImageDialog::handleBrowseBtnClicked);
fetchImageFromClipboard();
}
handleInputChanged(); handleInputChanged();
} }
VInsertImageDialog::~VInsertImageDialog() VInsertImageDialog::~VInsertImageDialog()
{ {
if (image) { if (m_image) {
delete image; delete m_image;
image = NULL; m_image = NULL;
} }
} }
void VInsertImageDialog::setupUI() void VInsertImageDialog::setupUI(const QString &p_title,
const QString &p_imageTitle,
const QString &p_imagePath)
{ {
pathLabel = new QLabel(tr("&From:")); QLabel *pathLabel = new QLabel(tr("&From:"));
pathEdit = new QLineEdit(defaultPath); m_pathEdit = new QLineEdit(p_imagePath);
pathLabel->setBuddy(pathEdit); pathLabel->setBuddy(m_pathEdit);
browseBtn = new QPushButton(tr("&Browse")); browseBtn = new QPushButton(tr("&Browse"));
m_pathEdit->setReadOnly(!m_browsable);
browseBtn->setEnabled(m_browsable);
imageTitleLabel = new QLabel(tr("&Image title:")); QLabel *imageTitleLabel = new QLabel(tr("&Image title:"));
m_imageTitleEdit = new VLineEdit(defaultImageTitle); m_imageTitleEdit = new VLineEdit(p_imageTitle);
m_imageTitleEdit->selectAll(); m_imageTitleEdit->selectAll();
imageTitleLabel->setBuddy(m_imageTitleEdit); imageTitleLabel->setBuddy(m_imageTitleEdit);
QValidator *validator = new QRegExpValidator(QRegExp(VUtils::c_imageTitleRegExp), QValidator *validator = new QRegExpValidator(QRegExp(VUtils::c_imageTitleRegExp),
@ -48,7 +80,7 @@ void VInsertImageDialog::setupUI()
QGridLayout *topLayout = new QGridLayout(); QGridLayout *topLayout = new QGridLayout();
topLayout->addWidget(pathLabel, 0, 0); topLayout->addWidget(pathLabel, 0, 0);
topLayout->addWidget(pathEdit, 0, 1); topLayout->addWidget(m_pathEdit, 0, 1);
topLayout->addWidget(browseBtn, 0, 2); topLayout->addWidget(browseBtn, 0, 2);
topLayout->addWidget(imageTitleLabel, 1, 0); topLayout->addWidget(imageTitleLabel, 1, 0);
topLayout->addWidget(m_imageTitleEdit, 1, 1, 1, 2); topLayout->addWidget(m_imageTitleEdit, 1, 1, 1, 2);
@ -71,28 +103,19 @@ void VInsertImageDialog::setupUI()
mainLayout->addWidget(imagePreviewLabel); mainLayout->addWidget(imagePreviewLabel);
setLayout(mainLayout); setLayout(mainLayout);
mainLayout->setSizeConstraint(QLayout::SetFixedSize); mainLayout->setSizeConstraint(QLayout::SetFixedSize);
setWindowTitle(title); setWindowTitle(p_title);
m_imageTitleEdit->setFocus(); m_imageTitleEdit->setFocus();
} }
void VInsertImageDialog::handleInputChanged() void VInsertImageDialog::handleInputChanged()
{ {
bool pathOk = true;
if (pathEdit->isVisible() && !pathEdit->isReadOnly()) {
QString path = pathEdit->text();
if (path.isEmpty()
|| !VUtils::checkPathLegal(path)) {
pathOk = false;
}
}
QString title = m_imageTitleEdit->getEvaluatedText(); QString title = m_imageTitleEdit->getEvaluatedText();
QRegExp reg(VUtils::c_imageTitleRegExp); QRegExp reg(VUtils::c_imageTitleRegExp);
bool titleOk = reg.exactMatch(title); bool titleOk = reg.exactMatch(title);
QPushButton *okBtn = m_btnBox->button(QDialogButtonBox::Ok); QPushButton *okBtn = m_btnBox->button(QDialogButtonBox::Ok);
okBtn->setEnabled(pathOk && titleOk); okBtn->setEnabled(titleOk && m_image);
} }
QString VInsertImageDialog::getImageTitleInput() const QString VInsertImageDialog::getImageTitleInput() const
@ -102,7 +125,7 @@ QString VInsertImageDialog::getImageTitleInput() const
QString VInsertImageDialog::getPathInput() const QString VInsertImageDialog::getPathInput() const
{ {
return pathEdit->text(); return m_pathEdit->text();
} }
void VInsertImageDialog::handleBrowseBtnClicked() void VInsertImageDialog::handleBrowseBtnClicked()
@ -117,34 +140,39 @@ void VInsertImageDialog::handleBrowseBtnClicked()
// Update lastPath // Update lastPath
lastPath = QFileInfo(filePath).path(); lastPath = QFileInfo(filePath).path();
pathEdit->setText(filePath); m_imageType = ImageType::LocalFile;
QImage image(filePath);
if (image.isNull()) { m_pathEdit->setText(filePath);
return;
} m_imageTitleEdit->setFocus();
setImage(image);
} }
void VInsertImageDialog::setImage(const QImage &image) void VInsertImageDialog::setImage(const QImage &image)
{ {
if (image.isNull()) { if (image.isNull()) {
qWarning() << "set Null image"; imagePreviewLabel->setVisible(false);
if (m_image) {
delete m_image;
m_image = NULL;
}
handleInputChanged();
return; return;
} }
int width = 512 * VUtils::calculateScaleFactor(); int width = 512 * VUtils::calculateScaleFactor();
QSize previewSize(width, width); QSize previewSize(width, width);
if (!this->image) { if (!m_image) {
this->image = new QImage(image); m_image = new QImage(image);
} else { } else {
*(this->image) = image; *m_image = image;
} }
QPixmap pixmap; QPixmap pixmap;
if (image.width() > width || image.height() > width) { if (image.width() > width || image.height() > width) {
pixmap = QPixmap::fromImage(this->image->scaled(previewSize, Qt::KeepAspectRatio)); pixmap = QPixmap::fromImage(m_image->scaled(previewSize, Qt::KeepAspectRatio));
} else { } else {
pixmap = QPixmap::fromImage(*(this->image)); pixmap = QPixmap::fromImage(*m_image);
} }
imagePreviewLabel->setPixmap(pixmap); imagePreviewLabel->setPixmap(pixmap);
@ -153,26 +181,87 @@ void VInsertImageDialog::setImage(const QImage &image)
handleInputChanged(); handleInputChanged();
} }
void VInsertImageDialog::setBrowseable(bool browseable, bool visible)
{
pathEdit->setReadOnly(!browseable);
browseBtn->setEnabled(browseable);
pathLabel->setVisible(visible);
pathEdit->setVisible(visible);
browseBtn->setVisible(visible);
handleInputChanged();
}
void VInsertImageDialog::imageDownloaded(const QByteArray &data) void VInsertImageDialog::imageDownloaded(const QByteArray &data)
{ {
m_imageType = ImageType::ImageData;
setImage(QImage::fromData(data)); setImage(QImage::fromData(data));
} }
QImage VInsertImageDialog::getImage() const QImage VInsertImageDialog::getImage() const
{ {
if (!image) { if (!m_image) {
return QImage(); return QImage();
} else return *image; } else return *m_image;
}
void VInsertImageDialog::fetchImageFromClipboard()
{
if (!m_browsable || !m_pathEdit->text().isEmpty()) {
return;
}
Q_ASSERT(!m_image);
QClipboard *clipboard = QApplication::clipboard();
const QMimeData *mimeData = clipboard->mimeData();
QUrl url;
if (mimeData->hasImage()) {
QImage im = qvariant_cast<QImage>(mimeData->imageData());
if (im.isNull()) {
return;
}
setImage(im);
m_imageType = ImageType::ImageData;
qDebug() << "fetch image data from clipboard to insert";
return;
} else if (mimeData->hasUrls()) {
QList<QUrl> urls = mimeData->urls();
if (urls.size() != 1) {
return;
}
url = urls[0];
} else if (mimeData->hasText()) {
url = QUrl(mimeData->text());
}
if (url.isValid()) {
if (url.isLocalFile()) {
m_pathEdit->setText(url.toLocalFile());
} else {
m_pathEdit->setText(url.toString());
}
}
}
void VInsertImageDialog::handlePathEditChanged()
{
QString text = m_pathEdit->text();
QUrl url = QUrl::fromUserInput(text);
if (!url.isValid()) {
setImage(QImage());
return;
}
QImage image(text);
if (image.isNull()) {
setImage(QImage());
// Try to treat it as network image.
m_imageType = ImageType::ImageData;
VDownloader *downloader = new VDownloader(this);
connect(downloader, &VDownloader::downloadFinished,
this, &VInsertImageDialog::imageDownloaded);
downloader->download(url.toString());
qDebug() << "try to fetch network image to insert" << text;
} else {
// Local image path.
setImage(image);
m_imageType = ImageType::LocalFile;
qDebug() << "fetch local file image to insert" << text;
}
handleInputChanged();
} }

View File

@ -11,44 +11,73 @@ class QLineEdit;
class VLineEdit; class VLineEdit;
class QPushButton; class QPushButton;
class QDialogButtonBox; class QDialogButtonBox;
class QTimer;
class VInsertImageDialog : public QDialog class VInsertImageDialog : public QDialog
{ {
Q_OBJECT Q_OBJECT
public: public:
VInsertImageDialog(const QString &title, const QString &defaultImageTitle, enum ImageType
const QString &defaultPath, {
QWidget *parent = 0); LocalFile = 0,
ImageData
};
VInsertImageDialog(const QString &p_title,
const QString &p_imageTitle,
const QString &p_imagePath,
bool p_browsable = true,
QWidget *p_parent = nullptr);
~VInsertImageDialog(); ~VInsertImageDialog();
QString getImageTitleInput() const; QString getImageTitleInput() const;
QString getPathInput() const; QString getPathInput() const;
void setImage(const QImage &image); void setImage(const QImage &image);
QImage getImage() const; QImage getImage() const;
void setBrowseable(bool browseable, bool visible = false);
VInsertImageDialog::ImageType getImageType() const;
public slots: public slots:
void imageDownloaded(const QByteArray &data); void imageDownloaded(const QByteArray &data);
private slots: private slots:
void handleInputChanged(); void handleInputChanged();
void handleBrowseBtnClicked(); void handleBrowseBtnClicked();
private: void handlePathEditChanged();
void setupUI();
private:
void setupUI(const QString &p_title,
const QString &p_imageTitle,
const QString &p_imagePath);
void fetchImageFromClipboard();
QLabel *imageTitleLabel;
VLineEdit *m_imageTitleEdit; VLineEdit *m_imageTitleEdit;
QLabel *pathLabel; QLineEdit *m_pathEdit;
QLineEdit *pathEdit;
QPushButton *browseBtn; QPushButton *browseBtn;
QDialogButtonBox *m_btnBox; QDialogButtonBox *m_btnBox;
QLabel *imagePreviewLabel; QLabel *imagePreviewLabel;
QString title; QImage *m_image;
QString defaultImageTitle;
QString defaultPath; // Whether enable the browse action.
QImage *image; bool m_browsable;
ImageType m_imageType;
// Timer for path edit change.
QTimer *m_timer;
}; };
inline VInsertImageDialog::ImageType VInsertImageDialog::getImageType() const
{
return m_imageType;
}
#endif // VINSERTIMAGEDIALOG_H #endif // VINSERTIMAGEDIALOG_H

View File

@ -17,7 +17,10 @@ void VDownloader::handleDownloadFinished(QNetworkReply *reply)
void VDownloader::download(const QUrl &p_url) void VDownloader::download(const QUrl &p_url)
{ {
Q_ASSERT(p_url.isValid()); if (!p_url.isValid()) {
return;
}
QNetworkRequest request(p_url); QNetworkRequest request(p_url);
webCtrl.get(request); webCtrl.get(request);
qDebug() << "VDownloader get" << p_url.toString(); qDebug() << "VDownloader get" << p_url.toString();

View File

@ -522,7 +522,7 @@ void VMainWindow::initEditToolBar(QSize p_iconSize)
// Insert image. // Insert image.
QAction *insertImageAct = new QAction(VIconUtils::toolButtonIcon(":/resources/icons/insert_image.svg"), QAction *insertImageAct = new QAction(VIconUtils::toolButtonIcon(":/resources/icons/insert_image.svg"),
tr("Insert Image"), tr("Insert Image\t%1").arg(VUtils::getShortcutText("Ctrl+'")),
this); this);
insertImageAct->setStatusTip(tr("Insert an image from file or URL")); insertImageAct->setStatusTip(tr("Insert an image from file or URL"));
connect(insertImageAct, &QAction::triggered, connect(insertImageAct, &QAction::triggered,

View File

@ -39,11 +39,12 @@ bool VMdEditOperations::insertImageFromMimeData(const QMimeData *source)
if (image.isNull()) { if (image.isNull()) {
return false; return false;
} }
VInsertImageDialog dialog(tr("Insert Image From Clipboard"), VInsertImageDialog dialog(tr("Insert Image From Clipboard"),
c_defaultImageTitle, c_defaultImageTitle,
"", "",
false,
m_editor->getEditor()); m_editor->getEditor());
dialog.setBrowseable(false);
dialog.setImage(image); dialog.setImage(image);
if (dialog.exec() == QDialog::Accepted) { if (dialog.exec() == QDialog::Accepted) {
insertImageFromQImage(dialog.getImageTitleInput(), insertImageFromQImage(dialog.getImageTitleInput(),
@ -51,6 +52,7 @@ bool VMdEditOperations::insertImageFromMimeData(const QMimeData *source)
m_file->getImageFolderInLink(), m_file->getImageFolderInLink(),
image); image);
} }
return true; return true;
} }
@ -157,9 +159,11 @@ bool VMdEditOperations::insertImageFromURL(const QUrl &imageUrl)
} }
VInsertImageDialog dialog(title, c_defaultImageTitle, VInsertImageDialog dialog(title,
imagePath, m_editor->getEditor()); c_defaultImageTitle,
dialog.setBrowseable(false, true); imagePath,
false,
m_editor->getEditor());
if (isLocal) { if (isLocal) {
dialog.setImage(image); dialog.setImage(image);
} else { } else {
@ -187,17 +191,30 @@ bool VMdEditOperations::insertImageFromURL(const QUrl &imageUrl)
bool VMdEditOperations::insertImage() bool VMdEditOperations::insertImage()
{ {
VInsertImageDialog dialog(tr("Insert Image From File"), // Use empty title and path to let the dialog auto complete.
c_defaultImageTitle, "", m_editor->getEditor()); VInsertImageDialog dialog(tr("Insert Image"),
"",
"",
true,
m_editor->getEditor());
if (dialog.exec() == QDialog::Accepted) { if (dialog.exec() == QDialog::Accepted) {
QString title = dialog.getImageTitleInput(); VInsertImageDialog::ImageType type = dialog.getImageType();
QString imagePath = dialog.getPathInput(); if (type == VInsertImageDialog::ImageType::LocalFile) {
qDebug() << "insert image from" << imagePath << "as" << title; insertImageFromPath(dialog.getImageTitleInput(),
insertImageFromPath(title, m_file->fetchImageFolderPath(),
m_file->fetchImageFolderPath(), m_file->getImageFolderInLink(),
m_file->getImageFolderInLink(), dialog.getPathInput());
imagePath); } else {
QImage img = dialog.getImage();
if (!img.isNull()) {
insertImageFromQImage(dialog.getImageTitleInput(),
m_file->fetchImageFolderPath(),
m_file->getImageFolderInLink(),
img);
}
}
} }
return true; return true;
} }
@ -292,6 +309,17 @@ bool VMdEditOperations::handleKeyPressEvent(QKeyEvent *p_event)
break; break;
} }
case Qt::Key_Apostrophe:
{
if (modifiers == Qt::ControlModifier) {
m_editor->insertImage();
p_event->accept();
ret = true;
}
break;
}
case Qt::Key_M: case Qt::Key_M:
{ {
if (modifiers == Qt::ControlModifier) { if (modifiers == Qt::ControlModifier) {