Thu, 06 Sep 2018 19:35:43 +0200
MqttConnectionProfilesDialog: finished implementing the connections profile dialog.
# -*- coding: utf-8 -*- # Copyright (c) 2018 Detlev Offenbach <detlev@die-offenbachs.de> # """ Module implementing a dialog to edit the MQTT connection profiles. """ from __future__ import unicode_literals import collections from PyQt5.QtCore import pyqtSlot, Qt, QUuid from PyQt5.QtWidgets import QDialog, QDialogButtonBox, QAbstractButton, \ QListWidgetItem, QInputDialog, QLineEdit from E5Gui import E5MessageBox from .Ui_MqttConnectionProfilesDialog import Ui_MqttConnectionProfilesDialog import UI.PixmapCache from Utilities.crypto import pwConvert class MqttConnectionProfilesDialog(QDialog, Ui_MqttConnectionProfilesDialog): """ Class implementing a dialog to edit the MQTT connection profiles. """ def __init__(self, client, profiles, parent=None): """ Constructor @param client reference to the MQTT client object @type MqttClient @param profiles dictionary containing dictionaries containing the connection parameters. Each entry must have the keys "BrokerAddress", "BrokerPort", "ClientId", "Keepalive", "CleanSession", "Username", "Password", "WillTopic", "WillMessage", "WillQos", "WillRetain". @type dict @param parent reference to the parent widget @type QWidget """ super(MqttConnectionProfilesDialog, self).__init__(parent) self.setupUi(self) self.__client = client self.__profiles = collections.defaultdict(self.__defaultProfile) self.__profiles.update(profiles) self.__profilesChanged = False self.plusButton.setIcon(UI.PixmapCache.getIcon("plus.png")) self.minusButton.setIcon(UI.PixmapCache.getIcon("minus.png")) self.profileTabWidget.setCurrentIndex(0) if len(self.__profiles) == 0: self.minusButton.setEnabled(False) self.profileFrame.setEnabled(False) self.__populatingProfile = False self.__deletingProfile = False self.__populateProfilesList() @pyqtSlot(str) def on_profileEdit_textChanged(self, name): """ Private slot to handle changes of the profile name. @param name name of the profile @type str """ self.__updateApplyButton() @pyqtSlot(QAbstractButton) def on_profileButtonBox_clicked(self, button): """ Private slot handling presses of the profile buttons. @param button reference to the pressed button @type QAbstractButton """ if button == self.profileButtonBox.button(QDialogButtonBox.Apply): currentProfile = self.__applyProfile() self.__populateProfilesList(currentProfile) elif button == self.profileButtonBox.button(QDialogButtonBox.Reset): self.__resetProfile() elif button == self.profileButtonBox.button( QDialogButtonBox.RestoreDefaults): self.__populateProfileDefault() @pyqtSlot(QListWidgetItem, QListWidgetItem) def on_profilesList_currentItemChanged(self, current, previous): """ Private slot to handle a change of the current profile. @param current new current item @type QListWidgetItem @param previous previous current item @type QListWidgetItem """ self.minusButton.setEnabled(current is not None) if current is not previous: if not self.__deletingProfile and self.__isChangedProfile(): # modified profile belongs to previous yes = E5MessageBox.yesNo( self, self.tr("Changed Connection Profile"), self.tr("""The current profile has unsaved changes.""" """ Shall these be saved?"""), icon=E5MessageBox.Warning, yesDefault=True) if yes: self.__applyProfile() if current: profileName = current.text() self.__populateProfile(profileName) else: self.__clearProfile() @pyqtSlot() def on_plusButton_clicked(self): """ Private slot to add a new empty profile entry. """ profileName, ok = QInputDialog.getText( self, self.tr("New Connection Profile"), self.tr("Enter name for the new Connection Profile:"), QLineEdit.Normal) if ok and bool(profileName) and profileName not in self.__profiles: itm = QListWidgetItem(profileName, self.profilesList) self.profilesList.setCurrentItem(itm) self.brokerAddressEdit.setFocus(Qt.OtherFocusReason) @pyqtSlot() def on_minusButton_clicked(self): """ Private slot to delete the selected entry. """ itm = self.profilesList.currentItem() if itm: profileName = itm.text() yes = E5MessageBox.yesNo( self, self.tr("Delete Connection Profile"), self.tr("""<p>Shall the Connection Profile <b>{0}</b>""" """ really be deleted?</p>""").format(profileName) ) if yes: self.__deletingProfile = True del self.__profiles[profileName] self.__profilesChanged = True self.__populateProfilesList() self.__deletingProfile = False self.profilesList.setFocus(Qt.OtherFocusReason) def getProfiles(self): """ Public method to return a dictionary of profiles. @return dictionary containing dictionaries containing the defined connection profiles. Each entry have the keys "BrokerAddress", "BrokerPort", "ClientId", "Keepalive", "CleanSession", "Username", "Password", "WillTopic", "WillMessage", "WillQos", "WillRetain". @rtype dict """ profilesDict = {} profilesDict.update(self.__profiles) return profilesDict def __applyProfile(self): """ Private method to apply the entered data to the list of profiles. @return name of the applied profile @rtype str """ profileName = self.profileEdit.text() profile = { "BrokerAddress": self.brokerAddressEdit.text(), "BrokerPort": self.brokerPortSpinBox.value(), "ClientId": self.clientIdEdit.text(), "Keepalive": self.keepaliveSpinBox.value(), "CleanSession": self.cleanSessionCheckBox.isChecked(), "Username": self.usernameEdit.text(), "Password": pwConvert(self.passwordEdit.text(), encode=True), "WillTopic": self.willTopicEdit.text(), "WillMessage": self.willMessageEdit.toPlainText(), "WillQos": self.willQosSpinBox.value(), "WillRetain": self.willRetainCheckBox.isChecked(), } self.__profiles[profileName] = profile self.__profilesChanged = True return profileName def __defaultProfile(self): """ Private method to populate non-existing profile items. @return default dictionary entry @rtype dict """ defaultProfile = self.__client.defaultConnectionOptions() defaultProfile["BrokerAddress"] = "" defaultProfile["BrokerPort"] = 1883 return defaultProfile def __populateProfilesList(self, currentProfile=""): """ Private method to populate the list of defined profiles. @param currentProfile name of the current profile @type str """ if not currentProfile: currentItem = self.profilesList.currentItem() if currentItem: currentProfile = currentItem.text() self.profilesList.clear() self.profilesList.addItems(sorted(self.__profiles.keys())) if currentProfile: items = self.profilesList.findItems( currentProfile, Qt.MatchExactly) if items: self.profilesList.setCurrentItem(items[0]) if len(self.__profiles) == 0: self.profileFrame.setEnabled(False) def __populateProfile(self, profileName): """ Private method to populate the profile data entry fields. @param profileName name of the profile to get data from @type str """ if profileName: profile = self.__profiles[profileName] else: profile = self.__defaultProfile() self.__populatingProfile = True if profileName is not None: self.profileEdit.setText(profileName) self.brokerAddressEdit.setText(profile["BrokerAddress"]) self.brokerPortSpinBox.setValue(profile["BrokerPort"]) self.clientIdEdit.setText(profile["ClientId"]) self.keepaliveSpinBox.setValue(profile["Keepalive"]) self.cleanSessionCheckBox.setChecked(profile["CleanSession"]) self.usernameEdit.setText(profile["Username"]) self.passwordEdit.setText(pwConvert(profile["Password"], encode=False)) self.willTopicEdit.setText(profile["WillTopic"]) self.willMessageEdit.setPlainText(profile["WillMessage"]) self.willQosSpinBox.setValue(profile["WillQos"]) self.willRetainCheckBox.setChecked(profile["WillRetain"]) self.__populatingProfile = False self.profileFrame.setEnabled(True) self.__updateApplyButton() def __clearProfile(self): """ Private method to clear the profile data entry fields. """ self.__populatingProfile = True self.profileEdit.setText("") self.brokerAddressEdit.setText("") self.brokerPortSpinBox.setValue(1883) self.clientIdEdit.setText("") self.keepaliveSpinBox.setValue(60) self.cleanSessionCheckBox.setChecked(True) self.usernameEdit.setText("") self.passwordEdit.setText("") self.willTopicEdit.setText("") self.willMessageEdit.setPlainText("") self.willQosSpinBox.setValue(0) self.willRetainCheckBox.setChecked(False) self.__populatingProfile = False self.profileFrame.setEnabled(False) self.__updateApplyButton() def __resetProfile(self): """ Private method to reset the profile data entry fields to their stored values. """ profileName = self.profileEdit.text() if profileName in self.__profiles: self.__populateProfile(profileName) def __populateProfileDefault(self): """ Private method to populate the profile data entry fields with default profile values. """ self.__populateProfile(None) def __isChangedProfile(self): """ Private method to check, if the currently shown profile contains some changed data. @return flag indicating changed data @type bool """ profileName = self.profileEdit.text() if profileName == "": return False elif profileName in self.__profiles: profile = self.__profiles[profileName] return ( self.brokerAddressEdit.text() != profile["BrokerAddress"] or self.brokerPortSpinBox.value() != profile["BrokerPort"] or self.clientIdEdit.text() != profile["ClientId"] or self.keepaliveSpinBox.value() != profile["Keepalive"] or self.cleanSessionCheckBox.isChecked() != profile["CleanSession"] or self.usernameEdit.text() != profile["Username"] or self.passwordEdit.text() != pwConvert(profile["Password"], encode=False) or self.willTopicEdit.text() != profile["WillTopic"] or self.willMessageEdit.toPlainText() != profile["WillMessage"] or self.willQosSpinBox.value() != profile["WillQos"] or self.willRetainCheckBox.isChecked() != profile["WillRetain"] ) else: return True def __updateApplyButton(self): """ Private method to set the state of the Apply button. """ # condition 1: profile name and broker address need to be given enable = (bool(self.profileEdit.text()) and bool(self.brokerAddressEdit.text())) # condition 2: if client ID is empty, clean session must be selected if not self.__populatingProfile: if self.clientIdEdit.text() == "" and \ not self.cleanSessionCheckBox.isChecked(): enable = False E5MessageBox.critical( self, self.tr("Invalid Connection Parameters"), self.tr("An empty Client ID requires a clean session.")) self.profileButtonBox.button(QDialogButtonBox.Apply).setEnabled(enable) @pyqtSlot(str) def on_brokerAddressEdit_textChanged(self, address): """ Private slot handling a change of the broker address. @param address broker address @type str """ self.__updateApplyButton() @pyqtSlot() def on_generateIdButton_clicked(self): """ Private slot to generate a client ID. """ uuid = QUuid.createUuid() self.clientIdEdit.setText(uuid.toString(QUuid.WithoutBraces)) @pyqtSlot() def reject(self): """ Public slot to reject the dialog changes. """ if self.__isChangedProfile(): button = E5MessageBox.warning( self, self.tr("Changed Connection Profile"), self.tr("""The current profile has unsaved changes. Shall""" """ these be saved?"""), E5MessageBox.StandardButtons( E5MessageBox.Discard | E5MessageBox.Save), E5MessageBox.Save) if button == E5MessageBox.Save: self.__applyProfile() return if self.__profilesChanged: button = E5MessageBox.warning( self, self.tr("Changed Connection Profiles"), self.tr("""The list of connection profiles has unsaved""" """ changes."""), E5MessageBox.StandardButtons( E5MessageBox.Abort | E5MessageBox.Discard | E5MessageBox.Save), E5MessageBox.Save) if button == E5MessageBox.Save: super(MqttConnectionProfilesDialog, self).accept() return elif button == E5MessageBox.Abort: return super(MqttConnectionProfilesDialog, self).reject() @pyqtSlot() def accept(self): """ Public slot to accept the dialog. """ if self.__isChangedProfile(): yes = E5MessageBox.yesNo( self, self.tr("Changed Connection Profile"), self.tr("""The current profile has unsaved changes. Shall""" """ these be saved?"""), icon=E5MessageBox.Warning, yesDefault=True) if yes: self.__applyProfile() super(MqttConnectionProfilesDialog, self).accept() @pyqtSlot(str) def on_clientIdEdit_textChanged(self, clientId): """ Private slot handling a change of the client ID string. @param clientId client ID @type str """ self.__updateApplyButton() @pyqtSlot(bool) def on_cleanSessionCheckBox_clicked(self, checked): """ Private slot to handle a change of the clean session selection. @param checked current state of the clean session selection @type bool """ self.__updateApplyButton()