MqttMonitor/MqttConnectionProfilesDialog.py

Thu, 22 Jul 2021 19:02:32 +0200

author
Detlev Offenbach <detlev@die-offenbachs.de>
date
Thu, 22 Jul 2021 19:02:32 +0200
branch
eric7
changeset 103
5fe4f179975f
parent 97
21f9c010dc42
child 104
9a4c9b7f078c
permissions
-rw-r--r--

Continued implementing support for MQTT v5 user properties.

# -*- coding: utf-8 -*-

# Copyright (c) 2018 - 2021 Detlev Offenbach <detlev@die-offenbachs.de>
#

"""
Module implementing a dialog to edit the MQTT connection profiles.
"""

import collections
import copy

from PyQt6.QtCore import pyqtSlot, Qt, QUuid
from PyQt6.QtWidgets import (
    QDialog, QDialogButtonBox, QAbstractButton, QListWidgetItem, QInputDialog,
    QLineEdit
)

from EricWidgets import EricMessageBox
from EricWidgets.EricPathPicker import EricPathPickerModes

from .Ui_MqttConnectionProfilesDialog import Ui_MqttConnectionProfilesDialog

from .MqttClient import MqttClient, MqttProtocols

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, profiles, parent=None):
        """
        Constructor
        
        @param profiles dictionary containing dictionaries containing the
            connection parameters. Each entry must have the keys
            "BrokerAddress", "BrokerPort", "ClientId", "Protocol",
            "ConnectionTimeout", "Keepalive", "CleanSession", "Username",
            "Password", "WillTopic", "WillMessage", "WillQos", "WillRetain",
            "TlsEnable", "TlsCaCert", "TlsClientCert", "TlsClientKey",
            "UserProperties".
        @type dict
        @param parent reference to the parent widget
        @type QWidget
        """
        super().__init__(parent)
        self.setupUi(self)
        
        self.__profiles = collections.defaultdict(self.__defaultProfile)
        self.__profiles.update(profiles)
        self.__profilesChanged = False
        
        self.plusButton.setIcon(UI.PixmapCache.getIcon("plus"))
        self.copyButton.setIcon(UI.PixmapCache.getIcon("editCopy"))
        self.minusButton.setIcon(UI.PixmapCache.getIcon("minus"))
        self.showPasswordButton.setIcon(UI.PixmapCache.getIcon("showPassword"))
        
        self.tlsCertsFilePicker.setMode(EricPathPickerModes.OPEN_FILE_MODE)
        self.tlsCertsFilePicker.setFilters(
            self.tr("Certificate Files (*.crt *.pem);;All Files (*)"))
        self.tlsSelfSignedCertsFilePicker.setMode(
            EricPathPickerModes.OPEN_FILE_MODE)
        self.tlsSelfSignedCertsFilePicker.setFilters(
            self.tr("Certificate Files (*.crt *.pem);;All Files (*)"))
        self.tlsSelfSignedClientCertFilePicker.setMode(
            EricPathPickerModes.OPEN_FILE_MODE)
        self.tlsSelfSignedClientCertFilePicker.setFilters(
            self.tr("Certificate Files (*.crt *.pem);;All Files (*)"))
        self.tlsSelfSignedClientKeyFilePicker.setMode(
            EricPathPickerModes.OPEN_FILE_MODE)
        self.tlsSelfSignedClientKeyFilePicker.setFilters(
            self.tr("Key Files (*.key *.pem);;All Files (*)"))
        
        self.profileTabWidget.setCurrentIndex(0)
        
        self.connectPropertiesButton.clicked[bool].connect(
            self.__propertiesTypeSelected)
        self.disconnectPropertiesButton.clicked[bool].connect(
            self.__propertiesTypeSelected)
        
        if len(self.__profiles) == 0:
            self.minusButton.setEnabled(False)
            self.copyButton.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.StandardButton.Apply
        ):
            currentProfile = self.__applyProfile()
            self.__populateProfilesList(currentProfile)
        
        elif button == self.profileButtonBox.button(
            QDialogButtonBox.StandardButton.Reset
        ):
            self.__resetProfile()
        
        elif button == self.profileButtonBox.button(
                QDialogButtonBox.StandardButton.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)
        self.copyButton.setEnabled(current is not None)
        
        if (
            current is not previous and
            not self.__deletingProfile and
            self.__isChangedProfile()
        ):
            # modified profile belongs to previous
            yes = EricMessageBox.yesNo(
                self,
                self.tr("Changed Connection Profile"),
                self.tr("""The current profile has unsaved changes."""
                        """ Shall these be saved?"""),
                icon=EricMessageBox.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.EchoMode.Normal)
        if ok and bool(profileName):
            if profileName in self.__profiles:
                EricMessageBox.warning(
                    self,
                    self.tr("New Connection Profile"),
                    self.tr("""<p>A connection named <b>{0}</b> exists"""
                            """ already. Aborting...</p>""").format(
                        profileName))
            else:
                itm = QListWidgetItem(profileName, self.profilesList)
                self.profilesList.setCurrentItem(itm)
                self.brokerAddressEdit.setFocus(
                    Qt.FocusReason.OtherFocusReason)
    
    @pyqtSlot()
    def on_copyButton_clicked(self):
        """
        Private slot to copy the selected profile entry.
        """
        itm = self.profilesList.currentItem()
        if itm:
            profileName = itm.text()
            newProfileName, ok = QInputDialog.getText(
                self,
                self.tr("Copy Connection Profile"),
                self.tr("Enter name for the copied Connection Profile:"),
                QLineEdit.EchoMode.Normal)
            if ok and bool(newProfileName):
                if newProfileName in self.__profiles:
                    EricMessageBox.warning(
                        self,
                        self.tr("Copy Connection Profile"),
                        self.tr("""<p>A connection named <b>{0}</b> exists"""
                                """ already. Aborting...</p>""").format(
                            newProfileName))
                else:
                    profile = self.__defaultProfile()
                    profile.update(self.__profiles[profileName])
                    self.__profiles[newProfileName] = profile
                    
                    itm = QListWidgetItem(newProfileName, self.profilesList)
                    self.profilesList.setCurrentItem(itm)
                    self.brokerAddressEdit.setFocus(
                        Qt.FocusReason.OtherFocusReason)
    
    @pyqtSlot()
    def on_minusButton_clicked(self):
        """
        Private slot to delete the selected entry.
        """
        itm = self.profilesList.currentItem()
        if itm:
            profileName = itm.text()
            yes = EricMessageBox.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.FocusReason.OtherFocusReason)
    
    def getProfiles(self):
        """
        Public method to return a dictionary of profiles.
        
        @return dictionary containing dictionaries containing the defined
            connection profiles. Each entry has the keys "BrokerAddress",
            "BrokerPort", "ClientId", "Protocol", "ConnectionTimeout",
            "Keepalive", "CleanSession", "Username", "Password", "WillTopic",
            "WillMessage", "WillQos", "WillRetain", "TlsEnable", "TlsCaCert",
            "TlsClientCert", "TlsClientKey", "UserProperties".
        @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
        """
        if self.mqttv31Button.isChecked():
            protocol = MqttProtocols.MQTTv31
        elif self.mqttv311Button.isChecked():
            protocol = MqttProtocols.MQTTv311
        elif self.mqttv5Button.isChecked():
            protocol = MqttProtocols.MQTTv5
        else:
            protocol = MqttProtocols.MQTTv311
        
        if protocol == MqttProtocols.MQTTv5:
            if self.connectPropertiesButton.isChecked():
                self.__userProperties["connect"] = (
                    self.propertiesWidget.getProperties())
            else:
                self.__userProperties["disconnect"] = (
                    self.propertiesWidget.getProperties())
            self.__userProperties["use_connect"] = (
                self.samePropertiesCheckBox.isChecked())
        else:
            self.__userProperties = {}
        
        profileName = self.profileEdit.text()
        connectionProfile = {
            "BrokerAddress": self.brokerAddressEdit.text(),
            "BrokerPort": self.brokerPortSpinBox.value(),
            "ClientId": self.clientIdEdit.text(),
            "Protocol": protocol,
            "ConnectionTimeout": self.connectionTimeoutSpinBox.value(),
            "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(),
            "TlsEnable": self.tlsGroupBox.isChecked(),
            "TlsCaCert": "",
            "TlsClientCert": "",
            "TlsClientKey": "",
            "UserProperties": copy.deepcopy(self.__userProperties),
        }
        if connectionProfile["TlsEnable"]:
            if self.tlsCertsFileButton.isChecked():
                connectionProfile["TlsCaCert"] = self.tlsCertsFilePicker.text()
            elif self.tlsSelfSignedCertsButton.isChecked():
                connectionProfile["TlsCaCert"] = (
                    self.tlsSelfSignedCertsFilePicker.text())
                connectionProfile["TlsClientCert"] = (
                    self.tlsSelfSignedClientCertFilePicker.text())
                connectionProfile["TlsClientKey"] = (
                    self.tlsSelfSignedClientKeyFilePicker.text())
        
        self.__profiles[profileName] = connectionProfile
        self.__profilesChanged = True
        
        return profileName
    
    def __defaultProfile(self):
        """
        Private method to populate non-existing profile items.
        
        @return default dictionary entry
        @rtype dict
        """
        defaultProfile = MqttClient.defaultConnectionOptions()
        defaultProfile["BrokerAddress"] = ""
        if defaultProfile["TlsEnable"]:
            defaultProfile["BrokerPort"] = 8883
        else:
            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.MatchFlag.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
        """
        connectionProfile = self.__defaultProfile()
        if profileName:
            connectionProfile.update(self.__profiles[profileName])
        
        self.__populatingProfile = True
        if profileName is not None:
            self.profileEdit.setText(profileName)
        self.brokerAddressEdit.setText(connectionProfile["BrokerAddress"])
        self.brokerPortSpinBox.setValue(connectionProfile["BrokerPort"])
        self.clientIdEdit.setText(connectionProfile["ClientId"])
        
        # general tab
        self.mqttv31Button.setChecked(
            connectionProfile["Protocol"] == MqttProtocols.MQTTv31)
        self.mqttv311Button.setChecked(
            connectionProfile["Protocol"] == MqttProtocols.MQTTv311)
        self.mqttv5Button.setChecked(
            connectionProfile["Protocol"] == MqttProtocols.MQTTv5)
        self.on_mqttv5Button_toggled(self.mqttv5Button.isChecked())
        self.connectionTimeoutSpinBox.setValue(
            connectionProfile["ConnectionTimeout"])
        self.keepaliveSpinBox.setValue(connectionProfile["Keepalive"])
        self.cleanSessionCheckBox.setChecked(connectionProfile["CleanSession"])
        
        # user credentials tab
        self.usernameEdit.setText(connectionProfile["Username"])
        self.passwordEdit.setText(
            pwConvert(connectionProfile["Password"], encode=False))
        
        # will tab
        self.willTopicEdit.setText(connectionProfile["WillTopic"])
        self.willMessageEdit.setPlainText(connectionProfile["WillMessage"])
        self.willQosSpinBox.setValue(connectionProfile["WillQos"])
        self.willRetainCheckBox.setChecked(connectionProfile["WillRetain"])
        
        # SSL/TLS tab
        self.tlsGroupBox.setChecked(connectionProfile["TlsEnable"])
        if (
            connectionProfile["TlsCaCert"] and
            connectionProfile["TlsClientCert"]
        ):
            self.tlsSelfSignedCertsButton.setChecked(True)
            self.tlsSelfSignedCertsFilePicker.setText(
                connectionProfile["TlsCaCert"])
            self.tlsSelfSignedClientCertFilePicker.setText(
                connectionProfile["TlsClientCert"])
            self.tlsSelfSignedClientKeyFilePicker.setText(
                connectionProfile["TlsClientKey"])
        elif connectionProfile["TlsCaCert"]:
            self.tlsCertsFileButton.setChecked(True)
            self.tlsCertsFilePicker.setText(connectionProfile["TlsCaCert"])
        else:
            self.tlsDefaultCertsButton.setChecked(True)
        
        # user properties tab
        self.__userProperties = copy.deepcopy(
            connectionProfile.get("UserProperties", {}))
        if not self.__userProperties:
            self.__userProperties = {
                "connect": [],
                "disconnect": [],
                "use_connect": True,
            }
        
        if connectionProfile["Protocol"] == MqttProtocols.MQTTv5:
            self.connectPropertiesButton.setChecked(True)
            self.propertiesWidget.setProperties(
                self.__userProperties["connect"])
            self.samePropertiesCheckBox.setChecked(
                self.__userProperties["use_connect"])
            self.disconnectPropertiesButton.setEnabled(
                not self.__userProperties["use_connect"])
        else:
            self.propertiesWidget.clear()
        
        self.__populatingProfile = False
        
        self.showPasswordButton.setChecked(False)
        self.profileFrame.setEnabled(True)
        self.__updateApplyButton()
        
        self.profileTabWidget.setCurrentIndex(0)
    
    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.mqttv311Button.setChecked(True)
        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.tlsGroupBox.setChecked(False)
        self.tlsDefaultCertsButton.setChecked(True)
        self.tlsCertsFileButton.setChecked(True)
        self.tlsCertsFilePicker.setText("")
        self.tlsSelfSignedCertsButton.setChecked(False)
        self.tlsSelfSignedCertsFilePicker.setText("")
        self.tlsSelfSignedClientCertFilePicker.setText("")
        self.tlsSelfSignedClientKeyFilePicker.setText("")
        
        self.__userProperties = {
            "connect": [],
            "disconnect": [],
            "use_connect": True,
        }
        self.propertiesWidget.clear()
        self.samePropertiesCheckBox.setChecked(True)
        self.connectPropertiesButton.setChecked(True)
        self.disconnectPropertiesButton.setEnabled(False)
        
        self.__populatingProfile = False
        
        self.showPasswordButton.setChecked(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
        @rtype bool
        """
        profileName = self.profileEdit.text()
        if profileName == "":
            return False
        
        elif profileName in self.__profiles:
            if self.mqttv31Button.isChecked():
                protocol = MqttProtocols.MQTTv31
            elif self.mqttv311Button.isChecked():
                protocol = MqttProtocols.MQTTv311
            elif self.mqttv5Button.isChecked():
                protocol = MqttProtocols.MQTTv5
            else:
                protocol = MqttProtocols.MQTTv311
            
            connectionProfile = self.__defaultProfile()
            connectionProfile.update(self.__profiles[profileName])
            changed = (
                self.brokerAddressEdit.text() !=
                connectionProfile["BrokerAddress"] or
                self.brokerPortSpinBox.value() !=
                connectionProfile["BrokerPort"] or
                self.clientIdEdit.text() != connectionProfile["ClientId"] or
                protocol != connectionProfile["Protocol"] or
                self.connectionTimeoutSpinBox.value() !=
                connectionProfile["ConnectionTimeout"] or
                self.keepaliveSpinBox.value() !=
                connectionProfile["Keepalive"] or
                self.cleanSessionCheckBox.isChecked() !=
                connectionProfile["CleanSession"] or
                self.usernameEdit.text() != connectionProfile["Username"] or
                self.passwordEdit.text() !=
                pwConvert(connectionProfile["Password"], encode=False) or
                self.willTopicEdit.text() != connectionProfile["WillTopic"] or
                self.willMessageEdit.toPlainText() !=
                connectionProfile["WillMessage"] or
                self.willQosSpinBox.value() != connectionProfile["WillQos"] or
                self.willRetainCheckBox.isChecked() !=
                connectionProfile["WillRetain"] or
                self.tlsGroupBox.isChecked() != connectionProfile["TlsEnable"]
            )
            # check TLS stuff only, if not yet changed
            if not changed:
                if self.tlsCertsFileButton.isChecked():
                    changed |= (
                        self.tlsCertsFilePicker.text() !=
                        connectionProfile["TlsCaCert"]
                    )
                elif self.tlsSelfSignedCertsButton.isChecked():
                    changed |= (
                        self.tlsSelfSignedCertsFilePicker.text() !=
                        connectionProfile["TlsCaCert"] or
                        self.tlsSelfSignedClientCertFilePicker.text() !=
                        connectionProfile["TlsClientCert"] or
                        self.tlsSelfSignedClientKeyFilePicker.text() !=
                        connectionProfile["TlsClientKey"]
                    )
            # check user properties only, if not yet changed
            if not changed and protocol == MqttProtocols.MQTTv5:
                properties = {
                    "connect": self.propertiesWidget.getProperties(),
                    "disconnect": self.__userProperties["disconnect"],
                } if self.connectPropertiesButton.isChecked() else {
                    "connect": self.__userProperties["connect"],
                    "disconnect": self.propertiesWidget.getProperties(),
                }
                changed |= (
                    self.samePropertiesCheckBox.isChecked() !=
                    connectionProfile["UserProperties"]["use_connect"] or
                    sorted(properties["connect"]) !=
                    sorted(connectionProfile["UserProperties"]["connect"]) or
                    sorted(properties["disconnect"]) !=
                    sorted(connectionProfile["UserProperties"]["disconnect"])
                )
            
            return changed
        
        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 and
            self.clientIdEdit.text() == "" and
            not self.cleanSessionCheckBox.isChecked()
        ):
            enable = False
            EricMessageBox.critical(
                self,
                self.tr("Invalid Connection Parameters"),
                self.tr("An empty Client ID requires a clean session."))
        
        if self.tlsGroupBox.isChecked():
            if self.tlsCertsFileButton.isChecked():
                # condition 3a: if CA certificates file shall be used, it must
                # be given
                enable &= bool(self.tlsCertsFilePicker.text())
            elif self.tlsSelfSignedCertsButton.isChecked():
                # condition 3b: if client certificates shall be used, all files
                # must be given
                enable &= (
                    bool(self.tlsSelfSignedCertsFilePicker.text()) and
                    bool(self.tlsSelfSignedClientCertFilePicker.text()) and
                    bool(self.tlsSelfSignedClientKeyFilePicker.text())
                )
        
        self.profileButtonBox.button(
            QDialogButtonBox.StandardButton.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.StringFormat.WithoutBraces))
    
    @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()
    
    @pyqtSlot(bool)
    def on_mqttv5Button_toggled(self, checked):
        """
        Private slot to handle the selection of the MQTT protocol.
        
        @param checked state of the button
        @type bool
        """
        self.profileTabWidget.setTabEnabled(
            self.profileTabWidget.indexOf(self.propertiesTab),
            checked
        )
        # TODO: add code to enable the WILL properties button
    
    @pyqtSlot(bool)
    def on_showPasswordButton_toggled(self, checked):
        """
        Private slot to show or hide the password.
        
        @param checked flag indicating the button state
        @type bool
        """
        (
            self.passwordEdit.setEchoMode(QLineEdit.EchoMode.Normal)
            if checked else
            self.passwordEdit.setEchoMode(QLineEdit.EchoMode.Password)
        )
    
    @pyqtSlot(str)
    def on_tlsCertsFilePicker_textChanged(self, path):
        """
        Private slot handling a change of the TLS CA certificates file.
        
        @param path file path
        @type str
        """
        self.__updateApplyButton()
    
    @pyqtSlot(str)
    def on_tlsSelfSignedCertsFilePicker_textChanged(self, path):
        """
        Private slot handling a change of the TLS CA certificates file.
        
        @param path file path
        @type str
        """
        self.__updateApplyButton()
    
    @pyqtSlot(str)
    def on_tlsSelfSignedClientCertFilePicker_textChanged(self, path):
        """
        Private slot handling a change of the TLS client certificate file.
        
        @param path file path
        @type str
        """
        self.__updateApplyButton()
    
    @pyqtSlot(str)
    def on_tlsSelfSignedClientKeyFilePicker_textChanged(self, path):
        """
        Private slot handling a change of the TLS client key file.
        
        @param path file path
        @type str
        """
        self.__updateApplyButton()
    
    @pyqtSlot(bool)
    def on_tlsGroupBox_toggled(self, checked):
        """
        Private slot handling the selection of TLS mode.
        
        @param checked state of the selection
        @type bool
        """
        if checked and self.brokerPortSpinBox.value() == 1883:
            # port is still standard non-TLS port
            yes = EricMessageBox.yesNo(
                self,
                self.tr("SSL/TLS Enabled"),
                self.tr(
                    """Encrypted connection using SSL/TLS has been enabled."""
                    """ However, the broker port is still the default"""
                    """ unencrypted port (port 1883). Shall this be"""
                    """ changed?"""),
                icon=EricMessageBox.Warning,
                yesDefault=True)
            if yes:
                self.brokerPortSpinBox.setValue(8883)
        elif not checked and self.brokerPortSpinBox.value() == 8883:
            # port is still standard TLS port
            yes = EricMessageBox.yesNo(
                self,
                self.tr("SSL/TLS Disabled"),
                self.tr(
                    """Encrypted connection using SSL/TLS has been disabled."""
                    """ However, the broker port is still the default"""
                    """ encrypted port (port 8883). Shall this be"""
                    """ changed?"""),
                icon=EricMessageBox.Warning,
                yesDefault=True)
            if yes:
                self.brokerPortSpinBox.setValue(1883)
        
        self.__updateApplyButton()
    
    @pyqtSlot(bool)
    def on_tlsDefaultCertsButton_toggled(self, checked):
        """
        Private slot handling the selection of using the default
        certificates file.
        
        @param checked state of the selection
        @type bool
        """
        self.__updateApplyButton()
    
    @pyqtSlot(bool)
    def on_tlsCertsFileButton_toggled(self, checked):
        """
        Private slot handling the selection of using a non-default
        certificates file.
        
        @param checked state of the selection
        @type bool
        """
        self.__updateApplyButton()
    
    @pyqtSlot(bool)
    def on_tlsSelfSignedCertsButton_toggled(self, checked):
        """
        Private slot handling the selection of using self signed
        client certificate and key files.
        
        @param checked state of the selection
        @type bool
        """
        self.__updateApplyButton()
    
    @pyqtSlot(bool)
    def on_samePropertiesCheckBox_toggled(self, checked):
        """
        Private slot to handle a change of the properties usage.
        
        @param checked flag indicating to use the same user properties for
            CONNECT and DISCONNECT
        @type bool
        """
        if checked and not self.connectPropertiesButton.isChecked():
            self.connectPropertiesButton.click()
        self.disconnectPropertiesButton.setEnabled(not checked)
    
    @pyqtSlot(bool)
    def __propertiesTypeSelected(self, checked):
        """
        Private slot to handle the switching of the user properties type.
        
        @param checked state of the buttons
        @type bool
        """
        if checked:
            # handle the selection only
            if self.connectPropertiesButton.isChecked():
                self.__userProperties["disconnect"] = (
                    self.propertiesWidget.getProperties())
                self.propertiesWidget.setProperties(
                    self.__userProperties["connect"])
            else:
                self.__userProperties["connect"] = (
                    self.propertiesWidget.getProperties())
                self.propertiesWidget.setProperties(
                    self.__userProperties["disconnect"])
    
    @pyqtSlot()
    def reject(self):
        """
        Public slot to reject the dialog changes.
        """
        if self.__isChangedProfile():
            button = EricMessageBox.warning(
                self,
                self.tr("Changed Connection Profile"),
                self.tr("""The current profile has unsaved changes. Shall"""
                        """ these be saved?"""),
                EricMessageBox.Discard |
                EricMessageBox.Save,
                EricMessageBox.Save)
            if button == EricMessageBox.Save:
                self.__applyProfile()
                return
        
        if self.__profilesChanged:
            button = EricMessageBox.warning(
                self,
                self.tr("Changed Connection Profiles"),
                self.tr("""The list of connection profiles has unsaved"""
                        """ changes."""),
                EricMessageBox.Abort |
                EricMessageBox.Discard |
                EricMessageBox.Save,
                EricMessageBox.Save)
            if button == EricMessageBox.Save:
                super().accept()
                return
            elif button == EricMessageBox.Abort:
                return
        
        super().reject()
    
    @pyqtSlot()
    def accept(self):
        """
        Public slot to accept the dialog.
        """
        if self.__isChangedProfile():
            yes = EricMessageBox.yesNo(
                self,
                self.tr("Changed Connection Profile"),
                self.tr("""The current profile has unsaved changes. Shall"""
                        """ these be saved?"""),
                icon=EricMessageBox.Warning,
                yesDefault=True)
            if yes:
                self.__applyProfile()
        
        super().accept()

eric ide

mercurial