QWebEngineView history canGoForward/canGoBack works only after three items - python

I have a simple application where the back and forward buttons are enabled/disabled based on the history of the visited web pages. For this, I have found the canGoForward and canGoBack functions
of the QWebEngineHistory. However, the functions return True only after there are at least three items in the history. Normally, browsers work right after visiting the second different page.
Is this supposed to be working like that? I there any way to change it to the 2 web pages? I have looked at the QWebEngineSettings, but there is nothing related to this.
Here is a working example:
#!/usr/bin/python
import sys
from PyQt5.QtCore import QUrl
from PyQt5.QtGui import QIcon
from PyQt5.QtWidgets import (QApplication, QLineEdit, QMainWindow,
QPushButton, QToolBar)
from PyQt5.QtWebEngineWidgets import QWebEnginePage, QWebEngineView
class Example(QMainWindow):
def __init__(self):
super(Example, self).__init__()
self.initUI()
def initUI(self):
self.toolBar = QToolBar(self)
self.addToolBar(self.toolBar)
self.backBtn = QPushButton(self)
self.backBtn.setEnabled(False)
self.backBtn.setIcon(QIcon(':/qt-project.org/styles/commonstyle/images/left-32.png'))
# self.backBtn.setIcon(QIcon('stock_left.png'))
self.backBtn.clicked.connect(self.back)
self.toolBar.addWidget(self.backBtn)
self.forBtn = QPushButton(self)
self.forBtn.setEnabled(False)
# self.forBtn.setIcon(QIcon('stock_right.png'))
self.forBtn.setIcon(QIcon(':/qt-project.org/styles/commonstyle/images/right-32.png'))
self.forBtn.clicked.connect(self.forward)
self.toolBar.addWidget(self.forBtn)
self.address = QLineEdit(self)
self.address.returnPressed.connect(self.load)
self.toolBar.addWidget(self.address)
self.webEngineView = QWebEngineView(self)
self.setCentralWidget(self.webEngineView)
self.webEngineView.page().urlChanged.connect(self.onLoadFinished)
print(self.webEngineView.history().backItem().url())
print(self.webEngineView.history().forwardItem().url())
self.setGeometry(300, 300, 500, 400)
self.setWindowTitle('QWebEnginePage')
self.show()
# self.webEngineView.page().urlChanged.connect(self.urlChanged)
def onLoadFinished(self):
print(dir(self.webEngineView.history()))
print('load finished')
# print(self.webEngineView.history().backItem().url())
# print(self.webEngineView.history().forwardItem().url())
# print(self.webEngineView.history().backItem())
# print(self.webEngineView.history().forwardItem())
# print(self.webEngineView.history().count())
# print(self.webEngineView.history().items())
# print(self.webEngineView.history().canGoForward())
# print(self.webEngineView.history().canGoBack())
if self.webEngineView.history().canGoBack():
self.backBtn.setEnabled(True)
else:
self.backBtn.setEnabled(False)
if self.webEngineView.history().canGoForward():
self.forBtn.setEnabled(True)
else:
self.forBtn.setEnabled(False)
def load(self):
url = QUrl.fromUserInput(self.address.text())
if url.isValid():
self.webEngineView.load(url)
def back(self):
self.webEngineView.page().triggerAction(QWebEnginePage.Back)
def forward(self):
self.webEngineView.page().triggerAction(QWebEnginePage.Forward)
def urlChanged(self, url):
self.address.setText(url.toString())
def main():
app = QApplication(sys.argv)
ex = Example()
sys.exit(app.exec_())
if __name__ == '__main__':
main()

Related

I'm trying to save the state of qtreeview with the directories expanded or not (load in the state I left when I closed .) using Qsettings but I can't

