diff --git a/src/dialog/vinsertlinkdialog.cpp b/src/dialog/vinsertlinkdialog.cpp new file mode 100644 index 00000000..866300d5 --- /dev/null +++ b/src/dialog/vinsertlinkdialog.cpp @@ -0,0 +1,132 @@ +#include "vinsertlinkdialog.h" + +#include + +#include "vlineedit.h" + +VInsertLinkDialog::VInsertLinkDialog(const QString &p_title, + const QString &p_text, + const QString &p_info, + const QString &p_linkText, + const QString &p_linkUrl, + QWidget *p_parent) + : QDialog(p_parent) +{ + setupUI(p_title, p_text, p_info, p_linkText, p_linkUrl); + + fetchLinkFromClipboard(); + + handleInputChanged(); +} + +void VInsertLinkDialog::setupUI(const QString &p_title, + const QString &p_text, + const QString &p_info, + const QString &p_linkText, + const QString &p_linkUrl) +{ + QLabel *textLabel = NULL; + if (!p_text.isEmpty()) { + textLabel = new QLabel(p_text); + textLabel->setWordWrap(true); + } + + QLabel *infoLabel = NULL; + if (!p_info.isEmpty()) { + infoLabel = new QLabel(p_info); + infoLabel->setWordWrap(true); + } + + m_linkTextEdit = new VLineEdit(p_linkText); + m_linkTextEdit->selectAll(); + + m_linkUrlEdit = new QLineEdit(p_linkUrl); + m_linkUrlEdit->setToolTip(tr("Absolute or relative path of the link")); + + QFormLayout *inputLayout = new QFormLayout(); + inputLayout->addRow(tr("&Text:"), m_linkTextEdit); + inputLayout->addRow(tr("&URL:"), m_linkUrlEdit); + + // Ok is the default button. + m_btnBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel); + connect(m_btnBox, &QDialogButtonBox::accepted, this, &QDialog::accept); + connect(m_btnBox, &QDialogButtonBox::rejected, this, &QDialog::reject); + + QPushButton *okBtn = m_btnBox->button(QDialogButtonBox::Ok); + m_linkTextEdit->setMinimumWidth(okBtn->sizeHint().width() * 3); + + QVBoxLayout *mainLayout = new QVBoxLayout; + if (textLabel) { + mainLayout->addWidget(textLabel); + } + + if (infoLabel) { + mainLayout->addWidget(infoLabel); + } + + mainLayout->addLayout(inputLayout); + mainLayout->addWidget(m_btnBox); + + setLayout(mainLayout); + setWindowTitle(p_title); + + connect(m_linkTextEdit, &QLineEdit::textChanged, + this, &VInsertLinkDialog::handleInputChanged); + connect(m_linkUrlEdit, &QLineEdit::textChanged, + this, &VInsertLinkDialog::handleInputChanged); +} + +void VInsertLinkDialog::handleInputChanged() +{ + bool textOk = true; + if (m_linkTextEdit->getEvaluatedText().isEmpty()) { + textOk = false; + } + + bool urlOk = true; + if (m_linkUrlEdit->text().isEmpty()) { + urlOk = false; + } + + QPushButton *okBtn = m_btnBox->button(QDialogButtonBox::Ok); + okBtn->setEnabled(textOk && urlOk); +} + +void VInsertLinkDialog::fetchLinkFromClipboard() +{ + if (!m_linkUrlEdit->text().isEmpty() + || !m_linkTextEdit->text().isEmpty()) { + return; + } + + QClipboard *clipboard = QApplication::clipboard(); + const QMimeData *mimeData = clipboard->mimeData(); + + if (!mimeData->hasText()) { + return; + } + + QString text = mimeData->text(); + if (text.isEmpty()) { + return; + } + + QUrl url = QUrl::fromUserInput(text); + if (url.isValid()) { + if (m_linkUrlEdit->text().isEmpty()) { + m_linkUrlEdit->setText(text); + } + } else if (m_linkTextEdit->text().isEmpty()) { + m_linkTextEdit->setText(text); + } +} + +QString VInsertLinkDialog::getLinkText() const +{ + return m_linkTextEdit->getEvaluatedText(); +} + +QString VInsertLinkDialog::getLinkUrl() const +{ + return m_linkUrlEdit->text(); +} diff --git a/src/dialog/vinsertlinkdialog.h b/src/dialog/vinsertlinkdialog.h new file mode 100644 index 00000000..d31d8982 --- /dev/null +++ b/src/dialog/vinsertlinkdialog.h @@ -0,0 +1,46 @@ +#ifndef VINSERTLINKDIALOG_H +#define VINSERTLINKDIALOG_H + +#include +#include + +class VLineEdit; +class QLineEdit; +class QDialogButtonBox; + +class VInsertLinkDialog : public QDialog +{ + Q_OBJECT +public: + VInsertLinkDialog(const QString &p_title, + const QString &p_text, + const QString &p_info, + const QString &p_linkText, + const QString &p_linkUrl, + QWidget *p_parent = nullptr); + + QString getLinkText() const; + + QString getLinkUrl() const; + +private slots: + void handleInputChanged(); + +private: + void setupUI(const QString &p_title, + const QString &p_text, + const QString &p_info, + const QString &p_linkText, + const QString &p_linkUrl); + + // Infer link text/url from clipboard only when text and url are both empty. + void fetchLinkFromClipboard(); + + VLineEdit *m_linkTextEdit; + + QLineEdit *m_linkUrlEdit; + + QDialogButtonBox *m_btnBox; +}; + +#endif // VINSERTLINKDIALOG_H diff --git a/src/resources/icons/link.svg b/src/resources/icons/link.svg new file mode 100644 index 00000000..97465186 --- /dev/null +++ b/src/resources/icons/link.svg @@ -0,0 +1,15 @@ + + + + + + + + + + diff --git a/src/src.pro b/src/src.pro index 8831b063..4dcc8d70 100644 --- a/src/src.pro +++ b/src/src.pro @@ -80,7 +80,8 @@ SOURCES += main.cpp\ vfilesessioninfo.cpp \ vtableofcontent.cpp \ utils/vmetawordmanager.cpp \ - vlineedit.cpp + vlineedit.cpp \ + dialog/vinsertlinkdialog.cpp HEADERS += vmainwindow.h \ vdirectorytree.h \ @@ -148,7 +149,8 @@ HEADERS += vmainwindow.h \ vfilesessioninfo.h \ vtableofcontent.h \ utils/vmetawordmanager.h \ - vlineedit.h + vlineedit.h \ + dialog/vinsertlinkdialog.h RESOURCES += \ vnote.qrc \ diff --git a/src/vedit.cpp b/src/vedit.cpp index 46b3d8ec..4d1669f0 100644 --- a/src/vedit.cpp +++ b/src/vedit.cpp @@ -10,7 +10,7 @@ #include "utils/vmetawordmanager.h" #include "veditoperations.h" #include "vedittab.h" - +#include "dialog/vinsertlinkdialog.h" extern VConfigManager *g_config; @@ -199,6 +199,49 @@ void VEdit::insertImage() } } +void VEdit::insertLink() +{ + if (!m_editOps) { + return; + } + + QString text; + QString linkText, linkUrl; + QTextCursor cursor = textCursor(); + if (cursor.hasSelection()) { + text = VEditUtils::selectedText(cursor).trimmed(); + // Only pure space is accepted. + QRegExp reg("[\\S ]*"); + if (reg.exactMatch(text)) { + QUrl url = QUrl::fromUserInput(text, + m_file->fetchBasePath()); + QRegExp urlReg("[\\.\\\\/]"); + if (url.isValid() + && text.contains(urlReg)) { + // Url. + linkUrl = text; + } else { + // Text. + linkText = text; + } + } + } + + VInsertLinkDialog dialog(tr("Insert Link"), + "", + "", + linkText, + linkUrl, + this); + if (dialog.exec() == QDialog::Accepted) { + linkText = dialog.getLinkText(); + linkUrl = dialog.getLinkUrl(); + Q_ASSERT(!linkText.isEmpty() && !linkUrl.isEmpty()); + + m_editOps->insertLink(linkText, linkUrl); + } +} + bool VEdit::peekText(const QString &p_text, uint p_options, bool p_forward) { if (p_text.isEmpty()) { diff --git a/src/vedit.h b/src/vedit.h index bb9a9961..f2d7e4b8 100644 --- a/src/vedit.h +++ b/src/vedit.h @@ -91,6 +91,9 @@ public: // User requests to insert an image. virtual void insertImage(); + // User requests to insert a link. + virtual void insertLink(); + // Used for incremental search. // User has enter the content to search, but does not enter the "find" button yet. bool peekText(const QString &p_text, uint p_options, bool p_forward = true); diff --git a/src/veditoperations.cpp b/src/veditoperations.cpp index 18c87bb3..74e3183d 100644 --- a/src/veditoperations.cpp +++ b/src/veditoperations.cpp @@ -90,3 +90,10 @@ void VEditOperations::requestUpdateVimStatus() { emit vimStatusUpdated(m_vim); } + +void VEditOperations::setVimMode(VimMode p_mode) +{ + if (m_vim && m_editConfig->m_enableVimMode) { + m_vim->setMode(p_mode); + } +} diff --git a/src/veditoperations.h b/src/veditoperations.h index 6eed4299..8085e2f1 100644 --- a/src/veditoperations.h +++ b/src/veditoperations.h @@ -18,11 +18,18 @@ class VEditOperations: public QObject Q_OBJECT public: VEditOperations(VEdit *p_editor, VFile *p_file); + virtual ~VEditOperations(); + virtual bool insertImageFromMimeData(const QMimeData *source) = 0; + virtual bool insertImage() = 0; + virtual bool insertImageFromURL(const QUrl &p_imageUrl) = 0; + virtual bool insertLink(const QString &p_linkText, + const QString &p_linkUrl) = 0; + // Return true if @p_event has been handled and no need to be further // processed. virtual bool handleKeyPressEvent(QKeyEvent *p_event) = 0; @@ -63,11 +70,4 @@ protected: VVim *m_vim; }; -inline void VEditOperations::setVimMode(VimMode p_mode) -{ - if (m_vim) { - m_vim->setMode(p_mode); - } -} - #endif // VEDITOPERATIONS_H diff --git a/src/vedittab.cpp b/src/vedittab.cpp index 52b5f241..db8c4400 100644 --- a/src/vedittab.cpp +++ b/src/vedittab.cpp @@ -120,3 +120,7 @@ bool VEditTab::tabHasFocus() const QWidget *wid = QApplication::focusWidget(); return wid == this || isAncestorOf(wid); } + +void VEditTab::insertLink() +{ +} diff --git a/src/vedittab.h b/src/vedittab.h index 3a904e5f..23110fa2 100644 --- a/src/vedittab.h +++ b/src/vedittab.h @@ -49,6 +49,9 @@ public: // User requests to insert image. virtual void insertImage() = 0; + // User requests to insert link. + virtual void insertLink(); + // Search @p_text in current note. virtual void findText(const QString &p_text, uint p_options, bool p_peek, bool p_forward = true) = 0; diff --git a/src/vmainwindow.cpp b/src/vmainwindow.cpp index 0fd2916a..688da0ea 100644 --- a/src/vmainwindow.cpp +++ b/src/vmainwindow.cpp @@ -463,6 +463,19 @@ void VMainWindow::initEditToolBar(QSize p_iconSize) m_editToolBar->addAction(inlineCodeAct); + // Insert link. + QAction *insetLinkAct = new QAction(QIcon(":/resources/icons/link.svg"), + tr("Insert Link (Ctrl+L)"), this); + insetLinkAct->setStatusTip(tr("Insert a link")); + connect(insetLinkAct, &QAction::triggered, + this, [this]() { + if (m_curTab) { + m_curTab->insertLink(); + } + }); + + m_editToolBar->addAction(insetLinkAct); + // Insert image. QAction *insertImageAct = new QAction(QIcon(":/resources/icons/insert_image.svg"), tr("Insert Image"), diff --git a/src/vmdeditoperations.cpp b/src/vmdeditoperations.cpp index 4b2ec903..d8c5d926 100644 --- a/src/vmdeditoperations.cpp +++ b/src/vmdeditoperations.cpp @@ -278,6 +278,17 @@ bool VMdEditOperations::handleKeyPressEvent(QKeyEvent *p_event) break; } + case Qt::Key_L: + { + if (modifiers == Qt::ControlModifier) { + m_editor->insertLink(); + p_event->accept(); + ret = true; + } + + break; + } + case Qt::Key_O: { if (modifiers == Qt::ControlModifier) { @@ -832,3 +843,16 @@ void VMdEditOperations::decorateStrikethrough() cursor.endEditBlock(); m_editor->setTextCursor(cursor); } + +bool VMdEditOperations::insertLink(const QString &p_linkText, + const QString &p_linkUrl) +{ + QString link = QString("[%1](%2)").arg(p_linkText).arg(p_linkUrl); + QTextCursor cursor = m_editor->textCursor(); + cursor.insertText(link); + m_editor->setTextCursor(cursor); + + setVimMode(VimMode::Insert); + + return true; +} diff --git a/src/vmdeditoperations.h b/src/vmdeditoperations.h index 57478328..5f96367b 100644 --- a/src/vmdeditoperations.h +++ b/src/vmdeditoperations.h @@ -16,11 +16,18 @@ class VMdEditOperations : public VEditOperations Q_OBJECT public: VMdEditOperations(VEdit *p_editor, VFile *p_file); + bool insertImageFromMimeData(const QMimeData *source) Q_DECL_OVERRIDE; + bool insertImage() Q_DECL_OVERRIDE; + bool handleKeyPressEvent(QKeyEvent *p_event) Q_DECL_OVERRIDE; + bool insertImageFromURL(const QUrl &p_imageUrl) Q_DECL_OVERRIDE; + bool insertLink(const QString &p_linkText, + const QString &p_linkUrl); + // Insert decoration markers or decorate selected text. // If it is Vim Normal mode, change to Insert mode first. void decorateText(TextDecoration p_decoration) Q_DECL_OVERRIDE; diff --git a/src/vmdtab.cpp b/src/vmdtab.cpp index 67943180..658d5d07 100644 --- a/src/vmdtab.cpp +++ b/src/vmdtab.cpp @@ -469,6 +469,16 @@ void VMdTab::insertImage() m_editor->insertImage(); } +void VMdTab::insertLink() +{ + if (!m_isEditMode) { + return; + } + + Q_ASSERT(m_editor); + m_editor->insertLink(); +} + void VMdTab::findText(const QString &p_text, uint p_options, bool p_peek, bool p_forward) { diff --git a/src/vmdtab.h b/src/vmdtab.h index ec06b2ac..267df642 100644 --- a/src/vmdtab.h +++ b/src/vmdtab.h @@ -36,6 +36,8 @@ public: void insertImage() Q_DECL_OVERRIDE; + void insertLink() Q_DECL_OVERRIDE; + // Search @p_text in current note. void findText(const QString &p_text, uint p_options, bool p_peek, bool p_forward = true) Q_DECL_OVERRIDE; diff --git a/src/vnote.qrc b/src/vnote.qrc index 883a2be2..d62efb59 100644 --- a/src/vnote.qrc +++ b/src/vnote.qrc @@ -132,5 +132,6 @@ resources/icons/create_subdir.svg resources/icons/compact_mode.svg resources/icons/heading_sequence.svg + resources/icons/link.svg