Empty image in QClipboard [duplicate] - qt

I'm trying to run a simple PyQt5 application on Linux, the code is as follows:
#!/usr/bin/python
import sys
from PyQt5.QtWidgets import QApplication, QWidget
def main():
app = QApplication(sys.argv)
w = QWidget()
w.resize(250, 150)
w.move(300, 300)
w.setWindowTitle('Simple')
w.show()
mime = app.clipboard().mimeData()
print(mime.hasImage()) # True
print(mime.imageData()) # None
sys.exit(app.exec_())
if __name__ == '__main__':
main()
Before running it, I copied an image into the clipboard, so mime.hasImage() should return True. No problem, that's also the case. But what's weird is, mime.imageData() sometimes returns None. that shouldn't happen. mime.imageData() should contain the image that I copied instead of None. Is there anything wrong with the code?
By the way, this seems to only happen on Linux, mime.imageData() never returns None on Windows. I'm using python3

That hasImage() returns True does not imply that imageData() returns a QImage since it only indicates that the user copied an image to the clipboard, and in what format do I copy the image? Well, it could be png, jpg, etc or it could provide the url for the client application to download or html to insert it into the client application and then obtain the image by rendering the HTML.
So in general the application from which the image was copied is responsible for the sending format and that there is no restrictive standard for that format but there are common formats.
The following example shows the logic to handle the images that come from urls and HTML:
#!/usr/bin/python
import sys
from functools import cached_property
from PyQt5.QtCore import pyqtSignal, QObject, QUrl
from PyQt5.QtNetwork import QNetworkAccessManager, QNetworkRequest, QNetworkReply
from PyQt5.QtGui import QGuiApplication, QImage, QPixmap
from PyQt5.QtWidgets import QApplication, QWidget, QLabel
from bs4 import BeautifulSoup
class ImageDownloader(QObject):
finished = pyqtSignal(QImage)
def __init__(self, parent=None):
super().__init__(parent)
self.manager.finished.connect(self.handle_finished)
#cached_property
def manager(self):
return QNetworkAccessManager()
def start_download(self, url):
self.manager.get(QNetworkRequest(url))
def handle_finished(self, reply):
if reply.error() != QNetworkReply.NoError:
print("error: ", reply.errorString())
return
image = QImage()
image.loadFromData(reply.readAll())
self.finished.emit(image)
class ClipboardManager(QObject):
imageChanged = pyqtSignal(QImage)
def __init__(self, parent=None):
super().__init__(parent)
QGuiApplication.clipboard().dataChanged.connect(
self.handle_clipboard_datachanged
)
self.downloader.finished.connect(self.imageChanged)
#cached_property
def downloader(self):
return ImageDownloader()
def handle_clipboard_datachanged(self):
mime = QGuiApplication.clipboard().mimeData()
if mime.hasImage():
image = mime.imageData()
if image is not None:
self.imageChanged.emit(image)
elif mime.hasUrls():
url = mime.urls()[0]
self.downloader.start_download(urls[0])
elif mime.hasHtml():
html = mime.html()
soup = BeautifulSoup(html, features="lxml")
imgs = soup.findAll("img")
if imgs:
url = QUrl.fromUserInput(imgs[0]["src"])
self.downloader.start_download(url)
else:
for fmt in mime.formats():
print(fmt, mime.data(fmt))
def main():
app = QApplication(sys.argv)
label = QLabel(scaledContents=True)
label.resize(250, 150)
label.move(300, 300)
label.setWindowTitle("Simple")
label.show()
manager = ClipboardManager()
manager.imageChanged.connect(
lambda image: label.setPixmap(QPixmap.fromImage(image))
)
sys.exit(app.exec_())
if __name__ == "__main__":
main()

Related

Qt: QMediaPlayer.setSource() was blocking the GUI so I moved it to a separate thread; am I doing this right?

