QGraphicsItem: when paint function is called - qt

I am creating an animation with QGraphicsView, QGraphicsScene and QGraphicsItem. Can someone explain me when the paint function is called? Although I does not change variables of the item, the paint function is called every ≈100 ms. Can I stop that, so i can repaint the item when I want?

You are approaching it the wrong way. The item should be repainted only when needed - when you change how it looks or where it's located. That's when you call the QGraphicsItem::update(). The rest will be handled for you. It seems you're overcomplicating things.
Do note that you need to be determining the current time-dependent parameter of the animation within the paint() method, or "close" to it (say, right before update() is called), using actual time! If your animations are derived from QAbstractAnimation, it's already done for you. If they are not, then you'll have to use QElapsedTimer yourself.
The relevant Qt documentation says:
The animation framework calls updateCurrentTime() when current time has changed. By reimplementing this function, you can track the animation progress. Note that neither the interval between calls nor the number of calls to this function are defined; though, it will normally be 60 updates per second.
This means that Qt will do animations on a best-effort basis. The currentTime reported by the animation is the most recent time snapshot at the moment the animation was updated in the event loop. This is pretty much what you want.
The simplest way to deal with all this would be to use QVariantAnimation with QGraphicsObject. An example is below. Instead of rotating the object, you may have your own slot and modify it in some other way. You can also, instead of using signal-slot connection, have a customized QVariantAnimation that takes your custom QGraphicsItem-derived class as a target.
main.cpp
#include <QApplication>
#include <QGraphicsScene>
#include <QGraphicsView>
#include <QGraphicsObject>
#include <QPropertyAnimation>
#include <QGraphicsRectItem>
class EmptyGraphicsObject : public QGraphicsObject
{
public:
EmptyGraphicsObject() {}
QRectF boundingRect() const { return QRectF(0, 0, 0, 0); }
void paint(QPainter *, const QStyleOptionGraphicsItem *, QWidget *) {}
};
class View : public QGraphicsView
{
public:
View(QGraphicsScene *scene, QWidget *parent = 0) : QGraphicsView(scene, parent) {
setRenderHint(QPainter::Antialiasing);
}
void resizeEvent(QResizeEvent *) {
fitInView(-2, -2, 4, 4, Qt::KeepAspectRatio);
}
};
void setupScene(QGraphicsScene &s)
{
QGraphicsObject * obj = new EmptyGraphicsObject;
QGraphicsRectItem * rect = new QGraphicsRectItem(-1, 0.3, 2, 0.3, obj);
QPropertyAnimation * anim = new QPropertyAnimation(obj, "rotation", &s);
s.addItem(obj);
rect->setPen(QPen(Qt::darkBlue, 0.1));
anim->setDuration(2000);
anim->setStartValue(0);
anim->setEndValue(360);
anim->setEasingCurve(QEasingCurve::InBounce);
anim->setLoopCount(-1);
anim->start();
}
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
QGraphicsScene s;
setupScene(s);
View v(&s);
v.show();
return a.exec();
}

You can set the viewportUpdateMode of the QGraphicsView to change how it updates. The options are: -
QGraphicsView::FullViewportUpdate
QGraphicsView::MinimalViewportUpdate
QGraphicsView::SmartViewportUpdate
QGraphicsView::BoundingRectViewportUpdate
QGraphicsView::NoViewportUpdate
The Qt docs explains what the different options do, but if you want full control, just set to QGraphicsView::NoViewportUpdate and control it yourself using a QTimer event.

Related

Qt: Track mouse position while QDrag is running

