--- a/MqttMonitor/MqttConnectionProfilesDialog.py Thu Sep 06 19:34:49 2018 +0200 +++ b/MqttMonitor/MqttConnectionProfilesDialog.py Thu Sep 06 19:35:43 2018 +0200 @@ -20,6 +20,7 @@ from .Ui_MqttConnectionProfilesDialog import Ui_MqttConnectionProfilesDialog import UI.PixmapCache +from Utilities.crypto import pwConvert class MqttConnectionProfilesDialog(QDialog, Ui_MqttConnectionProfilesDialog): @@ -48,18 +49,21 @@ 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.__populateProfilesList() + self.profileTabWidget.setCurrentIndex(0) if len(self.__profiles) == 0: self.minusButton.setEnabled(False) - self.__updateApplyButton() + self.profileFrame.setEnabled(False) + self.__populatingProfile = False + self.__deletingProfile = False - self.profileTabWidget.setCurrentIndex(0) + self.__populateProfilesList() @pyqtSlot(str) def on_profileEdit_textChanged(self, name): @@ -83,7 +87,12 @@ currentProfile = self.__applyProfile() self.__populateProfilesList(currentProfile) - # TODO: not implemented other paths + 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): @@ -96,9 +105,25 @@ @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): @@ -130,8 +155,13 @@ """ 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): """ @@ -162,13 +192,14 @@ "Keepalive": self.keepaliveSpinBox.value(), "CleanSession": self.cleanSessionCheckBox.isChecked(), "Username": self.usernameEdit.text(), - "Password": self.passwordEdit.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 @@ -205,6 +236,9 @@ currentProfile, Qt.MatchExactly) if items: self.profilesList.setCurrentItem(items[0]) + + if len(self.__profiles) == 0: + self.profileFrame.setEnabled(False) def __populateProfile(self, profileName): """ @@ -218,27 +252,114 @@ else: profile = self.__defaultProfile() - self.profileEdit.setText(profileName) + 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(profile["Password"]) + 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) @@ -258,3 +379,79 @@ """ 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()