Receive mouse inputs in inactive region - c++

I have horizontal slider element which is disabled. I want to enable it when I click on it. As the slider is disabled I am unable to capture mouse events on it.
Thanks in advance

I must admit that my first suggestion was wrong:
However, if the slider is disabled the mouse event will be captured probably by something else - I would expect the parent widget.
That expectation was wrong.
I found out about this by trying myself in an MCVE.
For my luck, the other option – using an event filter – does work.
Sample testQClickDisabled.cc:
// Qt header:
#include <QtWidgets>
void populate(QListWidget &qLst)
{
for (int i = 1; i <= 20; ++i) {
qLst.addItem(QString("item %0").arg(i));
}
}
class EventFilter: public QObject {
private:
QListWidget &qLst;
public:
EventFilter(QListWidget &qLst): QObject(), qLst(qLst)
{
qApp->installEventFilter(this);
}
~EventFilter() { qApp->removeEventFilter(this); }
EventFilter(const EventFilter&) = delete;
EventFilter& operator=(const EventFilter&) = delete;
protected:
virtual bool eventFilter(QObject *pQObj, QEvent *pQEvent) override;
};
bool EventFilter::eventFilter(QObject *pQObj, QEvent *pQEvent)
{
if (QScrollBar *const pQScrBar = dynamic_cast<QScrollBar*>(pQObj)) {
if (pQScrBar == qLst.verticalScrollBar()
&& pQEvent->type() == QEvent::MouseButtonPress) {
qDebug() << "Vertical scrollbar hit.";
pQScrBar->setEnabled(true);
}
}
return QObject::eventFilter(pQObj, pQEvent);
}
// main application
int main(int argc, char **argv)
{
qDebug() << "Qt Version:" << QT_VERSION_STR;
QApplication app(argc, argv);
// setup GUI
QListWidget qLst;
qLst.setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOn);
qLst.verticalScrollBar()->setEnabled(false);
qLst.resize(200, 200);
qLst.show();
populate(qLst);
EventFilter qEventFilter(qLst);
// runtime loop
return app.exec();
}
Output:

I would just use a sibling MouseArea, like this:
Slider {
id: slider
enabled: false
}
MouseArea {
anchors.fill: slider
visible: !slider.enabled
onClicked: slider.enabled = true
}

Related

How do I capture the key press event for an edit cell of a QTableWidget?

Here are 2 answers for capturing the key press event for QTableWidget.
How to create a SIGNAL for QTableWidget from keyboard?
Follow the way above, I can "hook" key press event, When I press space, the background color becomes red.
However, it only works for a selected cell, but not for a in-editing cell.
When it's in editing state, the 2 ways both fail. I can type space freely.
When it's in "editing state" there is editor widget atop QTableWidget (item delegate) which receives key events, and since it's on top you can't see cell content and cell background behind it. But you can access and "hook" this events by setting QAbstractItemDelegate to QTableWidget.
// itemdelegate.h
class ItemDelegate : public QStyledItemDelegate
{
Q_OBJECT
public:
explicit ItemDelegate(QObject *parent = nullptr) : QStyledItemDelegate(parent)
{
}
bool eventFilter(QObject *object, QEvent *event) override
{
if (event->type() == QEvent::KeyPress) {
QKeyEvent* keyEvent = static_cast<QKeyEvent*>(event);
if (keyEvent->key() == Qt::Key_Space) {
qDebug() << "space pressed";
}
}
return false;
}
};
// main.cpp
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
QTableWidget* widget = new QTableWidget();
widget->setColumnCount(2);
widget->setRowCount(2);
widget->setItemDelegate(new ItemDelegate(widget));
widget->show();
return a.exec();
}

How to prevent default context menu on QGraphicsTextItem?

Is it possible to prevent right-click from opening the default context menu on QGraphicsTextItem ? The menu with "Undo, Redo, Cut, Copy, Paste..". On Ubuntu 18.04, that is. I don't know how this behaves on Windows.
I have overridden the mouse press handler to eat right-clicks in my view and tried to do that also in the item class itself. This actually did prevent the menu on Qt 5.10.0, but for some reason not anymore on 5.11.1:
void EditorView::mousePressEvent(QMouseEvent * event)
{
if (event->button() == Qt::RightButton)
{
return;
}
...
doOtherHandlingStuff();
...
}
In the item itself it doesn't have any effect if I do this:
void TextEdit::mousePressEvent(QGraphicsSceneMouseEvent * event)
{
event->ignore();
return;
}
You have to override the contextMenuEvent method of QGraphicsTextItem:
#include <QtWidgets>
class GraphicsTextItem: public QGraphicsTextItem
{
public:
using QGraphicsTextItem::QGraphicsTextItem;
protected:
void contextMenuEvent(QGraphicsSceneContextMenuEvent *event) override
{
event->ignore();
}
};
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
QGraphicsScene scene;
QGraphicsView w{&scene};
auto it = new GraphicsTextItem("Hello World");
it->setTextInteractionFlags(Qt::TextEditable);
scene.addItem(it);
w.show();
return a.exec();
}

