pyqt: add clicked event for a Qlabel - python

I've created label: self.labelOnlineHelp = QLabel('Online Help') and want to make it clickable. Ideally it should open firefox (but not a default browser) and also change mouse to pointer (in a nutshell: just to create an usual hyperlink). I see that there is no clicked event in qlabel. Is there any way how to perform this in a simple way?

You can do this using setOpenExternalLinks
self.labelOnlineHellp.setOpenExternalLinks(True)
If you want to do something different than the default behavior (ie. open link in the default browser), you can connect to the linkActivated signal instead (don't use setOpenExternalLinks to True if you're handling the opening of the link yourself).
self.labelOnlineHelp.linkActivated.connect(self.link_handler)
def link_handler(self, link):
subprocess.call(['/path/to/firefox', link])

You need to reimplement QLabel class and override the mousePressEvent or mouseReleaseEvent. Here is a simple example:
class MyLabel(QLabel):
def __init__(self, parent):
QLabel.__init__(self, parent)
self.link = "http://www.example.com"
def mousePressEvent(self, event):
# open the link on your browser
webbrowser.get('firefox').open_new_tab(self.link)

Related

Menubar sometimes does not become un-greyed when QFileDialog closes

OS: W10. This may be significant. If you have different results on a different platform, feedback would be helpful.
Here is an MRE. If you run it and go Ctrl+O, the menu labels become greyed. If you select a file in the QFileDialog by clicking the "Open" button or using its mnemonic (Alt+O), the open-file dialog is dismissed and the "Files" and "Help" menus become un-greyed.
However, if you go Ctrl+O again, and this time enter the name of a file in the "File name" box (QLineEdit), and then press Return, the dialog is dismissed (with a successful selection result) but the "Files" and "Help" menus remain greyed-out. It looks like this:
import sys, os
from PyQt5 import QtWidgets, QtCore, QtGui
class MainWindow(QtWidgets.QMainWindow):
def __init__(self):
super().__init__()
self.setWindowTitle('Greying of menus MRE')
self.setGeometry(QtCore.QRect(100, 100, 400, 200))
menubar = QtWidgets.QMenuBar(self)
self.setMenuBar(menubar)
self.files_menu = QtWidgets.QMenu('&Files', self)
menubar.addMenu(self.files_menu)
self.help_menu = QtWidgets.QMenu('&Help', self)
menubar.addMenu(self.help_menu)
self.new_action = QtWidgets.QAction('&New', self)
self.files_menu.addAction(self.new_action)
self.open_action = QtWidgets.QAction('&Open', self)
self.files_menu.addAction(self.open_action)
self.open_action.setShortcut("Ctrl+O")
self.open_action.triggered.connect(self.open_file)
def focusInEvent(self, event ):
print('main_window focusInEvent')
super().focusInEvent(event)
def focusOutEvent(self, event ):
print('main_window focusOutEvent')
super().focusInEvent(event)
def activateWindow(self):
print('main_window activateWindow')
super().activateWindow()
def open_file(self):
print('open file')
main_window_self = self
# open_doc_dialog = QtWidgets.QFileDialog(self.get_main_window())
class OpenDocFileDialog(QtWidgets.QFileDialog):
def accepted(self):
print('accepted')
super().accepted()
def accept(self):
print('accept')
super().accept()
def close(self):
print('close')
super().close()
def done(self, r):
print(f'done r {r}')
# neither of these solves the problem:
# main_window_self.activateWindow()
# main_window_self.files_menu.activateWindow()
super().done(r)
def hide(self):
print(f'hide')
super().hide()
def focusInEvent(self, event ):
print('focusInEvent')
super().focusInEvent(event)
def focusOutEvent(self, event ):
print('focusOutEvent')
super().focusInEvent(event)
def activateWindow(self):
print('activateWindow')
super().activateWindow()
open_doc_dialog = OpenDocFileDialog(self)
open_doc_dialog.setWindowTitle('Choose file')
open_doc_dialog.setDirectory(os.getcwd())
# we cannot use the native dialog, because we need control over the UI
options = open_doc_dialog.Options(open_doc_dialog.DontUseNativeDialog)
open_doc_dialog.setOptions(options)
open_doc_button = open_doc_dialog.findChild(QtWidgets.QDialogButtonBox).button(QtWidgets.QDialogButtonBox.Open)
lineEdit = open_doc_dialog.findChild(QtWidgets.QLineEdit)
# this does not solve the problem
# lineEdit.returnPressed.disconnect()
# lineEdit.returnPressed.connect(open_doc_button.click)
print(f'open_doc_button {open_doc_button}, lineEdit {lineEdit}')
# show the dialog
dialog_code = open_doc_dialog.exec()
if dialog_code != QtWidgets.QDialog.Accepted: return
sel_files = open_doc_dialog.selectedFiles()
print(f'sel_files: {sel_files}')
app = QtWidgets.QApplication([])
main_window = MainWindow()
main_window.show()
sys.exit(app.exec())
This problem can be understood, if not solved, with reference to this answer.
Note that this greying-out is not disablement. As explained in the above link, this has to do with "active/inactive states" of the menus (or their labels). The menus remain enabled throughout, although in this case it's impossible to know that while the open-file dialog is showing because it is modal. Clicking on one menu after the dialog has gone, or just hovering over it, is enough to un-grey them both...
The explanation, as I understand it, is that the "File name" box QLineEdit has a signal, returnPressed, which appears to activate something subtley different to the slot which is invoked when you use the "Choose" button. You can see I have experimented with trying to re-wire that signal, to no avail.
The method done of the QFileDialog appears to be called however the dialog closes (unlike close!), so I tried "activating" the main window... and then the individual QMenus... Doesn't work.
I am not clear how to get a handle on this "active state" business or why the slot connected to returnPressed is (seemingly) unable to give the "active state" back to the menus when the other slot manages to do so.
Edit
Searching on Musicamante's "unpolishing" suggestion led me to this:
lineEdit.returnPressed.disconnect()
def return_pressed():
style = main_window_self.menubar.style()
style.unpolish(main_window_self.menubar)
open_doc_button.click()
lineEdit.returnPressed.connect(return_pressed)
... unfortunately this doesn't work.
This looks like a possible Windows-related bug, since I can't reproduce it on Linux. As a work-around, you could try forcing a repaint after the dialog closes:
# show the dialog
dialog_code = open_doc_dialog.exec()
self.menubar.repaint()
Finally got it, thanks to Musicamante's suggestion:
lineEdit.returnPressed.disconnect()
def return_pressed():
style = main_window_self.menubar.style()
style.unpolish(main_window_self.menubar)
open_doc_button.click()
main_window_self.menubar.repaint()
lineEdit.returnPressed.connect(return_pressed)
... I actually tried this several times, just to make sure it was doing what was intended. So in fact, fortunately, no single-shot timer was needed in this case.

