MqttMonitor/MqttConnectionProfilesDialog.py

Sat, 19 Oct 2024 16:04:24 +0200

author
Detlev Offenbach <detlev@die-offenbachs.de>
date
Sat, 19 Oct 2024 16:04:24 +0200
branch
eric7
changeset 151
f9830d91be9a
parent 143
51bc5bcc672a
child 153
4f4c2e272dcc
permissions
-rw-r--r--

Fixed a backward compatibility issue with eric-ide < 24.11.

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

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

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

import collections
import copy

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

try:
    from eric7.EricGui import EricPixmapCache
except ImportError:
    from UI import PixmapCache as EricPixmapCache

from eric7.EricWidgets import EricMessageBox
from eric7.EricWidgets.EricPathPicker import EricPathPickerModes

try:
    from eric7.EricUtilities.crypto import pwConvert
except ImportError:
    # backward compatibility for eric-ide < 24.11
    from eric7.Utilities.crypto import pwConvert

from .MqttClient import MqttClient
from .MqttProtocols import MqttProtocols
from .Ui_MqttConnectionProfilesDialog import Ui_MqttConnectionProfilesDialog


class MqttConnectionProfilesDialog(QDialog, Ui_MqttConnectionProfilesDialog):
    """
    Class implementing a dialog to edit the MQTT connection profiles.
    """

    def __init__(self, profiles, currentProfile="", 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",
            "WillProperties", "TlsEnable", "TlsCaCert", "TlsClientCert",
            "TlsClientKey", "UserProperties".
        @type dict
        @param currentProfile name of the currently selected profile
        @type str
        @param parent reference to the parent widget
        @type QWidget
        """
        super().__init__(parent)
        self.setupUi(self)

        self.__profiles = collections.defaultdict(self.__defaultProfile)
        self.__profiles.update(copy.deepcopy(profiles))
        self.__profilesChanged = False

        self.plusButton.setIcon(EricPixmapCache.getIcon("plus"))
        self.copyButton.setIcon(EricPixmapCache.getIcon("editCopy"))
        self.minusButton.setIcon(EricPixmapCache.getIcon("minus"))
        self.showPasswordButton.setIcon(EricPixmapCache.getIcon("showPassword"))
        self.willPropertiesButton.setIcon(EricPixmapCache.getIcon("listSelection"))

        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(currentProfile=currentProfile)

    @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=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:
                    connectionProfile = self.__defaultProfile()
                    connectionProfile.update(
                        copy.deepcopy(self.__profiles[profileName])
                    )
                    self.__profiles[newProfileName] = connectionProfile
                    self.__profilesChanged = True

                    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", "WillProperties",
            "TlsEnable", "TlsCaCert", "TlsClientCert", "TlsClientKey",
            "UserProperties".
        @rtype dict
        """
        profilesDict = {}
        profilesDict.update(copy.deepcopy(dict(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:
            # should never happen
            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 = {}
            self.__willProperties = []

        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(),
            "WillProperties": copy.deepcopy(self.__willProperties),
            "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"])
        self.__willProperties = copy.deepcopy(
            connectionProfile.get("WillProperties", [])
        )

        # 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.__willProperties = []
        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:
                # should never happen
                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 will properties only, ig not yet changed
            if not changed and protocol == MqttProtocols.MQTTv5:
                changed |= sorted(self.__willProperties) != sorted(
                    connectionProfile["WillProperties"]
                )
            # 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
        )
        self.willPropertiesButton.setEnabled(checked)
        self.willPropertiesButton.setVisible(checked)

    @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()
    def on_willPropertiesButton_clicked(self):
        """
        Private slot to edit the last will user properties.
        """
        from .MqttUserPropertiesEditor import MqttUserPropertiesEditorDialog

        dlg = MqttUserPropertiesEditorDialog(
            self.tr("Last Will User Properties"), self.__willProperties, self
        )
        if dlg.exec() == QDialog.DialogCode.Accepted:
            self.__willProperties = dlg.getProperties()

    @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