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.
-
-
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 widgetforeach (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.
-
-
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.
- repainting issue: I think you can solve the problem like this in your QTextEdit:
-
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.
-
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.