I am developing a Qt application with multiple windows and want to implement cross-window drag&drop functionality for some elements in my program.
To do so, I attach an event filter to the to-be-dragged QML elements and listen for the MousePress/MouseMove events to start the drag procedure as follows:
QDrag *drag = new QDrag(quickItem);
QMimeData* mimeData = new QMimeData();
mimeData->setText("Test");
drag->setHotSpot(QPoint(0, 0));
drag->setMimeData(mimeData);
drag->exec();
This works fine, but now I would like to show a little tooltip (being a QWidget) while dragging, following the mouse cursor and displaying a short text depending on the element the mouse is currently over (similar to the "Copy to ..." or "Move to..." labels appearing when you drag files around in Windows Explorer).
However, while dragging the element, I don't receive any MouseMove events neither on the QDrag object nor on the quickItem itself, which makes it impossible to track the mouse position. Since the mouse is grabbed during dragging, there should be some event in Qt that frequently reports the mouse position, no matter where on the screen the mouse is.
I am aware of the QDrag::setPixmap method, however this won't allow me to change my tooltip text during dragging and has some other limitations I would like to avoid.
Is there some way to listen to mouse move events while QDrag is running, without using platform-specific system APIs?
Update:
I don't think that there is way of doing this without using OS libraries nor getting the mouse position every X miliseconds. It looks like a really specific problem that Qt framework does not contemplate. You will need to write your own class to control this using win32 for windows, x11 for linux and the equivalent of Mac.
If you want to get the mouse position when your window is active and you are dragging something, check this:
Searching a bit I've found a solution for getting it when your window has the focus using QObject::eventFilter.
Create a class (for example EventListener) that inherits from QObject and overrides eventFilter and a method to set this as your qml window (which inherits from QObject) event filter with installEventFilter.
eventslistener.h:
#include <QEvent>
#include <QObject>
#include <QDebug>
#include <QDropEvent>
class EventsListener : public QObject
{
Q_OBJECT
public:
EventsListener(QObject * ptr) : QObject (ptr) {
}
Q_INVOKABLE void handleEventsOf(QObject *object) {
if (object)
object->installEventFilter(this);
}
bool eventFilter(QObject *object, QEvent *event) override {
if(event->type() == QEvent::DragMove) {
QDragMoveEvent *mouseEvent = static_cast<QDragMoveEvent*>(event);
qDebug() << "Mouse position dragging (x, y): (" << mouseEvent->pos().x() << ", " << mouseEvent->pos().y() << ")";
return false; //this is must return false or drop event will be handled by this method and drag&drop won't work correctly
}
return false;
}
};
Now we need to access to an instance (singleton in this case) of this class with qmlRegisterSingletonType. You may wish to use qmlRegisterType instead to register this eventlistener as a type (instead of a singleton) and use signals to notify directly qml the mouse position.
main.cpp:
#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include "eventlistener.h"
static QObject *eventsListenerInstance(QQmlEngine *qmlEngine, QJSEngine *engine)
{
return new EventsListener(engine);
}
int main(int argc, char *argv[])
{
QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
QGuiApplication app(argc, argv);
qmlRegisterSingletonType<EventsListener>("AppEventListener", 1, 0, "EventsListener", eventsListenerInstance);
QQmlApplicationEngine engine;
engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
if (engine.rootObjects().isEmpty())
return -1;
return app.exec();
}
main.qml:
import ...
import AppEventListener 1.0
ApplicationWindow {
visible: true
width: 640
height: 480
id: item
property string display
property alias dropEnabled: acceptDropCB.checked
color: dropArea.containsDrag ? "#CFC" : "#EEE"
Component.onCompleted: EventsListener.handleEventsOf(item)
...
}

Qt - Overriding ResizeEvent give multiple calls when showed