How to directly detect the window position?

I'm coding in c++ with Qt creator. I can get all windows on the screen but I would like to detect their movement directly. Is there any way to detect the movement thanks to a signal emitted by the window ?
You can try creating a QWindow from the target window and then wrap it in a QWidget using QWidget::createWindowContainer().
You can have a look at this QTBUG thread : https://bugreports.qt.io/browse/QTBUG-40320
It needs some effort to make it work properly. The captured window doesn't keep its initial dimensions, and releasing the window behave weirdly. Read the last QTBUG comments to find an improvement.
I added an event filter to this code to capture the window position in real time. But that might be unsatisfying.
class EventFilter : public QObject
{
Q_OBJECT
public:
EventFilter(){}
virtual ~EventFilter(){}
protected:
bool eventFilter(QObject *obj, QEvent *event);
} ;
bool EventFilter::eventFilter(QObject *obj, QEvent *event)
{
qDebug() << event->type() ;
if (event->type() == QEvent::Move) {
QMoveEvent *moveEvent = static_cast<QMoveEvent *>(event);
qDebug() << "position" << moveEvent->pos() ;
return true;
} else {
// standard event processing
return QObject::eventFilter(obj, event);
}
}
I removed part 3 from the QTBUG snippet and installed the event handler on the inner widget. You can also remove the timers.
// From https://bugreports.qt.io/browse/QTBUG-40320
int main(int argc, char *argv[])
{
// Windows: Find HWND by window title
WId id = (WId)FindWindow(NULL, L"Calculator");
if (!id)
return -1;
QApplication a(argc, argv);
// Optional
QTimer t;
t.start(2500);
// Part 1
QWindow* window = QWindow::fromWinId(id);
window->show();
window->requestActivate();
// Optional
QObject::connect(&t, &QTimer::timeout, [=]
{
qDebug() << "=== Inner QWindow ===";
qDebug() << "Geometry:" << window->geometry();
qDebug() << "Active?:" << window->isActive();
qDebug() << "Flags:" << window->flags();
});
// Part 2
QWidget* widget = QWidget::createWindowContainer(window);
widget->show();
// Optional
QObject::connect(&t, &QTimer::timeout, [=]
{
qDebug() << "=== Outer QWidget ===";
qDebug() << "Geometry:" << widget->geometry();
qDebug() << "Active?" << widget->isActiveWindow();
qDebug() << "Flags:" << widget->windowFlags();
});
// Realtime position
EventFilter filter ;
widget->installEventFilter( &filter ) ;
return a.exec();
}
Output:
=== Inner QWindow ===
Geometry: QRect(0,0 640x480)
Active?: true
Flags: QFlags<Qt::WindowType>(ForeignWindow)
=== Outer QWidget ===
Geometry: QRect(2489,29 640x480)
Active? true
Flags: QFlags<Qt::WindowType>(Window|WindowTitleHint|WindowSystemMenuHint|WindowMinMaxButtonsHint|WindowCloseButtonHint)
QEvent::Type(Move)
position QPoint(2484,29)
QEvent::Type(Move)
position QPoint(2481,30)
QEvent::Type(Move)
position QPoint(2478,31)
QEvent::Type(Move)
position QPoint(2474,31)
See also http://blog.qt.io/blog/2013/02/19/introducing-qwidgetcreatewindowcontainer/

Rate limit of callbacks when control is modified

