Extraneous Information:
I am attempting to build an application using Qt. This application features a QMdiArea and a child-window. My child-window will have a menu which can be integrated into the QMdiArea or segregated and attached to the child itself. Though, this is a bit more detail than needed...
Problem:
I would like my child-widget to have a menu with a shortcut, "CTRL+W." But, because I am using a QMdiArea, the shortcut is already used causing:
QAction::eventFilter: Ambiguous shortcut overload: Ctrl+W
How can I get rid of this shortcut and claim it within my child widget instead?
Update:
Here is what I've tried with no luck:
class MDI : public QMdiArea
{
Q_OBJECT
private:
bool event(QEvent *tEvent)
{
if (tEvent->type() == QEvent::KeyPress)
{
QKeyEvent* ke = static_cast<QKeyEvent*>(tEvent);
if (ke->key()== Qt::Key_W && ke->modifiers() & Qt::ControlModifier)
emit KeyCW();
return true;
}
return QMdiArea::event(tEvent);
}
public:
signals:
void KeyCW();
};
This works if I do something as simple as change Qt::Key_W to Qt::Key_L. The key-combo is received and event is thrown. With W, it just never happens. I've also tried moving event to QMainWindow as well as an eventFilter in the subwindow to QMdiArea. It seems that it is a little overly complicated to do something as simple as remove default key-handlers from within QMdiArea.
You can disable this shortcut like this:
for( QAction *action : subWindow->systemMenu()->actions() ) {
if( action->shortcut() == QKeySequence( QKeySequence::Close ) ) {
action->setShortcut( QKeySequence() );
break;
}
}
You could get rid of the pre-defined close action of the QMdiSubWindow altogether by using Qt::CustomizeWindowHint as additional flag when adding the subwindow.
QMdiSubWindow *subWindow2 = mdiArea.addSubWindow(internalWidget2,
Qt::Widget | Qt::CustomizeWindowHint |
Qt::WindowMinMaxButtonsHint);
Subclass QMdiArea and reimplement keyPressEvent(). That should work.
void keyPressEvent(QKeyEvent* event){
if(event->key() == Qt::Key_W and event->modifiers() & Qt::ControlModifier){
// handle it
}else{
return QMdiArea::keyPressEvent(event);
}
}
You could also use event filters. I don't enough about your class hierarchy, but I hope you get the idea.
bool CustomMdiArea::eventFilter(QObject *object, QEvent *event){
if(object == yourChildWindow && event->type() == QEvent::KeyPress) {
QKeyEvent *keyEvent = static_cast<QKeyEvent *>(event);
if(keyEvent->key() == Qt::Key_W and keyEvent->modifiers() & Qt::ControlModifier) {
//handle it
return true;
}else{
return false;
}
}
return false;
}
From what I can tell, what I am looking for is not possible short of writing my own MDIArea.
The shortcut is set in QMdiSubWindowPrivate::createSystemMenu() during
the construction of a QMdiSubWindow, I doubt that you can remove it
without having to patch Qt libs.
Hopefully at some point someone will disprove this or QT will make changes. Meanwhile, it looks like we will all need to stay away from these pre-assigned shortcuts.
I was able to work around this by setting the shortcut context for my close action. By setting it to Qt::WidgetShortcut, I no longer get the ambiguous shortcut overload. Here is how I'm setting up my close action now:
closeAction = new QAction(tr("&Close"), this);
closeAction->setShortcut(Qt::CTRL|Qt::Key_W);
closeAction->setShortcutContext(Qt::WidgetShortcut);
connect(closeAction, SIGNAL(triggered()), mdiArea, SLOT(closeActiveSubWindow()));
Related
I'm working on a way of creating QWidgets that respond to touch, running as a plugin to a proprietary Qt 5.2 application. The application provides touch and gesture events (QTouchEvent, QGestureEvent), but no mouse events.
I have no control over the application itself, apart from what can be done at runtime, and I'm trying to be as minimally invasive as I can.
EDIT: To clarify the above paragraph, my code is being loaded automatically by Qt as a 'fake' image plugin. The host application appears to not have set Qt::AA_SynthesizeMouseForUnhandledTouchEvents, and when I tried to do it... things did not go well. The application has it's own set of touch and gesture handlers and Widgets it uses.
One option is to subclass each QWidget type and add custom event handling for touch events. I would rather not do this, however, as it would be tedious, and I don't really need advanced touch/gesture functionality for the most part.
Another option, which I've been trialing is to install an eventFilter on the widgets, which creates mouse events from the touch events, and sends the mouse event to the widget instead. This is working quite well for many simpler widgets, and even complex widgets like QFileDialog mostly work.
My problem comes with certain widget types (I've come across two so far), which either partially work, or don't work at all, and I'm not sure where I'm going wrong.
An example of a partially working widget with the eventFilter technique is the QComboBox. The combo box itself responds to the touch, and opens the dropdown list. However, the dropdown list itself just doesn't seem to work with the touch.
QDialogButtonBox is the most recent widget I've discovered that doesn't work. The eventFilter seems to trigger on the QDialogButtonBox itself, but not the button(s) for some reason.
Here are the relevant bits of the current (very) WIP experimental code:
From NDBWidgets.h
void setTouchFilter(QWidget *w);
class TouchFilter : public QObject
{
Q_OBJECT
public:
TouchFilter(QWidget* parent = nullptr);
protected:
bool eventFilter(QObject *obj, QEvent *event) override;
};
And from NDBWidgets.cc
static void installTouchFilter(QWidget *w)
{
if (!w->property(touchFilterSet).isValid()) {
w->setProperty(touchFilterSet, QVariant(true));
w->setAttribute(Qt::WA_AcceptTouchEvents);
auto ef = new TouchFilter(w);
w->installEventFilter(ef);
}
}
void setTouchFilter(QWidget *w)
{
if (!w) {
return;
}
auto children = w->children();
nh_log("installing eventFilter on the following children:");
for (int i = 0; i < children.size(); ++i) {
if (auto wi = qobject_cast<QWidget*>(children.at(i))) {
nh_log(" Class: %s Name: %s", children.at(i)->metaObject()->className(), children.at(i)->objectName().toUtf8().constData());
installTouchFilter(wi);
// if (auto cb = qobject_cast<QComboBox*>(wi)) {
// setTouchFilter(cb->view());
// }
}
}
installTouchFilter(w);
}
TouchFilter::TouchFilter(QWidget *parent) : QObject(parent) {}
bool TouchFilter::eventFilter(QObject *obj, QEvent *event)
{
// Only care about widgets...
auto widget = qobject_cast<QWidget*>(obj);
if (!widget) {
return false;
}
auto type = event->type();
if (type == QEvent::TouchBegin || type == QEvent::TouchUpdate || type == QEvent::TouchEnd) {
event->accept();
auto tp = static_cast<QTouchEvent*>(event)->touchPoints().at(0);
nh_log("eventFilter triggered for class: %s name: %s", widget->metaObject()->className(), widget->objectName().toUtf8().constData());
if (type == QEvent::TouchBegin) {
nh_log("event TouchBegin captured");
QMouseEvent md(QEvent::MouseButtonPress, tp.pos(), tp.screenPos(), Qt::LeftButton, Qt::LeftButton, Qt::NoModifier);
QCoreApplication::sendEvent(widget, &md);
} else if (type == QEvent::TouchUpdate) {
nh_log("event TouchUpdate captured");
QMouseEvent mm(QEvent::MouseMove, tp.pos(), tp.screenPos(), Qt::LeftButton, Qt::LeftButton, Qt::NoModifier);
QCoreApplication::sendEvent(widget, &mm);
} else if (type == QEvent::TouchEnd) {
nh_log("event TouchEnd captured");
QMouseEvent mu(QEvent::MouseButtonRelease, tp.pos(), tp.screenPos(), Qt::LeftButton, Qt::LeftButton, Qt::NoModifier);
QCoreApplication::sendEvent(widget, &mu);
}
return true;
}
return false;
}
An example usage:
void NDBDbus::dlgComboBox() {
NDB_DBUS_USB_ASSERT((void) 0);
auto d = new QDialog();
d->setAttribute(Qt::WA_DeleteOnClose);
auto vl = new QVBoxLayout();
auto cb = new QComboBox(d);
cb->addItem("Item 1", QVariant(1));
cb->addItem("Item 2", QVariant(2));
vl->addWidget(cb);
auto bb = new QDialogButtonBox(QDialogButtonBox::Close, d);
connect(bb, &QDialogButtonBox::rejected, d, &QDialog::reject);
vl->addWidget(bb);
d->setLayout(vl);
setTouchFilter(d);
d->open();
}
I'm still very new to Qt event handling, and eventFilters, and I've gotten to the point where I'm rather stumped, so if anyone has any suggestions or ideas, I would very much appreciate them!
So, TLDR, and the final question: Is it possible to transform touch events to mouse events inside an eventFilter for widgets such as QComboBox or QDialogButtonBox? And if so, how?
Something is fundamentally wrong with my eventFilter, as it lets every single event through, while I want to stop everything. I've read lots of documentation on QEvent, eventFilter() and so on, but obviously I'm missing something big. Essentially, I'm trying to create my own modal-functionality for my popup-window class based on QDialog. I want to implement my own since the built-in setModal(true) includes a lot of features, e.g. playing QApplication::Beep(), that I want to exclude. Basically, I want to discard all events going to the QWidget (window) that created my popup. What I have so far is,
// popupdialog.h
#ifndef POPUPDIALOG_H
#define POPUPDIALOG_H
#include <QDialog>
#include <QString>
namespace Ui {class PopupDialog;}
class PopupDialog : public QDialog
{
Q_OBJECT
public:
explicit PopupDialog(QWidget *window=0, QString messageText="");
~PopupDialog();
private:
Ui::PopupDialog *ui;
QString messageText;
QWidget window; // the window that caused/created the popup
void mouseReleaseEvent(QMouseEvent*); // popup closes when clicked on
bool eventFilter(QObject *, QEvent*);
};
...
// popupdialog.cpp
#include "popupdialog.h"
#include "ui_popupdialog.h"
PopupDialog::PopupDialog(QWidget *window, QString messageText) :
QDialog(NULL), // parentless
ui(new Ui::PopupDialog),
messageText(messageText),
window(window)
{
ui->setupUi(this);
setAttribute(Qt::WA_DeleteOnClose, true); // Prevents memory leak
setWindowFlags(Qt::Window | Qt::FramelessWindowHint);
ui->message_text_display->setText(messageText);
window->installEventFilter(this);
//this->installEventFilter(window); // tried this also, just to be sure ..
}
PopupDialog::~PopupDialog()
{
window->removeEventFilter(this);
delete ui;
}
// popup closes when clicked on
void PopupDialog::mouseReleaseEvent(QMouseEvent *e)
{
close();
}
Here's the problem, the filter doesn't work. Note that if I write a std::cout
inside the if(...), I see that it does trigger whenever events are sent to window, it just doesn't stop them.
bool PopupDialog::eventFilter(QObject *obj, QEvent *e)
{
if( obj == window )
return true; //should discard the signal (?)
else
return false; // I tried setting this to 'true' also without success
}
When the user interacts with the main program, a PopupDialog can be created like this:
PopupDialog *popup_msg = new PopupDialog(ptr_to_source_window, "some text message");
popup_msg->show();
// I understand that naming the source 'window' might be a little confusing.
// I apologise for that. The source can in fact be any 'QWidget'.
Everything else works as expected. Only the event filter fails. I want the filter to remove events sent to the window that created the popup; like mouse clicking and key pressing, until the popup is closed. I'm expecting to be extremely embarrassed when someone points out a trivial fix in my code.
You have to ignore all events that arrive in the widget tree of the window. Therefore, you need to install the eventFilter application-wide and check, if the object you are filtering on is a descendant of window. In other words: Replace
window->installEventFilter(this);
by
QCoreApplication::instance()->installEventFilter(this);
and implement the event filter function this way:
bool PopupDialog::eventFilter(QObject *obj, QEvent *e)
{
if ( !dynamic_cast<QInputEvent*>( event ) )
return false;
while ( obj != NULL )
{
if( obj == window )
return true;
obj = obj->parent();
}
return false;
}
I tried it, tested it and it worked for me.
Note: Using event filters in Qt is a bit messy in my experience, since it is not quite transparent what is happening. Expect bugs to pop up from time to time. You may consider disabling the main window instead, if you and your clients don't have a problem with the grayed-out main window as a consequence.
After the massive amount of responses, feedback, suggestions and time ivested in extensive research I've finally found what I believe to be the optimal, and safest solution. I wish to express my sincere gratidtude to everyone for their aid to what Kuba Ober describes as "(...) not as simple of a problem as you think".
We want to filter out all certain events from a widget, including its children. This is difficult, because events may be caught in the childrens default eventfilters and responded to, before they are caught and filtered by the the parent's custom filter for which the programmer implements. The following code solves this problem by installing the filter on all children upon their creation. This example assumes the use of Qt Creator UI-forms and is based on the following blog post: How to install eventfilters for all children.
// The widget class (based on QMainWindow, but could be anything) for
// which you want to install the event filter on, includings its children
class WidgetClassToBeFiltered : public QMainWindow
{
Q_OBJECT
public:
explicit WidgetClassToBeFiltered(QWidget *parent = 0);
~WidgetClassToBeFiltered();
private:
bool eventFilter(QObject*, QEvent*);
Ui::WidgetClassToBeFiltered *ui;
};
...
WidgetClassToBeFiltered::WidgetClassToBeFiltered(QWidget *parent) :
QMainWindow(parent), // Base Class constructor
ui(new Ui::WidgetClassToBeFiltered)
{
installEventFilter(this); // install filter BEFORE setupUI.
ui->setupUi(this);
}
...
bool WidgetClassToBeFiltered::eventFilter(QObject *obj, QEvent* e)
{
if( e->type() == QEvent::ChildAdded ) // install eventfilter on children
{
QChildEvent *ce = static_cast<QChildEvent*>(e);
ce->child()->installEventFilter(this);
}
else if( e->type() == QEvent::ChildRemoved ) // remove eventfilter from children
{
QChildEvent *ce = static_cast<QChildEvent*>(e);
ce->child()->removeEventFilter(this);
}
else if( (e->type() == QEvent::MouseButtonRelease) ) // e.g. filter out Mouse Buttons Relases
{
// do whatever ..
return true; // filter these events out
}
return QWidget::eventFilter( obj, e ); // apply default filter
}
Note that this works, because the eventfilter installs itself on added children! Hence, it should also work without the use of UI-forms.
Refer this code to filter out specific event:-
class MainWindow : public QMainWindow
{
public:
MainWindow();
protected:
bool eventFilter(QObject *obj, QEvent *ev);
private:
QTextEdit *textEdit;
};
MainWindow::MainWindow()
{
textEdit = new QTextEdit;
setCentralWidget(textEdit);
textEdit->installEventFilter(this);
}
bool MainWindow::eventFilter(QObject *obj, QEvent *event)
{
if (obj == textEdit) {
if (event->type() == QEvent::KeyPress) {
QKeyEvent *keyEvent = static_cast<QKeyEvent*>(event);
qDebug() << "Ate key press" << keyEvent->key();
return true;
} else {
return false;
}
} else {
// pass the event on to the parent class
return QMainWindow::eventFilter(obj, event);
}
}
If you want to set more specific event filter on multiple widgets you can refer following code:
class KeyPressEater : public QObject
{
Q_OBJECT
protected:
bool eventFilter(QObject *obj, QEvent *event);
};
bool KeyPressEater::eventFilter(QObject *obj, QEvent *event)
{
if (event->type() == QEvent::KeyPress) {
QKeyEvent *keyEvent = static_cast<QKeyEvent *>(event);
qDebug("Ate key press %d", keyEvent->key());
return true;
} else {
// standard event processing
return QObject::eventFilter(obj, event);
}
}
KeyPressEater *keyPressEater = new KeyPressEater(this);
QPushButton *pushButton = new QPushButton(this);
pushButton->installEventFilter(keyPressEater);
i am using visual Studio with Qt.
i do not have access to Qt designer. its all done through coding (C++);
i have an opensource software called easypaint.
i got stuck at trying to rename tabs. I want to be able to rename tabs when user double clicks on the tab itself.
i created a new function to filter the doubleClick event :
bool MainWindow::eventFilter(QObject *obj, QEvent *event)
`enter code here`{
if (event->type() == QEvent::MouseButtonDblClick) {
return true;
} else {
// standard event processing
return QObject::eventFilter(obj, event);
}
}
then i added this line to a function that initializes the TabWidget:
installEventFilter(mTabWidget);
can anyone please guide me through this.
Thank you
Most likely Qt doesn't allow an inline editor to open on the tab's name. So you'd most likely have to create and run a very small QDialog to query for the new name:
bool MainWindow::eventFilter(QObject *obj, QEvent *event)
{
if (obj == mTabWidget &&
event->type() == QEvent::MouseButtonDblClick) {
// query and set tab(s) names
QTabWidget *tab = qobject_cast<QTabWidget *>(obj);
if(tab)
{
QDialog dlg;
QVBoxLayout la(&dlg);
QLineEdit ed;
la.addWidget(&ed);
QDialogButtonBox bb(QDialogButtonBox::Ok | QDialogButtonBox::Cancel);
la.addWidget(&bb);
dlg.setLayout(&la);
if(dlg.exec() == QDialog::Accepted)
{
tab->setTabText(0, ed.text());
return true;
}
}
}
// Standard event processing
return QObject::eventFilter(obj, event);
}
It might be that Qt's dynamic memory management doesn't like the local class instances, so you'd have to convert all those class instances created to pointers created with new, but then please don't forget to tell the QDialog to delete on close or call dlg->deleteLater() after you queried the new name.
Another way to solve this via a fake inline editor would need a bit more work:
create a QLineEdit
move it right above the tab's, bring it up front and set keyboard focus to it
wire signals and slots
pressing enter should use the contents of the QLineEdit
leaving focus from the line edit should be treated as "abort" and delete the line editor
implement the slots to do what's needed.
You can write the event filter in the fallowing way:
bool MainWindow::eventFilter(QObject *obj, QEvent *event)
{
if (obj == mTabWidget &&
event->type() == QEvent::MouseButtonDblClick) {
QTabWidget *tab = qobject_cast<QTabWidget *>(obj);
// Set tab(s) names
tab->setTabText(0, "New Name");
}
// Standard event processing
return QObject::eventFilter(obj, event);
}
I have a graphics view and i have set my own function for scrolling by hand drag when the user presses control and mouse clicks.
I have removed the scroll bars but the mouse wheel will still scroll and even scroll past the image that's being displayed in the qGraphicsView showing blank (white) space which my hand drag doesn't.
How do i make it so wheel just straight up does nothing at all?
I know there is a function I can just put in my code without a derived class because I asked this once before and got the right answer but the answer was removed and i hadn't saved the code :(
The below code does nothing even close to the expected functionality, on start up I get the mouse still doing something message and then all clicks and wheel events everything just displays that second message ... so yea not working
bool GUI::eventFilter(QObject *object, QEvent *event)
{
if (object == ui->graphicsView && event->type() == QEvent::GraphicsSceneWheel)
{
std::cout << "Wheel Blocked";
return true;
}
std::cout << "Mouse Wheel still doing something";
return false;
}
and then this code to install the filter
ui->graphicsView->installEventFilter(this);
Install an eventfilter and filter for
QEvent::GraphicsSceneWheel
Create something like this in your app
bool MainWindow::eventFilter(QObject *object, QEvent *event)
{
if (object == ui->graphicsView->viewport() && event->type() == QEvent::Wheel) {
qDebug() << "SCroll";
return true;
}
return false;
}
and install to your widget.
yourwidget->viewport()->installEventFilter(this);
Edit:
I thought it should work with QEvent::GraphicsSceneWheel. If anyone knows why works with Wheel, please, explain
Also, you can to modify QGraphicsView using inheritance
class MyCustomGraphicsView : public QGraphicsView
{
virtual void wheelEvent ( QWheelEvent * event )
{
//here you can modify wheel-event
}
}
It is easy way, because qt-designer can associate GraphicsView element on your form with your inherited class
What is the best (as in simplest) way to obtain the pos of a mousePressedEvent in a QLabel? (Or basically just obtain the location of a mouse click relative to a QLabel widget)
EDIT
I tried what Frank suggested in this way:
bool MainWindow::eventFilter(QObject *someOb, QEvent *ev)
{
if(someOb == ui->label && ev->type() == QEvent::MouseButtonPress)
{
QMouseEvent *me = static_cast<QMouseEvent *>(ev);
QPoint coordinates = me->pos();
//do stuff
return true;
}
else return false;
}
However, I receive the compile error invalid static_cast from type 'QEvent*' to type 'const QMouseEvent*' on the line where I try to declare me. Any ideas what I'm doing wrong here?
You could subclass QLabel and reimplement mousePressEvent(QMouseEvent*). Or use an event filter:
bool OneOfMyClasses::eventFilter( QObject* watched, QEvent* event ) {
if ( watched != label )
return false;
if ( event->type() != QEvent::MouseButtonPress )
return false;
const QMouseEvent* const me = static_cast<const QMouseEvent*>( event );
//might want to check the buttons here
const QPoint p = me->pos(); //...or ->globalPos();
...
return false;
}
label->installEventFilter( watcher ); // watcher is the OneOfMyClasses instance supposed to do the filtering.
The advantage of event filtering is that is more flexible and doesn't require subclassing. But if you need custom behavior as a result of the received event anyway or already have a subclass, its more straightforward to just reimplement fooEvent().
I had the same problem
invalid static_cast...
I just forgot to include the header: #include "qevent.h"
Now everything is working well.