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 <QRegExp>
#include <QDebug>
#include <QTimer>
#include "vinsertimagedialog.h"
#include "utils/vutils.h"
#include "vlineedit.h"
#include "vdownloader.h"
VInsertImageDialog::VInsertImageDialog(const QString &title, const QString &defaultImageTitle,
const QString &defaultPath, QWidget *parent)
: QDialog(parent), title(title), defaultImageTitle(defaultImageTitle), defaultPath(defaultPath),
image(NULL)
VInsertImageDialog::VInsertImageDialog(const QString &p_title,
const QString &p_imageTitle,
const QString &p_imagePath,
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,
this, &VInsertImageDialog::handleInputChanged);
connect(pathEdit, &QLineEdit::textChanged,
this, &VInsertImageDialog::handleInputChanged);
connect(browseBtn, &QPushButton::clicked,
this, &VInsertImageDialog::handleBrowseBtnClicked);
if (m_browsable) {
m_timer = new QTimer(this);
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();
}
VInsertImageDialog::~VInsertImageDialog()
{
if (image) {
delete image;
image = NULL;
if (m_image) {
delete m_image;
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:"));
pathEdit = new QLineEdit(defaultPath);
pathLabel->setBuddy(pathEdit);
QLabel *pathLabel = new QLabel(tr("&From:"));
m_pathEdit = new QLineEdit(p_imagePath);
pathLabel->setBuddy(m_pathEdit);
browseBtn = new QPushButton(tr("&Browse"));
m_pathEdit->setReadOnly(!m_browsable);
browseBtn->setEnabled(m_browsable);
imageTitleLabel = new QLabel(tr("&Image title:"));
m_imageTitleEdit = new VLineEdit(defaultImageTitle);
QLabel *imageTitleLabel = new QLabel(tr("&Image title:"));
m_imageTitleEdit = new VLineEdit(p_imageTitle);
m_imageTitleEdit->selectAll();
imageTitleLabel->setBuddy(m_imageTitleEdit);
QValidator *validator = new QRegExpValidator(QRegExp(VUtils::c_imageTitleRegExp),
@ -48,7 +80,7 @@ void VInsertImageDialog::setupUI()
QGridLayout *topLayout = new QGridLayout();
topLayout->addWidget(pathLabel, 0, 0);
topLayout->addWidget(pathEdit, 0, 1);
topLayout->addWidget(m_pathEdit, 0, 1);
topLayout->addWidget(browseBtn, 0, 2);
topLayout->addWidget(imageTitleLabel, 1, 0);
topLayout->addWidget(m_imageTitleEdit, 1, 1, 1, 2);
@ -71,28 +103,19 @@ void VInsertImageDialog::setupUI()
mainLayout->addWidget(imagePreviewLabel);
setLayout(mainLayout);
mainLayout->setSizeConstraint(QLayout::SetFixedSize);
setWindowTitle(title);
setWindowTitle(p_title);
m_imageTitleEdit->setFocus();
}
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();
QRegExp reg(VUtils::c_imageTitleRegExp);
bool titleOk = reg.exactMatch(title);
QPushButton *okBtn = m_btnBox->button(QDialogButtonBox::Ok);
okBtn->setEnabled(pathOk && titleOk);
okBtn->setEnabled(titleOk && m_image);
}
QString VInsertImageDialog::getImageTitleInput() const
@ -102,7 +125,7 @@ QString VInsertImageDialog::getImageTitleInput() const
QString VInsertImageDialog::getPathInput() const
{
return pathEdit->text();
return m_pathEdit->text();
}
void VInsertImageDialog::handleBrowseBtnClicked()
@ -117,34 +140,39 @@ void VInsertImageDialog::handleBrowseBtnClicked()
// Update lastPath
lastPath = QFileInfo(filePath).path();
pathEdit->setText(filePath);
QImage image(filePath);
if (image.isNull()) {
return;
}
setImage(image);
m_imageType = ImageType::LocalFile;
m_pathEdit->setText(filePath);
m_imageTitleEdit->setFocus();
}
void VInsertImageDialog::setImage(const QImage &image)
{
if (image.isNull()) {
qWarning() << "set Null image";
imagePreviewLabel->setVisible(false);
if (m_image) {
delete m_image;
m_image = NULL;
}
handleInputChanged();
return;
}
int width = 512 * VUtils::calculateScaleFactor();
QSize previewSize(width, width);
if (!this->image) {
this->image = new QImage(image);
if (!m_image) {
m_image = new QImage(image);
} else {
*(this->image) = image;
*m_image = image;
}
QPixmap pixmap;
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 {
pixmap = QPixmap::fromImage(*(this->image));
pixmap = QPixmap::fromImage(*m_image);
}
imagePreviewLabel->setPixmap(pixmap);
@ -153,26 +181,87 @@ void VInsertImageDialog::setImage(const QImage &image)
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)
{
m_imageType = ImageType::ImageData;
setImage(QImage::fromData(data));
}
QImage VInsertImageDialog::getImage() const
{
if (!image) {
if (!m_image) {
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 QPushButton;
class QDialogButtonBox;
class QTimer;
class VInsertImageDialog : public QDialog
{
Q_OBJECT
public:
VInsertImageDialog(const QString &title, const QString &defaultImageTitle,
const QString &defaultPath,
QWidget *parent = 0);
enum ImageType
{
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();
QString getImageTitleInput() const;
QString getPathInput() const;
void setImage(const QImage &image);
QImage getImage() const;
void setBrowseable(bool browseable, bool visible = false);
VInsertImageDialog::ImageType getImageType() const;
public slots:
void imageDownloaded(const QByteArray &data);
private slots:
void handleInputChanged();
void handleBrowseBtnClicked();
private:
void setupUI();
void handlePathEditChanged();
private:
void setupUI(const QString &p_title,
const QString &p_imageTitle,
const QString &p_imagePath);
void fetchImageFromClipboard();
QLabel *imageTitleLabel;
VLineEdit *m_imageTitleEdit;
QLabel *pathLabel;
QLineEdit *pathEdit;
QLineEdit *m_pathEdit;
QPushButton *browseBtn;
QDialogButtonBox *m_btnBox;
QLabel *imagePreviewLabel;
QString title;
QString defaultImageTitle;
QString defaultPath;
QImage *image;
QImage *m_image;
// Whether enable the browse action.
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

View File

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

View File

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

View File

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