support pasting image in markdown

Signed-off-by: Le Tan <tamlokveer@gmail.com>
This commit is contained in:
Le Tan 2016-10-31 21:52:00 +08:00
parent ec7850685e
commit 047c26b598
13 changed files with 405 additions and 16 deletions

View File

@ -0,0 +1,120 @@
#include <QtWidgets>
#include "vinsertimagedialog.h"
VInsertImageDialog::VInsertImageDialog(const QString &title, const QString &defaultImageTitle,
const QString &defaultPath, QWidget *parent)
: QDialog(parent), title(title), defaultImageTitle(defaultImageTitle), defaultPath(defaultPath),
image(NULL), browseable(true)
{
setupUI();
connect(imageTitleEdit, &QLineEdit::textChanged, this, &VInsertImageDialog::enableOkButton);
connect(pathEdit, &QLineEdit::textChanged, this, &VInsertImageDialog::enableOkButton);
connect(browseBtn, &QPushButton::clicked, this, &VInsertImageDialog::handleBrowseBtnClicked);
connect(okBtn, &QPushButton::clicked, this, &VInsertImageDialog::accept);
connect(cancelBtn, &QPushButton::clicked, this, &VInsertImageDialog::reject);
enableOkButton();
}
VInsertImageDialog::~VInsertImageDialog()
{
if (image) {
delete image;
image = NULL;
}
}
void VInsertImageDialog::setupUI()
{
pathLabel = new QLabel(tr("&From"));
pathEdit = new QLineEdit(defaultPath);
pathLabel->setBuddy(pathEdit);
browseBtn = new QPushButton(tr("&Browse"));
QHBoxLayout *pathLayout = new QHBoxLayout();
pathLayout->addWidget(pathEdit);
pathLayout->addWidget(browseBtn);
imageTitleLabel = new QLabel(tr("&Title"));
imageTitleEdit = new QLineEdit(defaultImageTitle);
imageTitleEdit->selectAll();
imageTitleLabel->setBuddy(imageTitleEdit);
okBtn = new QPushButton(tr("&OK"));
okBtn->setDefault(true);
cancelBtn = new QPushButton(tr("&Cancel"));
QVBoxLayout *topLayout = new QVBoxLayout();
topLayout->addWidget(pathLabel);
topLayout->addLayout(pathLayout);
topLayout->addWidget(imageTitleLabel);
topLayout->addWidget(imageTitleEdit);
QHBoxLayout *btmLayout = new QHBoxLayout();
btmLayout->addStretch();
btmLayout->addWidget(okBtn);
btmLayout->addWidget(cancelBtn);
imagePreviewLabel = new QLabel();
imagePreviewLabel->setVisible(false);
QVBoxLayout *mainLayout = new QVBoxLayout();
mainLayout->addLayout(topLayout);
mainLayout->addLayout(btmLayout);
mainLayout->addWidget(imagePreviewLabel);
setLayout(mainLayout);
layout()->setSizeConstraint(QLayout::SetFixedSize);
setWindowTitle(title);
}
void VInsertImageDialog::enableOkButton()
{
bool enabled = true;
if (imageTitleEdit->text().isEmpty() || !image) {
enabled = false;
}
okBtn->setEnabled(enabled);
}
QString VInsertImageDialog::getImageTitleInput() const
{
return imageTitleEdit->text();
}
QString VInsertImageDialog::getPathInput() const
{
return pathEdit->text();
}
void VInsertImageDialog::handleBrowseBtnClicked()
{
static QString lastPath = QDir::homePath();
QString filePath = QFileDialog::getOpenFileName(this, tr("Select the image to be inserted"),
lastPath, tr("Images (*.png *.xpm *.jpg *.bmp *.gif)"));
// Update lastPath
lastPath = QFileInfo(filePath).path();
pathEdit->setText(filePath);
}
void VInsertImageDialog::setImage(const QImage &image)
{
Q_ASSERT(!image.isNull());
QSize previewSize(256, 256);
if (!this->image) {
this->image = new QImage(image);
} else {
*(this->image) = image;
}
imagePreviewLabel->setPixmap(QPixmap::fromImage(this->image->scaled(previewSize)));
imagePreviewLabel->setVisible(true);
enableOkButton();
}
void VInsertImageDialog::setBrowseable(bool browseable)
{
this->browseable = browseable;
pathLabel->setVisible(browseable);
pathEdit->setVisible(browseable);
browseBtn->setVisible(browseable);
}