I get this strange behavior with a simple reimplementation of ResizeEvent from a standard Widget. After calling show, the resize event is called 2 times. I trace the behavior with breakpoint (see where below "<- - - -") and I got this :
first call :
event->oldSize = (-1, -1)
event->size = (5, 13)
second call :
event->oldSize = (640, 480)
event->size = (640, 480)
The first call seem coherent. But, is anyone can explain to me why I got a second call with inconsistant values and how to get rid of this second resize event?
class MyClass : public QLabel
{
Q_OBJECT
public:
MyClass(QWidget *parent = 0, Qt::WindowFlags f = 0);
~MyClass();
protected:
void resizeEvent(QResizeEvent *event);
};
MyClass::MyClass(QWidget *parent, Qt::WindowFlags f)
: QLabel(parent, f)
{
}
MyClass::~MyClass()
{
}
void MyClass::resizeEvent(QResizeEvent *event)
{
int a = 0; // <- - - - use break point here
}
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
MyClass w;
w.show();
return a.exec();
}
Your last question make me do some tests and I found a solution.
I just add in the constructor :
this->setGeometry(...)
With that, I got only one resizeEvent call (the first one).
I do not know why Qt make this second call but I can guess is to set an initial geometry.
However, thank you for your time. It help me a lot! :-)

How to achieve exclusive (e.g. Radio Button) type behavior with QGraphicObject?

I'm working with Qt (v 5.3) again after sometime away. As a learning exercise I am prototyping a module using QGraphicView in order to understand better how to use it.
The functionality is simple: I have several QGraphicObjects which function like buttons with states and behaviors:
OFF - image panel hidden; default button art
ON - image panel
displayed; highlight button art
At startup all buttons are in an OFF state
A clicked button will toggle it's state (non-Exclusive)
If a different button is already in an ON state, it must be turned off (Exclusive)
Everything is data driven and created dynamically at runtime.
So that is my little learning exercise. What I am trying to sort out is an efficient messaging mechanism to handle the "radio button sometimes" exclusive behavior and sending a message to a group of objects without strongly coupling them.
I've looked at signals and slots but that gets tedious if there are many connections to make.
QSignalMapper which seems somewhat better
QEvent is probably the most solid approach but I've had trouble finding good learning examples.
I am making this more complicated than need be but as I say, it's a learning exercise to get used to Qt again.
So my question (finally): is there an approach I am overlooking or one of these mentioned which would be better (e.g most flexible, maintainable, scalable). Not looking for code per se, but an understanding of how to go about coding something like this using the Qt framework.
Here's an image of the thing:
Signal and slots is the method I would use here. However, rather than connect each button to every other button, you can create an intermediate, Controller class.
The Controller class can either aggregate, or be a parent to all of the buttons.
When a button is created, it connects signals to the controller to inform it of being pressed. The receiving slot in the Controller class is then responsible for turning off any other buttons.
Skeleton code: -
class ButtonController : public QGraphicsObject
{
public slots:
void ButtonPressed();
private:
QList<MyButton*> m_buttonList;
};
void ButtonController::ButtonPressed()
{
foreach(MyButton* pButton, m_buttonList)
{
if(pButton != sender())
{
pButton->Off();
}
}
}
In the constructor of MyButton, assuming the controller is the parent: -
MyButton::MyButton(ButtonController* parent)
: QGraphicsObject(parent);
{
connect(this, &MyButton::pressed(), parent, &ButtonController::ButtonPressed());
...
}
Alternatively, the controller may just hold a list of the buttons, in which case you can do this: -
MyButton::MyButton(QObject* parent, ButtonController* pButtonController)
: QGraphicsObject(parent);
{
connect(this, &MyButton::pressed(), pButtonController, &ButtonController::ButtonPressed());
...
}
In the case of not being able to change the constructor of the buttons, the controller may simply have an AddButton function, in which it creates the connection when the button is added under its control.
Subclass QGraphicsScene
Reimplement mousePressEvent and check is left mouse button pressed.
If so, then get position of mouse by pos() method and get current graphics object under arrow by itemAt() method.
Try to cast it into your needed object, if it was successfully, then change background and other.
Store your graphics objects in vector, in this case you can check every object and decide which object should be turned off.
Fully working example:
header:
#ifndef GRAPHICSSCENE_H
#define GRAPHICSSCENE_H
#include <QGraphicsScene>
#include <QPoint>
#include <QMouseEvent>
class GraphicsScene : public QGraphicsScene
{
Q_OBJECT
public:
explicit GraphicsScene(QObject *parent = 0);
signals:
protected:
void mousePressEvent(QGraphicsSceneMouseEvent *mouseEvent);
public slots:
private:
QVector<QGraphicsEllipseItem * > vec;
};
#endif // GRAPHICSSCENE_H
cpp:
#include "graphicsscene.h"
#include <QDebug>
#include <QGraphicsSceneMouseEvent>
#include <QGraphicsItem>
GraphicsScene::GraphicsScene(QObject *parent) :
QGraphicsScene(parent)
{
//set circles in different positions.
vec.push_back(addEllipse(0,0,50,50,QPen(Qt::red),QBrush(Qt::blue)));
vec.push_back(addEllipse(0+100,0+100,50,50,QPen(Qt::red),QBrush(Qt::blue)));
vec.push_back(addEllipse(0+150,0+150,50,50,QPen(Qt::red),QBrush(Qt::blue)));
}
void GraphicsScene::mousePressEvent(QGraphicsSceneMouseEvent *mouseEvent)
{
//qDebug() << "in";
if (mouseEvent->button() == Qt::LeftButton)
{
QGraphicsItem *item = itemAt(mouseEvent->scenePos(), QTransform());
QGraphicsEllipseItem *ell = qgraphicsitem_cast<QGraphicsEllipseItem *>(item);
if(ell)
{
for(int i = 0; i < vec.size(); i++)
{
if(vec.at(i) != ell)
{
vec.at(i)->setBrush(QBrush(Qt::blue));//disable all other bettons
}
}
ell->setBrush(QBrush(Qt::black));//to chosen circle
qDebug() << "good";
}
else
qDebug() << "not ell" << mouseEvent->scenePos();
}
}
Usage:
GraphicsScene *scene = new GraphicsScene(this);
ui->graphicsView->setScene(scene);
With this case, your circles will be similar to radioButtons. If you want save state of all buttons you can use:
for(int i = 0; i < vec.size(); i++)
vec.at(i)->setData(0,false);
And get state with
if(!ell->data(0).toBool())
//do

