[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();
}
}