Changing mouse cursor in a QObject - QGraphicsItem inherited class - qt

I would like to change my mouse cursor when it is in a graphics item (MyCircle inherits from QObject and QGraphicsItem).
Had my class inherited from QWidget, I would have reimplemented enterEvent() and leaveEvent() and use it as follows :
MyCircle::MyCircle(QObject *parent)
: QWidget(parent), QGraphicsItem() // But I can't
{
rect = QRect(-100,-100,200,200);
connect(this,SIGNAL(mouseEntered()),this,SLOT(in()));
connect(this,SIGNAL(mouseLeft()),this,SLOT(out()));
}
void MyCircle::in()
{
QApplication::setOverrideCursor(Qt::PointingHandCursor);
}
void MyCircle::out()
{
QApplication::setOverrideCursor(Qt::ArrowCursor);
}
void MyCircle::enterEvent(QEvent *)
{
emit mouseEntered();
}
void MyCircle::leaveEvent(QEvent *)
{
emit mouseLeft();
}
Unfortunately, I need to animate that circle (it's a button actually), so I need QObject, is there an easy way to change the cursor ?

QGraphicsItem already has a method for changing the cursor, so you don't need to manually play around with hover events:
QGraphicsItem::setCursor(const QCursor &cursor)
http://doc.qt.io/qt-5/qgraphicsitem.html#setCursor
PS: The dual inheritance of QWidget and QGraphicsItem you do is also a bad idea, only inherit from QGraphicsItem.

You can probably use hover events.
In your class constructor make sure you do...
setAcceptHoverEvents(true);
Then override hoverEnterEvent and hoverLeaveEvent.
virtual void hoverEnterEvent (QGraphicsSceneHoverEvent *event) override
{
QGraphicsItem::hoverEnterEvent(event);
QApplication::setOverrideCursor(Qt::PointingHandCursor);
}
virtual void hoverLeaveEvent (QGraphicsSceneHoverEvent *event) override
{
QGraphicsItem::hoverLeaveEvent(event);
QApplication::setOverrideCursor(Qt::ArrowCursor);
}
As a side note: do you actually inherit from both QObject and QGraphicsItem? If so, you could probably achieve the same goal by simply inheriting from QGraphicsObject.
Edit 1: In answer to...
I have the pointing hand icone in my whole bounding rect, how can I
reduce the area only to my drawing (in this case a circle) ?
Override QGraphicsItem::shape to return a QPainterPath representing the actual shape...
virtual QPainterPath shape () const override
{
QPainterPath path;
/*
* Update path to represent the area in which you want
* the mouse pointer to change. This will probably be
* based on the code in your 'paint' method.
*/
return(path);
}
Now override QGraphicsItem::hoverMoveEvent to make use of the shape method...
virtual void hoverMoveEvent (QGraphicsSceneHoverEvent *event) override
{
QGraphicsItem::hoverMoveEvent(event);
if (shape().contains(event->pos())) {
QApplication::setOverrideCursor(Qt::PointingHandCursor);
} else {
QApplication::setOverrideCursor(Qt::ArrowCursor);
}
}
The above could, obviously, have an impact on performance depending on the complexity of the shape drawn and, hence, the QPainterPath.
(Note: rather than using QGraphicsItem::shape you could do a similar thing with QGraphicsItem::opaque instead.)

Related

QOpenGLWidget and transparency

I have small problem with QOpenGLWidget and its background color.
When I want to create semi-transparent rect on my custom QOpenGLWidget using QPainter there are 2 different results:
If MyCustomWidget have parent - on every update rect's color multiplies (and after few repaints it is opaque, like previous painting result not cleaned)
If MyCustomWidget doesn't have parent - color doesn't repaints each time
Here is code example for QPainter:
class Widget : public QOpenGLWidget
{
Q_OBJECT
public:
Widget(QWidget *parent = 0)
: QOpenGLWidget(parent)
{
resize(800, 600);
Test *test = new Test(this);
}
~Widget(){}
protected:
void paintEvent(QPaintEvent *) {}
protected:
void initializeGL() {
if(paintEngine()->type() != QPaintEngine::OpenGL &&
paintEngine()->type() != QPaintEngine::OpenGL2)
qDebug() << "ERROR. Type is: " << paintEngine()->type();
}
void resizeGL(int, int) {}
void paintGL() {
QPainter p;
p.begin(this);
{
p.fillRect(rect(), Qt::white);
}
p.end();
}
private:
class Test : public QOpenGLWidget
{
public:
Test(QWidget *parent = 0) : QOpenGLWidget(parent) {
resize(100, 100);
}
protected:
void paintEvent(QPaintEvent *) {
QPainter p(this);
p.fillRect(rect(), QColor(125, 125, 125, 255/10));
}
};
};
Also by default it has black background (I don't know how to fix it. setAttribute(Qt::WA_TranslucentBackground) doesn't helps).
Also, when I'm trying to clear color using glClear it ignores alpha (both on QOpenGLWidget with parent and not). Here is Test class from previous code, but now it is using opengl to clear color:
class Test : public QOpenGLWidget
{
public:
Test(QWidget *parent = 0) : QOpenGLWidget(parent) {
resize(100, 100);
}
void initializeGL() {
QOpenGLFunctions *f = context()->functions();
f->glClearColor(0.0f, 1.0f, 0.0f, 0.1f);
}
void paintGL() {
QOpenGLFunctions *f = context()->functions();
f->glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
}
};
How can I fix this problems?
I'm using Qt 5.5.0, Windows 10, MinGW 4.9.2
Xeed is correct when saying the QOpenGLWidget is painted first.
I'm not an expert but I think I found the solution. You need to set a widget attribute to always make the widget stacked on top (think of the widgets as layers on the window). Here is a link to where I got the following information:
P.S. As mentioned in the QQuickWidget post, there is a limitation regarding semi-transparency when using QQuickWidget or QOpenGLWidget as child widgets. For applications that absolutely need this, Qt 5.4 offers a workaround: the newly introduced Qt::WA_AlwaysStackOnTop widget attribute. This, at the expense of breaking the stacking order for other types of layouts, makes it possible to have a semi-transparent QQuickWidget or QOpenGLWidget with other widgets visible underneath. Of course, if the intention is only to make other applications on the desktop visible underneath, then the Qt::WA_TranslucentBackground attribute is sufficient
Solution in Python:
set attribute of OpenGL widget
setAttribute(Qt.WA_AlwaysStackOnTop)
Now the OpenGL widget is considered 'on top' in the window. Use 'glClearColor' function and specify the alpha channel to be zero (0.0).
glClearColor(0.0, 0.0, 0.0, 0.0)
I'm not sure how to write that in other languages but this worked for me. The OpenGL widget no longer has the default black background. It is transparent! Hope this helps.
As far as I know the QOpenGLWidget is always drawn first. Therefore you cannot show any widgets layered below. I'm currently looking into the same issue. I'll report back, when I find any solution.
I've had similar issue with QOpenGLWidget not repainting correctly in transparent areas and decided to switch to QOpenGLWindow wrapped inside QWidget::createWindowContainer()

How to animate the outline of a QGraphicsItem in real time?

I am designing a timer with Qt. With QGraphicsEllipseItem, I drew a circle and now I need to animate the QPen around this circle (change color) every second. I found QGraphicsPathItem, but I need some examples on how to move forward. Can anyone show me an example?
You have two problems:
QGraphicsEllipseItem is not a QObject so QPropertyAnimation can't be used directly on this item
QGraphicsItemAnimation doesn't cover property you want to animate.
What you can do?
IMO best approach is to provide some custom QObject on which you could do this animation. You can inherit QObject or use fake QGraphicsObject (which is a QObject).
class ShapeItemPenAnimator : public QGraphicsObject {
Q_OBJECT
private:
QAbstractGraphicsShapeItem *mParent;
QPropertyAnimation *mAnimation;
public:
QPROPERTY(QColor penColor
READ penColor
WRITE setPenColor)
explicit ShapeItemPenAnimator(QAbstractGraphicsShapeItem * parent)
: QGraphicsObject(parent)
, mParent(parent) {
setFlags(QGraphicsItem::ItemHasNoContents);
mAnimation = new QPropertyAnimation(this, "penColor", this);
}
QColor penColor() const {
return mParent->pen().color();
}
public slots:
void setPenColor(const QColor &color) {
QPen pen(mParent->pen());
pen.setColor(color);
mParent->setPen(pen);
}
public:
void paint(QPainter * painter, const QStyleOptionGraphicsItem * option, QWidget * widget = 0) {
}
QRectF boundingRect() const {
return QRectF();
}
QPropertyAnimation *animation() const {
return mAnimation;
}
}
Now you just attach this object to your QGraphicsEllipseItem and set animation you need.
// yourEllipse
ShapeItemPenAnimator *animator = new ShapeItemPenAnimator(yourEllipse);
animator->animation()->setEndValue(....);
animator->animation()->setStartValue(....);
animator->animation()->setDuration(....);
animator->animation()->setEasingCurve(....);
There are several classes helping with animations of QGraphicsItem in Qt. I suggest looking into QGraphicsItemAnimation and QPropertyAnimation. You can use the second one to animate the color of an item. Here is an example of using QPropertyAnimation:
How to make Qt widgets fade in or fade out?

QRubberBand doesn't work

I am using QRubberBand to draw a selection box on my QWidget that displays a QImage.
I am using the code exactly as it is shown in the documentation, but it doesn't work. I get no errors, but it does act strange. Instead of displaying a selection box when I hold my left button down and drag it across my QWidget which is painted with the QImage it just makes one of my buttons on my interface disappear and reappear based on my left button click. I should also add that the button that disappears isn't apart of the QWidget, or even apart of the parent QObject that creates the QWidget.
void Widget::mousePressEvent(QMouseEvent *event)
{
origin = event->pos();
if (!rubberBand)
rubberBand = new QRubberBand(QRubberBand::Rectangle, this);
rubberBand->setGeometry(QRect(origin, QSize()));
rubberBand->show();
}
void Widget::mouseMoveEvent(QMouseEvent *event)
{
rubberBand->setGeometry(QRect(origin, event->pos()).normalized());
}
void Widget::mouseReleaseEvent(QMouseEvent *event)
{
rubberBand->hide();
// determine selection, for example using QRect::intersects()
// and QRect::contains().
}
Any help is appreciated.
Thanks
It is totally my fault. I failed to set my pointer as NULL in my header.
Instead of this :
QRubberBand *rubberBand {rubberBand = NULL};
I just did :
QRubberBand *rubberBand;

how to use mouse move event for QGraphicsScene?

hey want to drag this bezier curve when mouse button is pressed and moved..
I did this:
void MainWindow::mouseMoveEvent(QMouseEvent *e)
{
qDebug()<<"in mouse move - outside if";
if((e->buttons() & Qt::RightButton) && isStart && enableDrag)
{
qDebug()<<"mouse dragging happening";
xc2=e->pos().x();
yc2=e->pos().y();
drawDragBezier(xc2,yc2);
}
}
this starts dragging when i press right button and start moving mouse in whole main window..but I want to start dragging only when I press mouse button and move mouse inside the QGraphicsScene.
how to solve this?
EDIT:
void mySubClass1::mousePressEvent(QGraphicsSceneMouseEvent *event)
{
if(event->button() == Qt::LeftButton)
{
qDebug()<<"in musubclass mouse press event: "<<event->pos().x()<<" "
<<event- >pos().y();
if(shape().contains(event->pos()))
{
currPosX=event->pos().x();
currPosY=event->pos().y();
qDebug()<<"currPosX currPosY: "<<currPosX<<" "<<currPosY;
}
}
}
And the mainwindow class is:
{
myGPath=new mySubClass1();
myScene=new QGraphicsScene;
myScene->addItem(myGPath);
ui->graphicsView->setScene(myScene);
QPointF *startPoint=new QPointF(50,50);
myPaintPath=new QPainterPath(*startPoint);
myPaintPath->quadTo(100,25,200,200);
myGPath->setPath(*myPaintPath);
}
is this the right way?
Personally, to solve this issue I'd take a different approach.
Create a class inherited from QGraphicsItem (or QGraphicsObject if you want signals and slots) to represent the bezier curve. Then implement the mouseMoveEvent of the object in this class.
class MyBezierCurve : public QGraphicsItem
{
protected:
void mousePressEvent(QGraphicsSceneMouseEvent*);
void mouseMoveEvent(QGraphicsSceneMouseEvent*);
void mouseReleaseEvent(QGraphicsSceneMouseEvent*);
};
This way, the object can detect in its mousePressEvent when the mouse is directly over one of its control points and update the control points with mouse move events until the release event occurs.
Handling the mouse events in the QGraphicsView will work, but if you introduce more bezier curves, or other objects you'll find you'll need to check which of them you need to be interacting with. Handling it in the object itself will take care of that for you.
You should subclass QGraphicsView and detect mouseMoveEvent over there.
class MyGraphicsView : public QGraphicsView
{
Q_OBJECT
...
protected:
void mouseMoveEvent(QMouseEvent *event);
...
};

Qt disable move window

I have a QMainWindow with this flag :
this->setWindowFlags(Qt::SubWindow);
How do to forbid the window moving, and this, keeping this window style ?
I don't think there is a cross-os Qt way to achieve this when using the standard window controls.
You can try stuff like:
class Widget : public QWidget {
Q_OBJECT
public:
Widget()
: fixed_pos_(QPoint(100, 100)) {
setWindowFlags(Qt::SubWindow);
}
void SetFixedPos(const QPoint& pos) {
fixed_pos_ = pos;
}
protected:
void moveEvent(QMoveEvent* ev) {
if (ev->pos() != fixed_pos_)
move(fixed_pos_);
}
private:
QPoint fixed_pos_;
};
These have a few issues like flicker, does not update until Mouse-release and so on that's also different per OS.
Most efficient way is to just make your Window a Qt::FramelessWindowHint and render a titlebar yourself. That way you can pretty much do what you want when it comes to handling events on that titlebar.

Resources