I am using the code below. Mainwindow's state is preserved but qtreeviw's is not.
import sys
from PyQt5.QtCore import QSettings, QByteArray
from PyQt5.QtWidgets import QApplication, QTreeView, QFileSystemModel, QVBoxLayout, QWidget
from PyQt5 import QtCore
class MyApp(QWidget):
def __init__(self):
super().__init__()
self.tree = QTreeView()
self.settings = QSettings('testorg', 'testapp')
try:
self.tree.header().restoreState(self.settings.value("estado_header"))
self.resize(self.settings.value('window size'))
self.move(self.settings.value('window position'))
except:
pass
self.model = QFileSystemModel()
self.model.setRootPath(r"C:\Users\dan-s>")
self.tree.setModel(self.model)
#self.tree.header().restoreState(self.settings.value("estado_header"))
#self.tree.collapseAll()
layout = QVBoxLayout()
layout.addWidget(self.tree)
self.setLayout(layout)
def closeEvent(self, event):
self.settings.setValue('window size', self.size())
self.settings.setValue('window position', self.pos())
state = self.tree.header().saveState()
self.settings.setValue('estado_header', state)
super().closeEvent(event)
app = QApplication(sys.argv)
demo = MyApp()
demo.show()
sys.exit(app.exec_())
I've tried other ways but I can't solve it.

Copy selected text from PDF in PyQt5 QWebEngineView

I use PyQt5.QtWebEngineWidgets to display a PDF and I'm trying to get the selected text.
Using hasSelection() and selectedText() works on HTML files, but it fails when the web view displays a PDF file. Any idea how to copy the selected text (apart from using CTRL+C)?
from PyQt5.QtCore import QUrl
from PyQt5.QtWidgets import QApplication, QMainWindow, QWidget, QMenu
from PyQt5.QtWebEngineWidgets import QWebEngineSettings, QWebEngineView
class Browser(QWebEngineView):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
def get_selection(self):
if self.hasSelection():
print(self.selectedText())
else:
print('No text selected')
def contextMenuEvent(self, event):
self.menu = QMenu()
get_selection = self.menu.addAction('Copy')
get_selection.triggered.connect(self.get_selection)
self.menu.popup(event.globalPos())
class MainWindow(QMainWindow):
def __init__(self):
super(QMainWindow, self).__init__()
self.setWindowTitle("PDF Viewer")
self.setGeometry(0, 28, 1000, 750)
self.webView = QWebEngineView()
self.webView = Browser()
self.webView.settings().setAttribute(QWebEngineSettings.PluginsEnabled, True)
self.webView.settings().setAttribute(QWebEngineSettings.PdfViewerEnabled, True)
self.setCentralWidget(self.webView)
if __name__ == '__main__':
import sys
app = QApplication(sys.argv)
win = MainWindow()
win.show()
win.webView.setUrl(QUrl.fromLocalFile("d:/test.pdf"))
sys.exit(app.exec_())

Opening new window while out of focus using "keyboard"