How to work with the "?" (what's this widget) on the title bar of a PyQT Dialog

On the right of the title bar of a PyQt QDialog (see below, next to the "x") there is a "?" that is supposed to help the user query help for any other widget on the Dialog window.
What should I do (programmatically) to get it to work. Once the "?" isClicked, one should be able to capture the next widget clicked and provide a ToolTip or something like that. In PyQt, I do not know how to capture the isClicked event on the "?".
I have seen a couple of posts where the question was how to make the "?" disappear, but the discussion there uses Qt, not PyQt, so I do not understand it, and they are not talking about what I need. I need to make it work as intended. See How can I hide/delete the "?" help button on the "title bar" of a Qt Dialog? and PyQt4 QInputDialog and QMessageBox window flags
You can set the whatsThis property to any widget you want:
self.someWidget.setWhatsThis('hello!')
From that point on, whenever you click on the "?" button and then click on that widget, a tooltip with that text will be shown.
Since the "what's this" mode is individually set to widgets, there's no easy way to capture it globally (as far as I know of) because if the widget has no whatsthis property set that feature won't be available for it.
Also, whenever you enter the "what's this" mode, the cursor will probably change according to the contents of the whatsthis property: if it's not set, the cursor will probably show a "disabled" icon.
I've created a basic workaround for this issue, which automatically enables any child widget's whatsthis (if none is already set) whenever the mode is activated: as soon as the EnterWhatsThisMode is fired, it automatically installs a custom object that acts as an event filter, and emits a signal if the whatsthis event is called; as soon as the mode exits, the filter is removed.
I used a separate object for the event filter because there's no way to know what event filter have been already installed to a widget, and if you already installed the parent's one, removing it automatically would be an issue.
class WhatsThisWatcher(QtCore.QObject):
whatsThisRequest = QtCore.pyqtSignal(QtWidgets.QWidget)
def eventFilter(self, source, event):
if event.type() == QtCore.QEvent.WhatsThis:
self.whatsThisRequest.emit(source)
return super(WhatsThisWatcher, self).eventFilter(source, event)
class W(QtWidgets.QWidget):
def __init__(self):
QtWidgets.QWidget.__init__(self)
layout = QtWidgets.QVBoxLayout(self)
hasWhatsThisButton = QtWidgets.QPushButton('Has whatsThis')
layout.addWidget(hasWhatsThisButton)
hasWhatsThisButton.setWhatsThis('I am a button!')
noWhatsThisButton = QtWidgets.QPushButton('No whatsThis')
layout.addWidget(noWhatsThisButton)
self.whatsThisWatchedWidgets = []
self.whatsThisWatcher = WhatsThisWatcher()
self.whatsThisWatcher.whatsThisRequest.connect(self.showCustomWhatsThis)
whatsThisButton = QtWidgets.QPushButton('Set "What\'s this" mode')
layout.addWidget(whatsThisButton)
whatsThisButton.clicked.connect(QtWidgets.QWhatsThis.enterWhatsThisMode)
def event(self, event):
if event.type() == QtCore.QEvent.EnterWhatsThisMode:
for widget in self.findChildren(QtWidgets.QWidget):
if not widget.whatsThis():
# install the custom filter
widget.installEventFilter(self.whatsThisWatcher)
# set an arbitrary string to ensure that the "whatsThis" is
# enabled and the cursor is correctly set
widget.setWhatsThis('whatever')
self.whatsThisWatchedWidgets.append(widget)
elif event.type() == QtCore.QEvent.LeaveWhatsThisMode:
while self.whatsThisWatchedWidgets:
widget = self.whatsThisWatchedWidgets.pop()
# reset the whatsThis string to none and uninstall the filter
widget.setWhatsThis('')
widget.removeEventFilter(self.whatsThisWatcher)
return super(W, self).event(event)
def showCustomWhatsThis(self, widget):
widgetPos = widget.mapTo(self, QtCore.QPoint())
QtWidgets.QWhatsThis.showText(
QtGui.QCursor.pos(),
'There is no "what\'s this" for {} widget at coords {}, {}'.format(
widget.__class__.__name__, widgetPos.x(), widgetPos.y()),
widget)
A couple of notes about this:
I used a button to activate the whatsthis mode, as on my window manager on Linux there's no window title button for that;
Some widgets may contain subwidgets, and you'll get those instead of the "main" one (the most common case are QAbstractScrollArea descendands, like QTextEdit or QGraphicsView, which might return the viewport, the inner "widget" or the scrollbars);
By default the task of that button is to enable whatsThis: press "?", then press the widget and you will see the message associated with whatsThis property.
If you want to add other actions(open url, add QToolTip, etc) you can monitor the QEvent::EnterWhatsThisMode and QEvent::LeaveWhatsThisMode events overriding the event() method or using an eventFilter().
from PyQt5 import QtCore, QtGui, QtWidgets
class Dialog(QtWidgets.QDialog):
def event(self, event):
if event.type() == QtCore.QEvent.EnterWhatsThisMode:
print("enter")
QtGui.QDesktopServices.openUrl(QtCore.QUrl("foo_url"))
elif event.type() == QtCore.QEvent.LeaveWhatsThisMode:
print("leave")
return super().event(event)
if __name__ == "__main__":
import sys
app = QtWidgets.QApplication(sys.argv)
w = Dialog()
w.setWhatsThis("Whats this")
w.setWindowFlags(
QtCore.Qt.WindowContextHelpButtonHint | QtCore.Qt.WindowCloseButtonHint
)
w.resize(640, 480)
w.show()
sys.exit(app.exec_())

Opening a QDialog and saving last state

I am trying to open a QDialog from a QMainWindow, and after closing the `QDialog, if I need to open it again, it has to open and show the same information that had when I close it.
Here is the code of the QMainWindow:
class A (QMainWindow):
def __init__(self):
QMainWindow.__init__(self)
#I create a QPushButton to open the QDialog
self.axes1 = self.figure_canvas.figure.add_axes ([0.8, 0.01, 0.19, 0.05])
self.button = QPushButton(self.axes1,"Open Dialog")
self.button.on_clicked(self.OpenDialog)
#This is the method to open the QDialog which is in another module
def OpenDialog(self, event):
text = configurePort.ConfigurePort.retrieve_data(self)
print text
What this code does is create a button in my QMainWindow and when I click it, it opens a QDialog, which is created in another module. And this is the code of the QDialog:
class ConfigurePort(QDialog):
def __init__(self, parent = None):
QDialog.__init__(self, parent)
uic.loadUi("configurePort.ui", self)
#I create a button to check active ports and show them
self.connect(self.btn_checkconn, SIGNAL("clicked()"), self.check_ports)
#This method calls another class which opens another QDialog
#and I select the port that I want
def check_ports(self):
self.check_serial = CheckPorts(self)
self.check_serial.exec_()
#After selecting the port, when I close the QDialog of the class named above
#the port´s name appears in the first QDialog
#classmethod
def retrieve_data(cls, parent = None):
dlg = cls(parent)
dlg.exec_()
text = dlg.getPortText()
return text
def closeEvent(self, event):
#Here is where I need to write the code to close the QDialog
#and it does not has to be an event
In the method, closeEvent, I need to write the necessary code, so I can close the window, and using the same button that I use to open it, open it again with the last information that it showed when I closed it.
I have tried to use QSettings but it did not worked (maybe I used it wrong). And I tried the show() and hide() classes of PyQt too, but it did not work. Hope you can help me.
----- EDIT -----
I edited the code of above. and I added some methods for a better understanding. So, i open the QDialog called ConfigurePort and it shows this:
The red circle, surrounds the port´s name. It is shown in a QLabel,and I take this text from the QDialog and then print it when I close the QDialog. I acomplish this thanks to a question I asked before, wich is in this link:
Getting data from child using PyQt
The check_port method shown in the code above, opens another QDialog that works great. With this I can select the ports that I need in my pc. So, this does not matter.
So, after closing the QDialog(and selecting for example "COM3", as you can see in the picture), I need to open it again, and see the same information that was shown before I closed it.
I tried to add this lines, using QSettings :
self.settings = QSettings("MyCompany", "MyApp")
if not self.settings.value("windowsState") == None:
self.restoreState(self.settings.value("windowState"))
But as I said before, I think that I did not use it right, but I hope that I solve this using something simpler.
----- EDIT 2 -----
Thank to the help of #Brendan Abel I have this code:
class ConfigurePort(QDialog):
def __init__(self, parent):
super(ConfigurePort, self).__init__(parent)
uic.loadUi("configurePort.ui", self)
self.myValue = 10
self.restoreSettings()
self.connect(self.btn_checkconn, SIGNAL("clicked()"), self.check_ports)
self.buttonBox.button(QDialogButtonBox.Cancel).clicked.connect(self.close)
self.buttonBox.button(QDialogButtonBox.Ok).clicked.connect(self.closeEvent)
self.iniUi()
def check_ports(self):
pass
def iniUi(self):
pass #I just create some QLabels in here
#classmethod
def retrieve_data(cls, parent = None):
dlg = cls(parent)
dlg.exec_()
text = dlg.getPortText()
return text
def closeEvent(self, event):
self.saveSettings()
super(QDialog,self).closeEvent(event)
def saveSettings(self):
settings = QSettings("MyOrg", "MyApp")
settings.setValue("myValue", self.myValue)
def restoreSettings(self):
settings = QSettings("MyOrg", "MyApp")
self.myValue = settings.value("myValue", self.myValue)
This gives me this error: TypeError: QWidget.closeEvent(QCloseEvent): argument 1 has unexpected type 'bool'
I know that I am missing something, but I can not see it.
There are a couple ways you could persist this data Generally, to persist data across sessions, you use QSettings and load the data in the __init__ and save it in the closeEvent method
Generally it looks something like this. This also assumes your using the v2 version of the QVariant api; otherwise, the results returned from QSettings.value is going to be a QVariant and you'll need to cast it to the appropriate python type. If you're using a recent version of PyQt then you should be on v2, but if not you can force it by sticking this at the top of your file
import sip
sip.setapi('QVariant', 2)
sip.setapi('QString', 2)
class MyDialog(QDialog):
def __init__(self, parent):
super(MyDialog, self).__init__(parent)
self.myvalue = 10
self.restoreSettings()
def closeEvent(self, event):
self.saveSettings()
super(MyDialog, self).closeEvent(event)
def saveSettings(self):
settings = QSettings('myorg', 'myapp')
settings.setValue('myvalue', self.myvalue)
def restoreSettings(self):
settings = QSettings('myorg', 'myapp')
self.myvalue = settings.value('myvalue', self.myvalue)
EDIT:
The error in your code is caused by this:
self.buttonBox.button(QDialogButtonBox.Ok).clicked.connect(self.closeEvent)
You shouldn't be calling or connecting to closeEvent directly. Instead, you should connect to .close or .accept
self.buttonBox.button(QDialogButtonBox.Ok).clicked.connect(self.accept)
You need to instantiate the ConfigurePort class then the self.configurePortDialog object should keep consistent. You will need to make sure if you have the user enter data that a cancel does not store the data and that an "ok" stores the data, but I not sure what you are putting in your dialog.
class A (QMainWindow):
def __init__(self):
QMainWindow.__init__(self)
#I create a QPushButton to open the QDialog
self.button = QPushButton("Open Dialog")
self.button.on_clicked(self.OpenDialog)
self.configurePortDialog = configurePort.ConfigurePort(parent=self)
self.configurePortDialog.accepted.connect(self.get_data)
#This is the method to open the QDialog which is in another module
def OpenDialog(self, event):
self.configurePortDialog.show()
#QtCore.Slot()
def get_data(self)
text = self.configurePortDialog.retrieve_data()
print text

How to connect to parent SIGNAL in PyQt?

I have a MainWindow that looks like this:
def __init__(self, parent = None):
QMainWindow.__init__(self, parent)
self.setupUi(self)
self.showMaximized()
menu=mainMenu.MainMenu()
classification=classificationMain.ClassificationMain()
self.stackedWidget.addWidget(menu)
self.stackedWidget.addWidget(classification)
self.stackedWidget.setCurrentWidget(menu)
self.stackedWidget.showFullScreen()
#connections
menu.pushButton.clicked.connect(self.showClassification)
classification.backButton.clicked.connect(self.showMainWindow)
def showClassification(self ):
self.stackedWidget.setCurrentIndex(3)
def showMainWindow(self):
self.stackedWidget.setCurrentIndex(2)
The MainWindows waits for signal from the rest of the dialogs. Now, the Classification dialog has another StackedWidget in it, since it works as a main window for an important part of the application. It looks like:
class ClassificationMain(QDialog, Ui_Dialog):
def __init__(self, parent = None):
QDialog.__init__(self, parent)
self.setupUi(self)
choose=choosePatient.ChoosePatient()
self.stackedWidget.addWidget(choose)
self.stackedWidget.setCurrentWidget(choose)
Now, I want to reload the data inside ChoosePatient every time the button "Show Classification" from MainMenu is clicked, but now the data is loaded only once in the line classification=classificationMain.ClassificationMain() of MainWindow.
I was thinking I had to connect a slot inside ChoosePatient with the click of "Show Classification" button inside MainMenu, but I would need an instance of MainMenu, which is not possible.
How can a method of ChoosePatient can be execute every time the button in the "parent" window is clicked? (also, please tell me if this is not the right way to work with pyqt windows)
You need to save references to your composed widgets, and also to expose some public methods to the parents:
class ClassificationMain(QDialog, Ui_Dialog):
def __init__(self, parent = None):
QDialog.__init__(self, parent)
self.setupUi(self)
self.chooseWidget=choosePatient.ChoosePatient()
self.stackedWidget.addWidget(self.chooseWidget)
self.stackedWidget.setCurrentWidget(self.chooseWidget)
def reloadPatients(self):
# whatever your operation should be on the ChoosePatient
self.chooseWidget.reload()
# MAIN WINDOW
def __init__(self, parent = None):
...
self.classification=classificationMain.ClassificationMain()
self.stackedWidget.addWidget(self.classification)
...
#connections
menu.pushButton.clicked.connect(self.showClassification)
def showClassification(self ):
self.stackedWidget.setCurrentIndex(3)
self.classification.reloadPatients()
You could also just skip the reloadPatients method and connect to the ChoosePatient directly if you want:
def showClassification(self ):
self.stackedWidget.setCurrentIndex(3)
self.classification.chooseWidget.reload()
My personal opinion is to make your custom classes wrap up the internal functionality nicely so that you only need to interface with it over the custom class, and not dig into its internals. That way you can change how it works inside without breaking the main window.

PyQT Window: I want to remember the location it was closed at

I have a QDialog, and when the user closes the QDialog, and reopens it later, I want to remember the location and open the window at the exact same spot. How would I exactly remember that location?
For that, you can use the saveState(), saveGeometry() resize() and move() methods, in conjunction with the closeEvent() and QSettings mentioned by the other answers. Here is some example, to get the idea:
class MyWindow(QMainWindow):
def __init__(self, parent):
QMainWindow.__init__(self, parent)
self.settings = QSettings("MyCompany", "MyApp")
self.restoreGeometry(self.settings.value("geometry", ""))
self.restoreState(self.settings.value("windowState", ""))
def closeEvent(self, event):
self.settings.setValue("geometry", self.saveGeometry())
self.settings.setValue("windowState", self.saveState())
QMainWindow.closeEvent(self, event)
EDIT:
Updated answer to use PyQt API v2. If using API v1, you have to manually cast the result of settings.value() to a ByteArray like
self.restoreState(self.settings.value("windowState").toByteArray())
I also used the window's own size() and pos(), since I'm already loading the windows from a .ui file. You may set it to defaults before those lines if coding the window from scratch. For the state, I'm defaulting to an empty string, which the function happily accepts as an empty ByteArray and does nothing on the first run.
Ronan Paixão's answer is almost correct.
When attempting this a got the error:
AttributeError: 'NoneType' object has no attribute 'toByteArray'
this is because there is, at first, no saved geometry and state. Additionally the return value is already a QByteArray. This code works for me:
class MyWindow(QMainWindow):
def __init__(self, parent):
QMainWindow.__init__(self, parent)
self.settings = QSettings("MyCompany", "MyApp")
if not self.settings.value("geometry") == None:
self.restoreGeometry(self.settings.value("geometry"))
if not self.settings.value("windowState") == None:
self.restoreState(self.settings.value("windowState"))
def closeEvent(self, event):
self.settings.setValue("geometry", self.saveGeometry())
self.settings.setValue("windowState", self.saveState())
QMainWindow.closeEvent(self, event)
You could reimplement the CloseEvent of the dialog (found here in the Qt documentation), and save the appropriate settings using QSettings (docs here).
class MyDialog(QDialog):
def closeEvent(event):
settings = QSettings()
settings.setValue('value1', 1)
event.accept()
It looks like you can use QSettings for this. If you look at the section of the documentation titled Restoring the State of a GUI Application you'll find an example for a main window.
In other words, save the size and location when the user closes the dialog, then next time they open it reload those settings.
_windowStatesEnum = {
0x00000000 : Qt.WindowNoState, # The window has no state set (in normal state).
0x00000001 : Qt.WindowMinimized, # The window is minimized (i.e. iconified).
0x00000002 : Qt.WindowMaximized, # The window is maximized with a frame around it.
0x00000004 : Qt.WindowFullScreen, # The window fills the entire screen without any frame around it.
0x00000008 : Qt.WindowActive, # The window is the active window, i.e. it has keyboard focus.
}
def __setstate__(self, data):
self.__init__()
self.setGeometry(data['geometry'])
self.setWindowState(self._windowStatesEnum[data['window state']])
def __getstate__(self):
return {
'geometry' : self.geometry(),
'window state' : int(self.windowState()),
}

Categories