1
## minimal code
import sys
from PyQt5 import QtWidgets
from PyQt5.QtWebEngineWidgets import QWebEngineView
from PyQt5.QtCore import Qt

class Browser(QWebEngineView):
    def __init__(self, parent=None):
        super(Browser, self).__init__(parent)
        self.setHtml(r"""
        <div><textarea></textarea></div>
        <style>
        *{margin:0;}
        div {
            height: 100%;
            position: relative;
            background:grey;
        }
        textarea {
            background:red;
            height: 50%;
            width: 50%;
            resize: none;
            position: absolute;
            top: 50%;
            left: 50%;
            transform: translate(-50%, -50%);
        }
        </style>
        """)

class MainWindow(QtWidgets.QMainWindow):
    def __init__(self):
        super().__init__()

        central=QtWidgets.QWidget(self)
        self.setCentralWidget(central)
        layout = QtWidgets.QGridLayout(central)
        self.browser = Browser(central)
        layout.addWidget(self.browser)
        another=QtWidgets.QWidget(central)
        layout.addWidget(another)
        another.setFocusPolicy(Qt.StrongFocus)

    def keyPressEvent(self, e):
        print("KEY PRESSED")
        return super().keyPressEvent(e)

if __name__ == '__main__':
    app = QtWidgets.QApplication(sys.argv)
    window = MainWindow()
    window.setGeometry(300, 50, 800, 600)
    window.show()

    sys.exit(app.exec_())

Current behavior:

Case 1:

  1. Click on the white area(a widget).
  2. Press a key.
  3. KEY PRESSED is logged into the console.

Case 2:

  1. Click on the grey area(the webview).
  2. Press a key.
  3. KEY PRESSED is logged into the console.

Case 3:

  1. Click on the red area(the textarea inside the webview).
  2. Press a key.
  3. Nothing is logged into the console.

Required behavior

I want all of the cases to log KEY PRESSED once into the console.

Things I tried

I tried adding an eventFilter to the MainWindow but it gives the same outout.

I also tried the solution from this answer by forwarding the key events to MainWindow but then it logs KEY PRESSED twice for Case 2.

I am not able to differentiate between the events that was propagated to MainWindow (Case 2) and those that weren't (Case 3) so that I could've implemented something to ignore the excess function call.

ekhumoro
  • 115,249
  • 20
  • 229
  • 336

2 Answers2

2

The problem is that Qt must pass the events to the internal Chrome browser, which complicates the normal process of event-propagation. The simplest solution I could find is to install an event-filter on the internal web-view delegate, whilst also ignoring any events that come from the delegate in the main key-press handler:

class MainWindow(QtWidgets.QMainWindow):
    def __init__(self):
        super().__init__()
        ...
        self.browser.focusProxy().installEventFilter(self)

    def eventFilter(self, source, event):
        if (event.type() == QEvent.KeyPress and
            source.parentWidget() is self.browser):
            self.handleKeyPress(event)
            return False
        return super().eventFilter(source, event)

    def keyPressEvent(self, event):
        target = QtWidgets.QApplication.focusWidget()
        if target.parentWidget() is not self.browser:
            self.handleKeyPress(event)
        super().keyPressEvent(event)

    def handleKeyPress(self, event):
        print(f'KEY PRESSED: {event.text()!r}\n')
ekhumoro
  • 115,249
  • 20
  • 229
  • 336
  • Thanks for this answer!! I used some of the ideas from this answer along with another post to find a solution that works with lesser changes in the old code. – Jake Korman Aug 03 '22 at 08:36
  • @JakeKorman I have updated my answer with an even simpler solution. This should require minimal changes, since all you need to do is factor out a section of the code from your current keypress handler into a separate method. – ekhumoro Aug 03 '22 at 10:44
0

What I did was forward the key events from the browser to the MainWindow using the ForwardKeyEvent class from this post. I ignored any QKeyEvent sent by the browser and accepted the ones that were sent artificially. This was more convenient for me since all the logging was done by one function, which is the function I needed to be called when there was a QKeyEvent.

class ForwardKeyEvent(QObject):
    ## https://stackoverflow.com/a/57012924/14908508
    def __init__(self, sender, receiver, parent=None):
        super(ForwardKeyEvent, self).__init__(parent)
        self.m_sender = sender
        self.m_receiver = receiver
        self.m_sender.installEventFilter(self)

    def eventFilter(self, obj, event):
        if self.m_sender is obj and event.type() == QEvent.KeyPress:
            new_event = QKeyEvent(
                QEvent.KeyPress,
                event.key(),
                event.modifiers(),
                event.text(),
            )
            new_event.artificial = True
            QCoreApplication.postEvent(self.m_receiver, new_event)
        return super().eventFilter(obj, event)

class MainWindow(QtWidgets.QMainWindow):
    def __init__(self):
        ...
        
        self.FKE = ForwardKeyEvent(self.browser.focusProxy(), self)
    
    def keyPressEvent(self, e):
        target = QtWidgets.QApplication.focusWidget()
        if target.parentWidget() is not self.browser or hasattr(e,"artificial"):
            print("KEY PRESSED:", target)
        return super().keyPressEvent(e)