I have a custom widget A within another custom widget B. Widget A normally has a mousePressEvent where I open a context menu. But now as it is in widget B, I want to open widget B's context menu and intercept the event so that only B knows it.
If I use this code in class B after adding an instance of A to B:
this->installEventFilter(instanceOf_A);
Or
instanceOf_A->installEventFilter(this);
Where
A::eventFilter(QObject *obj, QEvent *e){
if (e->type() == QEvent::MouseButtonPress){
QMouseEvent *mouseEvent = static_cast<QMouseEvent *>(e);
if (mouseEvent->button() == Qt::RightButton){
qDebug()<<" Right mouse button press detected. \
Return true, now child should not get the mouse event";
return true;
}
}
Nothing works. Also return false , e->accept(), e->ignore() don't work or show any impact. How do you setup an eventFilter? Where? In A or B?
EDIT: replaced the e->MouseButtonPress
Related
I have a QGraphicsView which contains many QGraphicsItem. If I click mouse right click on any QGraphicsItem, the item should get select and right menu options should appear and then I will choose one of the options among them.To do that I have installed eventFilter and through it, I am using ContextMenu to create right click menu. Right click menu are getting cretaed properly. But propblem is I am not getting how to connect them to some function so that I can write logic for it.
It means if I clicked on save option that particular QGraphicsItem should get select and I should be able to go to some function where I will write logic for saving.
bool myClass::eventFilter(QObject *watched, QEvent *event)
{
switch(event->type())
{
case QEvent::ContextMenu:
{
QMouseEvent *mouseEvent = static_cast<QMouseEvent*> (event);
menu = new QMenu(this);
option = menu->addMenu("CopyOption");
option->addAction("save");
menu->exec(mouseEvent->globalPos());
break;
}
default:
break;
}
}
In your approach you show a context menu when you have no information if any item is selected. It is rather bad idea. You don't want to show the context menu in any location of view. You have to check if a cursor mouse is over an item.
Why not to derive from QGraphicsItem and just overload mousePressEvent method. Inside this method check if right button of mouse is clicked. If so, show context menu and test which action is clicked. Minimal code would be:
class TItem : public QGraphicsItem
{
bool _selected = false;
public:
TItem(QGraphicsItem* parent = nullptr) : QGraphicsItem(parent) {}
QRectF boundingRect() const override { return QRectF(0,0,20,20); }
void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) override {
painter->fillRect(QRectF(0,0,20,20),_selected ? QColor(0,255,255) : QColor(255,255,0));
}
void mousePressEvent(QGraphicsSceneMouseEvent* e) override {
QGraphicsItem::mousePressEvent(e);
if (e->button() & Qt::RightButton) {
QMenu menu;
QAction* a1 = menu.addAction(QString("test1"));
QAction* a2 = menu.addAction(QString("test2"));
if(a2 == menu.exec(e->screenPos())) {
test2();
_selected = true;
update();
}
}
}
void test2() {
QMessageBox::information(nullptr,"test2","test2");
}
};
All the job with checking is an item is under mouse is done by QT, mousePressEvent is invoked only if it is necessary.
Another approach would be override mousePressEvent on QGraphicsView. Inside which:
get all items belonging to the scene
iterate over them, checking if an item is under mouse -> QGraphicsItem has isUnderMouse method
if any item is under mouse, create QMenu and show it
check selected QAction, if it is save call a proper method which doing the save and mark the item as selected
I have a QTableView where a small mark appears if the mouse hover on active cell. This mark is a widget and emit a signal if hover, changing the selecion mode of the table when I drag over the table.
The problem is that if I am over the mark, I can't drag over the table.
Things I have tried:
Set the widget mark like setWindowFlags(Qt::WindowTransparentForInput);, but I can't use it because I need hover event.
Ignore the events in the widget mark using event->ignore() or sending the event to parent using and eventFilter:
bool EventFilterMarca::eventFilter(QObject *obj, QEvent *event)
{
if( event->type() == QEvent::HoverMove)
{
....
}
else if (event->type() == QEvent::MouseButtonPress ||
event->type() == QEvent::MouseButtonRelease ||
event->type() == QEvent::MouseMove ||
event->type() == QEvent::MouseButtonDblClick)
{
//QApplication::sendEvent(parent(),event);//one try
//event->ignore();//another try
return QObject::eventFilter(obj,event);;
}
}
Subclassing mousePressEvent, mouseReleaseEvent and mouseMoveEvent in the mark widget and call to parent class. Looks like if it works (pass the event to parent) into the current cell of the table (the parent):
void Marca::mousePressEvent(QMouseEvent *event)
{
//event->setAccepted(false);
if(event->buttons() == Qt::LeftButton)
{
MiTabla* tabla = qobject_cast<MiTabla*>(parent());
if (tabla)
{
tabla->mousePressEvent(event);
//QApplication::sendEvent(parent(),event);
}
}
//event->ignore();
}
Well, the question is how could get that the behaviour of the table was the same if I am over the widget of the cell or directly on the cell.
Also I add a link with my first approach to getting it. It works but the code is awful and not easy to follow:
https://github.com/exodehm/tablacalc
I think the event filter is not what you want. This is what makes a widget not receive specific events. What you actually want to do is to mark the event to be unhandled by specific widget, putting it higher in the class for handling. In order to do that you should try and reimplement either the widgets ::event method, or more specific handler.
In qt docs it says that:
bool QWidget::event(QEvent *event)
This function returns true if the event was recognized, otherwise it returns false. If the recognized event was accepted (see QEvent::accepted), any further processing such as event propagation to the parent widget stops.
So what I think you should be doing is basically marking event as not accepted when specific event type you want to propagate to parent widget happens for your widget.
So in your case I would expect something like:
void QWidget::dropEvent(QDropEvent *event) {
event->setAccepted(false);
}
Also in order for this to work your table needs to be parent widget of the mark widget.
I have a QDialog with a QDialogButtonBox. The OK and Cancel buttons are active. Occasionally I disable or hide the OK button based on the state of my dialog. It seems, no matter what I do, the Enter key always activates the OK button. I really DON'T want this to happen. I have tried:
Setting default and autoDefault properties to false every time I show/hide/enable/disable/whatever the button
installing an event filter on the OK button to intercept key events (pressed and released) for return, enter and space
Setting the focus policy on the button to NoFocus
And with all combinations of those things above, the Enter key still accepts the dialog. Does anyone have any clue how to block this? It seems like I should be able to block something as simple as this?
The key press event filtering should be done on the dialog itself, because the code handling the forwarding of the Return and Enter keys to the default button is in QDialog::keyPressEvent.
void Dialog::keyPressEvent(QKeyEvent *evt)
{
if(evt->key() == Qt::Key_Enter || evt->key() == Qt::Key_Return)
return;
QDialog::keyPressEvent(evt);
}
Or
theDialog−>installEventFilter(anotherClassObject);
bool AnotherClass::eventFilter(QObject *obj, QEvent *evt)
{
if(evt->type() == QEvent::KeyPress) {
QKeyEvent *keyEvent = static_cast<QKeyEvent*>(evt);
if(keyEvent->key() == Qt::Key_Enter || keyEvent->key() == Qt::Key_Return )
return true; // mark the event as handled
}
return false;
}
If you have normal QPushButtons on the dialog then if the buttons have the autoDefault and/or default properties set on them then you get a default button - which is what the enter key triggers. In that case, get rid of autoDefault on the buttons and pressing enter in another widget no longer closes the dialog.
In the case of a QDialogButtonBox you can probably iterate over the buttons to turn this stuff off in the ctor of your dialog. Not tested here but ought to work. If not then you'll need to also see if there is a default button that gets set on the QDialog itself too.
The problem is the event filter shouldn't be installed on the OK button.
If your OK button is disabled, then it's not going to receive the enter event. Whichever widget has the focus will. And if they don't accept the enter event, then QDialog is going to accept() itself.
Two ways to solve the problem:
1) Override QDialog::accept(), and call QDialog's accept method in the new accept function only if OK is enabled
void MyDialog::accept() {
if (okEnabled) {
QDialog::accept();
}
}
2) Install an event filter on every widget in the dialog that doesn't accept the enter key (line edits, ...).
The event filter would be like so:
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);
bool res = QObject::eventFilter(obj, event);
if (keyEvent->key() == Qt::Key_Return) {
return true; /* Always accept return */
} else {
return res;
}
} else {
// standard event processing
return QObject::eventFilter(obj, event);
}
}
And in your code, for each widget in the dialog:
myWidget->installEventFilter(myKeyPressEater);
One option is to override your dialog's show event so as to allow the QDialogButtonBox to be shown, after which it will have set a default button with an AcceptRole, and to then set all buttons to not be defaults.
void MyDialog::showEvent(QShowEvent* event)
{
// When a QDialogButtonBox is shown, it will set a default button if none are found so we need to disable the
// default buttons after the button box has been shown.
QDialog::showEvent(event);
// For example, with a dialog which has two buttons, Save and Cancel, we remove all defaults
// It might be good enough to remove the default on just the buttons with have the AcceptRole, but
// I didn't test extensively enough to see what would happen if any buttons had "autoDefault" set or
// verify this behavior on all platforms.
ui->buttonBox->button(QDialogButtonBox::Save)->setDefault(false);
ui->buttonBox->button(QDialogButtonBox::Cancel)->setDefault(false);
}
Attempting to remove defaults before the QDialogButtonBox is shown, such as in the constructor of your dialog, will just be overriden by the code in the QDialogButtonBox::showEvent().
QDialog has a private slot called accept(). Whenever QDialogButtonBox emits accepted() (by pressing return key or clicking Ok), that private slot is called. So try disconnecting them.
disconnect(ui->buttonBox, SIGNAL(accepted()), this, SLOT(accept()));
This worked for me.
To avoid "OK" button or "Enter" key from closing dialog:
in the ui xml file, remove the connect/slot for accept/reject. Then, in your code , emmit accept() when and as needed;
example from ui file which connects accept() slot:
<connections>
<connection>
<sender>products_ButtonBox</sender>
<signal>accepted()</signal>
<receiver>Products_Dialog</receiver>
<slot>accept()</slot>
<hints>
<hint type="sourcelabel">
<x>248</x>
<y>254</y>
</hint>
<hint type="destinationlabel">
<x>157</x>
<y>274</y>
</hint>e
</hints>
</connection>
In your dialog's accept() method, check the Ok-button for focus:
void accept() override
{ if (!dialogButtonBox->button(QDialogButtonBox::Ok)->hasFocus())
return;
...
QDialog::accept();
}
The key is to set your own buttons, all with NoRole, and neither accept or reject signals from the buttonbox. This will allow you to set your own behavior for the default button.
class Dialog(QDialog):
def __init__():
super(Dialog, self).__init__()
self.buttonBox = QDialogButtonBox()
self.btn_save = self.buttonBox.addButton('Save', QDialogButtonBox.NoRole)
self.btn_cancel = self.buttonBox.addButton('Cancel', QDialogButtonBox.NoRole)
self.btn_save.clicked.connect(self.onAccept)
self.btn_save.setMouseTracking(True)
self.btn_cancel.clicked.connect(self.onReject)
# STATUS BAR
self.status = QStatusBar()
self.status.addPermanentWidget(self.buttonBox)
def onAccept(self):
if not self.btn_save.underMouse():
return
self.submitChanges(self.model)
self.accept()
def onReject(self):
self.reject()
In PySide (and I imagine PyQt) I was able to redefine the accept and reject functions of the QDialog.
def custom_accept ():
# perform custom actions when you hit open
pass
def custom_reject ():
# perform custom actions when you hit cancel
pass
file_dialog = QtGui.QFileDialog(directory=".")
file_dialog.accept = custom_accept
file_dialog.reject = custom_reject
This kept the file dialog from closing and gave me access to the data when the 'ok' (accept) or 'cancel' (reject) functions were triggered (either with enter or by clicking the buttons)
I have a QListWidget on a dialog that I want to do something (for example, open a QFileDialog window) when a user double-clicks on the QListWidget. Unfortunately, the void doubleClicked (const QModelIndex & index) only fires when there are items in the list.
Is it possible to get the widget to fire the signal whenever a double-click event is received, anywhere within the widget? Or is a different approach required?
You can install an event filter to the listwidget's viewport widget, something like this:
listWidget->viewport()->installEventFilter(this); // "this" could be your window object.
In the eventFilter method check for the QEvent::MouseButtonDblClick event:
bool YourWindowClass::eventFilter(QObject *obj, QEvent *event)
{
if (event->type() == QEvent::MouseButtonDblClick)
{
QMouseEvent *mouseEvent = static_cast<QMouseEvent*>(event);
qDebug("Mouse double click %d %d", mouseEvent->x(), mouseEvent->y());
return true;
}
else
{
return QMainWindow::eventFilter(obj, event);
}
}
I hope this helps.
I want to include a "remove" icon on entries in my QComboBox, but I am having trouble catching the mouse press event. I've tried to catch it on the combobox, and I've tried reimplemting the QIcon class to catch the mousepress there. No dice. Does anybody know how to do this?
-D
I've written code a bit like this, where I wanted to put a tree view inside a combo box and I needed to take an action when the check box on the tree was clicked. What I ended up doing was installing an event filter on the combo box to intercept mouse clicks, figure out where the mouse click was happening, and then take an action. Probably you can do the same kind of thing with your icon. Here is the code:
bool TreeComboBox::eventFilter(QObject* object, QEvent* event)
{
if (event->type() == QEvent::MouseButtonPress || event->type() == QEvent::MouseButtonRelease)
{
QMouseEvent* m = static_cast<QMouseEvent*>(event);
QModelIndex index = view()->indexAt(m->pos());
QRect vrect = view()->visualRect(index);
if(event->type() == QEvent::MouseButtonPress &&
(model()->flags(index) & Qt::ItemIsUserCheckable) &&
vrect.contains(m->pos()))
{
// Your action here
ToggleItem(index);
UpdateSelectionString();
}
if (view()->rect().contains(m->pos()))
skipNextHide = true;
}
return QComboBox::eventFilter(object, event);
}
Maybe you can reimplement QComboBox::mousePressEvent(QMouseEvent *e) and use e.x() together with QComboBox::iconSize() to find if the event occurred over the icon.
This will off cause break if a Qt style decides to switch label and icon position in combo boxes. Don't know if that is possible?