I am trying to track my keypresses, using the module "keyboard" while a PySide2 Widget is not in focus, which works fine. However when I try to create a new Widget using a "keyboard" shortcut the program crashes. Opening a window on a button press works fine. I can also call non UI functions using "keyboard" eg. the print function without any problem.
Do you know a way to fix this and open a new window using "keyboard" or any other method, while a PySide2 window is not in focus. In this example I want to open a new window on "CTRL+D". The Problem exists both for PySide2 and PyQt5.
This is my shortened code:
import sys
import json
import os
import keyboard
from PySide2.QtWidgets import QApplication, QWidget, QGridLayout, QKeySequenceEdit, QLabel, QPushButton, QShortcut
from PySide2.QtCore import Qt, QObject, Signal, Slot # Qt.Key_W beispielsweise
#from PyQt5.QtWidgets import QApplication, QWidget, QGridLayout, QKeySequenceEdit, QLabel, QPushButton, QShortcut
#from PyQt5.QtCore import Qt, QObject, pyqtSignal as Signal, pyqtSlot as Slot # Qt.Key_W beispielsweise
class ConfigWindow(QWidget):
def __init__(self):
super().__init__()
self.initUi()
self.init_shortcuts()
self.show()
def initUi(self):
self.setGeometry(300,300, 400, 250)
self.setWindowTitle("Settings")
grid = QGridLayout()
self.setLayout(grid)
self.keyseq = QKeySequenceEdit("CTRL+D")
grid.addWidget(self.keyseq, 0, 0)
s_button = QPushButton("Safe")
grid.addWidget(s_button, 1, 0)
cl_button = QPushButton("Close")
grid.addWidget(cl_button, 1, 1)
cl_button.clicked.connect(self.close)
open_button = QPushButton("openw")
grid.addWidget(open_button, 2, 0)
open_button.clicked.connect(self.call_item_parser)
def keyPressEvent(self, event): #event:PySide2.QtGui.QKeyEvent
if event.key() == Qt.Key_Escape:
self.close()
# shortcuts are listened to, while program is running
def init_shortcuts(self):
str_value = self.keyseq.keySequence().toString()
print("Binding _price_keyseq to {}".format(str_value))
keyboard.add_hotkey(str_value, self.call_item_parser)
# keyboard.add_hotkey(str_value, print, args=("this works")) # this would work
def call_item_parser(self):
self.h_w = ParseWindow()
self.h_w.setWindowTitle("New Window")
self.h_w.setGeometry(100, 100, 100, 100)
self.h_w.show()
class ParseWindow(QWidget):
def __init__(self):
super().__init__()
app = QApplication(sys.argv)
w = ConfigWindow()
sys.exit(app.exec_())
The problem is caused because the callback registered in keyboard is executed in a secondary thread as can be verified by modifying the following part of the code and printing threading.current_thread(). In Qt it is forbidden to create any widget in another thread since they are not thread-safe.
def call_item_parser(self):
print(threading.current_thread())
self.h_w = ParseWindow()
self.h_w.setWindowTitle("New Window")
self.h_w.setGeometry(100, 100, 100, 100)
self.h_w.show()
print(threading.current_thread())
app = QApplication(sys.argv)
w = ConfigWindow()
sys.exit(app.exec_())
Output:
<_MainThread(MainThread, started 140144979916608)>
Binding _price_keyseq to ctrl+a
<Thread(Thread-10, started daemon 140144220817152)>
One possible solution is to use a signal to send the information to the main thread, and invoke the callback in the main thread.
import sys
from functools import partial
import platform
import threading
import keyboard
from PySide2.QtCore import Qt, QObject, Signal, Slot
from PySide2.QtGui import QKeySequence
from PySide2.QtWidgets import (
QApplication,
QWidget,
QGridLayout,
QKeySequenceEdit,
QPushButton,
)
class KeyBoardManager(QObject):
activated = Signal(str)
def __init__(self, parent=None):
super().__init__(parent)
self._callbacks = dict()
self.activated.connect(self._handle_activated)
#property
def callbacks(self):
return self._callbacks
def register(self, shortcut, callback, *, args=(), kwargs=None):
self.callbacks[shortcut] = (callback, args, kwargs or {})
keyboard.add_hotkey(shortcut, partial(self.activated.emit, shortcut))
#Slot(str)
def _handle_activated(self, shortcut):
values = self.callbacks.get(shortcut)
if values is not None:
callback, args, kwargs = self._callbacks[shortcut]
callback(*args, **kwargs)
class ConfigWindow(QWidget):
def __init__(self):
super().__init__()
self.initUi()
self.init_shortcuts()
self.show()
def initUi(self):
self.setGeometry(300, 300, 400, 250)
self.setWindowTitle("Settings")
grid = QGridLayout(self)
self.keyseq = QKeySequenceEdit("CTRL+A")
grid.addWidget(self.keyseq, 0, 0)
s_button = QPushButton("Safe")
grid.addWidget(s_button, 1, 0)
cl_button = QPushButton("Close")
grid.addWidget(cl_button, 1, 1)
cl_button.clicked.connect(self.close)
open_button = QPushButton("openw")
grid.addWidget(open_button, 2, 0)
open_button.clicked.connect(self.call_item_parser)
def keyPressEvent(self, event): # event:PySide2.QtGui.QKeyEvent
if event.key() == Qt.Key_Escape:
self.close()
# shortcuts are listened to, while program is running
def init_shortcuts(self):
self.keyboard_manager = KeyBoardManager()
str_value = self.keyseq.keySequence().toString()
if platform.system() == "Linux":
str_value = str_value.lower()
print("Binding _price_keyseq to {}".format(str_value))
self.keyboard_manager.register(str_value, self.call_item_parser)
def call_item_parser(self):
print(threading.current_thread())
self.h_w = ParseWindow()
self.h_w.setWindowTitle("New Window")
self.h_w.setGeometry(100, 100, 100, 100)
self.h_w.show()
class ParseWindow(QWidget):
pass
def main():
print(threading.current_thread())
app = QApplication(sys.argv)
w = ConfigWindow()
sys.exit(app.exec_())
if __name__ == "__main__":
main()
Output:
<_MainThread(MainThread, started 140037641176896)>
Binding _price_keyseq to ctrl+a
<_MainThread(MainThread, started 140037641176896)>

