QT paintEvent for a QWidget - qt

I've a class that inherits QPushButton widget. I want to have custom look of that button so i've overrided paintEvent method. All buttons that I want to paint are childs of QFrame object.
And there I have a problem. I can't repaint those objects.
My paintEvent function:
void Machine::paintEvent(QPaintEvent*) {
QPainter painter(this);
QRect geo = this->geometry();
int x, y, width, height;
x = geo.x()-10;
y = geo.y()-10;
width = geo.width()-3;
height = geo.height()-5;
painter.fillRect(x, y, width, height, QColor(220,220,220));
painter.drawText(x+10, y+10, "Machine " + QString::number(id));
}
When a widget is in top left corner of QFrame, desired effect is ok. But when I move button somewhere else, widget starts to disappear. On images you can see whats going on:
Button is just moved some px down-left. Why it works like this? QFrame which is a container for that button is big enough.
Thanks in advance ;)

The reason is in coordinate system: geometry method returns position relatively to parent, but QPainter::drawRect accepts rectangle in local coordinates. Try this code:
void Machine::paintEvent(QPaintEvent*) {
QPainter painter(this);
int width = size().width() - 3;
int height = size().height() - 5;
painter.fillRect(0, 0, width, height, QColor(220,220,220));
painter.drawText(10, 10, "Machine " + QString::number(id));
}

Related

Why does the border of QWidget cover the contents?

