The software shown 3 widget:
Main window
Content widget, that cover most of the main window
custom widget, that cover part of both, the main window and the content widget.
The custom widget has a part (defined as a QRect) that need to be Event-opaque, while the surrounding zone has to be Event-transparent.
I tried with:
setAttribute(Qt::WA_TransparentForMouseEvents);
But all sub-widgets of custom become transparent also.
I also tried with setMask, but then the custom widget is unable to draw on the surrounding area.
How to achieve this partial event-transparency?
Example (it does not explain the full problem, just add a base on which to test solutions):
main.cpp
#include "transparentwidget.hpp"
#include "normalwidget.hpp"
#include <QApplication>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
// Main Window
NormalWidget window;
window.resize(500,500);
window.setObjectName("window");
window.setStyleSheet("background-color: rgba(0,0,128,128); ");
// Content window
NormalWidget content(&window);
content.setObjectName("content");
content.resize(400, 400);
content.move(0,0);
content.setStyleSheet("background-color: rgba(128,0,0,128);");
TransparentWidget custom(&window);
custom.setObjectName("custom");
custom.resize(500, 200);
custom.setStyleSheet("background-color:rgba(0,128,0,128);");
window.show();
return a.exec();
}
transparentwidget.hpp
#ifndef TRANSPARENTWIDGET_H
#define TRANSPARENTWIDGET_H
#include <QWidget>
#include <QStyleOption>
#include <QPainter>
#include <QDebug>
#include <QEvent>
// This widget shall be transparent in some parts
class TransparentWidget : public QWidget
{
Q_OBJECT
public:
explicit TransparentWidget(QWidget *parent = 0): QWidget(parent)
{
// Start of solution with WA_TransparentForMouseEvents (not working)
setAttribute(Qt::WA_TransparentForMouseEvents);
// end solution with WA_TransparentForMouseEvents
}
~TransparentWidget(){}
protected:
QRect opaqueRect = QRect(0,0,400,100);
void paintEvent(QPaintEvent *)
{
// Solution with setMask, not working
QRegion reg(opaqueRect);
setMask(reg);
// end of setMask solution
QStyleOption opt;
opt.init(this);
QPainter p(this);
style()->drawPrimitive(QStyle::PE_Widget, &opt, &p, this);
}
bool event(QEvent *event)
{
// Starting of solution with event propagation (not working)
if (event->type() == QEvent::MouseButtonPress ||
event->type() == QEvent::MouseButtonRelease)
{
QMouseEvent* e = static_cast<QMouseEvent*>(event);
if (e && !opaqueRect.contains(e->pos()) return false;
}
// end solution with event propagation.
if (event->type() == QEvent::MouseButtonPress) qDebug() << "Press: " << objectName();
else if(event->type() == QEvent::MouseButtonRelease) qDebug() << "Release: " << objectName();
return QWidget::event(event);
}
};
#endif
normalwidget.hpp
#ifndef NORMALWIDGET_H
#define NORMALWIDGET_H
#include <QWidget>
#include <QStyleOption>
#include <QPainter>
#include <QDebug>
#include <QEvent>
// Widgets that are not event-transparent
class NormalWidget : public QWidget
{
Q_OBJECT
public:
explicit NormalWidget(QWidget *parent = 0): QWidget(parent){}
~NormalWidget(){}
protected:
void paintEvent(QPaintEvent *)
{
QStyleOption opt;
opt.init(this);
QPainter p(this);
style()->drawPrimitive(QStyle::PE_Widget, &opt, &p, this);
}
bool event(QEvent *event)
{
if (event->type() == QEvent::MouseButtonPress) qDebug() << "Press: " << objectName();
else if(event->type() == QEvent::MouseButtonRelease) qDebug() << "Release: " << objectName();
}
};
#endif // NORMALWIDGET_H
Like the docs say:
When enabled, this attribute disables the delivery of mouse events to
the widget and its children.
The solution is to ignore all the mouse events inside TransparentWidget::event(). If you do a mouse event over a child of TransparentWidget, the event will be consumed by the child, otherwise it will be delivered to the parent of TransparentWidget:
bool TransparentWidget::event(QEvent *event)
{
if (event->type() == QEvent::MouseButtonPress ||
event->type() == QEvent::MouseButtonRelease ||
event->type() == QEvent::MouseButtonRelease)
return false;
else
return QWidget::event(event);
}
Related
I have a scene that extends QGraphicsScene and has an event filter to handle all my scene logic and subsequent hover events etc happen correctly unless the mouse button is held down.
For example I have child elements in groups that have hoverEnterEvent and hoverLeaveEvent that get called correctly unless I'm holding the mouse. I've tried adjusting the code according to some answers and the documentation but I think I may be overlooking something really simple like a scene setting
The element header:
#ifndef IOELEMENT_H
#define IOELEMENT_H
#include <QGraphicsEllipseItem>
#include <QGraphicsItemGroup>
#include <QPen>
class IOElement : public QGraphicsEllipseItem {
public:
IOElement(QGraphicsItemGroup *parent=nullptr);
...
protected:
virtual void hoverEnterEvent(QGraphicsSceneHoverEvent *event) override;
virtual void hoverLeaveEvent(QGraphicsSceneHoverEvent *event) override;
...
};
#endif // IOELEMENT_H
The element cpp:
#include "ioelement.h"
#include <QBrush>
#include <QDebug>
#include "../nodescene.h"
#include "../nodestyles.h"
IOElement::IOElement(QGraphicsItemGroup *parent) : QGraphicsEllipseItem(parent) {
...
setAcceptHoverEvents(true);
setFlags(QGraphicsItem::GraphicsItemFlag::ItemIsSelectable |
QGraphicsItem::GraphicsItemFlag::ItemSendsScenePositionChanges |
QGraphicsItem::ItemSendsGeometryChanges);
}
void IOElement::hoverEnterEvent(QGraphicsSceneHoverEvent *event) {
NodeScene *nodeScene = dynamic_cast<NodeScene *>(scene());
nodeScene->setChildHover(this);
QPen pen;
pen.setColor(NodeStyles::Color::Pen_Hover_Normie);
pen.setWidth(2);
setPen(pen);
update();
QGraphicsEllipseItem::hoverEnterEvent(event);
}
void IOElement::hoverLeaveEvent(QGraphicsSceneHoverEvent *event) {
NodeScene *nodeScene = dynamic_cast<NodeScene *>(scene());
nodeScene->setChildHover(nullptr);
QPen pen;
pen.setColor(NodeStyles::Color::IO_Pen_Normie);
pen.setWidth(1);
setPen(pen);
update();
QGraphicsEllipseItem::hoverLeaveEvent(event);
}
These hover routines work as expected and I can handle the rest of the mouse logic in the scene with a filter like so
The scene header:
#ifndef NODESCENE_H
#define NODESCENE_H
#include <QGraphicsScene>
class NodeScene : public QGraphicsScene {
Q_OBJECT
public:
using QGraphicsScene::QGraphicsScene;
explicit NodeScene(QObject *parent = nullptr);
...
IOElement * getChildHover() const;
void setChildHover(IOElement * hover=nullptr);
protected:
bool eventFilter(QObject *watched, QEvent *event);
private:
...
IOElement *hoverIO;
...
};
#endif // NODESCENE_H
The scene cpp:
#include "nodescene.h"
...
#include <QGraphicsSceneMouseEvent>
#include <QKeyEvent>
#include <QDebug>
NodeScene::NodeScene(QObject *parent) : QGraphicsScene(parent) {
...
// Events
installEventFilter(this);
}
IOElement * NodeScene::getChildHover() const { return hoverIO; }
void NodeScene::setChildHover(IOElement * hover) {
hoverIO = hover;
}
bool NodeScene::eventFilter(QObject *watched, QEvent *event) {
if(watched == this) {
QGraphicsSceneMouseEvent *mouseSceneEvent;
if(event->type() == QEvent::GraphicsSceneMousePress) { // Mouse Down
if(hoverIO) {
if(hoverIO->IsOutput()) {
activeConnection = new Connection(this, hoverIO->scenePos().x()+6, hoverIO->scenePos().y()+6);
} else {
// Check if connected first
qDebug() << "Grabbing intput";
}
}
}
else if (event->type() == QEvent::GraphicsSceneMouseRelease) { // Mouse Up
delete activeConnection;
activeConnection = nullptr;
}
else if (event->type() == QEvent::GraphicsSceneMouseMove) { // Mouse Move
mouseSceneEvent = static_cast<QGraphicsSceneMouseEvent *>(event);
int x = mouseSceneEvent->lastScenePos().rx();
int y = mouseSceneEvent->lastScenePos().ry();
if(activeConnection) {
activeConnection->DrawTo(x, y);
}
}
QKeyEvent *keyEvent;
if(event->type() == QEvent::KeyPress) { // Key Press
keyEvent = static_cast<QKeyEvent *>(event);
qDebug() << keyEvent->key();
}
}
return QGraphicsScene::eventFilter(watched, event);
}
When the mouse buttons are pressed nothing at all triggers in the hover events
By default, QSlider move his thumbtrack by a value belonging to the pageStep() prop on mouse click. To make thumbtrack jump directly at the mouse click point, we need to create a new class inherited by QSlider.
.h
#ifndef QIMPROVEDSLIDER_H
#define QIMPROVEDSLIDER_H
#include <QSlider>
class QImprovedSlider : public QSlider
{
Q_OBJECT
protected:
void mousePressEvent(QMouseEvent *event) override;
void mouseReleaseEvent(QMouseEvent *event) override;
public:
explicit QImprovedSlider(QWidget *parent = 0);
signals:
void clicked(int value) const;
};
#endif // QIMPROVEDSLIDER_H
.cpp
#include <QWidget>
#include <QMouseEvent>
#include <QStyle>
#include <QStyleOptionSlider>
#include <QProxyStyle>
#include "QImprovedSlider.h"
class QImprovedSliderStyle : public QProxyStyle
{
public:
using QProxyStyle::QProxyStyle;
int styleHint(QStyle::StyleHint hint, const QStyleOption* option = 0,
const QWidget* widget = 0, QStyleHintReturn* returnData = 0) const
{
if (hint == QStyle::SH_Slider_AbsoluteSetButtons)
return (Qt::LeftButton | Qt::MidButton | Qt::RightButton);
return QProxyStyle::styleHint(hint, option, widget, returnData);
}
};
QImprovedSlider::QImprovedSlider(QWidget *parent) :
QSlider(parent)
{
setStyle(new QImprovedSliderStyle(this->style()));
}
void QImprovedSlider::mousePressEvent(QMouseEvent *event) {
QStyleOptionSlider opt;
initStyleOption(&opt);
QRect sr = style()->subControlRect(QStyle::CC_Slider,
&opt,
QStyle::SC_SliderHandle,
this);
qDebug() << sr.height() << sr.width();
if (!sr.contains(event->pos()) && event->button() == Qt::LeftButton) {
if (orientation() == Qt::Vertical)
setValue(minimum() + ((maximum()-minimum()) * (height()-event->y())) / height() ) ;
else
setValue(minimum() + ((maximum()-minimum()) * event->x()) / width() ) ;
}
QSlider::mousePressEvent(event);
}
void QImprovedSlider::mouseReleaseEvent(QMouseEvent *event) {
if (event->button() == Qt::LeftButton) {
emit clicked(value());
QSlider::mouseReleaseEvent(event);
}
}
QImprovedSliderStyle make the handle drag more fluid, but in this way, the event is fired even when the click falls inside the handle, while the condition
!sr.contains(event->pos())
should avoid this.
I would like to get mouse position inside my QListWidget. The tracking is fine when mouse hovers over all other QWidgets - QMainWindow, QPushButton, CentralWidget, etc., except QListWidget.
c++ file: test_1.cpp
#include "test_1.h"
#include "ui_test_1.h"
test_1::test_1(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::test_1)
{
ui->setupUi(this);
this->setMouseTracking(true);
ui->centralWidget->setMouseTracking(true);
ui->listWidget->setMouseTracking(true);
ui->pushButton->setMouseTracking(true);
ui->listWidget->addItem("aaa");
ui->listWidget->addItem("bbb");
ui->listWidget->addItem("ccc");
ui->listWidget->addItem("ddd");
ui->listWidget->addItem("eee");
}
void test_1::mouseMoveEvent(QMouseEvent *event)
{
qDebug() << event->pos();
}
test_1::~test_1()
{
delete ui;
}
Header file: test_1.h
#ifndef TEST_1_H
#define TEST_1_H
#include <QMainWindow>
#include <QDebug>
#include <QMouseEvent>
namespace Ui {
class test_1;
}
class test_1 : public QMainWindow
{
Q_OBJECT
public:
explicit test_1(QWidget *parent = 0);
~test_1();
private:
Ui::test_1 *ui;
void mouseMoveEvent(QMouseEvent*);
};
#endif // TEST_1_H
Main: main.cpp
#include "test_1.h"
#include <QApplication>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
test_1 w;
w.show();
return a.exec();
}
Output:
QPoint(359,141)
QPoint(358,141)
QPoint(357,140)
QPoint(356,140)
QPoint(355,140)
QPoint(354,139)
QPoint(353,139)
QPoint(352,139)
QPoint(351,139)
void test_2::mouseMoveEvent(QMouseEvent *event)
{
QPoint p = event->pos();
QRect widgetRect = ui->listWidget->rect();
if(widgetRect.contains(p))
{
qDebug() << "Inside";
ui->listWidget->grabMouse();
}
else
{
qDebug() << "Outside";
ui->listWidget->releaseMouse();
}
}
The right way of solving this is inheriting QListWidget and implementing void mouseMoveEvent(QMouseEvent *event)
But you have also another option, like installing an event filter on your QListWidget.
Add this in your contructor:
ui->listWidget->viewport()->installEventFilter(this);
And implement the event filter:
bool MainWindow::eventFilter(QObject *obj, QEvent *event)
{
if(event->type() == QEvent::MouseButtonPress)
{
qDebug() << Q_FUNC_INFO << "QEvent::MouseButtonPress";
}
if(event->type() == QEvent::MouseMove)
{
qDebug() << Q_FUNC_INFO << " pos: " << this->mapFromGlobal(QCursor::pos());
}
return false;
}
The key event listener or the function repaint() are not working, I've tried calling paintEvent(QPaintEvent *) directly but it didn't work either, thanks to further answers.
Here's the code:
window.h
#ifndef WINDOW_H
#define WINDOW_H
#include <QWidget>
#include <QPainter>
#include <QPaintEvent>
#include <QRectF>
namespace Ui {
class Window;
}
class Window : public QWidget
{
Q_OBJECT
public:
explicit Window(QWidget *parent = 0);
~Window();
void paintEvent(QPaintEvent *);
void keyPressEvent(QKeyEvent * );
private:
Ui::Window *ui;
QRectF player;
QPainter * painter = new QPainter(this);
};
#endif // WINDOW_H
window.cpp
#include "window.h"
#include "ui_window.h"
Window::Window(QWidget *parent) :
QWidget(parent),
ui(new Ui::Window)
{
ui->setupUi(this);
player.setX(0);
player.setY(0);
player.setWidth(50);
player.setHeight(50);
}
Window::~Window()
{
delete ui;
}
void Window::paintEvent(QPaintEvent *)
{
QPainter painter(this);
painter.setPen(QPen(Qt::black, 3, Qt::DashDotLine, Qt::RoundCap));
painter.setBrush(QBrush(Qt::blue));
painter.setRenderHint(QPainter::Antialiasing, true);
painter.drawEllipse(player.x(), player.y(), player.width(), player.height());
}
void Window::keyPressEvent(QKeyEvent * event)
{
if(event->type() == Qt::Key_W)
player.setY(player.y() - 1);
if(event->type() == Qt::Key_S)
player.setY(player.y() + 1);
if(event->type() == Qt::Key_A)
player.setX(player.x() - 1);
if(event->type() == Qt::Key_D)
player.setX(player.x() + 1);
repaint();
}
main.cpp
#include "window.h"
#include <QApplication>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
Window w;
w.show();
return a.exec();
}
In Window::keyPressEvent, replace event->type() with event->key() everywhere.
Also, take a look at the compiler output. I think it should have warned you about comparing QEvent::Type with Qt::Key objects.
In my QWidget there are some subwidgets like a QLineEdit and QLabels. I can easily check if my mouse is over a QLabel and if it was clicked on the right button. Not so on QLineEdit.
I tried to subclass QLineEdit and re-implement the mouseRelease, but it is never called.
The findChild method is to get the corresponding widget out off my UI.
How do I get the mouseRelease and whether it's left or right mouse button in a QLineEdit?
void Q_new_LineEdit::mouseReleaseEvent(QMouseEvent *e){
qDebug() << "found release";
QLineEdit::mouseReleaseEvent(e);
}
m_titleEdit = new Q_new_LineEdit();
m_titleEdit = findChild<QLineEdit *>("titleEdit",Qt::FindChildrenRecursively);
Clicks on labels are recognized, but the click on QLineEdit is not, like below:
void GripMenu::mouseReleaseEvent(QMouseEvent *event){
if (event->button()==Qt::RightButton){
//get click on QLineEdit
if (uiGrip->titleEdit->underMouse()){
//DO STH... But is never called
}
//change color of Label ...
if (uiGrip->col1note->underMouse()){
//DO STH...
}
}
I seem to be able to detect clicks on the line edit and distinguish which type it is in the class posted below which is very similar to what has been posted in the mentioned link
#ifndef MYDIALOG_H
#define MYDIALOG_H
#include <QDialog>
#include <QMouseEvent>
#include <QLineEdit>
#include <QHBoxLayout>
#include <QtCore>
class MyClass: public QDialog
{
Q_OBJECT
public:
MyClass() :
layout(new QHBoxLayout),
lineEdit(new QLineEdit)
{
layout->addWidget(lineEdit);
this->setLayout(layout);
lineEdit->installEventFilter(this);
}
bool eventFilter(QObject* object, QEvent* event)
{
if(object == lineEdit && event->type() == QEvent::MouseButtonPress) {
QMouseEvent *k = static_cast<QMouseEvent *> (event);
if( k->button() == Qt::LeftButton ) {
qDebug() << "Left click";
} else if ( k->button() == Qt::RightButton ) {
qDebug() << "Right click";
}
}
return false;
}
private:
QHBoxLayout *layout;
QLineEdit *lineEdit;
};
#endif
main.cpp for completeness
#include "QApplication"
#include "myclass.h"
int main(int argc, char** argv)
{
QApplication app(argc, argv);
MyClass dialog;
dialog.show();
return app.exec();
}