View File

@ -0,0 +1,49 @@
#ifndef VINSERTIMAGEDIALOG_H
#define VINSERTIMAGEDIALOG_H
#include <QDialog>
#include <QImage>
#include <QString>
class QLabel;
class QLineEdit;
class QPushButton;
class VInsertImageDialog : public QDialog
{
Q_OBJECT
public:
VInsertImageDialog(const QString &title, const QString &defaultImageTitle,
const QString &defaultPath,
QWidget *parent = 0);
~VInsertImageDialog();
QString getImageTitleInput() const;
QString getPathInput() const;
void setImage(const QImage &image);
void setBrowseable(bool browseable);
private slots:
void enableOkButton();
void handleBrowseBtnClicked();
private:
void setupUI();
QLabel *imageTitleLabel;
QLineEdit *imageTitleEdit;
QLabel *pathLabel;
QLineEdit *pathEdit;
QPushButton *browseBtn;
QPushButton *okBtn;
QPushButton *cancelBtn;
QLabel *imagePreviewLabel;
QString title;
QString defaultImageTitle;
QString defaultPath;
QImage *image;
bool browseable;
};
#endif // VINSERTIMAGEDIALOG_H

View File

@ -36,7 +36,10 @@ SOURCES += main.cpp\
vmarkdownconverter.cpp \
dialog/vnotebookinfodialog.cpp \
dialog/vdirinfodialog.cpp \
dialog/vfileinfodialog.cpp
dialog/vfileinfodialog.cpp \
veditoperations.cpp \
vmdeditoperations.cpp \
dialog/vinsertimagedialog.cpp
HEADERS += vmainwindow.h \
vdirectorytree.h \
@ -63,7 +66,10 @@ HEADERS += vmainwindow.h \
vmarkdownconverter.h \
dialog/vnotebookinfodialog.h \
dialog/vdirinfodialog.h \
dialog/vfileinfodialog.h
dialog/vfileinfodialog.h \
veditoperations.h \
vmdeditoperations.h \
dialog/vinsertimagedialog.h
RESOURCES += \
vnote.qrc

View File

@ -1,6 +1,8 @@
#include "vutils.h"
#include <QFile>
#include <QDir>
#include <QDebug>
#include <QRegularExpression>
VUtils::VUtils()
{
@ -54,3 +56,23 @@ QRgb VUtils::QRgbFromString(const QString &str)
qWarning() << "error: fail to construct QRgb from string" << str;
return QRgb();
}
QString VUtils::generateImageFileName(const QString &path, const QString &title,
const QString &format)
{
Q_ASSERT(!title.isEmpty());
QRegularExpression regExp("[^a-zA-Z0-9_]+");
QString baseName(title.toLower());
baseName.replace(regExp, "_");
baseName.prepend('_');
QString imageName = baseName + "." + format.toLower();
QString filePath = QDir(path).filePath(imageName);
int index = 1;
while (QFile(filePath).exists()) {
imageName = QString("%1_%2.%3").arg(baseName).arg(index++)
.arg(format.toLower());
filePath = QDir(path).filePath(imageName);
}
return imageName;
}

View File

@ -14,6 +14,8 @@ public:
static bool writeFileToDisk(const QString &filePath, const QString &text);
// Transform FFFFFF string to QRgb
static QRgb QRgbFromString(const QString &str);
static QString generateImageFileName(const QString &path, const QString &title,
const QString &format = "png");
};
#endif // VUTILS_H

View File

