QGraphicsItem doesn't receive mouse hover events - c++

I have a class derived from QGraphicsView, which contains QGraphicsItem-derived elements. I want these elements to change color whenever the mouse cursor hovers over them, so I implemented hoverEnterEvent (and hoverLeaveEvent):
void MyGraphicsItem::hoverEnterEvent(QGraphicsSceneHoverEvent* event)
{
update (boundingRect());
}
However, this event handler code is never executed. I've explicitly enabled mouse tracking:
MyGraphicsView::MyGraphicsView(MainView *parent) :
QGraphicsView(parent)
{
setMouseTracking(true);
viewport()->setMouseTracking(true);
...
}
Still, no luck. What am I doing wrong?

Fixed it. I need to use setAcceptHoverEvents(true) in the constructor of my QGraphicsItem-derived class.

In my case, hover events wouldn't work if I overrode mouseMoveEvent in my implementation of the QGraphicsView class. I fixed this by adding a call to
QGraphicsView::mouseMoveEvent(event);
which propagated the event to the parent, which in turn sent it out to all the scene items.

Related

mouse tracking does not work In the widget in the main widget

I implemented showing button when mouse is under QTextEdit in the QWidget.
when I test code, I found it sometimes didn't work showing button
bug cause is that MouseMoveEvent doesn't work in Qwidget when I move in the QTextEdit.
I don't understand why MouseMoveEvent doesn't work...
here is my code
this->setMouseTracking(true); // this is QWidget
ui.textEdit_log->setMouseTracking(true); // this is textEdit
void QWidget::mouseMoveEvent(QMouseEvent* event) // reImplemented mouseMoveEvent
{
if (ui.textEdit_log->underMouse())
{
m_pButton->show();
}
else
{
m_pButton->hide();
}
}
Mouse tracking for QTextEdit will only work by subclassing QTextEdit and reimplementing it's mouseMoveEvent() from where you can pass the event up to the parent's mouseMoveEvent().
OR
Instead of using ui.textEdit_log->setMouseTracking(true) use ui.textEdit_log->setAttribute(Qt::WA_TransparentForMouseEvents) this will make your textedit_log invisible for mouse events. In this case you can use the childrenRect() function of your QWidget to determine if your mouse is over the textedit_log.
As far as software design goes, subclassing is a conceptually very heavy operation - it indicates a very deep coupling. It should be used with due concern for the maintainability impact it has. An almost no case should subclassing be used to merely observe the state of an object! - any API that forces you to do that is essentially broken.
Qt provides convenience "event listener" methods, but these methods are not meant to be used as external observers, i.e. you should never override them if all you want is to observe the objectfrom outside. The event filters should be used instead, since they introduce the weakest of possible couplings, and are very generic.
The mouse tracking events will be sent to the widget that tracks the mouse cursor - the editor widget. So no other widget will hear them. You have to implement QObject::filterEvent in a class of your choice (whatever class is convenient), then install that event filter on the editor widget. Your filter will intercept all events sent to the editor, including mouse move events. You should not subclass QTextEdit - use event filters instead!
In any case, the idea to do this via mouse movement tracking is overkill and has some overhead. Instead, look for QEnterEvent and QLeaveEvent - those should be sent to the editor widget without mouse tracking enabled. But still - do not intercept those via any subclassing!
Do note that QWidget is-a QObject, so the event filter can be a widget class, or a non-widget class.
I solved this problem like this
void Log::enterEvent(QEvent* event)
{
m_pButton->show();
}
void Log::leaveEvent(QEvent* event)
{
m_mousePos = this->mapFromGlobal(QCursor::pos());
QPoint logPos = mapToGlobal(this->normalGeometry().bottomLeft());
QPoint buttonPos = m_pButton->pos();
QPoint buttonRelatviePos = buttonPos - logPos;
// hide button when button is not in the button pos
if ((m_mousePos.x() < buttonRelatviePos.x()) or (m_mousePos.x() > buttonRelatviePos.x() + m_pButton->width()) or
(m_mousePos.y() < buttonRelatviePos.y()) or (m_mousePos.y() > buttonRelatviePos.y() + m_pButton->height()))
{
m_pButton->hide();
}
}