Line Animation with QPropertyAnimation

Trying to animate a line growing from nothing to a (0,0) to (200, 200) line with PyQt5 and using QPropertyAnimation. I already read a lot of documentation about Qt and tried several samples, but I just cannot get this to work. This is the code I have now:
from PyQt5 import QtCore
from PyQt5.QtWidgets import QApplication, QWidget, QLabel
from PyQt5.QtGui import QPainter, QPixmap, QPainterPath
from PyQt5.QtCore import QObject, QPointF, QPropertyAnimation, pyqtProperty
from PyQt5.QtCore import QLineF
import sys
class Sample(QWidget):
l1 = QLineF(QPointF(), QPointF())
def __init__(self):
super().__init__()
self.initView()
self.initAnimation()
def initView(self):
self.show()
def initAnimation(self):
self.anim = QPropertyAnimation(self.l1, b'geometry')
self.anim.setDuration(7000)
self.anim.setStartValue(QPointF(0, 0))
self.anim.setEndValue(QPointF(200, 200))
self.anim.start()
if __name__ == '__main__':
app = QApplication(sys.argv)
ex = Sample()
sys.exit(app.exec_())
Not posting all previous attemps, each one fails with a different error. I got a fade out animation on a widget to work, and a picture following a path, but I can seem to make a simple line drawing work. I was hoping to achieve something like this:
Codepen example
Qt documentation is huge and it seems there are several ways to achieve this, painter and timer, animation, variant animation, but I am not very familiar with C++ and the translation to Python is not always easy. Also samples are not that easy to find.
Am I missing something obvious?
Thanks!
For the record, this is what I achieved so far but as soon as I un-comment the QPropertyAnimation creation, app does not launch. Anyway I was still far from the result in the accepted answer.
from PyQt5.QtWidgets import *
from PyQt5.QtGui import *
from PyQt5.QtCore import *
import sys
class MyLine(QGraphicsLineItem, QObject):
def __init__(self):
super().__init__()
def _set_start(self, point):
self.setLine(point.x(), point.y(), self.line().p2().x(), self.line().p2().y())
def _set_end(self, point):
self.setLine(self.line().p1().x(), self.line().p1().y(), point.x(), point.y())
start = pyqtProperty(QPointF, fset=_set_start)
end = pyqtProperty(QPointF, fset=_set_end)
class Example(QWidget):
def __init__(self):
super().__init__()
self.initUI()
def initUI(self):
hbox = QHBoxLayout(self)
self.button = QPushButton("Start", self)
self.button.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)
hbox.addWidget(self.button)
hbox.addSpacing(40)
self.line = MyLine()
self.line.setLine(0, 0, 10, 10)
scene = QGraphicsScene()
scene.addItem(self.line)
view = QGraphicsView(scene)
hbox.addWidget(view)
self.anim = QPropertyAnimation(self.line, b"end") # crash without error here
# self.anim.setDuration(2500)
# self.anim.setLoopCount(1)
# self.anim.setStartValue(QPointF(10, 10))
# self.anim.setEndValue(QPointF(200, 200))
# self.button.clicked.connect(self.anim.start)
self.setGeometry(300, 300, 380, 250)
self.setWindowTitle('Color anim')
self.show()
if __name__ == "__main__":
app = QApplication([])
ex = Example()
ex.show()
app.exec_()
You have to use QGraphicsView, QGraphicsScene with QGraphicsLineItem as I show below:
from PyQt5 import QtCore, QtGui, QtWidgets
class LineAnimation(QtCore.QObject):
def __init__(self, parent=None):
super(LineAnimation, self).__init__(parent)
self.m_line = QtCore.QLineF()
self.m_item = QtWidgets.QGraphicsLineItem()
self.m_item.setLine(self.m_line)
self.m_item.setPen(
QtGui.QPen(
QtGui.QColor("salmon"),
10,
QtCore.Qt.SolidLine,
QtCore.Qt.SquareCap,
QtCore.Qt.RoundJoin,
)
)
self.m_animation = QtCore.QPropertyAnimation(
self,
b"p2",
parent=self,
startValue=QtCore.QPointF(0, 0),
endValue=QtCore.QPointF(200, 200),
duration=5 * 1000,
)
self.m_animation.start()
def p1(self):
return self.m_line.p1()
def setP1(self, p1):
self.m_line.setP1(p1)
self.m_item.setLine(self.m_line)
def p2(self):
return self.m_line.p2()
def setP2(self, p2):
self.m_line.setP2(p2)
self.m_item.setLine(self.m_line)
p1 = QtCore.pyqtProperty(QtCore.QPointF, fget=p1, fset=setP1)
p2 = QtCore.pyqtProperty(QtCore.QPointF, fget=p2, fset=setP2)
class MainWindow(QtWidgets.QMainWindow):
def __init__(self, parent=None):
super(MainWindow, self).__init__(parent)
scene = QtWidgets.QGraphicsScene(self)
view = QtWidgets.QGraphicsView(
scene, alignment=QtCore.Qt.AlignLeft | QtCore.Qt.AlignTop
)
self.setCentralWidget(view)
line_animation = LineAnimation(self)
scene.addItem(line_animation.m_item)
if __name__ == "__main__":
import sys
app = QtWidgets.QApplication(sys.argv)
w = MainWindow()
w.resize(640, 480)
w.show()
sys.exit(app.exec_())