@ -3,6 +3,7 @@
#include "vnote.h"
#include "vconfigmanager.h"
#include "hgmarkdownhighlighter.h"
#include "vmdeditoperations.h"
extern VConfigManager vconfig;
@ -13,14 +14,24 @@ VEdit::VEdit(VNoteFile *noteFile, QWidget *parent)
setAcceptRichText(false);
mdHighlighter = new HGMarkdownHighlighter(vconfig.getMdHighlightingStyles(),
500, document());
editOps = new VMdEditOperations(this, noteFile);
} else {
setAutoFormatting(QTextEdit::AutoBulletList);
editOps = NULL;
}
updateTabSettings();
updateFontAndPalette();
}
VEdit::~VEdit()
{
if (editOps) {
delete editOps;
editOps = NULL;
}
}
void VEdit::updateFontAndPalette()
{
switch (noteFile->docType) {
@ -125,3 +136,31 @@ void VEdit::keyPressEvent(QKeyEvent *event)
}
QTextEdit::keyPressEvent(event);
}
bool VEdit::canInsertFromMimeData(const QMimeData *source) const
{
return source->hasImage() || source->hasUrls()
|| QTextEdit::canInsertFromMimeData(source);
}
void VEdit::insertFromMimeData(const QMimeData *source)
{
if (source->hasImage()) {
// Image data in the clipboard
if (editOps) {
bool ret = editOps->insertImageFromMimeData(source);
if (ret) {
return;
}
}
} else if (source->hasUrls()) {
// Paste an image file
if (editOps) {
bool ret = editOps->insertURLFromMimeData(source);
if (ret) {
return;
}
}
}
QTextEdit::insertFromMimeData(source);
}

View File

@ -7,12 +7,14 @@
#include "vnotefile.h"
class HGMarkdownHighlighter;
class VEditOperations;
class VEdit : public QTextEdit
{
Q_OBJECT
public:
VEdit(VNoteFile *noteFile, QWidget *parent = 0);
~VEdit();
void beginEdit();
// Save buffer content to noteFile->content.
@ -25,6 +27,8 @@ public:
protected:
void keyPressEvent(QKeyEvent *event) Q_DECL_OVERRIDE;
bool canInsertFromMimeData(const QMimeData *source) const Q_DECL_OVERRIDE;
void insertFromMimeData(const QMimeData *source) Q_DECL_OVERRIDE;
private:
void updateTabSettings();
@ -34,6 +38,7 @@ private:
QString tabSpaces;
VNoteFile *noteFile;
HGMarkdownHighlighter *mdHighlighter;
VEditOperations *editOps;
};

16
src/veditoperations.cpp Normal file
View File

@ -0,0 +1,16 @@
#include <QTextCursor>
#include <QTextDocument>
#include "vedit.h"
#include "veditoperations.h"
VEditOperations::VEditOperations(VEdit *editor, VNoteFile *noteFile)
: editor(editor), noteFile(noteFile)
{
}
void VEditOperations::insertTextAtCurPos(const QString &text)
{
QTextCursor cursor(editor->document());
cursor.setPosition(editor->textCursor().position());
cursor.insertText(text);
}

21
src/veditoperations.h Normal file
View File

@ -0,0 +1,21 @@
#ifndef VEDITOPERATIONS_H
#define VEDITOPERATIONS_H
class VNoteFile;
class VEdit;
class QMimeData;
class VEditOperations
{
public:
VEditOperations(VEdit *editor, VNoteFile *noteFile);
virtual bool insertImageFromMimeData(const QMimeData *source) = 0;
virtual bool insertURLFromMimeData(const QMimeData *source) = 0;
protected:
void insertTextAtCurPos(const QString &text);
VEdit *editor;
VNoteFile *noteFile;
};
#endif // VEDITOPERATIONS_H

View File

