[graphics/drawy/gsoc2026] src: feat: allow opening hyperlinks from pasted html

Abdelhadi Wael <[email protected]>
Newsgroups gmane.comp.kde.cvs
Message-ID <[email protected]>
Git commit 4418e83d1ad047b3bbf59df890e8dbf476d00eda by Abdelhadi Wael.
Committed on 24/07/2026 at 09:34.
Pushed by mlaurent into branch 'gsoc2026'.

feat: allow opening hyperlinks from pasted html

M  +8    -0    src/gui/item/text.cpp
M  +1    -0    src/gui/item/text.hpp
M  +21   -1    src/widgets/tools/texttool.cpp

https://invent.kde.org/graphics/drawy/-/commit/4418e83d1ad047b3bbf59df890e8dbf476d00eda

diff --git a/src/gui/item/text.cpp b/src/gui/item/text.cpp
index bd2ddc58..707d7c92 100644
--- a/src/gui/item/text.cpp
+++ b/src/gui/item/text.cpp
@@ -240,6 +240,14 @@ qsizetype TextItem::getIndexFromCursor(const QPointF cursorPos) const
     return m_document.documentLayout()->hitTest(docPos, Qt::FuzzyHit);
 }
 
+QString TextItem::getLink(const QPointF cursorPos) const
+{
+    const QPointF localPos{m_transform.inverted().map(cursorPos)};
+    const QPointF docPos = localPos - m_boundingBox.topLeft();
+
+    return m_document.documentLayout()->anchorAt(docPos);
+}
+
 void TextItem::updateBoundingBox()
 {
     m_document.setDefaultTextOption(getTextOptions());
diff --git a/src/gui/item/text.hpp b/src/gui/item/text.hpp
index 93922d94..d8b54815 100644
--- a/src/gui/item/text.hpp
+++ b/src/gui/item/text.hpp
@@ -45,6 +45,7 @@ public:
     void setMode(Mode mode);
 
     [[nodiscard]] qsizetype getIndexFromCursor(QPointF cursor) const;
+    [[nodiscard]] QString getLink(QPointF cursorPos) const;
     void setCaret(QPointF cursorPos);
 
     [[nodiscard]] QString text() const;
diff --git a/src/widgets/tools/texttool.cpp b/src/widgets/tools/texttool.cpp
index ca4d4092..3ee3e0f9 100644
--- a/src/widgets/tools/texttool.cpp
+++ b/src/widgets/tools/texttool.cpp
@@ -4,6 +4,7 @@
 
 #include "texttool.hpp"
 
+#include <KLocalizedString>
 #include <memory>
 
 #include "canvas/canvas.hpp"
@@ -24,11 +25,13 @@
 #include "keybindings/keybindmanager.hpp"
 #include "properties/widgets/propertymanager.hpp"
 #include <QClipboard>
+#include <QDesktopServices>
 #include <QGuiApplication>
 #include <QInputMethod>
 #include <QTextBlock>
 #include <QTextCharFormat>
 #include <QTextLayout>
+#include <QToolTip>
 
 using namespace Qt::Literals::StringLiterals;
 
@@ -82,6 +85,13 @@ void TextTool::mousePressed(ApplicationContext *context)
             }
         } else {
             const auto clickedItem = std::dynamic_pointer_cast<TextItem>(intersectingItems.back());
+
+            const QString link = clickedItem->getLink(worldPos);
+            if (!link.isEmpty() && (uiContext->appEvent()->modifiers() & Qt::ControlModifier)) {
+                QDesktopServices::openUrl(QUrl(link));
+                return;
+            }
+
             if (m_curItem != nullptr && m_curItem != clickedItem) {
                 m_curItem->setMode(TextItem::Mode::Normal);
                 renderingContext->cacheGrid().markDirty(transformer.worldToGrid(m_curItem->boundingBox()).toRect());
@@ -126,15 +136,25 @@ void TextTool::mouseMoved(ApplicationContext *context)
 
     if (m_isSelecting) {
         renderingContext->canvas()->setCursor(Qt::IBeamCursor);
+        QToolTip::hideText();
     } else {
         const QList<std::shared_ptr<Item>> intersectingItems{quadTree.queryItems(worldPos, [](const std::shared_ptr<Item> &item, const QPointF &point) {
             return item->formType() == Item::FormType::Text && !item->locked() && item->boundingBox().contains(point);
         })};
 
         if (!intersectingItems.empty()) {
-            renderingContext->canvas()->setCursor(Qt::IBeamCursor);
+            const auto item = std::dynamic_pointer_cast<TextItem>(intersectingItems.back());
+            const QString link = item->getLink(worldPos);
+            if (!link.isEmpty()) {
+                renderingContext->canvas()->setCursor(Qt::PointingHandCursor);
+                QToolTip::showText(QCursor::pos(), i18n("ctrl + click to open: %1", link), renderingContext->canvas());
+            } else {
+                renderingContext->canvas()->setCursor(Qt::IBeamCursor);
+                QToolTip::hideText();
+            }
         } else {
             renderingContext->canvas()->setCursor(Qt::CrossCursor);
+            QToolTip::hideText();
         }
     }
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.