QTextBrowser - how to identify image from mouse click position

1k views Asked by At

I'm using a QTextBrowser to display rich text including a number of images, each of them specified with a HTML <img> tag and added as resources using QTextDocument::addResource().

What I'd like to be able to do is, in a context menu handler (i.e. with a mouse click position available), identify the image that the click was over. It's possible to tell whether the click is over an image, because cursorForPosition(event->pos()).block().text() returns a string starting with Unicode 0xFFFC. Unfortunately the same string is returned for every image in the view.

It's possible to get all of the formats in use with QTextDocument::allFormats(), identify which of those are image formats, and get their image resource name. Unfortunately there seems to be no way to get their actual display position or bounding rectangle.

1

There are 1 answers

0
Kuba hasn't forgotten Monica On

From the documentation:

Inline images are represented by an object replacement character (0xFFFC in Unicode) which has an associated QTextImageFormat. The image format specifies a name with setName() that is used to locate the image.

You can use charFormat().toImageFormat().name() on the cursor to extract the image's URL. Below is a self-contained example. There are two noteworthy details:

  1. The cursor will sometimes point one character prior to the image. Thus the workaround; it seems necessary for both Qt 4.8.5 and 5.1.1.

  2. The pop-up menus should be shown asynchronously so as not to block the rest of the application. The example code provided in the documentation is a source of bad user experience and should be considered an evil abomination. All widgets can automatically delete themselves when they get closed, so the menus won't leak. A QPointer is used only to demonstrate this fact. It tracks the menu's lifetime and nulls itself when the menu deletes itself.

#include <QApplication>
#include <QTextBrowser>
#include <QImage>
#include <QPainter>
#include <QMenu>
#include <QContextMenuEvent>
#include <QTextBlock>
#include <QPointer>
#include <QDebug>

class Browser : public QTextBrowser
{
    QPointer<QMenu> m_menu;
protected:
    void contextMenuEvent(QContextMenuEvent *ev) {
        Q_ASSERT(m_menu.isNull()); // make sure the menus aren't leaking
        m_menu = createStandardContextMenu();
        QTextCursor cur = cursorForPosition(ev->pos());
        QTextCharFormat fmt = cur.charFormat();
        qDebug() << "position in block" << cur.positionInBlock()
                 << "object type" << cur.charFormat().objectType();
        if (fmt.objectType() == QTextFormat::NoObject) {
            // workaround, sometimes the cursor will point one object to the left of the image
            cur.movePosition(QTextCursor::NextCharacter);
            fmt = cur.charFormat();
        }
        if (fmt.isImageFormat()) {
            QTextImageFormat ifmt = fmt.toImageFormat();
            m_menu->addAction(QString("Image URL: %1").arg(ifmt.name()));
        }
        m_menu->move(ev->globalPos());
        m_menu->setAttribute(Qt::WA_DeleteOnClose); // the menu won't leak
        m_menu->show(); // show the menu asynchronously so as not to block the application
    }
};

void addImage(QTextDocument * doc, const QString & url) {
    QImage img(100, 100, QImage::Format_ARGB32_Premultiplied);
    img.fill(Qt::white);
    QPainter p(&img);
    p.drawRect(0, 0, 99, 99);
    p.drawText(img.rect(), url);
    doc->addResource(QTextDocument::ImageResource, QUrl(url), img);
}

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    QTextDocument doc;
    Browser browser;
    doc.setHtml("<img src=\"data://image1\"/><br/><img src=\"data://image2\"/>");
    addImage(&doc, "data://image1");
    addImage(&doc, "data://image2");
    browser.show();
    browser.setDocument(&doc);
    return a.exec();
}