Sliding OSX Dock type "sidebar" rather embedded QDockWidget type?

I want to make a sliding "sidebar" similar to the functionality of the OSX "Dock" (e.g. mouse passes edge of screen and Dock slides out). I've been playing around with QDockWidget but since that is embedded in the window layout, it causes everything to shift when it becomes visible.
Can someone suggest a way to implement this?
Doesn't need to float (as a separate window/tool bar)
Should scale to window height (e.g. window can be fullscreen or default size)
Doesn't need to slide (animate) if that is complicated.
I'm new to Qt and so don't want to over-think this. Is this just a matter of a custom widget or should I be looking at a borderless window? The custom widget approach seems right but I don't know how to specify that it overlay other window content and also scale if the window scales.
QDockWidget has nothing to do with what you want - behaviorally. Just because it's called a Dock widget doesn't mean it's the same "Dock" concept as in OS X. It merely means that it docks somewhere. QDockWidget's documentation quite explicitly explains what is meant by the docking behavior.
The code below implements the behavior you seem to want. Whether it's good design or not is arguable. The reason the code is "convoluted" seems to hint that nobody is expected to come up with such a UI design. What's wrong with actually clicking a button somewhere to display the slider window?
The code works under both Qt 4.8 and 5.1.
Note: This begs to be implemented in Qt Quick 2. That's what it was designed for :) Of course Qt 4.6+ improved the behavior of the QWidget-moving animations, and Qt 5 does further tweaks, but really this code smells bad and there's a good reason it does: QWidget API, while powerful, ultimately encapsulates a set of APIs that date to 1984 when the original Macintosh was released. There's only so much you can do when you have to composite results from a bunch of stacked painters. In Qt Quick, the rendering is done by the GPU. The animation amounts to passing a couple of new floats to the GPU to update a single transformation matrix. That's it.
#include <QApplication>
#include <QWidget>
#include <QGridLayout>
#include <QLabel>
#include <QPainter>
#include <QGradient>
#include <QMouseEvent>
#include <QPropertyAnimation>
class Slider : public QWidget {
void paintEvent(QPaintEvent *) Q_DECL_OVERRIDE {
QPainter p(this);
QLinearGradient g(QPointF(0,0), QPointF(rect().bottomRight()));
g.setColorAt(0, Qt::blue);
g.setColorAt(1, Qt::gray);
p.setBackground(g);
p.eraseRect(rect());
p.setPen(Qt::yellow);
p.setFont(QFont("Helvetica", 48));
p.drawText(rect(), "Click Me To Hide");
}
void mousePressEvent(QMouseEvent *) Q_DECL_OVERRIDE {
hide();
}
public:
explicit Slider(QWidget *parent = 0) : QWidget(parent) {
setAttribute(Qt::WA_OpaquePaintEvent);
}
};
class Window : public QWidget {
QGridLayout m_layout;
Slider m_slider;
QLabel m_label;
QPropertyAnimation m_animation;
public:
explicit Window(QWidget *parent = 0, Qt::WindowFlags f = 0) :
QWidget(parent, f),
m_layout(this),
m_slider(this),
m_animation(&m_slider, "pos")
{
setMouseTracking(true);
m_layout.addWidget(&m_label);
m_slider.hide();
m_slider.setMouseTracking(false);
m_animation.setStartValue(QPoint(-width(), 0));
m_animation.setEndValue(QPoint(0, 0));
m_animation.setDuration(500);
m_animation.setEasingCurve(QEasingCurve::InCubic);
}
void leaveEvent(QEvent *) {
if (window() && QCursor::pos().x() <= window()->geometry().topLeft().x()) {
showSlider();
}
}
void childEvent(QChildEvent * ev) {
if (ev->added() && ev->child()->isWidgetType()) {
ev->child()->installEventFilter(this);
static_cast<QWidget*>(ev->child())->setMouseTracking(true);
}
}
bool event(QEvent * ev) {
eventFilter(this, ev);
return QWidget::event(ev);
}
bool eventFilter(QObject *, QEvent * ev) {
if (ev->type() == QEvent::MouseMove) {
auto pos = QCursor::pos();
if (window() && window()->isFullScreen()) {
if (pos.x() <= window()->geometry().topLeft().x()) {
showSlider();
}
}
m_label.setText(QString("%1, %2").arg(pos.x()).arg(pos.y()));
}
return false;
}
void resizeEvent(QResizeEvent *) {
m_slider.resize(size());
m_animation.setStartValue(QPoint(-width(), 0));
}
Q_SLOT void showSlider() {
if (m_slider.isVisible() || (window() && qApp->activeWindow() != window())) return;
m_slider.raise();
m_slider.show();
m_animation.start();
}
};
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
Window w;
w.show();
return a.exec();
}

