From 8d6fc6cb4f153205965dec96b15e485021faece1 Mon Sep 17 00:00:00 2001 From: Le Tan Date: Mon, 11 Dec 2017 20:49:42 +0800 Subject: [PATCH] VInsertImageDialog: fetch image from clipboard Support `Ctrl+'` to insert image. --- src/dialog/vinsertimagedialog.cpp | 203 +++++++++++++++++++++--------- src/dialog/vinsertimagedialog.h | 55 ++++++-- src/vdownloader.cpp | 5 +- src/vmainwindow.cpp | 2 +- src/vmdeditoperations.cpp | 54 ++++++-- 5 files changed, 234 insertions(+), 85 deletions(-) diff --git a/src/dialog/vinsertimagedialog.cpp b/src/dialog/vinsertimagedialog.cpp index a0823ab8..5a82f04f 100644 --- a/src/dialog/vinsertimagedialog.cpp +++ b/src/dialog/vinsertimagedialog.cpp @@ -2,44 +2,76 @@ #include #include #include +#include #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(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 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(); } diff --git a/src/dialog/vinsertimagedialog.h b/src/dialog/vinsertimagedialog.h index 65855af0..1b0f6222 100644 --- a/src/dialog/vinsertimagedialog.h +++ b/src/dialog/vinsertimagedialog.h @@ -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 diff --git a/src/vdownloader.cpp b/src/vdownloader.cpp index 74d9c4d9..8f6ec98d 100644 --- a/src/vdownloader.cpp +++ b/src/vdownloader.cpp @@ -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(); diff --git a/src/vmainwindow.cpp b/src/vmainwindow.cpp index 5a29821e..72a667ee 100644 --- a/src/vmainwindow.cpp +++ b/src/vmainwindow.cpp @@ -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, diff --git a/src/vmdeditoperations.cpp b/src/vmdeditoperations.cpp index 5aa9a91c..def725d8 100644 --- a/src/vmdeditoperations.cpp +++ b/src/vmdeditoperations.cpp @@ -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) {