Let's say I have a slider control and my user is sliding it back and forth really fast.
Is it possible to limit the rate at which QML calls the "new value available" C++ callback?
If you want to completely avoid the value being updated while the slider is dragged, you can use the updateValueWhileDragging property in Qt Quick Controls 1, and the live property in Qt Quick Controls 2.
In Qt Quick Controls 2, the slider controls have a valueAt() function which can be called to check the value at any time.
If you're writing your own slider in QML, you could limit the change signal emission using a Timer, for example:
property int value
readonly property int actualValue: // some calculation...
Timer {
running: slider.pressed
interval: 200
repeat: true
onTriggered: slider.value = slider.actualValue
}
Here's a generic C++-side solution that works with any QObject:
// https://github.com/KubaO/stackoverflown/tree/master/questions/qml-rate-limter-42284163
#include <QtCore>
class PropertyRateLimiter : public QObject {
Q_OBJECT
qint64 msecsPeriod{500};
const QByteArray property;
bool dirty{};
QVariant value;
QElapsedTimer time;
QBasicTimer timer;
QMetaMethod slot = metaObject()->method(metaObject()->indexOfSlot("onChange()"));
void signal() {
if (time.isValid()) time.restart(); else time.start();
if (dirty)
emit valueChanged(value, parent(), property);
else
timer.stop();
dirty = false;
}
Q_SLOT void onChange() {
dirty = true;
value = parent()->property(property);
auto elapsed = time.isValid() ? time.elapsed() : 0;
if (!time.isValid() || elapsed >= msecsPeriod)
signal();
else
if (!timer.isActive())
timer.start(msecsPeriod - elapsed, this);
}
void timerEvent(QTimerEvent *event) override {
if (timer.timerId() == event->timerId())
signal();
}
public:
PropertyRateLimiter(const char * propertyName, QObject * parent) :
QObject{parent}, property{propertyName}
{
auto mo = parent->metaObject();
auto property = mo->property(mo->indexOfProperty(this->property));
if (!property.hasNotifySignal())
return;
connect(parent, property.notifySignal(), this, slot);
}
void setPeriod(int period) { msecsPeriod = period; }
Q_SIGNAL void valueChanged(const QVariant &, QObject *, const QByteArray & name);
};
#include "main.moc"
And a test harness for it:
#include <QtQuick>
const char qmlData[] =
R"__end(
import QtQuick 2.6
import QtQuick.Controls 2.0
ApplicationWindow {
minimumWidth: 300
minimumHeight: 250
visible: true
Column {
anchors.fill: parent
Slider { objectName: "slider" }
Label { objectName: "label" }
}
}
)__end";
int main(int argc, char ** argv) {
QGuiApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
QGuiApplication app{argc, argv};
QQmlApplicationEngine engine;
engine.loadData(QByteArray::fromRawData(qmlData, sizeof(qmlData)-1));
auto window = engine.rootObjects().first();
auto slider = window->findChild<QObject*>("slider");
auto label = window->findChild<QObject*>("label");
PropertyRateLimiter limiter("position", slider);
QObject::connect(&limiter, &PropertyRateLimiter::valueChanged, [&](const QVariant & val){
label->setProperty("text", val);
});
return app.exec();
}

How to execute the mouseevent of qgraphicsitem only ? (ignoring the rest of mouseevent of qgraphicsview)

I know how to pass events from qgraphicsview to qgraphicsitem.
But the problem is after executing the mouseEvent of item ,i have to execute the mouse event of the view which is not desirable in my case.
so,the question is: "Is there a smart way to know if mousePress is on an item or on an empty space?"
Edit:The working code:
#include <QtGui>
class CustomView : public QGraphicsView
{
protected:
void mousePressEvent(QMouseEvent *event)
{
QGraphicsView::mousePressEvent(event);
qDebug() << "Custom view clicked.";
}
};
class CustomItem : public QGraphicsRectItem
{
protected:
void mousePressEvent(QGraphicsSceneMouseEvent *event)
{
qDebug() << "Custom item clicked.";
}
};
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
CustomItem item;
item.setRect(20, 20, 60, 60);
QGraphicsScene scene(0, 0, 100, 100);
scene.addItem(&item);
CustomView view;
view.setScene(&scene);
view.show();
return a.exec();
}
Appling the same concept in a qgraphics scene instead of view
#include <QtGui>
class CustomScene : public QGraphicsScene
{
protected:
void mousePressEvent(QGraphicsSceneMouseEvent *event)
{
if(itemAt(event->scenePos()))
QGraphicsScene::mousePressEvent((event));
else
qDebug() << "Custom view clicked.";
}
};
class CustomItem : public QGraphicsRectItem
{
protected:
void mousePressEvent(QGraphicsSceneMouseEvent *event)
{
qDebug() << "Custom item clicked.";
}
};
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
CustomItem item;
item.setRect(20, 20, 60, 60);
CustomScene scene;
//scene().set
scene.addItem(&item);
QGraphicsView view;
view.setScene(&scene);
view.show();
return a.exec();
}
There are 3 correct ways to solve your task:
1. Reimplement QGraphicsView::mousePressEvent and use QGraphicsView::itemAt to find clicked item.
2. Subclass QGraphicsScene and reimplement QGraphicsScene::mousePressEvent. Its argument event contains position in scene coordinates, and you can use QGraphicsScene::itemAt to determine which item has been clicked.
3. Subclass QGraphicsItem (or any derived class) and reimplement QGraphicsItem::mousePressEvent. It will be called only if this element was clicked.
To determine wether mouse event occured on item or not you can use QGraphicsView::itemAt:
void CustomView::mousePressEvent(QMouseEvent *event)
{
if (itemAt(event->pos()))
// Click on item
else
// Click on empty space
...
}