Search code examples
qtuser-interfaceqmlpysidepyqt6

How to select text in QtPdf View (QML)


I am developing an application in Qt with Quick and some QML.

I need to interact with the contents of a pdf file opened inside of QPdfMultipageView. More specifically, I need the text to be selectable with mouse. Currently, there is no clear example in Qt documentation of how to achieve this with an already pre-defined view, such as PdfMultipageView.

ApplicationWindow {
    id: root
    width: 800
    height: 1024
    color: "darkgrey"
    title: doc.title
    visible: true
    property string source // for main.cpp

    PdfMultiPageView {
        id: view
        anchors.fill: parent
        document: doc
    }

    PdfDocument {
        id: doc
        source: Qt.resolvedUrl(root.source)
    }


    PdfSelection {
        id: selection
        document: doc
        from: textSelectionDrag.centroid.pressPosition
        to: textSelectionDrag.centroid.position
        hold: !textSelectionDrag.active
    }

    Shape {
        ShapePath {
            PathMultiline {
                paths: selection.geometry
            }
        }
    }

    DragHandler {
        id: textSelectionDrag
        acceptedDevices: PointerDevice.Mouse | PointerDevice.Stylus
        target: null
}


In docs, under the PdfSelection type description, there is an example, but it does not include view, only the document.

In my case, PdfMultipageView handles the input in first place, hence not letting it pass to the DragHandler, which, in turn, should handle and update text selection. Also, there is no property in the view related to its ability to handle input, so I cannot tell it to not handle the input - it is always interactive and I cannot change this.

Any help would be greatly appreciated.


Solution

  • Solution

    I've found a way to achieve desired functionality.

    I put the PdfMultipageView under the MouseArea and placed the mouse area on top of the PdfMultipageView by specifying z: -1 for the PdfMultipageView component. MouseArea has handlers for different signals, but the important ones are onPressed, onPositionChanged and onReleased.

    In the onPressed handler I set the accepted flag of the incoming mouse event to true, so this event is not passed further, thus pdfView won't consume it and will not move. Also I set the textSelection.from point to the mouse press position.

    In the onPositionChanged handler (active when mouse is being moved and left key is pressed) the textSelection.to is being constantly updated until mouse button is released.

    Code

    ApplicationWindow{
        id: root
        width: 800
        height: 1024
        color: "darkgrey"
        title: doc.title
        visible: true
        required property url source // for main.py
        property real scaleStep: Math.sqrt(2)
    
        PdfDocument{
            id: doc
            source: Qt.resolvedUrl(root.source)
        }
    
        Shape {
            z: 1
            opacity: 0.3
            ShapePath {
                strokeWidth: 0
                fillColor: "blue"
                PathMultiline {
                    paths: textSelection.geometry
                }
            }
        }
    
        PdfSelection {
            id: textSelection
            anchors.fill: parent
            from: "0,0"
            to: "0,0"
            document: doc
            hold: !pdfMouseArea.dragIsActive
        }
        
        MouseArea {
            id: pdfMouseArea
            anchors.fill: parent
    
            property bool dragIsActive: false
    
            PdfMultiPageView {
                z: -1
                id: pdfView
                anchors.fill: parent
                anchors.leftMargin: -8
                document: doc
                Component.onCompleted: {
                    //pdfView.scaleToPage(parent.width, parent.height);
                    textSelection.renderScale = pdfView.renderScale;
                }
            }
    
    
            onPressed: (event) => {
                pdfMouseArea.dragIsActive = true;
                textSelection.from = textSelection.to = event.x + "," + event.y;
                console.log("MouseArea: onPressed");
            }
            
            onPositionChanged: (event) => {
                event.accepted = true;
                console.log("PosChangeSignalCoords: ", event.x, event.y);
                textSelection.to = event.x + "," + event.y;
                console.log("PosChangeSignalTextSelectionCoords: From: ", 
                             textSelection.from, " To: ", textSelection.to);
                cursorShape = Qt.IBeamCursor;
                console.log(textSelection.text);
            }
    
            onReleased: (event) => {
                event.accepted = true; 
                console.log("MouseArea: onReleased");
                cursorShape = Qt.ArrowCursor;
                pdfMouseArea.dragIsActive = false;
            }
        }
    }
    

    Bad news :(

    However, it is still very buggy since by default selection and its path are not linked to the page where the text content is. So the coordinates of the selection path don't consider any margins of the PdfMultipageView. In case of scrolling through pages the selection path is still being drawn at the same place it was previously drawn, like it is living on a separate layer. This logic needs to be implemented manually, unfortunately.