diff --git a/src/pegmarkdownhighlighter.h b/src/pegmarkdownhighlighter.h index 74b50afd..7c0ff2c8 100644 --- a/src/pegmarkdownhighlighter.h +++ b/src/pegmarkdownhighlighter.h @@ -42,6 +42,8 @@ public: const QTextDocument *getDocument() const; + const QVector &getImageRegions() const; + public slots: // Parse and rehighlight immediately. void updateHighlight(); @@ -215,6 +217,11 @@ inline const QVector &PegMarkdownHighlighter::getHeaderRegions() return m_result->m_headerRegions; } +inline const QVector &PegMarkdownHighlighter::getImageRegions() const +{ + return m_result->m_imageRegions; +} + inline const QSet &PegMarkdownHighlighter::getPossiblePreviewBlocks() const { return m_possiblePreviewBlocks; diff --git a/src/utils/vclipboardutils.cpp b/src/utils/vclipboardutils.cpp index c6c04121..79062ca1 100644 --- a/src/utils/vclipboardutils.cpp +++ b/src/utils/vclipboardutils.cpp @@ -163,3 +163,42 @@ void VClipboardUtils::setMimeDataLoop(QClipboard *p_clipboard, VUtils::sleepWait(100 /* ms */); } } + +QMimeData *linkMimeData(const QString &p_link) +{ + QList urls; + urls.append(VUtils::pathToUrl(p_link)); + QMimeData *data = new QMimeData(); + data->setUrls(urls); + + QString text = urls[0].toEncoded(); +#if defined(Q_OS_WIN) + if (urls[0].isLocalFile()) { + text = urls[0].toString(QUrl::EncodeSpaces); + } +#endif + + data->setText(text); + return data; +} + +void VClipboardUtils::setLinkToClipboard(QClipboard *p_clipboard, + const QString &p_link, + QClipboard::Mode p_mode) +{ + VClipboardUtils::setMimeDataToClipboard(p_clipboard, + linkMimeData(p_link), + p_mode); +} + +void VClipboardUtils::setImageAndLinkToClipboard(QClipboard *p_clipboard, + const QImage &p_image, + const QString &p_link, + QClipboard::Mode p_mode) +{ + QMimeData *data = linkMimeData(p_link); + data->setImageData(p_image); + VClipboardUtils::setMimeDataToClipboard(p_clipboard, + data, + p_mode); +} diff --git a/src/utils/vclipboardutils.h b/src/utils/vclipboardutils.h index 1e5beda9..a4f52b99 100644 --- a/src/utils/vclipboardutils.h +++ b/src/utils/vclipboardutils.h @@ -14,12 +14,21 @@ public: const QImage &p_image, QClipboard::Mode p_mode = QClipboard::Clipboard); + static void setImageAndLinkToClipboard(QClipboard *p_clipboard, + const QImage &p_image, + const QString &p_link, + QClipboard::Mode p_mode = QClipboard::Clipboard); + static void setMimeDataToClipboard(QClipboard *p_clipboard, QMimeData *p_mimeData, QClipboard::Mode p_mode = QClipboard::Clipboard); static QMimeData *cloneMimeData(const QMimeData *p_mimeData); + static void setLinkToClipboard(QClipboard *p_clipboard, + const QString &p_link, + QClipboard::Mode p_mode = QClipboard::Clipboard); + private: VClipboardUtils() { diff --git a/src/utils/vutils.cpp b/src/utils/vutils.cpp index c8efe38b..45e368c8 100644 --- a/src/utils/vutils.cpp +++ b/src/utils/vutils.cpp @@ -42,13 +42,17 @@ extern VConfigManager *g_config; QVector> VUtils::s_availableLanguages; -const QString VUtils::c_imageLinkRegExp = QString("\\!\\[([^\\]]*)\\]\\(([^\\)\"'\\s]+)\\s*" +const QString VUtils::c_imageLinkRegExp = QString("\\!\\[([^\\]]*)\\]\\(\\s*([^\\)\"'\\s]+)\\s*" "((\"[^\"\\)\\n]*\")|('[^'\\)\\n]*'))?\\s*" "(=(\\d*)x(\\d*))?\\s*" "\\)"); const QString VUtils::c_imageTitleRegExp = QString("[\\w\\(\\)@#%\\*\\-\\+=\\?<>\\,\\.\\s]*"); +const QString VUtils::c_linkRegExp = QString("\\[([^\\]]*)\\]\\(\\s*([^\\)\"'\\s]+)\\s*" + "((\"[^\"\\)\\n]*\")|('[^'\\)\\n]*'))?\\s*" + "\\)"); + const QString VUtils::c_fileNameRegExp = QString("(?:[^\\\\/:\\*\\?\"<>\\|\\s]| )*"); const QString VUtils::c_fencedCodeBlockStartRegExp = QString("^(\\s*)```([^`\\s]*)\\s*[^`]*$"); @@ -276,22 +280,39 @@ QVector VUtils::fetchImagesFromMarkdownFile(VFile *p_file, return images; } -QString VUtils::imageLinkUrlToPath(const QString &p_basePath, const QString &p_url) +QString VUtils::linkUrlToPath(const QString &p_basePath, const QString &p_url) { - QString path; + QString fullPath; QFileInfo info(p_basePath, p_url); if (info.exists()) { if (info.isNativePath()) { // Local file. - path = QDir::cleanPath(info.absoluteFilePath()); + fullPath = QDir::cleanPath(info.absoluteFilePath()); } else { - path = p_url; + fullPath = p_url; } } else { - path = QUrl(p_url).toString(); + QString decodedUrl(p_url); + VUtils::decodeUrl(decodedUrl); + QFileInfo dinfo(p_basePath, decodedUrl); + if (dinfo.exists()) { + if (dinfo.isNativePath()) { + // Local file. + fullPath = QDir::cleanPath(dinfo.absoluteFilePath()); + } else { + fullPath = p_url; + } + } else { + QUrl url(p_url); + if (url.isLocalFile()) { + fullPath = url.toLocalFile(); + } else { + fullPath = url.toString(); + } + } } - return path; + return fullPath; } bool VUtils::makePath(const QString &p_path) @@ -1678,3 +1699,74 @@ QPixmap VUtils::svgToPixmap(const QByteArray &p_content, renderer.render(&painter); return pm; } + +QString VUtils::fetchImageLinkUrlToPreview(const QString &p_text, int &p_width, int &p_height) +{ + QRegExp regExp(VUtils::c_imageLinkRegExp); + + p_width = p_height = -1; + + int index = regExp.indexIn(p_text); + if (index == -1) { + return QString(); + } + + int lastIndex = regExp.lastIndexIn(p_text); + if (lastIndex != index) { + return QString(); + } + + QString tmp(regExp.cap(7)); + if (!tmp.isEmpty()) { + p_width = tmp.toInt(); + if (p_width <= 0) { + p_width = -1; + } + } + + tmp = regExp.cap(8); + if (!tmp.isEmpty()) { + p_height = tmp.toInt(); + if (p_height <= 0) { + p_height = -1; + } + } + + return regExp.cap(2).trimmed(); +} + +QString VUtils::fetchImageLinkUrl(const QString &p_link) +{ + QRegExp regExp(VUtils::c_imageLinkRegExp); + + int index = regExp.indexIn(p_link); + if (index == -1) { + return QString(); + } + + return regExp.cap(2).trimmed(); +} + +QString VUtils::fetchLinkUrl(const QString &p_link) +{ + QRegExp regExp(VUtils::c_linkRegExp); + + int index = regExp.indexIn(p_link); + if (index == -1) { + return QString(); + } + + return regExp.cap(2).trimmed(); +} + +QUrl VUtils::pathToUrl(const QString &p_path) +{ + QUrl url; + if (QFileInfo::exists(p_path)) { + url = QUrl::fromLocalFile(p_path); + } else { + url = QUrl(p_path); + } + + return url; +} diff --git a/src/utils/vutils.h b/src/utils/vutils.h index 0d7547a8..2a64c525 100644 --- a/src/utils/vutils.h +++ b/src/utils/vutils.h @@ -133,7 +133,7 @@ public: ImageLink::ImageLinkType p_type = ImageLink::All); // Return the absolute path of @p_url according to @p_basePath. - static QString imageLinkUrlToPath(const QString &p_basePath, const QString &p_url); + static QString linkUrlToPath(const QString &p_basePath, const QString &p_url); // Create directories along the @p_path. // @p_path could be /home/tamlok/abc, /home/tamlok/abc/. @@ -365,6 +365,15 @@ public: const QString &p_background, qreal p_factor); + // Fetch the image link's URL if there is only one link. + static QString fetchImageLinkUrlToPreview(const QString &p_text, int &p_width, int &p_height); + + static QString fetchImageLinkUrl(const QString &p_link); + + static QString fetchLinkUrl(const QString &p_link); + + static QUrl pathToUrl(const QString &p_path); + // Regular expression for image link. // ![image title]( http://github.com/tamlok/vnote.jpg "alt text" =200x100) // Captured texts (need to be trimmed): @@ -381,6 +390,16 @@ public: // Regular expression for image title. static const QString c_imageTitleRegExp; + // Regular expression for link. + // [link text]( http://github.com/tamlok "alt text") + // Captured texts (need to be trimmed): + // 1. Link Alt Text (Title); + // 2. Link URL; + // 3. Link Optional Title with double quotes or quotes; + // 4. Unused; + // 5. Unused; + static const QString c_linkRegExp; + // Regular expression for file/directory name. // Forbidden char: \/:*?"<>| and whitespaces except space. static const QString c_fileNameRegExp; diff --git a/src/vmdeditor.cpp b/src/vmdeditor.cpp index 964f1ab9..e4c5e450 100644 --- a/src/vmdeditor.cpp +++ b/src/vmdeditor.cpp @@ -24,6 +24,7 @@ #include "dialog/vcopytextashtmldialog.h" #include "utils/vwebutils.h" #include "dialog/vinsertlinkdialog.h" +#include "utils/vclipboardutils.h" extern VWebUtils *g_webUtils; @@ -302,6 +303,8 @@ void VMdEditor::contextMenuEvent(QContextMenuEvent *p_event) if (textCursor().hasSelection()) { initCopyAsMenu(actions.isEmpty() ? NULL : actions.last(), menu.data()); } else { + initLinkMenu(actions.isEmpty() ? NULL : actions[0], menu.data(), p_event->pos()); + QAction *saveExitAct = new QAction(VIconUtils::menuIcon(":/resources/icons/save_exit.svg"), tr("&Save Changes And Read"), menu.data()); @@ -1458,3 +1461,142 @@ int VMdEditor::lineNumberAreaWidth() const { return VTextEdit::lineNumberAreaWidth(); } + +void VMdEditor::initLinkMenu(QAction *p_before, QMenu *p_menu, const QPoint &p_pos) +{ + QTextCursor cursor = cursorForPosition(p_pos); + int pos = cursor.position(); + QTextBlock block = cursor.block(); + const QString text(block.text()); + + // Image. + QRegExp regExp(VUtils::c_imageLinkRegExp); + if (regExp.indexIn(text) > -1) { + const QVector &imgRegs = m_pegHighlighter->getImageRegions(); + for (auto const & reg : imgRegs) { + if (!reg.contains(pos)) { + continue; + } + + if (reg.m_endPos > block.position() + text.length()) { + return; + } + + QString linkText = text.mid(reg.m_startPos - block.position(), + reg.m_endPos - reg.m_startPos); + QString surl = VUtils::fetchImageLinkUrl(linkText); + if (surl.isEmpty()) { + return; + } + + QString imgPath = VUtils::linkUrlToPath(m_file->fetchBasePath(), surl); + bool isLocalFile = QFileInfo::exists(imgPath); + + QAction *viewImageAct = new QAction(tr("View Image"), p_menu); + connect(viewImageAct, &QAction::triggered, + this, [this, imgPath]() { + QDesktopServices::openUrl(VUtils::pathToUrl(imgPath)); + }); + p_menu->insertAction(p_before, viewImageAct); + + QAction *copyImageLinkAct = new QAction(tr("Copy Image URL"), p_menu); + connect(copyImageLinkAct, &QAction::triggered, + this, [this, imgPath]() { + QClipboard *clipboard = QApplication::clipboard(); + VClipboardUtils::setLinkToClipboard(clipboard, + imgPath, + QClipboard::Clipboard); + }); + p_menu->insertAction(p_before, copyImageLinkAct); + + if (isLocalFile) { + QAction *copyImagePathAct = new QAction(tr("Copy Image Path"), p_menu); + connect(copyImagePathAct, &QAction::triggered, + this, [this, imgPath]() { + QClipboard *clipboard = QApplication::clipboard(); + QMimeData *data = new QMimeData(); + data->setText(imgPath); + VClipboardUtils::setMimeDataToClipboard(clipboard, + data, + QClipboard::Clipboard); + }); + p_menu->insertAction(p_before, copyImagePathAct); + + QAction *copyImageAct = new QAction(tr("Copy Image"), p_menu); + connect(copyImageAct, &QAction::triggered, + this, [this, imgPath]() { + QClipboard *clipboard = QApplication::clipboard(); + clipboard->clear(); + QImage img = VUtils::imageFromFile(imgPath); + if (!img.isNull()) { + VClipboardUtils::setImageAndLinkToClipboard(clipboard, + img, + imgPath, + QClipboard::Clipboard); + } + }); + p_menu->insertAction(p_before, copyImageAct); + } + + p_menu->insertSeparator(p_before ? p_before : NULL); + return; + } + } + + // Link. + QRegExp regExp2(VUtils::c_linkRegExp); + QString linkText; + int p = 0; + while (p < text.size()) { + int idx = text.indexOf(regExp2, p); + if (idx == -1) { + break; + } + + p = idx + regExp2.matchedLength(); + if (pos >= idx && pos < p) { + linkText = regExp2.cap(2); + break; + } + } + + if (linkText.isEmpty()) { + return; + } + + QString linkUrl = VUtils::linkUrlToPath(m_file->fetchBasePath(), linkText); + bool isLocalFile = QFileInfo::exists(linkUrl); + + QAction *viewLinkAct = new QAction(tr("View Link"), p_menu); + connect(viewLinkAct, &QAction::triggered, + this, [this, linkUrl]() { + QDesktopServices::openUrl(VUtils::pathToUrl(linkUrl)); + }); + p_menu->insertAction(p_before, viewLinkAct); + + QAction *copyLinkAct = new QAction(tr("Copy Link URL"), p_menu); + connect(copyLinkAct, &QAction::triggered, + this, [this, linkUrl]() { + QClipboard *clipboard = QApplication::clipboard(); + VClipboardUtils::setLinkToClipboard(clipboard, + linkUrl, + QClipboard::Clipboard); + }); + p_menu->insertAction(p_before, copyLinkAct); + + if (isLocalFile) { + QAction *copyLinkPathAct = new QAction(tr("Copy Link Path"), p_menu); + connect(copyLinkPathAct, &QAction::triggered, + this, [this, linkUrl]() { + QClipboard *clipboard = QApplication::clipboard(); + QMimeData *data = new QMimeData(); + data->setText(linkUrl); + VClipboardUtils::setMimeDataToClipboard(clipboard, + data, + QClipboard::Clipboard); + }); + p_menu->insertAction(p_before, copyLinkPathAct); + } + + p_menu->insertSeparator(p_before ? p_before : NULL); +} diff --git a/src/vmdeditor.h b/src/vmdeditor.h index f2c933b7..8902ad66 100644 --- a/src/vmdeditor.h +++ b/src/vmdeditor.h @@ -277,6 +277,8 @@ private: void initPasteAsBlockQuoteMenu(QMenu *p_menu); + void initLinkMenu(QAction *p_before, QMenu *p_menu, const QPoint &p_pos); + void insertImageLink(const QString &p_text, const QString &p_url); void setFontPointSizeByStyleSheet(int p_ptSize); diff --git a/src/vpreviewmanager.cpp b/src/vpreviewmanager.cpp index 9e798950..7e78eafa 100644 --- a/src/vpreviewmanager.cpp +++ b/src/vpreviewmanager.cpp @@ -180,44 +180,9 @@ void VPreviewManager::fetchImageLinksFromRegions(QVector p_image } } -QString VPreviewManager::fetchImageUrlToPreview(const QString &p_text, int &p_width, int &p_height) -{ - QRegExp regExp(VUtils::c_imageLinkRegExp); - - p_width = p_height = -1; - - int index = regExp.indexIn(p_text); - if (index == -1) { - return QString(); - } - - int lastIndex = regExp.lastIndexIn(p_text); - if (lastIndex != index) { - return QString(); - } - - QString tmp(regExp.cap(7)); - if (!tmp.isEmpty()) { - p_width = tmp.toInt(); - if (p_width <= 0) { - p_width = -1; - } - } - - tmp = regExp.cap(8); - if (!tmp.isEmpty()) { - p_height = tmp.toInt(); - if (p_height <= 0) { - p_height = -1; - } - } - - return regExp.cap(2).trimmed(); -} - void VPreviewManager::fetchImageInfoToPreview(const QString &p_text, ImageLinkInfo &p_info) { - QString surl = fetchImageUrlToPreview(p_text, p_info.m_width, p_info.m_height); + QString surl = VUtils::fetchImageLinkUrlToPreview(p_text, p_info.m_width, p_info.m_height); p_info.m_linkShortUrl = surl; if (surl.isEmpty()) { p_info.m_linkUrl = surl; @@ -225,39 +190,7 @@ void VPreviewManager::fetchImageInfoToPreview(const QString &p_text, ImageLinkIn } const VFile *file = m_editor->getFile(); - - QString imagePath; - QFileInfo info(file->fetchBasePath(), surl); - - if (info.exists()) { - if (info.isNativePath()) { - // Local file. - imagePath = QDir::cleanPath(info.absoluteFilePath()); - } else { - imagePath = surl; - } - } else { - QString decodedUrl(surl); - VUtils::decodeUrl(decodedUrl); - QFileInfo dinfo(file->fetchBasePath(), decodedUrl); - if (dinfo.exists()) { - if (dinfo.isNativePath()) { - // Local file. - imagePath = QDir::cleanPath(dinfo.absoluteFilePath()); - } else { - imagePath = surl; - } - } else { - QUrl url(surl); - if (url.isLocalFile()) { - imagePath = url.toLocalFile(); - } else { - imagePath = url.toString(); - } - } - } - - p_info.m_linkUrl = imagePath; + p_info.m_linkUrl = VUtils::linkUrlToPath(file->fetchBasePath(), surl); } QString VPreviewManager::imageResourceName(const ImageLinkInfo &p_link) diff --git a/src/vpreviewmanager.h b/src/vpreviewmanager.h index 77b88150..5d18d3a0 100644 --- a/src/vpreviewmanager.h +++ b/src/vpreviewmanager.h @@ -180,9 +180,6 @@ private: void fetchImageLinksFromRegions(QVector p_imageRegions, QVector &p_imageLinks); - // Fetch the image link's URL if there is only one link. - QString fetchImageUrlToPreview(const QString &p_text, int &p_width, int &p_height); - // Fetch the image's full path and size. void fetchImageInfoToPreview(const QString &p_text, ImageLinkInfo &p_info);