Creating shader program for QGraphicsItem - qt

I have a QGraphicsScene in which I add a QGraphicsItem. Inside the QGraphicsItem, I render the triangle used in the hello triangle example of OpenGL ES 2.0. The problem is that if I create and compile the shaders anyplace other than the QGraphicsItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) function, they do not get compiled. I know that you should have a GL rendering context active but doesn't that happen when I set my viewport to a GLWidget?? I tried various things like compiling them in the QGraphicsItem constructor or in the QGraphicsScene and setting the QGraphicsItem as a parent (which I learned they are very stupid things to do), but (obviously) nothing works. What seemed most logical to me was to create a initShaders() function inside the QGraphicsItem class and call it after the item is created in my scene, but that didn't work also.

Create a derived class of QGraphicsView. Override the setupViewport(QWidget *viewport) to initialize the shaders. This will allow you to ensure the context is current when compiling the shaders. However, it requires that the items be added to the scene before setViewport() is called on the graphics view.
void MyGraphicsView::setupViewport(QWidget *viewport)
{
QGLWidget *glWidget = qobject_cast<QGLWidget*>(viewport);
if (glWidget) {
glWidget->makeCurrent();
foreach (QGraphicsItem *item, scene()->items())
{
MyGraphicsShaderItem *glItem = qgraphicsitem_cast<MyGraphicsShaderItem*>(item);
if (glItem)
glItem->initShader();
}
glWidget->doneCurrent();
}
}

Related

Overriding QLabel to be able to draw graphs

I want to draw a graph on my main form, so I figured I'd use a QLabel and Override that. Like this:
// drawlabel.h
class DrawLabel : public QLabel
{
Q_OBJECT
public:
DrawLabel(QWidget *parent = 0);
private:
void paintEvent(QPaintEvent *);
};
// drawlabel.cpp
DrawLabel::DrawLabel(QWidget *parent)
: QLabel(parent)
{
}
void DrawLabel::paintEvent(QPaintEvent *)
{
qDebug() << "paint event" ;
QPainter painter(this);
painter.setPen(QPen(QBrush(QColor(0,0,0,180)),1,Qt::DashLine));
painter.setBrush(QBrush(QColor(255,255,255,120)));
QRect selectionRect(10, 10, 100, 101);
painter.drawRect(selectionRect);
}
On my main window I droppde a QLabel, sized it to about 500x200 and promoted it to DrawLLabel. When the application is run, a dashed square is drawn on the form.
All good so far.
If I add the line:
this->setText("123456");
into the DrawLabel constructor, or add it into the paintEvent() I don't see the text. I'd also like to be able to have a border around the DrawLabel, but
this->setFrameShape(QFrame::Box);
in the constructor doesn't work either.
What should I be doing to get these to work?
Well, I think you should call paintEvent of base class. Add parameter name e to method:
void DrawLabel::paintEvent(QPaintEvent *e)
And then at end of method add
QLabel::paintEvent (e);
The second option do all painting by yourself directly at paintEvent.
If you want something custom, then implement a custom widget inheriting QWidget. Then you get to draw whatever you want and have whatever members you want.
Your problem is you have overridden the label's paint event, so the code to draw the label text is not executed.
You could call the method from QLabel as Evgeny suggested, but it is better to implement a custom widget instead.
Calling the method from the base class might for example corrupt any previous drawing, unless the method was implemented with calling form derived classes in mind. I don't expect that is the case for stock widgets. I haven't tried doing it with QLabel re-implementations in particular, but I have tried it with other stock widgets and it did not work as expected.

Tiled image in QGraphicsView foreground

I'm working on a Qt application made with a main QGraphicsView.
This view can show and switch between differents QgraphicsScenes. This application needs to always have an overlay in front of each scenes, so the best way to do this overlay is by setForegroundBrush() method of QGraphicsView.
But my overlay is a tiled-image, where I could edit the opacity and the scale of the source image.
Here's the code written in my QGraphicsView class constructor :
QString imgPath("path/to/image.png");
QPixmap map(imgPath);
QPainter painter(this);
QRectF zone(0,0,map.width(),map.height());
painter.drawPixmap(zone,map,zone);
QBrush brush = painter.brush();
brush.setStyle(Qt::TexturePattern);
setForegroundBrush(brush);
But doesn't work, nothing is shown.
I tested a simple QBrush with a QPixmap and works fine, but I need to use QPainter to be able to edit the opacity of my image.
Finally I think the easiest way to have a tiled image in QGraphicsView foreground is by reimplmenting the drawForeground(QPainter *painter, const QRectF &rect).
void Frontend::drawForeground(QPainter *painter, const QRectF &rect){
float alpha = 0.15;
float scale = 2;
QString imgPath("path/to/image.png");
QPixmap img(imgPath);
painter->scale(scale,scale);
painter->setOpacity(alpha);
painter->drawTiledPixmap(rect,img);
}
You cannot paint on the widget outside of its paintEvent method. Perhaps you wanted to have the painter work on the pixmap (painter(&map)) instead of the widget (painter(this))?
You could also add an overlay by:
Painting it in the reimplemented paintEvent of your derived scene, making sure that you paint not on the scene, but on its viewport(). There are convenience methods that are called by the view's paintEvent, such as drawBackground and drawForeground.
Painting it in a generic QWidget overlay.
I have several answers that demonstrate how to get overlays over widgets in generaly, and also on scene views.

