NOTE: below is in edit with a more complete example
I want to implement the following in Qt (specifically PyQt, but I believe that the solution will be similar in both python and C++):
I want a widget to have an internal widget that is disabled by default, and when clicked, the widget will be enabled, and the mouse press will propagate to it. For example, in the following window/widget:
If I click between the c
and d
, I'd like the QLineEdit
to become enabled, take focus, and the cursor to be between the c
and d
. I got as far as re-enabling the QLineEdit
but I can't seem to send the event back to it.
This is my code so far:
from PyQt5.QtWidgets import QWidget, QLineEdit, QVBoxLayout, QPushButton, QApplication
class MyWidget(QWidget):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
layout = QVBoxLayout(self)
self.edit = QLineEdit('abcdef')
self.edit.setEnabled(False)
layout.addWidget(self.edit)
self.disable_btn = QPushButton('disable edit')
self.disable_btn.clicked.connect(self._disable_edit)
layout.addWidget(self.disable_btn)
def _disable_edit(self, *a):
self.edit.setEnabled(False)
def mousePressEvent(self, a0):
if not self.edit.isEnabled() and self.edit.underMouse():
self.edit.setEnabled(True)
QApplication.instance().sendEvent(self.edit, a0) # <-- this doesn't seem to work
super().mousePressEvent(a0)
if __name__ == '__main__':
from PyQt5.QtWidgets import QApplication
app = QApplication([])
w = MyWidget()
w.show()
res = app.exec_()
exit(res)
This is a simplified example, I also want to wrap other widgets in this way, so that modifying the inner widgets is practically impossible.
The problem is, as far as as I can tell, that the disabled child widget rejects the mouse event (since it is disabled), and refuses to take it (or any other event) again from the parent widget.
Any help at all would be greatly appreciated.
EDIT: following is a clearer example of what I mean:
from PyQt5.QtWidgets import QWidget, QVBoxLayout, QPushButton
class ComplexInnerWidget(QWidget):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
layout = QVBoxLayout(self)
self.btn1 = QPushButton('button 1')
self.btn1.clicked.connect(self._btn1_click)
layout.addWidget(self.btn1)
self.btn2 = QPushButton('button 2')
self.btn2.clicked.connect(self._btn2_click)
layout.addWidget(self.btn2)
def _btn1_click(self, *a):
print('button 1')
def _btn2_click(self, *a):
print('button 2')
class MyWidget(QWidget):
def __init__(self, inner_widget: QWidget, *args, **kwargs):
super().__init__(*args, **kwargs)
layout = QVBoxLayout(self)
self.inner = inner_widget
self.inner.setEnabled(False)
layout.addWidget(self.inner)
if __name__ == '__main__':
from PyQt5.QtWidgets import QApplication
app = QApplication([])
inner = ComplexInnerWidget()
w = MyWidget(inner)
w.show()
res = app.exec_()
exit(res)
what I want is to allow the user to press the disabled inner widget, hereby enabling it in its entirety (i.e. both btn1 and btn2 becoming enabled), and pressing the appropriate button at the same time. I need this done without changing ComplexInnerWidget
at all (since the user should be able to enter any widget as a parameter to MyWidget
)
EDIT 2: eyllanesc's solution works for the example provided, but I have adjusted it for MyWidget
to be able to support multiple widgets, and to be nested in other widgets:
from PyQt5 import QtCore, QtWidgets
class ComplexInnerWidget(QtWidgets.QWidget):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
layout = QtWidgets.QVBoxLayout(self)
self.btn1 = QtWidgets.QPushButton('button 1')
self.btn1.clicked.connect(self._btn1_click)
layout.addWidget(self.btn1)
self.btn2 = QtWidgets.QPushButton('button 2')
self.btn2.clicked.connect(self._btn2_click)
layout.addWidget(self.btn2)
self.le = QtWidgets.QLineEdit('abcdef')
layout.addWidget(self.le)
def _btn1_click(self, *a):
print('button 1')
def _btn2_click(self, *a):
print('button 2')
class MyWidget(QtWidgets.QWidget):
class EnableMouseHelper(QtCore.QObject):
def __init__(self, *args, warden):
super().__init__(*args)
self.warden = warden
def eventFilter(self, obj, event):
if obj.isWidgetType() and event.type() == QtCore.QEvent.MouseButtonPress:
if self.warden in obj.window().findChildren(QtWidgets.QWidget) \
and self.warden.underMouse() and not self.warden.isEnabled():
self.warden.setEnabled(True)
obj.setFocus()
return super().eventFilter(obj, event)
def __init__(self, inner_widget: QtWidgets.QWidget, *args, **kwargs):
super().__init__(*args, **kwargs)
layout = QtWidgets.QVBoxLayout(self)
self.inner = inner_widget
self.inner.setEnabled(False)
layout.addWidget(self.inner)
self.helper = self.EnableMouseHelper(warden=self.inner)
QtWidgets.QApplication.instance().installEventFilter(self.helper)
class OuterWidget(QtWidgets.QWidget):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
layout = QtWidgets.QVBoxLayout(self)
layout.addWidget(MyWidget(ComplexInnerWidget()))
layout.addWidget(MyWidget(ComplexInnerWidget()))
le = QtWidgets.QLineEdit('hi there')
le.setEnabled(False)
layout.addWidget(le)
le = QtWidgets.QLineEdit('hi there')
layout.addWidget(le)
if __name__ == '__main__':
from PyQt5.QtWidgets import QApplication
app = QApplication([])
w = OuterWidget()
w.show()
res = app.exec_()
exit(res)