Source code for pydm.widgets.pushbutton

import hashlib

from qtpy.QtWidgets import QPushButton, QMessageBox, QInputDialog, QLineEdit
from qtpy.QtCore import Slot, Property
from .base import PyDMWritableWidget

import logging
logger = logging.getLogger(__name__)


[docs]class PyDMPushButton(QPushButton, PyDMWritableWidget): """ Basic PushButton to send a fixed value. The PyDMPushButton is meant to hold a specific value, and send that value to a channel when it is clicked, much like the MessageButton does in EDM. The PyDMPushButton works in two different modes of operation, first, a fixed value can be given to the :attr:`.pressValue` attribute, whenever the button is clicked a signal containing this value will be sent to the connected channel. This is the default behavior of the button. However, if the :attr:`.relativeChange` is set to True, the fixed value will be added to the current value of the channel. This means that the button will increment a channel by a fixed amount with every click, a consistent relative move Parameters ---------- parent : QObject, optional Parent of PyDMPushButton label : str, optional String to place on button icon : QIcon, optional An Icon to display on the PyDMPushButton pressValue : int, float, str Value to be sent when the button is clicked relative : bool, optional Choice to have the button perform a relative put, instead of always setting to an absolute value init_channel : str, optional ID of channel to manipulate """ DEFAULT_CONFIRM_MESSAGE = "Are you sure you want to proceed?" def __init__(self, parent=None, label=None, icon=None, pressValue=None, relative=False, init_channel=None): if icon: QPushButton.__init__(self, icon, label, parent) elif label: QPushButton.__init__(self, label, parent) else: QPushButton.__init__(self, parent) PyDMWritableWidget.__init__(self, init_channel=init_channel) self._pressValue = pressValue self._relative = relative self._alarm_sensitive_border = False self._show_confirm_dialog = False self._confirm_message = PyDMPushButton.DEFAULT_CONFIRM_MESSAGE self._password_protected = False self._password = "" self._protected_password = "" self.clicked.connect(self.sendValue) @Property(bool) def passwordProtected(self): """ Whether or not this button is password protected. Returns ------- bool """ return self._password_protected @passwordProtected.setter def passwordProtected(self, value): """ Whether or not this button is password protected. Parameters ---------- value : bool """ if self._password_protected != value: self._password_protected = value @Property(str) def password(self): """ Password to be encrypted using SHA256. .. warning:: To avoid issues exposing the password this method always returns an empty string. Returns ------- str """ return "" @password.setter def password(self, value): """ Password to be encrypted using SHA256. Parameters ---------- value : str The password to be encrypted """ if value is not None and value != "": sha = hashlib.sha256() sha.update(value.encode()) # Use the setter as it also checks whether the existing password is the same with the # new one, and only updates if the new password is different self.protectedPassword = sha.hexdigest() @Property(str) def protectedPassword(self): """ The encrypted password. Returns ------- str """ return self._protected_password @protectedPassword.setter def protectedPassword(self, value): if self._protected_password != value: self._protected_password = value @Property(bool) def showConfirmDialog(self): """ Wether or not to display a confirmation dialog. Returns ------- bool """ return self._show_confirm_dialog @showConfirmDialog.setter def showConfirmDialog(self, value): """ Wether or not to display a confirmation dialog. Parameters ---------- value : bool """ if self._show_confirm_dialog != value: self._show_confirm_dialog = value @Property(str) def confirmMessage(self): """ Message to be displayed at the Confirmation dialog. Returns ------- str """ return self._confirm_message @confirmMessage.setter def confirmMessage(self, value): """ Message to be displayed at the Confirmation dialog. Parameters ---------- value : str """ if self._confirm_message != value: self._confirm_message = value @Property(str) def pressValue(self): """ This property holds the value to send back through the channel. The type of this value does not matter because it is automatically converted to match the prexisting value type of the channel. However, the sign of the value matters for both the fixed and relative modes. Returns ------- str """ return str(self._pressValue) @pressValue.setter def pressValue(self, value): """ This property holds the value to send back through the channel. The type of this value does not matter because it is automatically converted to match the prexisting value type of the channel. However, the sign of the value matters for both the fixed and relative modes. Parameters ---------- value : str """ if str(value) != self._pressValue: self._pressValue = str(value) @Property(bool) def relativeChange(self): """ The mode of operation of the PyDMPushButton. If set to True, the :attr:`pressValue` will be added to the current value of the channel. If False, the :attr:`pressValue` will be sent without any operation. This flag will be ignored if the connected channel sends a str type value to :meth:`.receiveValue`. This is designed to eliminate the undesirable behavior of concatenating strings as opposed to doing mathematical addition. Returns ------- bool """ return self._relative @relativeChange.setter def relativeChange(self, choice): """ The mode of operation of the PyDMPushButton. If set to True, the :attr:`pressValue` will be added to the current value of the channel. If False, the :attr:`pressValue` will be sent without any operation. This flag will be ignored if the connected channel sends a str type value to :meth:`.receiveValue`. This is designed to eliminate the undesirable behavior of concatenating strings as opposed to doing mathematical addition. Parameters ---------- choice : bool """ if self._relative != choice: self._relative = choice
[docs] def confirm_dialog(self): """ Show the confirmation dialog with the proper message in case ```showConfirmMessage``` is True. Returns ------- bool True if the message was confirmed or if ```showCofirmMessage``` is False. """ if self._show_confirm_dialog: if self._confirm_message == "": self._confirm_message = PyDMPushButton.DEFAULT_CONFIRM_MESSAGE msg = QMessageBox() msg.setIcon(QMessageBox.Question) msg.setText(self._confirm_message) msg.setStandardButtons(QMessageBox.Yes | QMessageBox.No) msg.setDefaultButton(QMessageBox.No) ret = msg.exec_() if ret == QMessageBox.No: return False return True
[docs] def validate_password(self): """ If the widget is ```passwordProtected```, this method will propmt the user for the correct password. Returns ------- bool True in case the password was correct of if the widget is not password protected. """ if not self._password_protected: return True pwd, ok = QInputDialog().getText(None, "Authentication", "Please enter your password:", QLineEdit.Password, "") pwd = str(pwd) if not ok or pwd == "": return False sha = hashlib.sha256() sha.update(pwd.encode()) pwd_encrypted = sha.hexdigest() if pwd_encrypted != self._protected_password: msg = QMessageBox() msg.setIcon(QMessageBox.Critical) msg.setText("Invalid password.") msg.setWindowTitle("Error") msg.setStandardButtons(QMessageBox.Ok) msg.setDefaultButton(QMessageBox.Ok) msg.setEscapeButton(QMessageBox.Ok) msg.exec_() return False return True
[docs] @Slot() def sendValue(self): """ Send a new value to the channel. This function interprets the settings of the PyDMPushButton and sends the appropriate value out through the :attr:`.send_value_signal`. Returns ------- None if any of the following condition is False: 1. There's no new value (pressValue) for the widget 2. There's no initial or current value for the widget 3. The confirmation dialog returns No as the user's answer to the dialog 4. The password validation dialog returns a validation error Otherwise, return the value sent to the channel: 1. The value sent to the channel is the same as the pressValue if the existing channel type is a str, or the relative flag is False 2. The value sent to the channel is the sum of the existing value and the pressValue if the relative flag is True, and the channel type is not a str """ send_value = None if self._pressValue is None or self.value is None: return None if not self.confirm_dialog(): return None if not self.validate_password(): return None if not self._relative or self.channeltype == str: send_value = self._pressValue self.send_value_signal[self.channeltype].emit(self.channeltype(send_value)) else: send_value = self.value + self.channeltype(self._pressValue) self.send_value_signal[self.channeltype].emit(send_value) return send_value
[docs] @Slot(int) @Slot(float) @Slot(str) def updatePressValue(self, value): """ Update the pressValue of a function by passing a signal to the PyDMPushButton. This is useful to dynamically change the pressValue of the button during runtime. This enables the applied value to be linked to the state of a different widget, say a QLineEdit or QSlider Parameters ---------- value : int, float or str """ try: self.pressValue = self.channeltype(value) except(ValueError, TypeError): logger.error("'{0}' is not a valid pressValue for '{1}'.".format(value, self.channel))