QWidget listening to other QWidgets events

To explain the situation, I have a QMainWindow with plenty of stuff inside.
My goal is to make some kind of a notification that appears at the bottom right corner of this window.
The notification widget must be over every other widgets.
So far I've created that Notification widget that inherits from QWidget.
By setting the QMainWindow as its parent I've got it 'floating' on top of every other widgets. It doesn't belong to any layout.
Now the big issue is to make it stick to the bottom right corner. I managed to place it manually but what I want now is to move it upon a resize event.
Unfortunately since it doesn't belong to any layout it doesn't receive any resizeEvent, so I can't overload the resizeEvent() function.
An attempt was to make the QMainWindow emit a signal in its resizeEvent method but I am not really happy with this method. Especially because in the Notification widget constructor I have this connect(static_cast<MainWindow*>(parent), &MainWindow::resized, this, &Notification::update_position);, and it kind of breaks the genericity of the widget since its parent must be a MainWindow widget.
So the question is how can one widget react to another widget event ?
In my case here how can the notification widget be notified when its parent is getting resized ?
And I forgot to mention that I don't want the MainWindow widget to know anything about this notification widget. The notification widget can be managed on its own. You just call something like new Notification(parent) and it will do its stuff on its own
Any ideas are welcome,
Thanks in advance :)
You can install the notification widget as an event filter for its parent, e.g.:
NotificationWidget::NotificationWidget(QWidget *parent) : QWidget(parent) {
parent->installEventFilter(this);
}
bool NotificationWidget::eventFilter(QObject *obj, QEvent *event) {
// normal event handling
bool ret = QObject::eventFilter(obj, event);
if (event->type() == QEvent::Resize) {
// Parent resized, adjust position
}
return ret;
}

Implementing mousePressEvent and setDragMode in Qt

I am doing a project in which I should implement mousePressEvent and setDragMode on QGraphicsScene. I am able to implement individually but I should implement both at the same time. I included both into the code but only mousePressEvent is working and not able to drag the scene . Could you please anyone tell me that how to implement both.Please find the attached code. Thankyou.
MyGraphicsView.cpp
MyGraphicsView::MyGraphicsView(QWidget* parent) : QGraphicsView(parent) {
Scene = new QGraphicsScene(this);
setScene(Scene);
setDragMode(ScrollHandDrag);
}
void MyGraphicsView::mousePressEvent(QMouseEvent *eve)
{
if (eve->button() == Qt::LeftButton) {
// handle Left mouse button here
QPointF pt = mapToScene(eve->pos());
}
}
MyGraphicsView.h
class MyGraphicsView : public QGraphicsView
{
Q_OBJECT
protected:
virtual void mousePressEvent(QMouseEvent *eve);
}
From QGraphicsView Qt docs on DragMode property:
(...) The default value, NoDrag, does nothing.
This behavior only affects mouse clicks that are not handled by any item. You can define a custom behavior by creating a subclass of QGraphicsView and reimplementing mouseMoveEvent().
This is probably why you can't make a DragMode to get to work when you implement a mousePressEvent: The event is processed, so the Qt framework thinks the "job is done", and no need to trigger a drag.
The doc suggest to re-implement the mouseMoveEvent. It still provides a QMouseEvent, so you can get the coordinates, and the information that what mouse button is currently pressed. From that on you only have to implement a state machine for Idle; StartDrag; Drag; FinishDrag
according to what buttons are pressed during a move, and implement your desired functionality in whichever states you need them.

Confused by doubleclickevent and mousepressevent in Qt