Running this code:
from PySide6 import QtWidgets as qtw
from PySide6 import QtMultimedia as qtm
from PySide6 import QtMultimediaWidgets as qtmw
from PySide6 import QtCore as qtc
app = qtw.QApplication()
path = "video.mp4"
video_widget = qtmw.QVideoWidget()
video_widget.show()
media_player = qtm.QMediaPlayer()
media_player.setVideoOutput(video_widget)
media_player.mediaStatusChanged.connect(media_player.play)
media_player.setSource(path)
qtc.QTimer.singleShot(2000, lambda: media_player.setSource(''))
app.exec()
, you will notice that when media_player.setSource('') is called after 2000 milliseconds, the GUI becomes unresponsive for a good second or so. And it seems like it is not possible to move only the setSource() call to a separate thread, I get a warning saying QObject::killTimer: Timers cannot be stopped from another thread (you can replicate this by replacing qtc.QTimer... call with qtc.QThreadPool.globalInstance().start(lambda: media_player.setSource(''))). So, after hours of pondering, as a work around, I moved the entire QMediaPlayer instance and all objects related to it to a thread of its own and used QVideoSink's videoFrameChanged signal to update the video on the video widget:
from PySide6 import QtWidgets as qtw
from PySide6 import QtMultimedia as qtm
from PySide6 import QtMultimediaWidgets as qtmw
from PySide6 import QtCore as qtc
path = "video.mp4"
class VideoPlayer(qtmw.QVideoWidget):
_stop_worker_signal = qtc.Signal()
_initialize_media_player_signal = qtc.Signal()
_set_media_source_signal = qtc.Signal(str)
_play_media_signal = qtc.Signal()
_stop_media_signal = qtc.Signal()
_pause_media_signal = qtc.Signal()
def __init__(self) -> None:
super().__init__()
self._worker = Worker()
self._worker_thread = qtc.QThread()
self._worker.moveToThread(self._worker_thread)
self._stop_worker_signal.connect(self._worker.stop)
self._initialize_media_player_signal.connect(self._worker.create_media_player)
self._worker.video_sink_frame_changed_signal.connect(lambda frame: self.videoSink().setVideoFrame(frame))
self._set_media_source_signal.connect(self._worker.set_media_source)
self._stop_media_signal.connect(self._worker.stop_media)
self._worker_thread.start()
self._initialize_media_player_signal.emit()
self._set_media_source_signal.emit(path)
self.show()
def keyPressEvent(self, event):
if event.key() == qtc.Qt.Key.Key_Q:
self._set_media_source_signal.emit('')
def closeEvent(self, event: qtc.QEvent):
loop = qtc.QEventLoop()
self._worker.stopped_signal.connect(loop.exit)
self._stop_worker_signal.emit()
loop.exec()
self._worker_thread.exit()
class Worker(qtc.QObject):
video_sink_frame_changed_signal = qtc.Signal(qtm.QVideoFrame)
stopped_signal = qtc.Signal()
def create_media_player(self):
self._audio_output = qtm.QAudioOutput(qtm.QMediaDevices.defaultAudioOutput())
self._video_sink = qtm.QVideoSink()
self._video_sink.videoFrameChanged.connect(self.video_sink_frame_changed_signal)
self._media_player = qtm.QMediaPlayer()
self._media_player.setAudioOutput(self._audio_output)
self._media_player.setVideoSink(self._video_sink)
self._media_player.mediaStatusChanged.connect(self._media_player.play)
def set_media_source(self, source: str) -> None:
self._media_player.setSource(source)
def stop_media(self) -> None:
if not self._media_player.playbackState() == qtm.QMediaPlayer.PlaybackState.StoppedState:
self._media_player.stop()
def stop(self):
self._media_player.stop()
self._audio_output.deleteLater()
self._video_sink.deleteLater()
self._media_player.deleteLater()
self.stopped_signal.emit()
app = qtw.QApplication()
vp = VideoPlayer()
app.exec()
Now, there's no unresponsiveness in the GUI when setSource() is called but, as one can see, it's a pretty convoluted way to do this. Is there a better way?

Pyside: QNetworkAccessManager sends no Request