QT QLabel (used as an image container) fullscreen bug

A experienced the following bug in Qt 4.8.5, under Ubuntu 13.04 (and I'm nem to Qt)
I have have an application with the following structure:
Mainwondow
-CentralWidget
--VerticalLayout
---TabWidget
---QLabel (created with code, and added to the layout)
---StatusBar
In fullscreen mode I hide the TabWidget, and the Statusbar, then the QLabel stops refreshing. (i have a thread to do the refresh) The strange thing is, when i restore the TabWidget or the StatusBar it works fine. It also works good, if i add a 1x1 pixel label to the VerticalLayout.
The slot responsible for the gui change;
void Mainview::onToggleFullScreen()
{
if (this->isFullScreen())
{
this->showNormal();
this->statusbar->show();
this->tabWidget->show();
}
else
{
this->showFullScreen();
this->statusbar->hide();
this->tabWidget->hide();
}
}
But the thing I cant understand if I put a QLabel near the image, it works, and if I add this single line to the MainWindow constructor, it stops refreshing:
label_10->hide(); //this is the label
Any idea what is the problem?
(Thanks in advance)
You're probably doing it in some wrong way, but you don't show the code, so how can we know?
Below is a safe SSCCE of how one might do it. Works under both Qt 4.8 and 5.1.
Nitpick: The status bar should not be a part of the centralWidget()! QMainWindow provides a statusBar() for you.
The only safe way of passing images between threads is via QImage. You can not use QPixmap anywhere but in the GUI thread. End of story right there.
In the example below, all of the important stuff happens behind the scenes. The DrawThing QObject lives in another thread. This QThread's default implementation of the run() method spins a message loop. That's why the timer can fire, you need a spinning message loop for that.
Every time the new image is generated, it is transmitted to the GUI thread by implicitly posting a message to MainWindow. The message is received by Qt event loop code and re-synthesized into a slot call. This is done since the two ends of a connection (DrawThing and MainWindow instances) live in different threads.
That the beauty of Qt's "code less, create more" approach to design :) The more you leverage what Qt does for you, the less you need to worry about the boilerplate.
//main.cpp
#include <QMainWindow>
#include <QVBoxLayout>
#include <QStatusBar>
#include <QLabel>
#include <QThread>
#include <QPainter>
#include <QImage>
#include <QApplication>
#include <QBasicTimer>
#include <QPushButton>
class DrawThing : public QObject {
Q_OBJECT
int m_ctr;
QBasicTimer t;
void timerEvent(QTimerEvent * ev) {
if (ev->timerId() != t.timerId()) return;
QImage img(128, 128, QImage::Format_RGB32);
QPainter p(&img);
p.translate(img.size().width()/2, img.size().height()/2);
p.scale(img.size().width()/2, img.size().height()/2);
p.eraseRect(-1, -1, 2, 2);
p.setBrush(Qt::NoBrush);
p.setPen(QPen(Qt::black, 0.05));
p.drawEllipse(QPointF(), 0.9, 0.9);
p.rotate(m_ctr*360/12);
p.setPen(QPen(Qt::red, 0.1));
p.drawLine(0, 0, 0, 1);
m_ctr = (m_ctr + 1) % 12;
emit newImage(img);
}
public:
explicit DrawThing(QObject *parent = 0) : QObject(parent), m_ctr(0) { t.start(1000, this); }
Q_SIGNAL void newImage(const QImage &);
};
class MainWindow : public QMainWindow {
Q_OBJECT
QLabel *m_label;
public:
explicit MainWindow(QWidget *parent = 0, Qt::WindowFlags flags = 0) : QMainWindow(parent, flags) {
QWidget * cw = new QWidget;
QTabWidget * tw = new QTabWidget();
QVBoxLayout * l = new QVBoxLayout(cw);
l->addWidget(tw);
l->addWidget(m_label = new QLabel("Label"));
setCentralWidget(cw);
QPushButton * pb = new QPushButton("Toggle Status Bar");
tw->addTab(pb, "Tab 1");
connect(pb, SIGNAL(clicked()), SLOT(toggleStatusBar()));
statusBar()->showMessage("The Status Bar");
}
Q_SLOT void setImage(const QImage & img) {
m_label->setPixmap(QPixmap::fromImage(img));
}
Q_SLOT void toggleStatusBar() {
statusBar()->setHidden(!statusBar()->isHidden());
}
};
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
QThread t;
DrawThing thing;
MainWindow w;
thing.moveToThread(&t);
t.start();
w.connect(&thing, SIGNAL(newImage(QImage)), SLOT(setImage(QImage)));
w.show();
t.connect(&a, SIGNAL(aboutToQuit()), SLOT(quit()));
int rc = a.exec();
t.wait();
return rc;
}
#include "main.moc"

Resources