mirror of
https://gitee.com/vnotex/vnote.git
synced 2025-07-05 13:59:52 +08:00
support pasting image in markdown
Signed-off-by: Le Tan <tamlokveer@gmail.com>
This commit is contained in:
parent
ec7850685e
commit
047c26b598
120
src/dialog/vinsertimagedialog.cpp
Normal file
120
src/dialog/vinsertimagedialog.cpp
Normal 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);
|
||||
}
|
49
src/dialog/vinsertimagedialog.h
Normal file
49
src/dialog/vinsertimagedialog.h
Normal 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
|
10
src/src.pro
10
src/src.pro
@ -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
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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
16
src/veditoperations.cpp
Normal 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
21
src/veditoperations.h
Normal 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
|
@ -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
102
src/vmdeditoperations.cpp
Normal 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("").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("").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
17
src/vmdeditoperations.h
Normal 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
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user