QWebEngineView, post KeyEvents inside the View - qt

I have my own "Virtual Keyboard". I already got to transform buttons clicked into KeyEvents and deliver it to QTextEdit and so on. My problem now is that I want to do the same for writable areas inside a QWebEngineView.
For example, I use my keyboard to edit my QLineEdit, and request a website. DONE
Let's say I requested google. Now I have the Google website right in front of me. I need to send KeyEvents from my Keyboard to it's search box.(Box that is inside my QWebEngineView.
Let's now point a few points:
I am using PyQt5
As I've read, the API says me that it's parent should consume the KeyEvent to the corect place. here
This snippet says "...like it was possible with QtWebKit."
I've seen now that there is no more QtWebKit, and so Chromium instead.(Maybe that's the reason I'm not getting to post these events)
This is what I have for example to simulate KeyEvents to my QEditText and whatever..
from PyQt5.QtCore import QCoreApplication
from PyQt5.QtCore import QEvent
from PyQt5.QtCore import QSize
from PyQt5.QtCore import Qt
from PyQt5.QtGui import QIcon
from PyQt5.QtGui import QKeyEvent
from PyQt5.QtGui import QPixmap
from PyQt5.QtWidgets import QPushButton
class KeyboardKey(QPushButton):
__path = ""
__size = [30, 30]
__style = ""
__icon_on = ""
__icon_off = ""
__auto_repeat = True
__receiver = None
__key = None
__str_key = None
def __init__(self, style, str_icon_on, str_icon_off, auto_repeat, size, receiver, key, str_key):
super(KeyboardKey, self).__init__()
self.__size = size
self.__style = style
self.__icon_on = str_icon_on
self.__icon_off = str_icon_off
self.__auto_repeat = auto_repeat
self.__receiver = receiver
self.__key = key
self.__str_key = str_key
self.set_up_button(style, str_icon_on, str_icon_off, auto_repeat, size, receiver, key, str_key)
def set_up_button(self, style, str_icon_on, str_icon_off, auto_repeat, size, receiver, key, str_key):
self.__size = size
self.__style = style
self.__icon_on = str_icon_on
self.__icon_off = str_icon_off
self.__auto_repeat = auto_repeat
self.__receiver = receiver
self.__key = key
self.__str_key = str_key
self.setText(str_key)
self.setFixedSize(size[0], size[1])
self.setStyleSheet(style)
self.setIconSize(QSize(size[0], size[1]))
self.setIcon(QIcon(self.__path + str_icon_off + ".png"))
self.setAutoRepeat(auto_repeat)
pixmap = QPixmap(self.__path + str_icon_off + ".png")
self.setMask(pixmap.mask())
self.pressed.connect(self.key_pressed)
self.released.connect(self.key_released)
def set_receiver(self, receiver):
self.__receiver = receiver
def key_pressed(self):
self.setStyleSheet("""
border-width: 5px;
border-color: rgb(37,43,52);
color: white;
background-color: rgb(0,187,255);
""",)
def key_released(self):
event = QKeyEvent(QEvent.KeyPress, Qt.Key_A, Qt.NoModifier,
"a", False)
# self.__receiver is my QEditText/QLineEdit/...
self.__receiver.keyPressEvent(event)
This Last part is the one that I post my events on the "self.__receiver". This receiver is set always for the "QWidget" that invoke it.
I have tried just to say something like:
def key_released(self):
event = QKeyEvent(QEvent.KeyPress, Qt.Key_A, Qt.NoModifier,
"a", False)
# web_view is my QWebEngineView... but it won't consume. Or maybe it's not consuming to the right place.
self.web_view.keyPressEvent(event)

This should work when you send the events to the QWebEngineViews focusProxy instead - something like this should work:
recipient = self.web_view.focusProxy()
QApplication.postEvent(recipient, event)

Related

How to press the okay button using pytest-qt whilst testing a pyqt5 app?

How can I "okay" a QMessageBox using pytestqt?
For instance, given:
# MessageBoxFactory.py
from PyQt5.QtWidgets import QMessageBox, QWidget
def messageBoxFactory(parent : QWidget, title:str, msg:str, level="info"):
"""Display a popup of type `level` containing msg[0] (title) and msg[1] the message.
Makes use of the full QMessageBox (rather than QMessageBox.warning, for example) because
it is necessary to have a handle to the box later for testing.
"""
box = None
if level == "info":
box = QMessageBox(QMessageBox.Information, title, msg, QMessageBox.Ok, parent)
elif level == "warning":
box = QMessageBox(QMessageBox.Warning, title, msg, QMessageBox.Ok, parent)
elif level == "critical":
box = QMessageBox(QMessageBox.Critical, title, msg, QMessageBox.Ok, parent)
if not box:
raise ValueError("no QMessageBox created")
box.exec()
return box
And the test code:
from PyQt5.QtWidgets import QMainWindow
from PyQt5.QtCore import Qt
import pytest
from pytestqt.qtbot import QtBot
from accos.view.MessageBoxFactory import messageBoxFactory
def test_mbf(qtbot: QtBot):
main_window = QMainWindow()
box = messageBoxFactory(main_window, "Informational Message", "Some information", level="info")
dialogButtonBox : QDialogButtonBox = box.children()[2]
okayBtn = dialogButtonBox.buttons()[0]
qtbot.mouseClick(okayBtn, Qt.LeftButton, Qt.NoModifier)
How can I get the qtbot to okay the message box?

Resize QDialog when RadioButton is Checked PyQt

not so many experience at all with pyqt..
I have designed an UI with Qt Designer with 2 Radiobuttons.
Depending on the RadioButton selected some widgets are visible and other not. What I'm not trying to do is to resize automatically the layout of the dialog depending on how many widgets are visible in the UI.
The question is very similar to this one . Here the extract of the code that might be helpful to understand the problem (I know it is just a piece of the code, but the entire dialog is pretty long):
class MyDialog(QDialog, FORM_CLASS):
..........
# connect the radioButton with a function that reloads the UI
self.radioSingle.toggled.connect(self.refreshWidgets)
.....
# dictionary with all the widgets and the values
self.widgetType = {
self.cmbModelName: ['all'],
self.cmbGridLayer: ['all'],
self.cmbRiverLayer: ['all'],
self.lineNewLayerEdit: ['all'],
self.lineLayerNumber: ['single']
}
# function that refresh the UI with the correct widgets depending on the radiobutton selected
def refreshWidgets(self, idx):
'''
refresh UI widgets depending on the radiobutton chosen
'''
tp = self.radioSingle.isChecked()
for k, v in self.widgetType.items():
if tp:
if 'all' or 'single' in v:
active = True
k.setEnabled(active)
k.setVisible(active)
else:
active = 'all' in v
k.setEnabled(active)
k.setVisible(active)
# attempt to resize the Dialog
self.setSizeConstraint()
Surely the code could be improved (not so skilled with code writing)...
You just gotta set_up your UI to resize when it's needed.
There is a lot of more beautiful way to do this, but here is a simple and fast example:
You just have to define your layout to behave the way you want. If you make use of Minimum and Maximum size it'll resize as you need.
You could also have your own personalized buttons, widgets, .... you would have a cleaner code.
import sys
from PyQt5.QtWidgets import QApplication
from PyQt5.QtWidgets import QDialog
from PyQt5.QtWidgets import QHBoxLayout
from PyQt5.QtWidgets import QRadioButton
from PyQt5.QtWidgets import QVBoxLayout
from PyQt5.QtWidgets import QWidget
class CustomDialog(QDialog):
w_container = None
v_layout_container = None
v_scroll_area = None
v_layout_preview = None
def __init__(self):
"""Init UI."""
super(CustomDialog, self).__init__()
self.init_ui()
def init_ui(self):
"""Init all ui object requirements."""
self.r1 = QRadioButton("smaller")
self.r2 = QRadioButton("bigger")
style = "background: green;"
size = [200, 50]
self.small_size = [200, 250]
self.big_size = [200, 500]
self.resize(200,200)
self.w1 = QWidget()
self.w2 = QWidget()
self.w3 = QWidget()
self.w4 = QWidget()
self.w5 = QWidget()
self.w6 = QWidget()
self.w1.setStyleSheet(style)
self.w2.setStyleSheet(style)
self.w3.setStyleSheet(style)
self.w4.setStyleSheet(style)
self.w5.setStyleSheet(style)
self.w6.setStyleSheet(style)
self.w1.setFixedSize(size[0], size[1])
self.w2.setFixedSize(size[0], size[1])
self.w3.setFixedSize(size[0], size[1])
self.w4.setFixedSize(size[0], size[1])
self.w5.setFixedSize(size[0], size[1])
self.w6.setFixedSize(size[0], size[1])
self.full_container = QVBoxLayout()
self.full_container.setContentsMargins(0,0,0,0)
self.radio_widget = QWidget()
self.radio_widget.setFixedSize(200, 50)
self.radio_container = QHBoxLayout()
self.radio_container.setContentsMargins(0,0,0,0)
self.widget_widget = QWidget()
self.widget_container = QVBoxLayout()
self.radio_widget.setLayout(self.radio_container)
self.widget_widget.setLayout(self.widget_container)
self.full_container.addWidget(self.radio_widget)
self.full_container.addWidget(self.widget_widget)
self.radio_container.addWidget(self.r1)
self.radio_container.addWidget(self.r2)
self.widget_container.addWidget(self.w1)
self.widget_container.addWidget(self.w2)
self.widget_container.addWidget(self.w3)
self.widget_container.addWidget(self.w4)
self.widget_container.addWidget(self.w5)
self.widget_container.addWidget(self.w6)
self.setLayout(self.full_container)
self.r1.setChecked(True)
def paintEvent(self, event):
if self.r1.isChecked():
self.w4.hide()
self.w5.hide()
self.w6.hide()
self.setFixedSize(self.small_size[0],self.small_size[1])
elif self.r2.isChecked():
self.w4.show()
self.w5.show()
self.w6.show()
self.setFixedSize(self.big_size[0], self.big_size[1])
self.full_container.update()
super(CustomDialog, self).paintEvent(event)
def run():
app = QApplication(sys.argv)
GUI = CustomDialog()
GUI.show()
sys.exit(app.exec_())
run()
Observe that I'm using PyQt5, so if you need you just gotta change your imports to the version you are using.

Current Screen Size in Python3 with PyQt5

Is there an alternative in Qt5 python3 to the following code :
https://askubuntu.com/questions/153549/how-to-detect-a-computers-physical-screen-size-in-gtk
from gi.repository import Gdk
s = Gdk.Screen.get_default()
print(s.get_width(), s.get_height())
You can get the primary screen from the QApplication, which returns a QScreen object giving access to many useful properties:
import sys
from PyQt5 import QtWidgets
app = QtWidgets.QApplication(sys.argv)
screen = app.primaryScreen()
print('Screen: %s' % screen.name())
size = screen.size()
print('Size: %d x %d' % (size.width(), size.height()))
rect = screen.availableGeometry()
print('Available: %d x %d' % (rect.width(), rect.height()))
Note that the primaryScreen method is static, so if you've already created an instance of QApplication elsewhere in your application, can easily get a QScreen object later on like this:
screen = QApplication.primaryScreen()
The following python3 code allows to get screen size but is there an alternative to
QtWidgets.QDesktopWidget().screenGeometry(-1) method :
import sys
from PyQt5 import QtWidgets
def main():
"""
allow you to get size of your current screen
-1 is to precise that it is the current screen
"""
sizeObject = QtWidgets.QDesktopWidget().screenGeometry(-1)
print(" Screen size : " + str(sizeObject.height()) + "x" + str(sizeObject.width()))
if __name__ == '__main__':
app = QtWidgets.QApplication(sys.argv)
main()
sys.exit(app.exec_())
The value for screenGeometry() is the display you take a look at.
0 is the main screen, 1, 2 indicates further monitors installed.
To list all availible displays:
def screen_resolutions():
for displayNr in range(QtWidgets.QDesktopWidget().screenCount()):
sizeObject = QtWidgets.QDesktopWidget().screenGeometry(displayNr)
print("Display: " + str(displayNr) + " Screen size : " + str(sizeObject.height()) + "x" + str(sizeObject.width()))
from PyQt5.QtWidgets import QApplication, QWidget
self.desktop = QApplication.desktop()
self.screenRect = self.desktop.screenGeometry()
self.height = self.screenRect.height()
self.width = self.screenRect.width()
You can see here - https://www.programmersought.com/article/58831562998/
Screen information is available from the QApplication object as noted by ekhumoro. However, there should be only one global instance of QApplication. It is often set in the main script of a PyQt Gui application:
import sys
from PyQt5.QtWidgets import QApplication
def main():
...
app = QApplication(sys.argv)
window = MainWindow()
window.show
sys.exit(app.exec_())
That's probably not where you want to work with screen properties.
As pointed out by Omkar76, to access the global instance from elsewhere (e.g. in MainWindow()) use its instance() function. You can then use its primaryScreen property to get access to the many useful pieces of information available from the QScreen object:
from PyQt5.QtWidgets import QApplication, QMainWindow
class MainWindow(QMainWindow):
def __init__(self):
# Window dimensions
app = QApplication.instance()
screen = app.primaryScreen()
geometry = screen.availableGeometry()
self.setFixedSize(geometry.width() * 0.8, geometry.height() * 0.7)
QDesktopWidget is obsolete and so should be avoided.
i honestly don't know why it's always such a hustle to find pyqt5 answers,but anyway i think this is what you were looking for .
print(self.frameSize())

PyQt listview with html rich text delegate moves text bit out of place(pic and code included)

Got a listview where items need to make use of bold text, so a html delegate is the way to go. I looked around and found several solutions and out of them I made this code, but it has the issue that you can see in the gif, which shows the results with the use of the delegate and without.
from PyQt4.QtCore import *
from PyQt4.QtGui import *
import sys
class HTMLDelegate(QStyledItemDelegate):
def __init__(self, parent=None):
super(HTMLDelegate, self).__init__(parent)
self.doc = QTextDocument(self)
def paint(self, painter, option, index):
painter.save()
options = QStyleOptionViewItemV4(option)
self.initStyleOption(options, index)
self.doc.setHtml(options.text)
options.text = ""
style = QApplication.style() if options.widget is None \
else options.widget.style()
style.drawControl(QStyle.CE_ItemViewItem, options, painter)
ctx = QAbstractTextDocumentLayout.PaintContext()
if option.state & QStyle.State_Selected:
ctx.palette.setColor(QPalette.Text, option.palette.color(
QPalette.Active, QPalette.HighlightedText))
textRect = style.subElementRect(QStyle.SE_ItemViewItemText, options)
painter.translate(textRect.topLeft())
self.doc.documentLayout().draw(painter, ctx)
painter.restore()
if __name__ == '__main__':
app = QApplication(sys.argv)
data = ['1','2','3','4','5','6','7','8','9']
main_list = QListView()
main_list.setItemDelegate(HTMLDelegate())
main_list.setModel(QStringListModel(data))
main_list.show()
sys.exit(app.exec_())
Here are PyQt5 and PySide versions
I dont use sizeHint, since through my testing it didnt feel like it was affecting this behavior.
Some attribute is positioning the text wrongly, I have been able to partly counter it with:
self.doc.setDocumentMargin(0)
the default margin is 4, and then move the whole item bit to the right
rect = options.rect
options.rect = QRect(rect.x()+3, rect.y(), rect.width(), rect.height())
But I dont think it addresses the cause of this issue, it just masks it and it becomes a problem again once you try add icons for example
this one is now the one I use
from PyQt5.QtCore import *
from PyQt5.QtWidgets import *
from PyQt5.QtGui import *
import sys
class HTMLDelegate(QStyledItemDelegate):
def __init__(self, parent=None):
super().__init__()
self.doc = QTextDocument(self)
def paint(self, painter, option, index):
painter.save()
options = QStyleOptionViewItem(option)
self.initStyleOption(options, index)
self.doc.setHtml(options.text)
options.text = ""
style = QApplication.style() if options.widget is None \
else options.widget.style()
style.drawControl(QStyle.CE_ItemViewItem, options, painter)
ctx = QAbstractTextDocumentLayout.PaintContext()
print(QStyle.State_Selected)
if option.state & QStyle.State_Selected:
ctx.palette.setColor(QPalette.Text, option.palette.color(
QPalette.Active, QPalette.HighlightedText))
else:
ctx.palette.setColor(QPalette.Text, option.palette.color(
QPalette.Active, QPalette.Text))
textRect = style.subElementRect(
QStyle.SE_ItemViewItemText, options)
if index.column() != 0:
textRect.adjust(5, 0, 0, 0)
thefuckyourshitup_constant = 4
margin = (option.rect.height() - options.fontMetrics.height()) // 2
margin = margin - thefuckyourshitup_constant
textRect.setTop(textRect.top() + margin)
painter.translate(textRect.topLeft())
painter.setClipRect(textRect.translated(-textRect.topLeft()))
self.doc.documentLayout().draw(painter, ctx)
painter.restore()
def sizeHint(self, option, index):
return QSize(self.doc.idealWidth(), self.doc.size().height())
if __name__ == '__main__':
app = QApplication(sys.argv)
data = ['1','2','3','4','5','6','7','8','9']
main_list = QListView()
main_list.setItemDelegate(HTMLDelegate())
main_list.setModel(QStringListModel(data))
main_list.show()
sys.exit(app.exec_())
Well after some more digging and trying, adjusting the text rectangle seems to give the feel that everything is allright and seems to behave consistently in various DEs
textRect.adjust(-1, -4, 0, 0)
I am not sure if its a bug, but one would expect that SE_ItemViewItemText should have give the correct position on its own
An old question, but since I ran across this with a similar problem I thought I'd address some problems with your code. Mostly, it is the self.doc member. It is not going to work as you had hoped. The sizeHint() method is called (multiple times) before paint() is called (multiple times) and will be getting an empty document (which is why you "didnt feel like it was affecting this behavior".
You might think well, just initialize the document in sizeHint() vs paint() then, but that would also be wrong. There is just one delegate and document for your list, so the document would always be based on the last one sized or painted, so having self.doc as a member only saves you the document construction which is probably not worth it considering the confusion it causes.

What class should I use to create painting application by Qt?

It is second time to post this question. Because first one didn't show any effort.
I want to create simple painting application which consist with just black/white pen and canvas. So to create my painting app I want to know what class should I use. Or is there painting soft which is open source and created by Qt?
I know there is QPainter class. So using QPainter is the right way? I heard it is low level graphical tool. So is there more useful one? And I think following source code is one of the implementation of painter app. But Is this right way? I think there is more good way.
My code:
https://gist.github.com/keimina/469fa17508ae2c0c90c4#file-simplepaintapp-py
from PySide.QtGui import QApplication, QMainWindow, QAction, QActionGroup, QWidget, QCursor, QPainter
from PySide.QtCore import QTimer
import PySide.QtCore as QtCore
class W(QWidget):
def __init__(self):
QWidget.__init__(self)
self.resize(400,400)
self.myIsMousePressing = False
self.p = QPainter(self)
self.autoFillBackground()
self.x = 0
self.y = 0
self.r = dict()#{(x,Y,49, 49):rect}
self.penColor = 1
def mousePressEvent(self, event):
self.myIsMousePressing = True
def mouseReleaseEvent(self, event):
self.myIsMousePressing = False
def myTimeOut(self):
if self.myIsMousePressing:
pos = self.mapFromGlobal(QCursor.pos())
self.x = pos.x()/50
self.y = pos.y()/50
self.r[(self.x*50, self.y*50, 49, 49)] = self.penColor
def paintEvent(self, event):
self.p.begin(self)
for k in self.r.keys():
if self.r[k] == 1:
self.p.setPen(QtCore.Qt.black)
self.p.setBrush(QtCore.Qt.black)
else:
self.p.setPen(QtCore.Qt.white)
self.p.setBrush(QtCore.Qt.white)
self.p.drawRect(*k)
self.p.end()
self.update()
class MyWidget(QMainWindow):
def __init__(self):
QMainWindow.__init__(self)
self.setMinimumSize(400, 400)
self.initMenu()
self.w = W()
self.setCentralWidget(self.w)
self.t = QTimer(self.w)
self.t.timeout.connect(self.w.myTimeOut)
self.t.start(1)
def initMenu(self):
self.fileMenu = self.menuBar().addMenu("&File")
self.editMenu = self.menuBar().addMenu("&Edit")
self.helpMenu = self.menuBar().addMenu("&Help")
self.fileMenuAction = QAction("&New", self)
self.editMenuAction1 = QAction("&Black", self)
self.editMenuAction2 = QAction("&White", self)
self.helpMenuAction = QAction("&About", self)
actGroup = QActionGroup(self)
actGroup.addAction(self.editMenuAction1)
actGroup.addAction(self.editMenuAction2)
self.editMenuAction1.setCheckable(True)
self.editMenuAction2.setCheckable(True)
self.editMenuAction1.setChecked(True)
self.fileMenu.addAction(self.fileMenuAction)
self.editMenu.addAction(self.editMenuAction1)
self.editMenu.addAction(self.editMenuAction2)
self.helpMenu.addAction(self.helpMenuAction)
self.editMenuAction1.triggered.connect(self.action1)
self.editMenuAction2.triggered.connect(self.action2)
def action1(self):
self.w.penColor = 1
def action2(self):
self.w.penColor = 2
app = QApplication([])
mainWin = MyWidget()
mainWin.show()
app.exec_()
Thanks.
P.S. I'm using PySide but any other Qt is OK.
QPainter is essentially the only way, short of manipulating individual pixels in a QImage, or using OpenGL, to paint something in Qt. So its use goes without saying and if you paint in Qt, you will have to use QPainter. That's how painting is done. But this has nothing to do with an application that a human might use to "paint". The painting we're talking about is what your application has to do to show something to the user, no matter what the user is doing.
What you're asking is if there's something application-specific in Qt that would help with implementing a "drawing" application. If you're after a vector drawing application, then the graphics scene framework might be of use. Otherwise, there's nothing to help you. You'll have to start with a plain QWidget and implement the behavior you need.

Resources