How to make a Qt widget invisible without changing the position of the other Qt widgets?

I've got a window full of QPushButtons and QLabels and various other fun QWidgets, all layed out dynamically using various QLayout objects... and what I'd like to do is occasionally make some of those widgets become invisible. That is, the invisible widgets would still take up their normal space in the window's layout, but they wouldn't be rendered: instead, the user would just see the window's background color in the widget's rectangle/area.
hide() and/or setVisible(false) won't do the trick because they cause the widget to be removed from the layout entirely, allowing other widgets to expand to take up the "newly available" space; an effect that I want to avoid.
I suppose I could make a subclass of every QWidget type that override paintEvent() (and mousePressEvent() and etc) to be a no-op (when appropriate), but I'd prefer a solution that doesn't require me to create three dozen different QWidget subclasses.
This problem was solved in Qt 5.2. The cute solution is:
QSizePolicy sp_retain = widget->sizePolicy();
sp_retain.setRetainSizeWhenHidden(true);
widget->setSizePolicy(sp_retain);
http://doc.qt.io/qt-5/qsizepolicy.html#setRetainSizeWhenHidden
The only decent way I know of is to attach an event filter to the widget, and filter out repaint events. It will work no matter how complex the widget is - it can have child widgets.
Below is a complete stand-alone example. It comes with some caveats, though, and would need further development to make it complete. Only the paint event is overridden, thus you can still interact with the widget, you just won't see any effects.
Mouse clicks, mouse enter/leave events, focus events, etc. will still get to the widget. If the widget depends on certain things being done upon an a repaint, perhaps due to an update() triggered upon those events, there may be trouble.
At a minimum you'd need a case statement to block more events -- say mouse move and click events. Handling focus is a concern: you'd need to move focus over to the next widget in the chain should the widget be hidden while it's focused, and whenever it'd reacquire focus.
The mouse tracking poses some concerns too, you'd want to pretend that the widget lost mouse tracking if it was tracking before. Properly emulating this would require some research, I don't know off the top of my head what is the exact mouse tracking event protocol that Qt presents to the widgets.
//main.cpp
#include <QEvent>
#include <QPaintEvent>
#include <QWidget>
#include <QLabel>
#include <QPushButton>
#include <QGridLayout>
#include <QDialogButtonBox>
#include <QApplication>
class Hider : public QObject
{
Q_OBJECT
public:
Hider(QObject * parent = 0) : QObject(parent) {}
bool eventFilter(QObject *, QEvent * ev) {
return ev->type() == QEvent::Paint;
}
void hide(QWidget * w) {
w->installEventFilter(this);
w->update();
}
void unhide(QWidget * w) {
w->removeEventFilter(this);
w->update();
}
Q_SLOT void hideWidget()
{
QObject * s = sender();
if (s->isWidgetType()) { hide(qobject_cast<QWidget*>(s)); }
}
};
class Window : public QWidget
{
Q_OBJECT
Hider m_hider;
QDialogButtonBox m_buttons;
QWidget * m_widget;
Q_SLOT void on_hide_clicked() { m_hider.hide(m_widget); }
Q_SLOT void on_show_clicked() { m_hider.unhide(m_widget); }
public:
Window() {
QGridLayout * lt = new QGridLayout(this);
lt->addWidget(new QLabel("label1"), 0, 0);
lt->addWidget(m_widget = new QLabel("hiding label2"), 0, 1);
lt->addWidget(new QLabel("label3"), 0, 2);
lt->addWidget(&m_buttons, 1, 0, 1, 3);
QWidget * b;
b = m_buttons.addButton("&Hide", QDialogButtonBox::ActionRole);
b->setObjectName("hide");
b = m_buttons.addButton("&Show", QDialogButtonBox::ActionRole);
b->setObjectName("show");
b = m_buttons.addButton("Hide &Self", QDialogButtonBox::ActionRole);
connect(b, SIGNAL(clicked()), &m_hider, SLOT(hideWidget()));
QMetaObject::connectSlotsByName(this);
}
};
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
Window w;
w.show();
return a.exec();
}
#include "main.moc"
You can use a QStackedWidget. Put your button on the first page, a blank QWidget on the second, and change the page index to make your button vanish while retaining its original space.
I've 3 solutions in my mind:
1) Subclass your QWidget and use a special/own setVisible() replacement method witch turns on/off the painting of the widget (if the widget should be invisible simply ignore the painting with an overridden paintEvent() method). This is a dirty solution, don't use it if you can do it other ways.
2) Use a QSpacerItem as a placeholder and set it's visibility to the opposite of the QWidget you want to hide but preserve it's position+size in the layout.
3) You can use a special container widget (inherit from QWidget) which gets/synchronizes it's size based on it's child/children widgets' size.
I had a similar problem and I ended up putting a spacer next to my control with a size of 0 in the dimension I cared about and an Expanding sizeType. Then I marked the control itself with an Expanding sizeType and set its stretch to 1. That way, when it's visible it takes priority over the spacer, but when it's invisible the spacer expands to fill the space normally occupied by the control.
May be QWidget::setWindowOpacity(0.0) is what you want? But this method doesn't work everywhere.
One option is to implement a new subclass of QWidgetItem that always returns false for QLayoutItem::isEmpty. I suspect that will work due to Qt's QLayout example subclass documentation:
We ignore QLayoutItem::isEmpty(); this means that the layout will treat hidden widgets as visible.
However, you may find that adding items to your layout is a little annoying that way. In particular, I'm not sure you can easily specify layouts in UI files if you were to do it that way.
Here's a PyQt version of the C++ Hider class from Kuba Ober's answer.
class Hider(QObject):
"""
Hides a widget by blocking its paint event. This is useful if a
widget is in a layout that you do not want to change when the
widget is hidden.
"""
def __init__(self, parent=None):
super(Hider, self).__init__(parent)
def eventFilter(self, obj, ev):
return ev.type() == QEvent.Paint
def hide(self, widget):
widget.installEventFilter(self)
widget.update()
def unhide(self, widget):
widget.removeEventFilter(self)
widget.update()
def hideWidget(self, sender):
if sender.isWidgetType():
self.hide(sender)
I believe you could use a QFrame as a wrapper. Although there might be a better idea.
Try void QWidget::erase (). It works on Qt 3.

