Global image position inside a QTextEdit



  • Hi.
    I'm working on a WYSIWYG-Editor which contain images and Text. Now I want to resize an image through the mouse. Through the QTextImageFormat i can resize my image. To solve the task i need the global position of an image-edge. Does anybody can help me find thuch a global position?



  • Is "QCursor::pos() ":http://doc.qt.nokia.com/4.7/qcursor.html#pos what you need? Maybe you'll have to use "QWidget::mapFromGlobal() ":http://doc.qt.nokia.com/4.7/qwidget.html#mapFromGlobal to translate between global and widget-local coordinates.



  • no. QCursor::pos() is the mouse position. I need the position of my image insiede the QTextEdit. Hopefully in some screen position. At the moment i only get the position in my QTextDocument, but thats only the positon of the x char in my document.



  • As far as I know, there is no public API for that - unfortunately :-/



  • okay. thanks for your time



  • Now don't know how to programm my WYSIWYG-Texteditor. Maybe you can give me a Hint. I want to design a Texteditor with the possibility to resize images through the mouse.



  • Sorry, I don't have any ideas for that - maybe someone else?



  • It's a very intersting question and I want to show you a possible solution I've invented.

    So the problem is that wen you insert an image into a QTextDocument you don't have an object to manipulate that image, like change size, detect mouse events on it and so on. We start with introducing such an object.

    @
    class MyTextEditAdvancedObject : public QObject
    {
    Q_OBJECT
    public:
    explicit MyTextEditAdvancedObject(QObject *parent = 0);

    void setRect(const QRectF &_rect)
    {
        m_rect=_rect;
    }
    
    const QRectF &rect()
    {
        return m_rect;
    }
    
    void paint(QPainter &painter);
    QSizeF getSize();
    void pressed();
    

    private:
    QRectF m_rect;

    };

    @

    It's just an example, you can implement the class in an abstract fashion and then make a whole hierarchy of such classes, that can be inserted into QTextEdit. Methods of the class:

    • paint - paints whatever you want yo be painte
    • getSize - returns the size of your object
    • pressed - for demonstration, the code inside is executed whenever a user presses a mouse button over an object
    • rect setter and getter are used to keep object position up-to-date

    Then we have to insert this somehow in a QTextEdit. Qt has a special QTextObjectInterface class to implement custom objects. We can do like this:

    @
    class MyImageTextObject : public QObject, public QTextObjectInterface
    {
    Q_OBJECT
    Q_INTERFACES(QTextObjectInterface)

    public:
    MyImageTextObject();

    QSizeF intrinsicSize(QTextDocument *doc, int posInDocument,
                         const QTextFormat &format);
    void drawObject(QPainter *painter, const QRectF &rect, QTextDocument *doc,
                    int posInDocument, const QTextFormat &format);
    

    };
    @

    So this is just a declaration. We've got to methods to implement - one returning size and one painting the object. We can just delegate that to our class, but the question is, how to connect this interface and our object/objects?

    Let's have a look at how to add custom objects to QTextEdit. I've made a method for that:
    @
    void MyTextEdit::addAdvancedObject(MyTextEditAdvancedObject *advObject)
    {
    advObjects<<advObject;

    QObject *_advObject=advObject;
    QTextCharFormat myCharFormat;
    myCharFormat.setObjectType(MyTextEdit::MyTextFormat);
    

    myCharFormat.setProperty(MyTextEdit::pointerData,qVariantFromValue(_advObject));

    QTextCursor cursor = this->textCursor();
    cursor.insertText(QString(QChar::ObjectReplacementCharacter), myCharFormat);
    this->setTextCursor(cursor);
    }
    @

    We need to create a QTextCharFormat object, set it type and insert then. But the QTextCharFormat object is accessible from QTextObjectInterface methods! So we can add a dynamic property there, containing a pointer to our object. We have to cast it to QObject*, so it's not type-safe, but it works.

    After that implementing our custom interface can be done like that:
    @
    void MyImageTextObject::drawObject(QPainter *painter, const QRectF &rect,
    QTextDocument * /doc/, int pos,
    const QTextFormat &format)
    {

    MyTextEditAdvancedObject *advObject;
    advObject=qobject_cast<MyTextEditAdvancedObject*>(qVariantValue<QObject*>(format.property(MyTextEdit::pointerData)));
    advObject->setRect(rect);
    
    advObject->paint(*painter);
    

    }

    @

    The final thing is to reimplement QTextEdit's mousePressed method.
    @
    void MyTextEdit::mousePressEvent(QMouseEvent *e)
    {
    QPoint point=e->pos();
    bool hit=false;
    point.ry()+=verticalScrollBar()->value(); //adjusting coordinates as they are mapped to widget

    foreach (MyTextEditAdvancedObject *advObject,advObjects)
    {
        if (advObject->rect().contains(point))
        {
            advObject->pressed();       
            hit=true;
        }
    
    }
    if (!hit) QTextEdit::mousePressEvent(e);
    

    }
    @

    That's it! Now you can work with an object inside a QTextEdit like with a widget, or may be implement a real proxy for QWidget.



  • That's looks like an very interesting solution. But there are some Points ich don't understand:

    • advObjects in your addAdvancedObject Methode fit a QSet?
    • If it is a QSet, how do you track whether the MyImageTextObject are deleted in the Textfield?


  • a QList, but yes, that problem exists - we need to track if the special placeholder symbol is still in a QTextDocument. It's not a great problem, but some sort of a memory leak. As for me, I see several issues in this method:

    • deleting objects when the are deleted in QTextEdit
    • changing cursor - of course you want the cursor to be a hand or an arrow over the image (or maybe some resize cursors), but calling setCursor does nothing
    • how to save QTextDocument with such objects into a file
    • repainting issue - I tested the idea with selecting an image with a border after clicking on it. The problem was that the text cursor was put after that action after the image and only some area around it was repainted. Calling repaint() or update() explicitly didn't help.

    But we can work on that! You really would like to see Volker's and other pros' comments on that.



  • I look forward to a good collaboration. Here are some of my thoughts:

    • repainting issue: I think you can solve the problem like this in your QTextEdit:
      @this->viewport()->update();@
    • save issue: Maybe we can replace the QChar::ObjectReplacementCharacter with a Html-Image-Tag. Afterwards it is easy to save the whole document as a Html-page
    • cursor issue: Are you sure that somethink like this don't work?
      @QCursor cursor;
      cursor.setShape(Qt::SizeFDiagCursor);
      this->setCursor(cursor);@
    • deleting issue: The only way coming through my mind is to search the whole document for QChar::ObjectReplacementCharacter. That sounds not like an very pretty way.


  • Yeah, you are right, the viewport works! And the cursor thing also needed viewport (this->viewport()->setCursor())

    The problem with saving is still a huge one - we just don't have an inerface for that. Of course we can try some dirty tricks, like

    find all images

    replace them with some id tokens

    render to html

    replace token with <img>



  • I worked a little on resizing the image and i noticed that the Image get smaller but not the place it allocate in the document. I searcht a bit and find another way to solve the repainting issue:

    • track the document position like you did with the rect
    • mark the QChar::ObjectReplacementCharacter in your document from QTextEdit as "dirty":
      @this->document()->markContentsDirty(advObject->position(),1);@
      if you use more than one char as replacement you have to replace the "1" with the lenght of your replacment.


  • We have a new problem. MyTextEditAdvancedObject can't be copy or past at the moment.



  • Some time ago I have implemented a rubber band selection for an inside-editor image resizing. The complete idea I have described in the following "link":http://stackoverflow.com/questions/3720502/how-to-resize-an-image-in-a-qtextedit/25743848#25743848 .

    The following code might help you get what you want.
    @
    QRect MyTextEditDecorator::imageRect(const QTextCursor &cursor) const
    {
    QTextImageFormat fmt = cursor.charFormat().toImageFormat();
    if (!fmt.isValid())
    return QRect();
    if (!cursor.block().layout())
    return QRect();

    QRect cursorRect = textEdit()->cursorRect(cursor);
    QRect cursorFrameRect(cursorRect.topLeft(), QSize(fmt.width(), fmt.height()));
    
    QTextLayout *blockLayout = cursor.block().layout();
    QTextLine curLine = blockLayout->lineForTextPosition(cursor.positionInBlock());
    QRectF curLineRect = curLine.rect();
    curLineRect.moveTopLeft(blockLayout->position() + curLineRect.topLeft());
    
    int x = cursorRect.topLeft().x() - fmt.width();
    int y = curLineRect.y() + curLine.ascent() + (curLine.leadingIncluded() ? qMax(curLine.leading(), 0.0) : 0); //baseline
    y -= textEdit()->verticalScrollBar()->value();
    
    return QRect(x, y, fmt.width(), fmt.height());
    

    }
    @

    The above code does not take into account the image alignment(only baseline) and right to left text layout.
    Assume that the textEdit() function above, returns a pointer to the QTextEdit object. If you put the above function inside a QTextEdit derived class simply replace it with a "this" pointer.

    Hope it helps.


Log in to reply
 

Looks like your connection to Qt Forum was lost, please wait while we try to reconnect.