@ -88,17 +88,9 @@ void VMainWindow::setupUI()
mainSplitter->addWidget(nbContainer);
mainSplitter->addWidget(fileList);
mainSplitter->addWidget(tabs);
QList<int> sizes;
int sa = nbContainer->minimumSizeHint().width();
int sb = fileList->minimumSizeHint().width();
int sc = qMax(mainSplitter->sizeHint().width() - sa - sb, sa + sb);
sizes.append(sa);
sizes.append(sb);
sizes.append(sc);
mainSplitter->setSizes(sizes);
mainSplitter->setStretchFactor(0, 1);
mainSplitter->setStretchFactor(1, 1);
mainSplitter->setStretchFactor(2, 200);
mainSplitter->setStretchFactor(0, 0);
mainSplitter->setStretchFactor(1, 0);
mainSplitter->setStretchFactor(2, 1);
// Signals
connect(notebookComboBox, SIGNAL(currentIndexChanged(int)), this,

102
src/vmdeditoperations.cpp Normal file
View File

@ -0,0 +1,102 @@
#include <QtDebug>
#include <QImage>
#include <QVariant>
#include <QMimeData>
#include <QObject>
#include <QWidget>
#include <QImageReader>
#include <QDir>
#include <QMessageBox>
#include "vmdeditoperations.h"
#include "dialog/vinsertimagedialog.h"
#include "vnotefile.h"
#include "utils/vutils.h"
#include "vedit.h"
VMdEditOperations::VMdEditOperations(VEdit *editor, VNoteFile *noteFile)
: VEditOperations(editor, noteFile)
{
}
bool VMdEditOperations::insertImageFromMimeData(const QMimeData *source)
{
QImage image = qvariant_cast<QImage>(source->imageData());
if (image.isNull()) {
return false;
}
VInsertImageDialog dialog(QObject::tr("Insert image from clipboard"), QObject::tr("image_title"),
"", (QWidget *)editor);
dialog.setBrowseable(false);
dialog.setImage(image);
if (dialog.exec() == QDialog::Accepted) {
QString title = dialog.getImageTitleInput();
QString path = QDir::cleanPath(QDir(noteFile->basePath).filePath("images"));
QString fileName = VUtils::generateImageFileName(path, title);
qDebug() << "insert image" << path << title << fileName;
QString filePath = QDir(path).filePath(fileName);
Q_ASSERT(!QFile(filePath).exists());
bool ret = image.save(filePath);
if (!ret) {
QMessageBox msgBox(QMessageBox::Warning, QObject::tr("Warning"), QString("Fail to save image %1").arg(filePath),
QMessageBox::Ok, (QWidget *)editor);
msgBox.exec();
return true;
}
QString md = QString("![%1](images/%2)").arg(title).arg(fileName);
insertTextAtCurPos(md);
}
return true;
}
bool VMdEditOperations::insertImageFromPath(const QString &imagePath)
{
QImage image(imagePath);
if (image.isNull()) {
qWarning() << "error: image is null";
return false;
}
VInsertImageDialog dialog(QObject::tr("Insert image from file"), QObject::tr("image_title"),
"", (QWidget *)editor);
dialog.setBrowseable(false);
dialog.setImage(image);
if (dialog.exec() == QDialog::Accepted) {
QString title = dialog.getImageTitleInput();
QString path = QDir::cleanPath(QDir(noteFile->basePath).filePath("images"));
QString fileName = VUtils::generateImageFileName(path, title, QFileInfo(imagePath).suffix());
qDebug() << "insert image" << path << title << fileName;
QString filePath = QDir(path).filePath(fileName);
Q_ASSERT(!QFile(filePath).exists());
bool ret = QFile::copy(imagePath, filePath);
if (!ret) {
qWarning() << "error: fail to copy" << imagePath << "to" << filePath;
QMessageBox msgBox(QMessageBox::Warning, QObject::tr("Warning"), QString("Fail to save image %1").arg(filePath),
QMessageBox::Ok, (QWidget *)editor);
msgBox.exec();
return false;
}
QString md = QString("![%1](images/%2)").arg(title).arg(fileName);
insertTextAtCurPos(md);
}
return true;
}
bool VMdEditOperations::insertURLFromMimeData(const QMimeData *source)
{
foreach (QUrl url, source->urls()) {
if (url.isLocalFile()) {
QFileInfo info(url.toLocalFile());
if (QImageReader::supportedImageFormats().contains(info.suffix().toLower().toLatin1())) {
insertImageFromPath(info.filePath());
} else {
insertTextAtCurPos(url.toLocalFile());
}
} else {
// TODO: download http image
// Just insert the URL for non-image
insertTextAtCurPos(url.toString());
}
}
return true;
}

17
src/vmdeditoperations.h Normal file
View File

@ -0,0 +1,17 @@
#ifndef VMDEDITOPERATIONS_H
#define VMDEDITOPERATIONS_H
#include <QObject>
#include "veditoperations.h"
// Editor operations for Markdown
class VMdEditOperations : public VEditOperations
{
public:
VMdEditOperations(VEdit *editor, VNoteFile *noteFile);
bool insertImageFromMimeData(const QMimeData *source) Q_DECL_OVERRIDE;
bool insertURLFromMimeData(const QMimeData *source) Q_DECL_OVERRIDE;
bool insertImageFromPath(const QString &imagePath);
};
#endif // VMDEDITOPERATIONS_H

View File

@ -69,9 +69,7 @@ void VTabWidget::openFile(QJsonObject fileJson)
out:
setCurrentIndex(idx);
if (mode == OpenFileMode::Edit) {
VEditor *editor = dynamic_cast<VEditor *>(currentWidget());
Q_ASSERT(editor);
editor->editFile();
editFile();
}
}