1
0
Эх сурвалжийг харах

MdEditor: aware of links and images in edit mode

Le Tan 7 жил өмнө
parent
commit
f98c2f5382

+ 7 - 0
src/pegmarkdownhighlighter.h

@@ -42,6 +42,8 @@ public:
 
     const QTextDocument *getDocument() const;
 
+    const QVector<VElementRegion> &getImageRegions() const;
+
 public slots:
     // Parse and rehighlight immediately.
     void updateHighlight();
@@ -215,6 +217,11 @@ inline const QVector<VElementRegion> &PegMarkdownHighlighter::getHeaderRegions()
     return m_result->m_headerRegions;
 }
 
+inline const QVector<VElementRegion> &PegMarkdownHighlighter::getImageRegions() const
+{
+    return m_result->m_imageRegions;
+}
+
 inline const QSet<int> &PegMarkdownHighlighter::getPossiblePreviewBlocks() const
 {
     return m_possiblePreviewBlocks;

+ 39 - 0
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<QUrl> 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);
+}

+ 9 - 0
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()
     {

+ 99 - 7
src/utils/vutils.cpp

@@ -42,13 +42,17 @@ extern VConfigManager *g_config;
 
 QVector<QPair<QString, QString>> 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<ImageLink> 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;
+}

+ 20 - 1
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;

+ 142 - 0
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<VElementRegion> &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);
+}

+ 2 - 0
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);

+ 2 - 69
src/vpreviewmanager.cpp

@@ -180,44 +180,9 @@ void VPreviewManager::fetchImageLinksFromRegions(QVector<VElementRegion> 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)

+ 0 - 3
src/vpreviewmanager.h

@@ -180,9 +180,6 @@ private:
     void fetchImageLinksFromRegions(QVector<VElementRegion> p_imageRegions,
                                     QVector<ImageLinkInfo> &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);