PDA

View Full Version : How to write rich text at a specific position given a const QPainter object?



TriKri
7th October 2016, 17:07
I want to create a class method for drawing rich text at a specific position with a specific QPainter object:


void MyWidget::drawRichText(QString html, QPointF position, const QPainter* qp);

How do I implement this method? The solution I was able to come up with by searching on Google was the following:



void MyWidget::drawRichText(QString html, QPointF position, QPainter* qp)
{
QTextDocument td;
td.setHtml(html);
qp->translate(position); // Move the painter to the desired location
td.drawContents(qp);
qp->translate(-position); // Move the painter back to where it was before
}


which I already find quite distasteful as I make changes to a painter object which may be used after me (which is why I discretely move the painter back after I have used it, but I realize that the more changes I make to it that I don't want to be permanent, the greater the risk is that I will forget to change some of them back). I cannot make a personal copy of it either, since QPainter uses the Q_DISABLE_COPY macro.

Actually, the proposed solution won't even work in my case since I need to pass qp as const, and the translate method cannot be called on const QPainter objects.

(Besides, I realized that if someone has already moved the pointer before me, the pointer will move to the wrong location. But maybe that's a secondary issue. I don't know. Anyway...)

So, how do I implement this function assuming that the QPainter object is declared const (and is located at (0, 0) when the method is called)?

anda_skoa
7th October 2016, 18:18
which I already find quite distasteful as I make changes to a painter object which may be used after me (which is why I discretely move the painter back after I have used it, but I realize that the more changes I make to it that I don't want to be permanent, the greater the risk is that I will forget to change some of them back).


Moving a painter and then moving it back is a common construct, nothing distasteful about it.
If a method changes more then it can save() and restore() the painter, but that is also more "expensive".



Actually, the proposed solution won't even work in my case since I need to pass qp as const, and the translate method cannot be called on const QPainter objects.


A const painter wouldn't make sense, unless it is not needed for drawing and only as a reference for, e.g. what pen or font is being used in drawing operations elsewhere.



(Besides, I realized that if someone has already moved the pointer before me, the pointer will move to the wrong location. But maybe that's a secondary issue. I don't know. Anyway...)

Then the other method hasn't cleaned up correctly or hasn't called the method correctly.



So, how do I implement this function assuming that the QPainter object is declared const (and is located at (0, 0) when the method is called)?
That's what you have now, no?

Cheers,
_

Camryn64
8th October 2016, 01:19
Moving a painter and then moving it back is a common construct, nothing distasteful about it.
If a method changes more then it can save() and restore() the painter, but that is also more "expensive".



A const painter wouldn't make sense, unless it is not needed for drawing and only as a reference for, e.g. what pen or font is being used in drawing operations elsewhere.


Then the other method hasn't cleaned up correctly or hasn't called the method correctly.


That's what you have now, no?

Cheers,
_

nice.. it is,,, thanks for post

TriKri
10th October 2016, 10:10
Moving a painter and then moving it back is a common construct, nothing distasteful about it.

That may be your opinion, but I don't agree. If you have to clean after yourself it becomes easy to make a mistake as you may forget to do so. If you have made many changes that you need to undo, the risk of making at least some mistake starts to become quite high. It should be easy to write bug-free code and hard to make a mistake. In a high-level language, as much cleaning as possible should be abstracted away. Why isn't there for example a method for drawing at a specific location that moves the painter back and forth for you so you don't have to do that yourself? Or why isn't it possible to make a copy of the painter? That would make a lot of cleaning unnecessary. What is the justification for making QPainter uncopiable?


If a method changes more then it can save() and restore() the painter, but that is also more "expensive".

That may be a useful strategy. (Still feel a copy would have been better in this case, though.) Why aren't the methods called push and pop? That would make it more clear what they actually do. I at first thought that you could only have one state saved at a time and that you would write over the old saved state if you called save twice.


Then the other method hasn't cleaned up correctly or hasn't called the method correctly.

See what I mean? :P I want to avoid the risk completely of getting in that situation.


A const painter wouldn't make sense, unless it is not needed for drawing and only as a reference for, e.g. what pen or font is being used in drawing operations elsewhere.

I didn't think the painter got modified when I painted with it. Why does it do that? I guess I cannot declare it const then.

anda_skoa
10th October 2016, 11:56
That may be your opinion, but I don't agree. If you have to clean after yourself it becomes easy to make a mistake as you may forget to do so. If you have made many changes that you need to undo, the risk of making at least some mistake starts to become quite high. It should be easy to write bug-free code and hard to make a mistake. In a high-level language, as much cleaning as possible should be abstracted away. Why isn't there for example a method for drawing at a specific location that moves the painter back and forth for you so you don't have to do that yourself? Or why isn't it possible to make a copy of the painter? That would make a lot of cleaning unnecessary. What is the justification for making QPainter uncopiable?

In C++ one of the core principles is to not prematurely pessimise, e.g. not cleary newly allocated memory by default, and it is always possible to add more convenience but almost never possible to remove default overhead.

Automatically saving and restoring the state would incur the cost of doing that even when it is not necessary, e.g. when the code is aware of the state changes and changes state as required.
A full save/restore is costly, so code that only changes, e.g. the pen, can simply keep the old pen and set that again instead of incuring a full save/restore overhead.

If you want to have a fully scoped based save/restore, you could easily do that with a RAII class, e.g. like QMutexLocker does for QMutex lock/unlock or QScopedValueRollback does for single values.

You could even consider contributing such a "QScopedPainterState" to Qt.

All drawing methods, i.e. the QPainter API, actually draw at a specified location, because the location is either inherent in what is being drawn, e.g. coordinates of a rectangle, or passed as an argument, e.g. for text.

I guess one could have added overloads to QTextDocument, but since there are many types of transformations one would either need a lot of overloads or one that takes a QTransform object.
The assumption probably was that the code using the text document would know best where to draw.

My guess on the painter not being copyable is to avoid developers assuming that these copies would be independent, while they would actually be more like shared pointers.
I.e. these copies, if that were possible, would all work on the same paint device, and to work properly each copy would have to go through a full save and restore for each call.
Not sure developers would understand that something as apparently simple as creating a copy would increase the overhead of each draw call or would require extra code to make a copy "current".



That may be a useful strategy. (Still feel a copy would have been better in this case, though.) Why aren't the methods called push and pop?

Yes, I guess they could have been called pushState() and popState(), just push/pop would not have enough meaning in the context of drawing.


I at first thought that you could only have one state saved at a time and that you would write over the old saved state if you called save twice.

Right, fortunately the behavior is documented.



See what I mean? :P I want to avoid the risk completely of getting in that situation.

As pointed out above you can always do that and even specifically in a way that only saves/restores things you change, only incurring the necessary cost and not more.



I didn't think the painter got modified when I painted with it. Why does it do that? I guess I cannot declare it const then.
As you said yourself, a const would indicate that nothing changes, while of course there is change, e.g. pixels in an image change for draw calls, state changes for setter or transformation calls.

Would be highly confusing if you passed a const painter and suddenly end up with different content in the thing you are painting on, wouldn't it?

Cheers,
_

TriKri
10th October 2016, 14:42
In C++ one of the core principles is to not prematurely pessimise, e.g. not cleary newly allocated memory by default, and it is always possible to add more convenience but almost never possible to remove default overhead.

Hm, that makes sense, better getting the constructs right the first time than getting them wrong and having to live with them forever.


Not sure developers would understand that something as apparently simple as creating a copy would increase the overhead of each draw call or would require extra code to make a copy "current".

Of course, that's a good point.


You could even consider contributing such a "QScopedPainterState" to Qt.

If I ever write such a wrapper, I will consider contributing with it. ;)


Would be highly confusing if you passed a const painter and suddenly end up with different content in the thing you are painting on, wouldn't it?

Not at all – what should get modified is the thing you paint on, and I don't see why the thing you paint with should be modified?

anda_skoa
10th October 2016, 18:03
Hm, that makes sense, better getting the constructs right the first time than getting them wrong and having to live with them forever.

And no API is perfect, sometimes less then ideal API is kept as changing it would otherwise have massive impact on existing code.

Your idea of using instance copying for keeping states is quite good IMHO from a readability point of view, though it might also require a different approach in savind/restoring state then :)




If I ever write such a wrapper, I will consider contributing with it. ;)

Excellent :-)
I've never actually considered that until you brought it up.

It is small improvements like this that allow a framework to evolve rather than to stagnate.



Not at all – what should get modified is the thing you paint on, and I don't see why the thing you paint with should be modified?
Theoretically true, the paint device is held by pointer and the pointer value doesn't change so the QPainter method could call non-const paint device methods.

Maybe there are even compilers which generated warnings for this or it was done for consistency (methods starting with an imperative verb usually do change the state of the object they are invoked on)

Cheers,
_