I tried to establish a network connection with PySide (Ubuntu 15.04, Python3.4, PySide 1.2.4). I used the example code from the documentation.
The QNetworkAccessManager does not send the request and I recieve no answer. I checked the Network state with QNetworkSession(QNetworkConfigurationManager().defaultConfiguration()).State() but it says the State is invalid. This seems to make no sense since I am on a desktop pc with a network connection via ethernet cable.
My complete example for the test is the following code:
#!/usr/bin/python3
# -*- coding: utf-8 -*-
import sys
from PySide.QtGui import QApplication
from PySide.QtCore import QUrl
from PySide.QtNetwork import QNetworkAccessManager, QNetworkRequest, QNetworkSession, QNetworkConfigurationManager
def replyFinished(reply):
print(reply)
if __name__ == "__main__":
app = QApplication(sys.argv)
manager = QNetworkAccessManager()
manager.finished.connect(replyFinished)
print(QNetworkSession(QNetworkConfigurationManager().defaultConfiguration()).State())
print("Sending request")
print(manager.get(QNetworkRequest(QUrl("http://www.heise.de/ct/"))))
This prints
PySide.QtNetwork.QNetworkSession.State.Invalid
Sending request
<PySide.QtNetwork.QNetworkReply object at 0x7f4b59c9af08>
but it should display the PySide.QtNetwork.QNetworkReply object twice.
My example was too small. I realized this because of the comment of Pavel Strakhov. I extended it to display a window with a button. By clicking the button it connects successfully. QNetworkSession(QNetworkConfigurationManager().defaultConfiguration()).State() is still invalid but it works.
This is the working code:
#!/usr/bin/python3
# -*- coding: utf-8 -*-
import sys
from PySide.QtGui import QApplication, QWidget, QBoxLayout, QPushButton
from PySide.QtCore import QUrl
from PySide.QtNetwork import QNetworkAccessManager, QNetworkRequest, QNetworkSession, QNetworkConfigurationManager
class Window(QWidget):
def __init__(self):
super().__init__()
self.manager = QNetworkAccessManager()
self.manager.finished.connect(self.reply_finished)
layout = QBoxLayout(QBoxLayout.TopToBottom)
button = QPushButton("connect")
button.clicked.connect(self.network_connect)
layout.addWidget(button)
self.setLayout(layout)
self.setWindowTitle("Network test")
self.setGeometry(100, 100, 200, 150)
self.show()
def network_connect(self):
print(QNetworkSession(QNetworkConfigurationManager().defaultConfiguration()).State())
request = QNetworkRequest(QUrl("http://example.org"))
print("Sending request")
self.manager.get(request)
def reply_finished(self, reply):
print(reply)
print(reply.readData(500))
if __name__ == "__main__":
app = QApplication(sys.argv)
window = Window()
app.exec_()

PyQt5 file dialog not showing up

I have connected a QPushButton to a method that call file dialog. The simplified code look like this:
def init_buttons(self):
self.browse_button = QPushButton('&Browse')
self.browse_button.clicked.connect(self.browse_file)
def browse_file(self):
file_name = QFileDialog.getExistingDirectory()
# Just for checking
print(file_name)
Sometimes QFileDialog won't showing up. The process is indeed running, since the main class/widget doesn't response to my clicking. Sometimes it's showing up.
If QFileDialog doesn't show up, with pycharm, I have to stop and kill process to end the program. If I run the program directly from terminal, I have to manually end the running process to end the program. I can't figure out what causing this, since terminal not showing any exception or warning.
So, what is this?
The parameters for the getExistingDirectory were wrong. Please try this. Also, I have added further information in my pull request.
import os
def browse_file(self):
self.save_dir = QFileDialog.getExistingDirectory(self,
"Open Save Directory", os.path.expanduser('~'))
print(self.save_dir)
#!/usr/bin/python3
# -*- coding: utf-8 -*-
import sys
from PyQt5.QtWidgets import (QMainWindow, QTextEdit,
QAction,QMessageBox, QFileDialog, QApplication,QPushButton,QInputDialog,QLineEdit)
from PyQt5.QtGui import QIcon
class Example(QMainWindow):
def __init__(self):
super().__init__()
self.initUI()
def initUI(self):
self.fileName=""
self.text=""
btn1 = QPushButton("Encrypt", self)
btn1.clicked.connect(self.onBtn1)
self.show()
def onBtn1(self):
self.fileName, _ = QFileDialog.getOpenFileName(self, 'Open file', '/Users/Jarvis/Desktop/')
if __name__ == '__main__':
app = QApplication(sys.argv)
ex = Example()
sys.exit(app.exec_())

Single QTreeWidgetItem instance on multiple QTreeWidget?

I'm trying to show a single QTreeWidgetItem instance on 2 QTreeWidgets, which ends up the item shown only on the 1st tree without being notified. I haven't seen its API doc talks about a limitation if any. Is there a way to workaround?
#!/usr/bin/python
import os
import sys
from PySide.QtCore import QFile
from PySide.QtUiTools import QUiLoader
from PySide.QtGui import QApplication, QTreeWidget, QTreeWidgetItem, QWidget
class MyTreeWidgetItem(QTreeWidgetItem):
def __init__(self, *args):
super(MyTreeWidgetItem, self).__init__()
class MyWidget(QWidget):
def __init__(self, *args):
super(MyWidget, self).__init__()
loader = QUiLoader()
file = QFile('./src/prove_qtreewidget/qtree_mainwidget.ui')
file.open(QFile.ReadOnly)
self.widget_ui = loader.load(file, self)
file.close()
item1 = MyTreeWidgetItem(self)
item1.setText(0, 'Item 1')
_tw1 = self.widget_ui.findChild(QTreeWidget, '_tree_widget_1')
_tw2 = self.widget_ui.findChild(QTreeWidget, '_tree_widget_2')
_tw1.addTopLevelItem(item1)
_tw2.addTopLevelItem(item1)
if __name__ == '__main__':
print("Running in " + os.getcwd() + " .\n")
app = QApplication(sys.argv)
win = MyWidget()
win.show()
app.exec_()
.ui file above is available here.
Using Qt 4.8, Ubuntu 12.04
I haven't tried PyQt binding but I just assume shouldn't be any different w/o proof.
What you need is a model and a QTreeView, that's for what they are for:
Model/View Programming: Widgets do not maintain internal data containers. They access external data through a standardized interface and therefore avoid data duplication.

