4

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.

Kuba hasn't forgotten Monica
  • 95,931
  • 16
  • 151
  • 313
user2760608
  • 141
  • 2

1 Answers1

2

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();
}
Kuba hasn't forgotten Monica
  • 95,931
  • 16
  • 151
  • 313