I have a custom widget derived from QWidget, which has a minimumSize of (30, 30) and a QLabel as a childWidget:
MyWidget::MyWidget (QWidget *parent, QPoint p,
QWidget *childWidget) : QWidget (parent)
{
childWidget = this->childWidget;
setAttribute (Qt::WA_DeleteOnClose);
this->move (p);
verticalLayout = new QVBoxLayout (this);
if (childWidget != NULL)
{
childWidget->setParent (this);
childWidget->releaseMouse();
childWidget->setAttribute (Qt::WA_TransparentForMouseEvents, true);
verticalLayout->addWidget (childWidget);
}
this->setLayout(verticalLayout);
};
MyWidget::mouseMoveEvent (QMouseEvent *e)
{
if (! (e->button() == Qt::RightButton))
{
this->update();
this->raise();
}
}
void MyWidget::mouseReleaseEvent (QMouseEvent *evt)
{
QWidget::mouseReleaseEvent(evt);
this->update();
}
MyWidget::mousePressEvent (QMouseEvent *e)
{
if (! (e->button() == Qt::RightButton))
{
this->update();
this->raise();
}
}
void MyWidget::paintEvent(QPaintEvent *)
{
QPainter painter(this);
painter.setRenderHint(QPainter::Antialiasing);
painter.setPen(Qt::darkGreen);
painter.drawRect(1, 2, 6, 4);
painter.setPen(Qt::darkGray);
painter.drawLine(2, 8, 6, 2);
}
//And some getter/setter methods.
In order to set a border to the widget I use the following code:
customWidget->setStyleSheet("*{border-width:" +
2 +
";border-style:solid;border-color:" +
#FFFFFF + " ;color:white;}");
It looks like this (the parent widget has an orange background):
.
When I change the border-width to 10, the border covers the contents:
Both images show the widget in its minimum height.
To me it looks as if the border were applied inwards. What shall I modify to point the border outwards, so for a larger border-width the text remains visible?
Cause
The border does go outwards:
You have a problem with the size. (30, 30) is too small for this border. 30 - 2*10 (the minimum height - 2 times the width of the border) equals 10. Your font is larger than 10px, so it does not fit in the remaining space.
Solution
You might want to set a reasonable size, e.g. (100, 50). However, setting the minimum size is not flexible, meaning, that it does not account for changes in the widget's content. If the sizeHint and minimumSizeHint are implemented though, the necessary space will be reported whenever needed, as it is done in QLabel for example.
Since you already have a QLabel as a child widget, just avoid setting the minimumSize of your custom widget and the correct size will be calculated automatically.

How to size the texture to occupy only a portion of a QQuickItem UI

I have overriden updatePaintNode in the following way to draw an OpenGL texture on a QQuickItem derived class called MyQQuickItem here.
QSGNode *MyQQuickItem::updatePaintNode(QSGNode * oldNode, QQuickItem::UpdatePaintNodeData * /*updatePaintNodeData*/)
{
QSGSimpleTextureNode * textureNode = static_cast<QSGSimpleTextureNode *>(oldNode);
if (!textureNode) {
textureNode = new QSGSimpleTextureNode();
}
QSize size(800, 800);
// myTextureId is a GLuint here
textureNode.reset(window()->createTextureFromId(myTextureId, size));
textureNode->setTexture(my_texture);
textureNode->markDirty(QSGBasicGeometryNode::DirtyMaterial);
QSizeF myiewport = boundingRect().size();
qreal xOffset = 0;
qreal yOffset = 10;
textureNode->setRect(xOffset, yOffset, myViewport.width(), myViewport.height());
return textureNode;
}
This renders the texture content well but covers the whole of my MyQQuickItem UI.
How can reduce the bottom margin of the texture to say fit 80% of the height of MyQQuickItem.
I want to render the texture to a portion of MyQQuickItem & leave the rest blank or black? Is that possible within updatePaintNode.
Note that the texture size is not the UI window size here. My texture size is 800 by 800. Whereas the UI window size is different and depends on the screen.
I found the answer to this:
Changing myViewport.height() gives the required end in Y direction one wishes to set. Similarly, changing myViewport.width() gives the required end in X direction one wishes to set.
4 parameters in TextureNode's setRect can stretch & fit the texture in the way one wishes within a portion of the QQuickItem.

How can I show/hide background drawing on QGraphicsScene or QGraphicsView?

I would like to have certain things drawn on QGraphicsScene, but not be QGraphicsItem (it would interfere with the processing of the QGraphicsItem collection).
Example: a scene bounding rectangle, a grid
I am overriding the drawBackground(QPainter *painter, const QRectF &rect) for that purpose. (I should subclass the scene... )
void MyView::showHideBounds()
{
m_showBackgroundBounds = !m_showBackgroundBounds;
// can't triger an update ???
update(); // neither does anything
viewport()->update();
}
void MyView::drawBackground(QPainter *painter, const QRectF &rect)
{
QPen pen;
if(m_showBackgroundBounds)
pen = QPen(QColor(0, 0, 0), 10, Qt::PenStyle(Qt::SolidLine));
else
pen = QPen(QColor(255, 255, 255), 10, Qt::PenStyle(Qt::SolidLine));
painter->setPen(pen);
painter->drawRect(QRect(QPoint(-scene()->sceneRect().size().toSize().width()/2,
-scene()->sceneRect().size().toSize().height()/2),
scene()->sceneRect().size().toSize()));
}
I would like the option to show/hide either the bounding rectangle or the grid.
The only thing I can think of is paint over them with the color of the background brush ? Is there any other option ?
As I have written it above, it works - except I need user action on items (or a zoom or some other scene changing action) to trigger refresh, or call an update... (the function showHideBounds doesn't - not sure how to make it force a refresh)
I would call the drawBackground from the showHideBounds function - but I don't know how to get the painter
[Also, the drawBackground seems to be drawn automatically... how can I give it the rect argument it needs ? (it seems if I draw the rect it does draw the scene rectangle but I only see the right and bottom edges)]
In order to redraw a particular section of scene, you can call
QGraphicsScene->invalidate(rect_to_redraw, Backgroundlayer)
Note that if drawBackground(*painter, rect) paints over area outside rect, it will not update automatically. In that case invalidate has to be called with appropriate rect parameters.

Shift `QGraphicsTextItem` position relative to the center of the text?

I have a number of classes that inherit from QGraphicsItem, that get to be arranged in a certain way. For simplicity of calculations, I made the scenes, and items, centered in (0, 0) (with the boundingRect() having +/- coordinates).
QGraphicsTextItem subclass defies me, its pos() is relative to top left point.
I have tried a number of things to shift it so it centers in the text center (for example, the suggested solution here - the code referenced actually cuts my text and only shows the bottom left quarter).
I imagined that the solution should be something simple, like
void TextItem::paint(QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget)
{
painter->translate( -boundingRect().width()/2.0, -boundingRect().height()/2.0 );
QGraphicsTextItem::paint(painter, option, widget );
}
the above "sort of" works - but as I increase the item scale -> increase the font, the displayed item is cut off...
I tried to set the pos() - but the problem is, I still need to track the actual position on the scene, so I cannot just replace it.
A slightly unpleasant side effect - centering the QGraphicsView on the element does not work either.
How can I make my QGraphicsTextItem show its position relative to the center of the text ?
Edit: one of the experiments of changing the boundingRect():
QRectF TextItem::boundingRect() const
{
QRectF rect = QGraphicsTextItem::boundingRect();
rect.translate(QPointF(-rect.width()/2.0, -rect.height()/2.0));
return rect;
}
I had to shift the initial position, as well as the resize, to trigger a new position - I was unable to do it in paint() because, as I thought from the start, any repaint would continuously recalculate the position.
Only the initial position needs to be adjusted - but as the font size (or style...) changes, its bounding rectangle also changes, so the position must be recalculated - based on previous position.
In the constructor,
setPos(- boundingRect().width()/2, - boundingRect().height()/2);
in the function that modifies item (font) size,
void TextItem::setSize(int s)
{
QRectF oldRect = boundingRect();
QFont f;
f.setPointSize(s);
setFont(f);
if(m_scale != s)
{
m_scale = s;
qreal x = pos().x() - boundingRect().width()/2.0 + oldRect.width()/2.0;
qreal y = pos().y() - boundingRect().height()/2.0 + oldRect.height()/2.0;
setPos(QPointF(x, y));
}
}

How to move a QGraphicsItem when scrolling the QGraphicsView?

I have a QGraphicsScene and a QGraphicsView that displays it. I need to add a QGraphicsItem to the scene and keep it at the same position even when I scroll the view. I tried overriding view's scrollContentsBy() method as follows but it didn't do the trick.
void FETimelineView::scrollContentsBy( int dx, int dy )
{
QGraphicsView::scrollContentsBy(dx, dy);
QRectF oRect = p_CatBar->rect();
p_CatBar->setPos( oRect.x() + dx, oRect.y() + dy );
}
Btw, FETimelineView is my QGraphicsView and p_CatBar is of type QGraphicsItem. Please help, thanks in advance.
Rather than moving it by the scrolled amount, You can get the position that you want it to be relative to the view and then set it directly according to that. So it would be something like this: -
// Assuming that the Graphics Item top left needs to be at 50,50
// and has a width and height of 30,20
void FETimelineView::scrollContentsBy( int dx, int dy )
{
QGraphicsView::scrollContentsBy(dx, dy);
// get the item's view position in scene coordinates
QRect scenePos = mapToScene(QRect(50, 50, 30, 20));
p_CatBar->setPos(scenePos);
}
I think the easier way it's actually the inverse of what you ask: try setting the flag ItemIgnoresTransformations in QGraphicsItem::GraphicsItemFlags.
There are other flags that could help you, see the docs.

Resources