How do I print in multiple QLabel when I click the PushButton?

How do I print in multiple QLabel when I click the PushButton?, because it only works in Quantity I also want it in Item Name and Price. I tried putting multiple print_click(self) it wont work it say redefinition of unused 'print_clink'. Thanks in advance
My Code:
import sys
from PyQt5.QtWidgets import QApplication, QWidget, QHBoxLayout, QPushButton, QLabel, QLineEdit
from PyQt5.QtCore import pyqtSlot`
class Window(QWidget):
def __init__(self):
super().__init__()
self.initUI()
def initUI(self):
self.ItemName = QLabel('Item Name:')
self.Item_Line = QLabel('')
self.Item_Name = QLineEdit('')
self.PriceName = QLabel('Price:')
self.Price_Line = QLabel('')
self.Price_Name = QLineEdit('')
self.QuantityName = QLabel('Quantity:')
self.Quantity_Line = QLabel('0')
self.Quantity_Name = QLineEdit()
self.Update_button = QPushButton("Update")
self.Update_button.clicked.connect(self.print_click)
self.Clear_button = QPushButton("Clear")
self.Clear_button.clicked.connect(self.clear_click)
hbox = QHBoxLayout(self)
hbox.addWidget(self.ItemName)
hbox.addWidget(self.Item_Name)
hbox.addWidget(self.PriceName)
hbox.addWidget(self.Price_Name)
hbox.addWidget(self.QuantityName)
hbox.addWidget(self.Quantity_Line)
hbox.addWidget(self.Quantity_Name)
hbox.addWidget(self.Update_button)
hbox.addWidget(self.Clear_button)
self.show()
self.Clear_button.clicked.connect(self.Item_Line.clear)
self.Clear_button.clicked.connect(self.Item_Name.clear)
self.Clear_button.clicked.connect(self.Price_Line.clear)
self.Clear_button.clicked.connect(self.Price_Name.clear)
self.Clear_button.clicked.connect(self.Quantity_Line.clear)
self.Clear_button.clicked.connect(self.Quantity_Name.clear)
#pyqtSlot()
def print_click(self):
self.Quantity_Line.setText(self.Quantity_Name.text())
def clear_click(self):
self.Quantity_Line(self.Quantity_Name.text(''))
return self.Quantity
if __name__ == "__main__":
app = QApplication(sys.argv)
win = Window()
sys.exit(app.exec_())
I'm not completely sure of the expected result but I guess there are some mistakes and redundancies in your code :
the Price_Line and Item_Line weren't added to the QHBoxLayout
the method print_click wasn't setting the text from Price_Name and Item_Name on the respective Price_Line and Item_Line.
the clear_click method wasn't really useful as you already connected the clear method of every other elements on that button.
The following code is adapted from yours, paying attention to the points mentioned above :
import sys
from PyQt5.QtWidgets import QApplication, QWidget, QHBoxLayout, QPushButton, QLabel, QLineEdit
from PyQt5.QtCore import pyqtSlot
class Window(QWidget):
def __init__(self):
super().__init__()
self.initUI()
def initUI(self):
self.ItemName = QLabel('Item Name:')
self.Item_Line = QLabel('')
self.Item_Name = QLineEdit()
self.PriceName = QLabel('Price:')
self.Price_Line = QLabel('')
self.Price_Name = QLineEdit()
self.QuantityName = QLabel('Quantity:')
self.Quantity_Line = QLabel('0')
self.Quantity_Name = QLineEdit()
self.Update_button = QPushButton("Update")
self.Update_button.clicked.connect(self.print_click)
self.Clear_button = QPushButton("Clear")
hbox = QHBoxLayout(self)
hbox.addWidget(self.ItemName)
hbox.addWidget(self.Item_Line)
hbox.addWidget(self.Item_Name)
hbox.addWidget(self.PriceName)
hbox.addWidget(self.Price_Line)
hbox.addWidget(self.Price_Name)
hbox.addWidget(self.QuantityName)
hbox.addWidget(self.Quantity_Line)
hbox.addWidget(self.Quantity_Name)
hbox.addWidget(self.Update_button)
hbox.addWidget(self.Clear_button)
self.show()
self.Clear_button.clicked.connect(self.Item_Line.clear)
self.Clear_button.clicked.connect(self.Item_Name.clear)
self.Clear_button.clicked.connect(self.Price_Line.clear)
self.Clear_button.clicked.connect(self.Price_Name.clear)
self.Clear_button.clicked.connect(self.Quantity_Line.clear)
self.Clear_button.clicked.connect(self.Quantity_Name.clear)
def print_click(self):
self.Price_Line.setText(self.Price_Name.text())
self.Item_Line.setText(self.Item_Name.text())
self.Quantity_Line.setText(self.Quantity_Name.text())
if __name__ == "__main__":
app = QApplication(sys.argv)
win = Window()
sys.exit(app.exec_())

Categories

Resources