Search code examples
htmlcssqthyperlinkqml

How to make links within a QML `Text` element show pointing hand mouse cursor when hovering?


In a web browser, when you hover the mouse over a link it changes to the pointing hand. I want to achieve the same in QML.

Here is a QML Text element containing a block of text, using HTML markup, that includes a couple of links:

Text { id: bodyMessage
   anchors.fill: parent
   textFormat: Text.RichText
   wrapMode: Text.WordWrap

   text:
      '<p>' + qsTr('Here is my first paragraph.') + '</p>' +
      '<p>' + qsTr('My second paragraph contains the <a href="link1">first link</a>.') + '</p>' +
      '<p>' + qsTr('My third paragraph contains the <a href="link2">second link</a> and that is it.') + '</p>'

   onLinkActivated:
      (link) =>
      console.log('Link activated to: `' + link + '`')
}

The problem is that the mouse cursor does not change to a pointing hand when it is over either link.

I tried a child MouseArea as follows:

   MouseArea {
      anchors.fill: parent
      hoverEnabled: true

      onPositionChanged:
         (mouse) =>
         cursorShape = bodyMessage.linkAt(mouse.x, mouse.y) ? Qt.PointingHandCursor : Qt.ArrowCursor
   }

This does get the mouse cursor to change as desired, but it blocks mouse signals to Text, so onLinkActivated no longer works.

To solve that new problem, I added the following to MouseArea:

                onClicked:
                    (mouse) =>
                    {
                        let link = bodyMessage.linkAt(mouse.x, mouse.y)
                        if (link.length > 0)
                            console.log('Link activated to: `' + link + '`')
                    }

While this does work, it renders Text.linkActivated obsolete, which seems to me the wrong approach and the code looks bloated to me.


Solution

  • Consider adding HoverHandler to your Text:

    Text {
        HoverHandler {
            enabled: parent.hoveredLink
            cursorShape: Qt.PointingHandCursor
        }
    }
    

    If we wish to control the color of the link when it's hovered, not hovered, we use a conditional on hoveredLink:

    Text {
        text: qsTr('<style>a:link{color:%1;}</style>My second paragraph contains the <a href="https://stackoverflow.com"> StackOverflow link</a>.').arg(hoveredLink ? "purple" : "red")
    }
    

    Here's a full working example:

    import QtQuick
    import QtQuick.Controls
    import QtQuick.Layouts
    Page {
        title: "HoverHandler Demo"
        ColumnLayout {
            width: parent.width
            spacing: 10
    
            Frame { Label { text: "Initial Solution"  } }
    
            LinkText {
                text: `
    <p>${qsTr('Here is my first paragraph.')}</p>
    <p>${qsTr('My second paragraph contains the <a href="link1">first link</a>.')}</p>
    <p>${qsTr('My third paragraph contains the <a href="link2">second link</a> and that is it.')}</p>
    
    `
            }
    
            Frame { Label { text: "Revised Solution" } }
    
            LinkText { text: qsTr('Here is my first paragraph.') }
            LinkText { text: qsTr('<style>a:link{color:%1;}</style>My second paragraph contains the <a href="https://stackoverflow.com"> StackOverflow link</a>.').arg(hoveredLink ? "purple" : "red") }
            LinkText { text: qsTr('<style>a:link{color:%1;}</style>My third paragraph contains the <a href="https://www.google.com" style="{link:red}">Google link</a> and that is it.').arg(hoveredLink ? "purple" : "red") }
        }
    }
    
    // LinkText.qml
    import QtQuick
    import QtQuick.Controls
    import QtQuick.Layouts
    Text {
        Layout.fillWidth: true
        textFormat: Text.RichText
        wrapMode: Text.WordWrap
    
        HoverHandler {
            enabled: parent.hoveredLink
            cursorShape: Qt.PointingHandCursor
        }
            
        onLinkActivated: {
            console.log('Link activated to: `' + link + '`');
            Qt.openUrlExternally(link);
        }
    }
    

    You can Try it Online!

    References: