QPlainTextEdit double click event - qt

I need to capture the double click event on a QPlainTextEdit that is inside a QDockWidget.
In my actual code I have installed an event filter in the QDockWidget, to handle resize operations, and in the QPlainTextEdit, to handle the double click events:
// Resize eventfilter
this->installEventFilter(this);
ui->myPlainTextEdit->installEventFilter(this);
But, although it works for the QDockWidget I am unable to catch the double click event for the QPlainTextEdit:
bool MyDockWidget::eventFilter(QObject *obj, QEvent *event) {
if (event->type() == QEvent::Resize && obj == this) {
QResizeEvent *resizeEvent = static_cast<QResizeEvent*>(event);
qDebug("Dock Resized (New Size) - Width: %d Height: %d",
resizeEvent->size().width(),
resizeEvent->size().height());
} else if (obj == ui->myPlainTextEdit && event->type() == QMouseEvent::MouseButtonDblClick) {
qDebug() << "Double click";
}
return QWidget::eventFilter(obj, event);
}
With this code the message "Double click" is never shown. Any idea what is wrong with the code?

QTextEdit inherits a QScrollView and when you double click on the viewport of the QTextEdit, the viewport receives the double click event. You can cross check your current code by double clicking on the edges of the text edit. It will capture the event.
To solve this, add the event filter to the view port in addition to the current event filters you have installed as shown below:
ui->myPlainTextEdit->viewport()->installEventFilter(this);
Next, capture the event using this if statement:
if ((obj == ui->myPlainTextEdit||obj==ui->myPlainTextEdit->viewport()) &&
event->type() == QEvent::MouseButtonDblClick)
{
qDebug() << "Double click"<<obj->objectName();
}
You can capture the click position using QMouseEvent:
QMouseEvent *mouseEvent = static_cast<QMouseEvent*>(event);
qDebug()<<QString("Click location: (%1,%2)").arg(mouseEvent->x()).arg(mouseEvent->y());

Related

How can I intercept and cancel the minimizing of a Window?

I have a Window subclass in my project, and at runtime the instance is created and shown entirely on the QML side. I know that I can prevent the window from being minimized by not including the WindowMinimizeButtonHint in the flags:, but I actually need to have the minimize button present and enabled but be able to intercept the minimize button click, cancel the actual minimizing, and do something else (FYI my client is requiring this non-standard windowing behavior, not me).
So far, the only thing I've been able to achieve is to handle the onWindowStateChanged: event, check if windowState === Qt.WindowStateMinimized and call show() from a timer (calling it inside the event handler directly does nothing). This results in the window moving down to the system tray and then suddenly coming back up to normal.
Is there any way to do this, something like an OnMinimized event that can be cancelled?
Edit: based on Benjamin T's answer, I'm at least part way to a solution for OSX:
#import <AppKit/AppKit.h>
bool NativeFilter::nativeEventFilter(const QByteArray &eventType,
void *message, long *result)
{
if (eventType == "mac_generic_NSEvent") {
NSEvent *event = static_cast<NSEvent *>(message);
if ([event type] == NSKeyDown) {
return true;
}
}
return false;
}
In this example I'm able to intercept and cancel all NSKeyDown events (while leaving other events like mouse clicks etc. still working). The remaining problem is that I still don't know to intercept a minimize event - NSEvent.h doesn't seem to have anything that covers that. Perhaps I need to cast to a different type of event?
Edit 2 - working solution:
I was not able to find any way to intercept the minimize event proper and cancel it, so my workaround is to instead intercept the click on the window, determine if the click is over the minimize button (or the close or zoom buttons) and cancel the event if so (and send a notification to my qml window that the click occurred). I also handle the case of double-clicking the titlebar to zoom the window, and using the Command-M keys to minimize the window.
First step is to implement a QAbstractNativeEventFilter. In your header:
#include <QAbstractNativeEventFilter>
class NativeFilter : public QAbstractNativeEventFilter {
public:
bool nativeEventFilter(const QByteArray &eventType, void *message,
long *result);
};
The implementation:
#import <AppKit/AppKit.h>
#import <AppKit/NSWindow.h>
#import <AppKit/NSButton.h>
bool NativeFilter::nativeEventFilter(const QByteArray &eventType, void
*message, long *result)
{
if (eventType == "mac_generic_NSEvent") {
NSEvent *event = static_cast<NSEvent *>(message);
NSWindow *win = [event window];
// TODO: determine whether or not this is a window whose
// events you want to intercept. I did this by checking
// [win title] but you may want to find and use the
// window's id instead.
// Detect a double-click on the titlebar. If the zoom button
// is enabled, send the full-screen message to the window
if ([event type] == NSLeftMouseUp) {
if ([event clickCount] > 1) {
NSPoint pt = [event locationInWindow];
CGRect rect = [win frame];
// event coordinates have y going in the opposite direction from frame coordinates, very annoying
CGFloat yInverted = rect.size.height - pt.y;
if (yInverted <= 20) {
// TODO: need the proper metrics for the height of the title bar
NSButton *btn = [win standardWindowButton:NSWindowZoomButton];
if (btn.enabled) {
// notify qml of zoom button click
}
return true;
}
}
}
if ([event type] == NSKeyDown) {
// detect command-M (for minimize app)
if ([event modifierFlags] & NSCommandKeyMask) {
// M key
if ([event keyCode] == 46) {
// notify qml of miniaturize button click
return true;
}
}
// TODO: we may be requested to handle keyboard actions for close and zoom buttons. e.g. ctrl-cmd-F is zoom, I think,
// and Command-H is hide.
}
if ([event type] == NSLeftMouseDown) {
NSPoint pt = [event locationInWindow];
CGRect rect = [win frame];
// event coordinates have y going in the opposite direction from frame coordinates, very annoying
CGFloat yInverted = rect.size.height - pt.y;
NSButton *btn = [win standardWindowButton:NSWindowMiniaturizeButton];
CGRect rectButton = [btn frame];
if ((yInverted >= rectButton.origin.y) && (yInverted <= (rectButton.origin.y + rectButton.size.height))) {
if ((pt.x >= rectButton.origin.x) && (pt.x <= (rectButton.origin.x + rectButton.size.width))) {
// notify .qml of miniaturize button click
return true;
}
}
btn = [win standardWindowButton:NSWindowZoomButton];
rectButton = [btn frame];
if (btn.enabled) {
if ((yInverted >= rectButton.origin.y) && (yInverted <= (rectButton.origin.y + rectButton.size.height))) {
if ((pt.x >= rectButton.origin.x) && (pt.x <= (rectButton.origin.x + rectButton.size.width))) {
// notify qml of zoom button click
return true;
}
}
}
btn = [win standardWindowButton:NSWindowCloseButton];
rectButton = [btn frame];
if ((yInverted >= rectButton.origin.y) && (yInverted <= (rectButton.origin.y + rectButton.size.height))) {
if ((pt.x >= rectButton.origin.x) && (pt.x <= (rectButton.origin.x + rectButton.size.width))) {
// notify qml of close button click
return true;
}
}
}
return false;
}
return false;
}
Then in main.cpp:
Application app(argc, argv);
app.installNativeEventFilter(new NativeFilter());
Generally speaking, you should use the event system ans not signal/slots to intercept events and changes.
The easiest way to do so is either to subclass the object you use and reimplement the appropriate event handler, or to use an event filter.
Since you are using QML, subclassing might be difficult as you don't have access to all Qt internal classes.
Here is what the code would look like when using event filtering.
int main(int argc, char *argv[])
{
QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
QGuiApplication app(argc, argv);
QQmlApplicationEngine engine;
engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
if (engine.rootObjects().isEmpty())
return -1;
auto root = engine.rootObjects().first();
root->installEventFilter(new EventFilter());
return app.exec();
}
class EventFilter : public QObject
{
Q_OBJECT
public:
explicit EventFilter(QObject *parent = nullptr);
bool eventFilter(QObject *watched, QEvent *event) override;
};
bool EventFilter::eventFilter(QObject *watched, QEvent *event)
{
if (event->type() == QEvent::WindowStateChange) {
auto e = static_cast<QWindowStateChangeEvent *>(event);
auto window = static_cast<QWindow *>(watched);
if (window->windowStates().testFlag(Qt::WindowMinimized)
&& ! e->oldState().testFlag(Qt::WindowMinimized))
{
// Restore old state
window->setWindowStates(e->oldState());
return true;
}
}
// Do not filter event
return false;
}
However, you will quickly run into the same issue that when using the signal/slot mechanism: Qt only notify you when the window has already been minimized. Meaning that restoring the window at this point will make a hide/show effect.
So you need to go deeper and you a native event filter.
The following code works on Windows, you should adapt it for macOS:
class NativeFilter : public QAbstractNativeEventFilter {
public:
bool nativeEventFilter(const QByteArray &eventType, void *message, long *result);
};
bool NativeFilter::nativeEventFilter(const QByteArray &eventType, void *message, long *result)
{
/* On Windows we interceot the click in the title bar. */
/* If we wait for the minimize event, it is already too late. */
#ifdef Q_OS_WIN
auto msg = static_cast<MSG *>(message);
// Filter out the event when the minimize button is pressed.
if (msg->message == WM_NCLBUTTONDOWN && msg->wParam == HTREDUCE)
return true;
#endif
/* Example macOS code from Qt doc, adapt to your need */
#ifdef Q_OS_MACOS
if (eventType == "mac_generic_NSEvent") {
NSEvent *event = static_cast<NSEvent *>(message);
if ([event type] == NSKeyDown) {
// Handle key event
qDebug() << QString::fromNSString([event characters]);
}
}
#endif
return false;
}
In your main():
QGuiApplication app(argc, argv);
app.installNativeEventFilter(new NativeFilter());
For more info, you can read the Qt documentation about QAbstractNativeEventFilter.
You may need to use QWindow::winId() to check to which window the native events are targeted.
As I am not a macOS developer, I do not know what you can do with NSEvent.
Also it seems the NSWindowDelegate class could be useful to you: https://developer.apple.com/documentation/appkit/nswindowdelegate
If you can retrieve a NSWindow from QWindow::winId(), you should be able to use it.

Mouse Events on QGraphicsview and Object Movement

I implemented a Graphicsview in which I load an Image and the user can add different Item such as rectangle or line. The user has also the possibility to move these objects in the scene. Up to here everything is ok.
Now I tried to implement some mouse events in this way:
bool MainWindow::eventFilter(QObject *obj, QEvent *event)
{
if (obj == ui->graphicsView && enable_mouse_event==true)
{
if (event->type() == QEvent::MouseButtonPress)
{
QMouseEvent *mouseEvent = static_cast<QMouseEvent*>(event);
point_mouse = ui->graphicsView->mapFrom(ui->graphicsView, mouseEvent->pos());
return true;
}
if (event->type() == QEvent::MouseMove)
{
int scroll_x, scroll_y;
scroll_x = ui->graphicsView->horizontalScrollBar()->value();
scroll_y = ui->graphicsView->verticalScrollBar()->value();
QMouseEvent *mouseEvent = static_cast<QMouseEvent*>(event);
point_mouse_move = ui->graphicsView->mapFrom(ui->graphicsView, mouseEvent->pos());
QRect rect(point_mouse_move.x()+scroll_x-50, point_mouse_move.y()+scroll_y-50, 100, 100);
zoom_image = image_clone.copy(rect);
ui->zoom_label->setPixmap(QPixmap::fromImage(zoom_image));
return true;
}
}
return false
}
with in MainWindow.cpp
ui->graphicsView->installEventFilter(this);
In this way the press event is recognized while the move event is recognized only on the border of the Qgraphicview object (the property mouseTracking is set to true) and I'm still able to move the other object.
To solve the problem of the move event I tried to modify the code by adding:
->viewport()
to the graphicview object.
In this way both the mouse events (press and move) work correctly but I'm no longer able to move the other objects (rect and line)
Any Idea how can I recognize all the mouse events and at the same time move the other objects??
thanks
The problem was that I was filtering out the events and they never go to the window. Instead of setting the returns true just set the return false!
Thanks to #thuga

Handle mouse events in QListView

I've Dialog that shows folders (in treeView) and files (in listView) respectively. In listView doubleClick signal is handled by a slot that Qt created while I used Designer with aproppriate slot to be implemented. The problem is that I'm not able to handle RIGHT MOUSE click. Is there a solution?
P.S.
I've googled for a while to solve this problem, it seems that inheriting QListView and overriding solve the problem. But in my case I've already populated Qt's standart QListView using Designer.
In this case you can use event filter:
bool MainWindow::eventFilter(QObject *obj, QEvent *event)
{
if (obj == ui->listView->viewport() && event->type() == QEvent::MouseButtonDblClick)
{
QMouseEvent *ev = static_cast<QMouseEvent *>(event);
if (ev->buttons() & Qt::RightButton)
{
qDebug()<< "double clicked" << ev->pos();
qDebug()<< ui->listView->indexAt(ev->pos()).data();
}
}
return QObject::eventFilter(obj, event);
}
To use eventFilter you should also:
protected:
bool eventFilter(QObject *obj, QEvent *event);//in header
and
qApp->installEventFilter(this);//in constructor
Possible addition to your problem. If you want do different things when user clicks left or right mouse buttons you should handle lest and right clicks in filter, without doubleClick signal (because it emits signal in both cases) and your code can be something like:
QMouseEvent *ev = static_cast<QMouseEvent *>(event);
if (ev->buttons() & Qt::RightButton)
{
qDebug()<< "RightButton double clicked";
//do something
}
if (ev->buttons() & Qt::LeftButton)
{
qDebug()<< "LeftButton double clicked";
//do something
}
In my case, I started trying to catch mouse events when a user right-clicked on a line in the QListView, but they never came through. However, all I really wanted to do was popup a context menu, and it turns out the contextMenuEvent did get through! So I didn't have to subclass QListView, just added a contextMenuEvent() to my widget that contained the QListView.
This was Qt3, so your mileage will most definitely differ.

Qt keyPressEvent, "Hold", and keyReleaseEvent Handling with Buttons/Mouse Clicks

I am writing a Qt program to simulate a piece of hardware and I would like to simulate button press, hold, and release events. In my application, I'd like to handle input from both the keyboard and mouse clicks to make things convenient to the user (i.e. me). I've noticed some odd behavior and I don't understand it.
The application uses QPushButton with autoRepeat enabled and a 100 ms autoRepeatDelay and autoRepeatInterval. If I mouse click on a button, I receive alternating "pressed" and "released" events. I would have expected to see 1 to N-1 "pressed" events followed by a "released" event. Why is Qt behaving that way?
I've also implemented the following code to handle button presses from the keyboard:
void MyApp::keyPressEvent(QKeyEvent *event)
{
QString s = QString("My PRESS key is %1. The counter is %2").arg(event->text(), QString::number(keyCounter));
qDebug() << s;
keyCounter++;
}
void MyApp::keyReleaseEvent(QKeyEvent *event)
{
QString s = QString("My RELEASE key is %1. The counter is %2").arg(event->text(), QString::number(keyCounter));
qDebug() << s;
keyCounter = 0;
}
bool MyApp::eventFilter(QObject *obj, QEvent *event)
{
if (event->type() == QEvent::KeyPress)
{
this->keyPressEvent(dynamic_cast<QKeyEvent*>(event));
return true;
}
else if (event->type() == QEvent::KeyRelease)
{
this->keyReleaseEvent(dynamic_cast<QKeyEvent*>(event));
return true;
}
else
{
return QObject::eventFilter(obj, event);
}
}
Here I see two types of behavior. For alphanumeric keys, I see the alternating "pressed" and "released" events. For arrow keys, I only see the "released" events. Again, I would have expected to see 1 to N-1 "pressed" events followed by a "released" event. Why do the arrow keys behave differently than the alphanumeric keys?
Is what I'm trying to do possible in Qt?
Here was my solution: First I disabled autoRepeat and stopped handling keyPressEvents because I found that the arrow keys weren't generating them. Instead I registered shortcuts for the keyboard buttons I wanted to use:
QShortcut *shortcutUp = new QShortcut(QKeySequence("Up"), this);
QObject::connect(shortcutUp, SIGNAL(activated()), this, SLOT(on_upButton_pressed()));
shortcutUp->setAutoRepeat(false);
Then in the on_upButton_pressed() function (for example) I set a flag indicating the button was pressed. This flag is cleared in the on_upButton_released() function. That flag is checked on a 100 ms interval (using a QTimer). If the flag is true, I call on_upButton_pressed() again. This means:
Each mouse click or keyboard button press generates one "Press" event
The "Press" event sets a flag that is checked by a QTimer
If the flag is true, another "Press" event is generated
When the mouse or keyboard button is released, the "Release" event is generated and the flag is cleared.
It's working now.
This is caused by some keys in QT generate auto-repeat event:
Here I see two types of behavior. For alphanumeric keys, I see the
alternating "pressed" and "released" events. For arrow keys, I only
see the "released" events. Again, I would have expected to see 1 to
N-1 "pressed" events followed by a "released" event. Why do the arrow
keys behave differently than the alphanumeric keys?
In QT5 you can detect key held down by function isAutoRepeat() of QKeyEvent object. If the key is held down then isAutoRepeat() will return true. for example:
void MainWindow::keyPressEvent(QKeyEvent *event)
{
if (event->isAutoRepeat())
{
return;
}
if (!event->isAutoRepeat())
{
qDebug() << "[MainWindow::keyPressEvent()] " << event->key() << "; " << event->isAutoRepeat();
}
}
void MainWindow::keyReleaseEvent(QKeyEvent *event)
{
if (event->isAutoRepeat())
{
return;
}
qDebug() << "[MainWindow::keyReleaseEvent()] " << event->key() << "; " << event->isAutoRepeat();
}

Qt mousemoveevent + Qt::LeftButton

Quick question, why does:
void roiwindow::mouseMoveEvent(QGraphicsSceneMouseEvent *event)
{
QGraphicsScene::mouseMoveEvent(event);
qDebug() << event->button();
}
return 0 instead of 1 when I'm holding down the left mouse button while moving the cursor around in a graphicscene. Is there anyway to get this to return 1 so I can tell when a user is dragging the mouse across the graphicscene. Thanks.
Though Spyke's answer is correct, you can just use buttons() (docs). button() returns the mouse button that caused the event, which is why it returns Qt::NoButton; but buttons() returns the buttons held down when the event was fired, which is what you're after.
You can know if the left button was pressed by looking at the buttons property:
if ( e->buttons() & Qt::LeftButton )
{
// left button is held down while moving
}
Hope that helped!
The returned value is always Qt::NoButton for mouse move events. You can use Event filter to solve this.
Try this
bool MainWindow::eventFilter(QObject *object, QEvent *e)
{
if (e->type() == QEvent::MouseButtonPress && QApplication::mouseButtons()==Qt::LeftButton)
{
leftbuttonpressedflag=true;
}
if (e->type() == QEvent::MouseMove)
{
QMouseEvent *mouseEvent = static_cast<QMouseEvent*>(e);
if(leftbuttonpressedflag && mouseEvent->pos()==Inside_Graphics_Scene)
qDebug("MouseDrag On GraphicsScene");
}
return false;
}
And also don't forget to install this event filter in mainwindow.
qApplicationobject->installEventFilter(this);

Resources