Redefine QPainter options when redrawing QGraphicsItem

I want to redefine the QPainter before drawing earch QGraphicsItem in a QGraphics scene.
void GraphicsScene::drawItems( QPainter * painter, int nbItem, QGraphicsItem *[] items, const QStyleOptionGraphicsItem[] options, QWidget * widget = NULL )
Is now obsolete, what's the "new" method ?
Thx
The QGraphicsScene isn't in charge of the painter... it is in charge of the storage and retrieval of the items. The QGraphicsView is in charge of the painter and transformation of the view.
If you want to set render hints to modify the QPainter behavior, you can do that from the view using QGraphicsView::setRenderHint.
If you just want to do a single render to another special QPainter engine, the documentation shows an example for it to a printer here.
Here is link to the docs on QGraphicsView.

Animating image replacement in Qt

I'm trying to animate the change of a QPixmap, inside QLabel.
I have MainWindow which holds several objects that derive from QScrollArea. Each of these holds a QLabel member.
Using mousePressEvent() I am able to replace the picture of each QLabel using setPixmap(). However, that simply switches the image in each QLabel, while what I would like to achieve is an animation where a new image slides over the existing one.
First I tried using a QTimeLine to draw the QPixmap on the QLabel myself (I've created a class that derives from QLabel for that, and wrote my own setPixmap()) but that didn't work. Next I tried using QPropertyAnimation but it can't construct on a Pixmap without me implementing a sub class for that as well.
Any thoughts or ideas are appreciated.
You will need a QObject with a property that can be animated, and generates the intermediate frames for the animation. An incomplete example:
class LabelAnimator : public QObject
{
Q_OBJECT
Q_PROPERTY(float progress READ progress WRITE setProgress)
public:
LabelAnimator(QLabel* label) : mProgress(0.0f),
mLabel(label),
mAnimation(new QPropertyAnimation(this, "progress", this)
{
mAnimation->setStartValue(0.0f);
mAnimation->setEndValue(1.0f);
}
void setProgress(float progress) {
mProgress = progress;
QPixmap pix = mOriginalPixmap;
int offset = - mLabel->width() * (1.0f-progress);
QPainter painter(&pix);
painter.paint(off, 0, mNewPixmap);
painter.end();
mLabel->setPixmap(pix);
}
void setPixmap(const QPixmap& pix) {
mOriginalPixmap = mLabel->pixmap();
mNewPixmap = pix;
mAnimation->start();
}
};
QLabel was never designed for such uses. Draw your QPixmaps inside a QGraphicsView, it is far more focused towards rendering effects and animations.

Resources