The situation is:
In side a QGraphicsView, I used its doubleClickEvent function to create a QGraphicsItem.
I rewrite the QGraphicsItem's mousePressEvent function to simply print a qDebug information.
However, what I found is that: even if I just double click to create that QGraphicsItem, the newly created QGraphicsItem's mousePressEvent is also called.
How would this happen? What can I do to prevent this?
A QGraphicsView is a widget, so it has a doubleClickEvent. In contrast, a QGraphicsItem is not a widget, but has a mousePressEvent.
When you override events, if you don't want them to be propagated to other objects, you need to accept the event to tell the system that you've handled it.
void MyGraphicsView::mouseDoubleClickEvent(QMouseEvent * event)
{
// Create a graphics item..
...
event->accept(); // tell the system that the event has been handled
}
Without accepting the event, the double click is passed on to the QGraphicsScene and then QGraphicsItem.
If you want to be able to double-click to create an item when clicking on an empty area of a scene, but have an item react if you double-click on an item, you can override the scene's mouseDoubleClickEvent instead of the view and check if there's an item at the cursor position before deciding upon whether or not to create an item, or pass the event on.
void QGraphicsScene::mouseDoubleClickEvent( QGraphicsSceneMouseEvent * mouseEvent )
{
if(items(mouseEvent->scenePos()).isEmpty()) // if no items are at this position
{
// create new graphics item
...
mouseEvent->accept(); // don't propagate the event
}
// no items, so allow the event to propagate by doing nothing
}

QGraphicsScene not responding to synthetic events

I have a very simple QGraphicsScene dervied class of my own:
class SceneComponent : public QGraphicsScene
{
Q_OBJECT
public:
explicit SceneComponent(QObject* parent = 0);
protected:
void mousePressEvent(QGraphicsSceneMouseEvent*);
};
And mousePressEvent(QGraphicsSceneMouseEvent*) is defined as:
void SceneComponent::mousePressEvent(QGraphicsSceneMouseEvent* event) {
Q_UNUSED(event);
std::cout<<"[Processing] MouseEvent"<<std::endl;
}
To display this QGraphicsView, this is as simple as:
QGraphicsView view(sceneComp);
view.show();
Now, when I click on the Window (for the QGraphicsView) that is displayed, I get the following output:
[Processing] MouseEvent
[Processing] MouseEvent
However, when I send a synthetic event using:
QMouseEvent* mevent = new QMouseEvent(
QMouseEvent::MouseButtonPress, QPoint(50, 50),
Qt::LeftButton, Qt::NoButton, Qt::NoModifier
);
QApplication::sendEvent(&view, mevent);
I get absolutely no output. Why is this?
On a related note, installing a eventFilter on the QGraphicsScene yields no results at all. This is probably (in my opinion) because of the fact that a QGraphicsScene expects a QGraphicsSceneMouseEvent instead of a QMouseEvent. This props up two questions:
Why is it so that a QGraphicsScene not accept standard QEvent and only QGraphicsSceneEvent?
Why is any QGraphicsSceneEvent derived class not instantiable?
Why is that I can send events to a QGraphicsScene using the sceneEvent method, but I need to specify a specific QGraphicsItem to send the event to?
From the docs for QGraphicsSceneEvent:
When a QGraphicsView receives Qt
mouse, keyboard, and drag and drop
events (QMouseEvent, QKeyEvent,
QDragEvent, etc.), it translates them
into instances of QGraphicsSceneEvent
subclasses and forwards them to the
QGraphicsScene it displays. The scene
then forwards the events to the
relevant items.
For example, when a QGraphicsView
receives a QMouseEvent of type
MousePress as a response to a user
click, the view sends a
QGraphicsSceneMouseEvent of type
GraphicsSceneMousePress to the
underlying QGraphicsScene through its
mousePressEvent() function. The
default
QGraphicsScene::mousePressEvent()
implementation determines which item
was clicked and forwards the event to
QGraphicsItem::mousePressEvent().
QGraphicsView uses a viewport widget to display the contents of the scene and to receive user events. Try sending your event to the QGraphicsView::viewport() widget - it should work.
By design - see the quote, these events are probably not meant to be sent manually. QMouseEvent is associated with a physical action - ex. mouse click, while QGraphicsSceneMouseEvent is something abstract...
The question is not clear, but that quote might be helpful too. Which event in particular are you talking about? You should also keep in mind that QGraphicsScene has QObject level events, sent with QApplication::sendEvent(), and QGraphicsItem events, that are sent with QGraphicsScene::sendEvent(). These two kinds work in different planes and have different purposes. Refer to the docs for more info.