catch link clicks in QtWebView and open in default browser

I am opening a page in QtWebView (in PyQt if that matters) and I want to open all links in the system default browser. I.e. a click on a link should not change the site in the QtWebView but it should open it with the default browser. I want to make it impossible to the user to change the site in the QtWebView.
How can I do that?
Thanks,
Albert
That does it:
import sys, webbrowser
from PyQt4.QtCore import *
from PyQt4.QtGui import *
from PyQt4.QtWebKit import *
app = QApplication(sys.argv)
web = QWebView()
web.load(QUrl("http://www.az2000.de/projects/javascript-project/"))
web.page().setLinkDelegationPolicy(QWebPage.DelegateAllLinks)
def linkClicked(url): webbrowser.open(str(url.toString()))
web.connect(web, SIGNAL("linkClicked (const QUrl&)"), linkClicked)
web.show()
sys.exit(app.exec_())
Updated example for PyQt5 (the magic is to re-implement the "acceptNavigationRequest" method):
from PyQt5 import QtWidgets, QtCore, QtGui, QtWebEngineWidgets
class RestrictedQWebEnginePage(QtWebEngineWidgets.QWebEnginePage):
""" Filters links so that users cannot just navigate to any page on the web,
but just to those pages, that are listed in allowed_pages.
This is achieved by re-implementing acceptNavigationRequest.
The latter could also be adapted to accept, e.g. URLs within a domain."""
def __init__(self, parent=None):
super().__init__(parent)
self.allowed_pages = []
def acceptNavigationRequest(self, qurl, navtype, mainframe):
# print("Navigation Request intercepted:", qurl)
if qurl in self.allowed_pages: # open in QWebEngineView
return True
else: # delegate link to default browser
QtGui.QDesktopServices.openUrl(qurl)
return False
class RestrictedWebViewWidget(QtWidgets.QWidget):
"""A QWebEngineView is required to display a QWebEnginePage."""
def __init__(self, parent=None, url=None, html_file=None):
super().__init__(parent)
self.view = QtWebEngineWidgets.QWebEngineView()
self.page = RestrictedQWebEnginePage()
if html_file:
print("Loading File:", html_file)
self.url = QtCore.QUrl.fromLocalFile(html_file)
self.page.allowed_pages.append(self.url)
self.page.load(self.url)
elif url:
print("Loading URL:", url)
self.url = QtCore.QUrl(url)
self.page.allowed_pages.append(self.url)
self.page.load(self.url)
# associate page with view
self.view.setPage(self.page)
# set layout
self.vl = QtWidgets.QVBoxLayout()
self.vl.addWidget(self.view)
self.setLayout(self.vl)
if __name__ == '__main__':
import sys
app = QtWidgets.QApplication(sys.argv)
web = RestrictedWebViewWidget(url="YOUR URL") # or YOUR local HTML file
web.show()
sys.exit(app.exec_())
When you click a link that has the target="_blank" attribute, QT calls the CreateWindow method in QWebEnginePage to create a new tab/new window.
The key is to re-implement this method to, instead of opening a new tab, open a new browser window.
class WebEnginePage(QtWebEngineWidgets.QWebEnginePage):
def createWindow(self, _type):
page = WebEnginePage(self)
page.urlChanged.connect(self.open_browser)
return page
def open_browser(self, url):
page = self.sender()
QDesktopServices.openUrl(url)
page.deleteLater()
class MainWindow(QtWidgets.QMainWindow):
def __init__(self, parent=None):
super(MainWindow, self).__init__(parent)
self.url = QUrl("https://stackoverflow.com/")
self.webView = QWebEngineView()
self.page = WebEnginePage(self.webView)
self.webView.setPage(self.page)
self.webView.load(self.url)
if __name__ == '__main__':
import sys
app = QtWidgets.QApplication(sys.argv)
web = MainWindow()
web.show()
sys.exit(app.exec_())

Resources