Merged with the 'connection_profiles' branch.

Sat, 08 Sep 2018 16:55:42 +0200

author
Detlev Offenbach <detlev@die-offenbachs.de>
date
Sat, 08 Sep 2018 16:55:42 +0200
changeset 28
0f02baed8308
parent 27
aeb276d76ec7 (current diff)
parent 26
ad232a5129cc (diff)
child 30
17ef10819773
child 33
97d3047d184b

Merged with the 'connection_profiles' branch.

MqttMonitor/MqttMonitorWidget.py file | annotate | diff | comparison | revisions
MqttMonitor/MqttMonitorWidget.ui file | annotate | diff | comparison | revisions
--- a/MqttMonitor/MqttClient.py	Sat Sep 08 16:51:39 2018 +0200
+++ b/MqttMonitor/MqttClient.py	Sat Sep 08 16:55:42 2018 +0200
@@ -13,6 +13,8 @@
 
 import paho.mqtt.client as mqtt
 
+from Utilities.crypto import pwConvert
+
 
 class MqttClient(QObject):
     """
@@ -158,6 +160,20 @@
         self.__mqttClient.will_set(topic, payload=payload, qos=qos,
                                    retain=retain)
     
+    def setTLS(self, caCerts=None, certFile=None, keyFile=None):
+        """
+        Public method to enable secure connections and set the TLS parameters.
+        
+        @param caCerts path to the Certificate Authority certificates file
+        @type str
+        @param certFile PEM encoded client certificate file
+        @type str
+        @param keyFile PEM encoded private key file
+        @type str
+        """
+        self.__mqttClient.tls_set(ca_certs=caCerts, certfile=certFile,
+                                  keyfile=keyFile)
+    
     def startLoop(self):
         """
         Public method to start the MQTT client loop.
@@ -210,7 +226,8 @@
         @param options dictionary containing the connection options. This
             dictionary should contain the keys "ClientId", "Keepalive",
             "CleanSession", "Username", "Password", "WillTopic", "WillMessage",
-            "WillQos", "WillRetain"
+            "WillQos", "WillRetain", "TlsEnable", "TlsCaCert", "TlsClientCert",
+            "TlsClientKey"
         @type dict
         """
         if options:
@@ -226,8 +243,9 @@
             # step 2: set username and password
             if parametersDict["Username"]:
                 if parametersDict["Password"]:
-                    self.setUserCredentials(parametersDict["Username"],
-                                            parametersDict["Password"])
+                    self.setUserCredentials(
+                        parametersDict["Username"],
+                        pwConvert(parametersDict["Password"], encode=False))
                 else:
                     self.setUserCredentials(parametersDict["Username"])
             
@@ -243,7 +261,22 @@
                                  parametersDict["WillQos"],
                                  parametersDict["WillRetain"])
             
-            # step 4: connect to server
+            # step 4: set TLS parameters
+            if parametersDict["TlsEnable"]:
+                if parametersDict["TlsCaCert"] and \
+                        parametersDict["TlsClientCert"]:
+                    # use self signed client certificate
+                    self.setTLS(caCerts=parametersDict["TlsCaCert"],
+                                certFile=parametersDict["TlsClientCert"],
+                                keyFile=parametersDict["TlsClientKey"])
+                elif parametersDict["TlsCaCert"]:
+                    # use CA certificate file
+                    self.setTLS(caCerts=parametersDict["TlsCaCert"])
+                else:
+                    # use default TLS configuration
+                    self.setTLS()
+            
+            # step 5: connect to server
             self.connectToServer(host, port=port,
                                  keepalive=parametersDict["Keepalive"])
         else:
@@ -258,7 +291,8 @@
         
         @return dictionary containing the default connection options. It has
             the keys "ClientId", "Keepalive", "CleanSession", "Username",
-            "Password", "WillTopic", "WillMessage", "WillQos", "WillRetain"
+            "Password", "WillTopic", "WillMessage", "WillQos", "WillRetain",
+            "TlsEnable", "TlsCaCert", "TlsClientCert", "TlsClientKey".
         @rtype dict
         """
         return {
@@ -271,6 +305,10 @@
             "WillMessage": "",
             "WillQos": 0,
             "WillRetain": False,
+            "TlsEnable": False,
+            "TlsCaCert": "",
+            "TlsClientCert": "",
+            "TlsClientKey": "",
         }
     
     def reconnectToServer(self):
--- a/MqttMonitor/MqttConnectionOptionsDialog.py	Sat Sep 08 16:51:39 2018 +0200
+++ b/MqttMonitor/MqttConnectionOptionsDialog.py	Sat Sep 08 16:55:42 2018 +0200
@@ -12,8 +12,13 @@
 from PyQt5.QtCore import pyqtSlot, QUuid
 from PyQt5.QtWidgets import QDialog, QDialogButtonBox, QAbstractButton
 
+from E5Gui import E5MessageBox
+from E5Gui.E5PathPicker import E5PathPickerModes
+
 from .Ui_MqttConnectionOptionsDialog import Ui_MqttConnectionOptionsDialog
 
+from Utilities.crypto import pwConvert
+
 
 class MqttConnectionOptionsDialog(QDialog, Ui_MqttConnectionOptionsDialog):
     """
@@ -28,8 +33,8 @@
         @param options dictionary containing the connection options to
             populate the dialog with. It must have the keys "ClientId",
             "Keepalive", "CleanSession", "Username", "Password", "WillTopic",
-            "WillMessage", "WillQos", "WillRetain".
-        @@type dict
+            "WillMessage", "WillQos", "WillRetain", "TlsEnable", "TlsCaCert".
+        @type dict
         @param parent reference to the parent widget
         @type QWidget
         """
@@ -38,7 +43,29 @@
         
         self.__client = client
         
+        self.tlsCertsFilePicker.setMode(E5PathPickerModes.OpenFileMode)
+        self.tlsCertsFilePicker.setFilters(
+            self.tr("Certificate Files (*.crt *.pem);;All Files (*)"))
+        
         self.__populateDefaults(options=options)
+        
+        self.__updateOkButton()
+    
+    def __updateOkButton(self):
+        """
+        Private method to update the enabled state of the OK button.
+        """
+        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."""))
+        else:
+            enable = True
+        
+        self.buttonBox.button(QDialogButtonBox.Ok).setEnabled(enable)
     
     @pyqtSlot()
     def on_generateIdButton_clicked(self):
@@ -69,7 +96,7 @@
         @param options dictionary containing the connection options to populate
             the dialog with. It must have the keys "ClientId", "Keepalive",
             "CleanSession", "Username", "Password", "WillTopic", "WillMessage",
-            "WillQos", "WillRetain".
+            "WillQos", "WillRetain", "TlsEnable", "TlsCaCert".
         @type dict
         """
         if options is None:
@@ -82,13 +109,17 @@
         
         # user credentials
         self.usernameEdit.setText(options["Username"])
-        self.passwordEdit.setText(options["Password"])
+        self.passwordEdit.setText(pwConvert(options["Password"], encode=False))
         
         # last will and testament
         self.willQosSpinBox.setValue(options["WillQos"])
         self.willRetainCheckBox.setChecked(options["WillRetain"])
         self.willTopicEdit.setText(options["WillTopic"])
         self.willMessageEdit.setPlainText(options["WillMessage"])
+        
+        # TLS parameters
+        self.tlsEnableCheckBox.setChecked(options["TlsEnable"])
+        self.tlsCertsFilePicker.setText(options["TlsCaCert"])
     
     def getConnectionOptions(self):
         """
@@ -96,7 +127,8 @@
         
         @return dictionary containing the connection options. It has the keys
             "ClientId", "Keepalive", "CleanSession", "Username", "Password",
-            "WillTopic", "WillMessage", "WillQos", "WillRetain".
+            "WillTopic", "WillMessage", "WillQos", "WillRetain", "TlsEnable",
+            "TlsCaCert".
         @rtype tuple of (int, dict)
         """
         return {
@@ -104,9 +136,31 @@
             "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(),
+            "TlsEnable": self.tlsEnableCheckBox.isChecked(),
+            "TlsCaCert": self.tlsCertsFilePicker.text()
         }
+    
+    @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.__updateOkButton()
+    
+    @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.__updateOkButton()
--- a/MqttMonitor/MqttConnectionOptionsDialog.ui	Sat Sep 08 16:51:39 2018 +0200
+++ b/MqttMonitor/MqttConnectionOptionsDialog.ui	Sat Sep 08 16:55:42 2018 +0200
@@ -16,7 +16,7 @@
   <property name="sizeGripEnabled">
    <bool>true</bool>
   </property>
-  <layout class="QVBoxLayout" name="verticalLayout_2">
+  <layout class="QVBoxLayout" name="verticalLayout">
    <item>
     <widget class="QGroupBox" name="groupBox">
      <property name="title">
@@ -73,6 +73,9 @@
           <property name="singleStep">
            <number>5</number>
           </property>
+          <property name="value">
+           <number>60</number>
+          </property>
          </widget>
         </item>
         <item>
@@ -92,6 +95,9 @@
       </item>
       <item row="2" column="0" colspan="3">
        <widget class="QCheckBox" name="cleanSessionCheckBox">
+        <property name="toolTip">
+         <string>Select to start with a clean session</string>
+        </property>
         <property name="text">
          <string>Clean Session</string>
         </property>
@@ -147,7 +153,7 @@
      </property>
      <layout class="QGridLayout" name="gridLayout_3">
       <item row="0" column="0">
-       <widget class="QLineEdit" name="willTopicEdit">
+       <widget class="E5ClearableLineEdit" name="willTopicEdit">
         <property name="toolTip">
          <string>Enter the topic of the last will</string>
         </property>
@@ -200,6 +206,51 @@
     </widget>
    </item>
    <item>
+    <widget class="QGroupBox" name="groupBox_4">
+     <property name="title">
+      <string>SSL/TLS</string>
+     </property>
+     <layout class="QGridLayout" name="gridLayout_4">
+      <item row="0" column="0" colspan="2">
+       <widget class="QCheckBox" name="tlsEnableCheckBox">
+        <property name="toolTip">
+         <string>Select to enable SSL/TLS connections</string>
+        </property>
+        <property name="text">
+         <string>SSL/TLS Enabled</string>
+        </property>
+       </widget>
+      </item>
+      <item row="1" column="0">
+       <widget class="QLabel" name="label_8">
+        <property name="text">
+         <string>CA File:</string>
+        </property>
+       </widget>
+      </item>
+      <item row="1" column="1">
+       <widget class="E5PathPicker" name="tlsCertsFilePicker" native="true">
+        <property name="enabled">
+         <bool>false</bool>
+        </property>
+        <property name="sizePolicy">
+         <sizepolicy hsizetype="Expanding" vsizetype="Preferred">
+          <horstretch>0</horstretch>
+          <verstretch>0</verstretch>
+         </sizepolicy>
+        </property>
+        <property name="focusPolicy">
+         <enum>Qt::StrongFocus</enum>
+        </property>
+        <property name="toolTip">
+         <string>Enter the full path to the CA certificate file; leave empty to use platform default</string>
+        </property>
+       </widget>
+      </item>
+     </layout>
+    </widget>
+   </item>
+   <item>
     <widget class="QDialogButtonBox" name="buttonBox">
      <property name="orientation">
       <enum>Qt::Horizontal</enum>
@@ -217,6 +268,12 @@
    <extends>QLineEdit</extends>
    <header>E5Gui/E5LineEdit.h</header>
   </customwidget>
+  <customwidget>
+   <class>E5PathPicker</class>
+   <extends>QWidget</extends>
+   <header>E5Gui/E5PathPicker.h</header>
+   <container>1</container>
+  </customwidget>
  </customwidgets>
  <tabstops>
   <tabstop>clientIdEdit</tabstop>
@@ -239,8 +296,8 @@
    <slot>accept()</slot>
    <hints>
     <hint type="sourcelabel">
-     <x>248</x>
-     <y>254</y>
+     <x>257</x>
+     <y>590</y>
     </hint>
     <hint type="destinationlabel">
      <x>157</x>
@@ -255,8 +312,8 @@
    <slot>reject()</slot>
    <hints>
     <hint type="sourcelabel">
-     <x>316</x>
-     <y>260</y>
+     <x>325</x>
+     <y>590</y>
     </hint>
     <hint type="destinationlabel">
      <x>286</x>
@@ -264,5 +321,21 @@
     </hint>
    </hints>
   </connection>
+  <connection>
+   <sender>tlsEnableCheckBox</sender>
+   <signal>toggled(bool)</signal>
+   <receiver>tlsCertsFilePicker</receiver>
+   <slot>setEnabled(bool)</slot>
+   <hints>
+    <hint type="sourcelabel">
+     <x>82</x>
+     <y>523</y>
+    </hint>
+    <hint type="destinationlabel">
+     <x>92</x>
+     <y>544</y>
+    </hint>
+   </hints>
+  </connection>
  </connections>
 </ui>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/MqttMonitor/MqttConnectionProfilesDialog.py	Sat Sep 08 16:55:42 2018 +0200
@@ -0,0 +1,699 @@
+# -*- 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 E5Gui.E5PathPicker import E5PathPickerModes
+
+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", "TlsEnable", "TlsCaCert",
+            "TlsClientCert", "TlsClientKey".
+        @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.copyButton.setIcon(UI.PixmapCache.getIcon("editCopy.png"))
+        self.minusButton.setIcon(UI.PixmapCache.getIcon("minus.png"))
+        
+        self.tlsCertsFilePicker.setMode(E5PathPickerModes.OpenFileMode)
+        self.tlsCertsFilePicker.setFilters(
+            self.tr("Certificate Files (*.crt *.pem);;All Files (*)"))
+        self.tlsSelfSignedCertsFilePicker.setMode(
+            E5PathPickerModes.OpenFileMode)
+        self.tlsSelfSignedCertsFilePicker.setFilters(
+            self.tr("Certificate Files (*.crt *.pem);;All Files (*)"))
+        self.tlsSelfSignedClientCertFilePicker.setMode(
+            E5PathPickerModes.OpenFileMode)
+        self.tlsSelfSignedClientCertFilePicker.setFilters(
+            self.tr("Certificate Files (*.crt *.pem);;All Files (*)"))
+        self.tlsSelfSignedClientKeyFilePicker.setMode(
+            E5PathPickerModes.OpenFileMode)
+        self.tlsSelfSignedClientKeyFilePicker.setFilters(
+            self.tr("Key Files (*.key *.pem);;All Files (*)"))
+        
+        self.profileTabWidget.setCurrentIndex(0)
+        
+        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.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)
+        self.copyButton.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):
+            if profileName in self.__profiles:
+                E5MessageBox.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.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.Normal)
+            if ok and bool(newProfileName):
+                if newProfileName in self.__profiles:
+                    E5MessageBox.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.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",
+            "TlsEnable", "TlsCaCert", "TlsClientCert", "TlsClientKey".
+        @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(),
+            "TlsEnable": self.tlsGroupBox.isChecked(),
+            "TlsCaCert": "",
+            "TlsClientCert": "",
+            "TlsClientKey": "",
+        }
+        if profile["TlsEnable"]:
+            if self.tlsCertsFileButton.isChecked():
+                profile["TlsCaCert"] = self.tlsCertsFilePicker.text()
+            elif self.tlsSelfSignedCertsButton.isChecked():
+                profile["TlsCaCert"] = \
+                    self.tlsSelfSignedCertsFilePicker.text()
+                profile["TlsClientCert"] = \
+                    self.tlsSelfSignedClientCertFilePicker.text()
+                profile["TlsClientKey"] = \
+                    self.tlsSelfSignedClientKeyFilePicker.text()
+        
+        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"] = ""
+        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.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
+        """
+        profile = self.__defaultProfile()
+        if profileName:
+            profile.update(self.__profiles[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(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.tlsGroupBox.setChecked(profile["TlsEnable"])
+        if profile["TlsCaCert"] and profile["TlsClientCert"]:
+            self.tlsSelfSignedCertsButton.setChecked(True)
+            self.tlsSelfSignedCertsFilePicker.setText(profile["TlsCaCert"])
+            self.tlsSelfSignedClientCertFilePicker.setText(
+                profile["TlsClientCert"])
+            self.tlsSelfSignedClientKeyFilePicker.setText(
+                profile["TlsClientKey"])
+        elif profile["TlsCaCert"]:
+            self.tlsCertsFileButton.setChecked(True)
+            self.tlsCertsFilePicker.setText(profile["TlsCaCert"])
+        else:
+            self.tlsDefaultCertsButton.setChecked(True)
+        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.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.__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.__defaultProfile()
+            profile.update(self.__profiles[profileName])
+            changed = (
+                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"] or
+                self.tlsGroupBox.isChecked() != profile["TlsEnable"]
+            )
+            # check TLS stuff only, if not yet changed
+            if not changed:
+                if self.tlsCertsFileButton.isChecked():
+                    changed |= (
+                        self.tlsCertsFilePicker.text() != profile["TlsCaCert"]
+                    )
+                elif self.tlsSelfSignedCertsButton.isChecked():
+                    changed |= (
+                        self.tlsSelfSignedCertsFilePicker.text() !=
+                        profile["TlsCaCert"] or
+                        self.tlsSelfSignedClientCertFilePicker.text() !=
+                        profile["TlsClientCert"] or
+                        self.tlsSelfSignedClientKeyFilePicker.text() !=
+                        profile["TlsClientKey"]
+                    )
+            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:
+            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."))
+        
+        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.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(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(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 = E5MessageBox.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=E5MessageBox.Warning,
+                yesDefault=True)
+            if yes:
+                self.brokerPortSpinBox.setValue(8883)
+        elif not checked and self.brokerPortSpinBox.value() == 8883:
+            # port is still standard TLS port
+            yes = E5MessageBox.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=E5MessageBox.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()
+    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()
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/MqttMonitor/MqttConnectionProfilesDialog.ui	Sat Sep 08 16:55:42 2018 +0200
@@ -0,0 +1,760 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>MqttConnectionProfilesDialog</class>
+ <widget class="QDialog" name="MqttConnectionProfilesDialog">
+  <property name="geometry">
+   <rect>
+    <x>0</x>
+    <y>0</y>
+    <width>800</width>
+    <height>600</height>
+   </rect>
+  </property>
+  <property name="windowTitle">
+   <string>MQTT Connection Profiles</string>
+  </property>
+  <property name="sizeGripEnabled">
+   <bool>true</bool>
+  </property>
+  <layout class="QVBoxLayout" name="verticalLayout_2">
+   <item>
+    <widget class="QSplitter" name="splitter">
+     <property name="orientation">
+      <enum>Qt::Horizontal</enum>
+     </property>
+     <property name="childrenCollapsible">
+      <bool>false</bool>
+     </property>
+     <widget class="QFrame" name="profileListFrame">
+      <property name="frameShape">
+       <enum>QFrame::StyledPanel</enum>
+      </property>
+      <property name="frameShadow">
+       <enum>QFrame::Raised</enum>
+      </property>
+      <layout class="QVBoxLayout" name="verticalLayout">
+       <item>
+        <widget class="QListWidget" name="profilesList">
+         <property name="alternatingRowColors">
+          <bool>true</bool>
+         </property>
+         <property name="sortingEnabled">
+          <bool>true</bool>
+         </property>
+        </widget>
+       </item>
+       <item>
+        <layout class="QHBoxLayout" name="horizontalLayout">
+         <item>
+          <widget class="QToolButton" name="plusButton">
+           <property name="toolTip">
+            <string>Press to add a new profile</string>
+           </property>
+          </widget>
+         </item>
+         <item>
+          <widget class="QToolButton" name="copyButton">
+           <property name="toolTip">
+            <string>Press to copy the selected profile</string>
+           </property>
+          </widget>
+         </item>
+         <item>
+          <widget class="QToolButton" name="minusButton">
+           <property name="toolTip">
+            <string>Press to delete the selected profile</string>
+           </property>
+          </widget>
+         </item>
+         <item>
+          <spacer name="horizontalSpacer">
+           <property name="orientation">
+            <enum>Qt::Horizontal</enum>
+           </property>
+           <property name="sizeHint" stdset="0">
+            <size>
+             <width>40</width>
+             <height>20</height>
+            </size>
+           </property>
+          </spacer>
+         </item>
+        </layout>
+       </item>
+      </layout>
+     </widget>
+     <widget class="QFrame" name="profileFrame">
+      <property name="frameShape">
+       <enum>QFrame::StyledPanel</enum>
+      </property>
+      <property name="frameShadow">
+       <enum>QFrame::Raised</enum>
+      </property>
+      <layout class="QVBoxLayout" name="verticalLayout_3">
+       <item>
+        <layout class="QGridLayout" name="gridLayout">
+         <item row="0" column="0">
+          <widget class="QLabel" name="label">
+           <property name="text">
+            <string>Profile Name:</string>
+           </property>
+          </widget>
+         </item>
+         <item row="0" column="1">
+          <widget class="QLineEdit" name="profileEdit">
+           <property name="readOnly">
+            <bool>true</bool>
+           </property>
+          </widget>
+         </item>
+         <item row="1" column="0" colspan="2">
+          <widget class="Line" name="line">
+           <property name="orientation">
+            <enum>Qt::Horizontal</enum>
+           </property>
+          </widget>
+         </item>
+         <item row="2" column="0">
+          <widget class="QLabel" name="label_2">
+           <property name="text">
+            <string>Broker Address:</string>
+           </property>
+          </widget>
+         </item>
+         <item row="2" column="1">
+          <widget class="E5ClearableLineEdit" name="brokerAddressEdit">
+           <property name="toolTip">
+            <string>Enter the broker server address</string>
+           </property>
+          </widget>
+         </item>
+         <item row="3" column="0">
+          <widget class="QLabel" name="label_3">
+           <property name="text">
+            <string>Broker Port:</string>
+           </property>
+          </widget>
+         </item>
+         <item row="3" column="1">
+          <layout class="QHBoxLayout" name="horizontalLayout_2">
+           <item>
+            <widget class="QSpinBox" name="brokerPortSpinBox">
+             <property name="toolTip">
+              <string>Enter the broker port number (default 1883)</string>
+             </property>
+             <property name="alignment">
+              <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
+             </property>
+             <property name="maximum">
+              <number>65535</number>
+             </property>
+             <property name="value">
+              <number>1883</number>
+             </property>
+            </widget>
+           </item>
+           <item>
+            <spacer name="horizontalSpacer_2">
+             <property name="orientation">
+              <enum>Qt::Horizontal</enum>
+             </property>
+             <property name="sizeHint" stdset="0">
+              <size>
+               <width>318</width>
+               <height>20</height>
+              </size>
+             </property>
+            </spacer>
+           </item>
+          </layout>
+         </item>
+         <item row="4" column="0">
+          <widget class="QLabel" name="label_4">
+           <property name="text">
+            <string>Client ID:</string>
+           </property>
+          </widget>
+         </item>
+         <item row="4" column="1">
+          <layout class="QHBoxLayout" name="horizontalLayout_3">
+           <item>
+            <widget class="E5ClearableLineEdit" name="clientIdEdit">
+             <property name="toolTip">
+              <string>Enter the ID string for this client</string>
+             </property>
+            </widget>
+           </item>
+           <item>
+            <widget class="QPushButton" name="generateIdButton">
+             <property name="toolTip">
+              <string>Press to generate a client ID</string>
+             </property>
+             <property name="text">
+              <string>Generate</string>
+             </property>
+            </widget>
+           </item>
+          </layout>
+         </item>
+         <item row="5" column="0" colspan="2">
+          <widget class="Line" name="line_2">
+           <property name="orientation">
+            <enum>Qt::Horizontal</enum>
+           </property>
+          </widget>
+         </item>
+        </layout>
+       </item>
+       <item>
+        <widget class="QTabWidget" name="profileTabWidget">
+         <property name="currentIndex">
+          <number>0</number>
+         </property>
+         <widget class="QWidget" name="generalTab">
+          <attribute name="title">
+           <string>General</string>
+          </attribute>
+          <layout class="QGridLayout" name="gridLayout_2">
+           <item row="0" column="0">
+            <widget class="QLabel" name="label_5">
+             <property name="text">
+              <string>Keep Alive Interval:</string>
+             </property>
+            </widget>
+           </item>
+           <item row="0" column="1">
+            <layout class="QHBoxLayout" name="horizontalLayout_4">
+             <item>
+              <widget class="QSpinBox" name="keepaliveSpinBox">
+               <property name="toolTip">
+                <string>Enter the keep alive interval in seconds</string>
+               </property>
+               <property name="alignment">
+                <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
+               </property>
+               <property name="suffix">
+                <string> s</string>
+               </property>
+               <property name="maximum">
+                <number>300</number>
+               </property>
+               <property name="singleStep">
+                <number>5</number>
+               </property>
+               <property name="value">
+                <number>60</number>
+               </property>
+              </widget>
+             </item>
+             <item>
+              <spacer name="horizontalSpacer_3">
+               <property name="orientation">
+                <enum>Qt::Horizontal</enum>
+               </property>
+               <property name="sizeHint" stdset="0">
+                <size>
+                 <width>148</width>
+                 <height>20</height>
+                </size>
+               </property>
+              </spacer>
+             </item>
+            </layout>
+           </item>
+           <item row="1" column="0" colspan="2">
+            <widget class="QCheckBox" name="cleanSessionCheckBox">
+             <property name="toolTip">
+              <string>Select to start with a clean session</string>
+             </property>
+             <property name="text">
+              <string>Clean Session</string>
+             </property>
+            </widget>
+           </item>
+           <item row="2" column="1">
+            <spacer name="verticalSpacer">
+             <property name="orientation">
+              <enum>Qt::Vertical</enum>
+             </property>
+             <property name="sizeHint" stdset="0">
+              <size>
+               <width>20</width>
+               <height>227</height>
+              </size>
+             </property>
+            </spacer>
+           </item>
+          </layout>
+         </widget>
+         <widget class="QWidget" name="credentialsTab">
+          <attribute name="title">
+           <string>User Credentials</string>
+          </attribute>
+          <layout class="QGridLayout" name="gridLayout_3">
+           <item row="0" column="0">
+            <widget class="QLabel" name="label_7">
+             <property name="text">
+              <string>User Name:</string>
+             </property>
+            </widget>
+           </item>
+           <item row="0" column="1">
+            <widget class="E5ClearableLineEdit" name="usernameEdit">
+             <property name="toolTip">
+              <string>Enter the user name</string>
+             </property>
+            </widget>
+           </item>
+           <item row="1" column="0">
+            <widget class="QLabel" name="label_6">
+             <property name="text">
+              <string>Password:</string>
+             </property>
+            </widget>
+           </item>
+           <item row="1" column="1">
+            <widget class="E5ClearableLineEdit" name="passwordEdit">
+             <property name="toolTip">
+              <string>Enter the password</string>
+             </property>
+             <property name="echoMode">
+              <enum>QLineEdit::Password</enum>
+             </property>
+            </widget>
+           </item>
+           <item row="2" column="0">
+            <spacer name="verticalSpacer_2">
+             <property name="orientation">
+              <enum>Qt::Vertical</enum>
+             </property>
+             <property name="sizeHint" stdset="0">
+              <size>
+               <width>20</width>
+               <height>228</height>
+              </size>
+             </property>
+            </spacer>
+           </item>
+          </layout>
+         </widget>
+         <widget class="QWidget" name="lastWillTab">
+          <attribute name="title">
+           <string>Last Will</string>
+          </attribute>
+          <layout class="QGridLayout" name="gridLayout_4">
+           <item row="0" column="0">
+            <widget class="E5ClearableLineEdit" name="willTopicEdit">
+             <property name="toolTip">
+              <string>Enter the topic of the last will</string>
+             </property>
+            </widget>
+           </item>
+           <item row="0" column="1">
+            <widget class="QSpinBox" name="willQosSpinBox">
+             <property name="toolTip">
+              <string>Enter the desired QoS value</string>
+             </property>
+             <property name="alignment">
+              <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
+             </property>
+             <property name="maximum">
+              <number>2</number>
+             </property>
+            </widget>
+           </item>
+           <item row="0" column="2">
+            <widget class="QCheckBox" name="willRetainCheckBox">
+             <property name="toolTip">
+              <string>Select to retain the last will message</string>
+             </property>
+             <property name="text">
+              <string>Retain</string>
+             </property>
+            </widget>
+           </item>
+           <item row="1" column="0" colspan="3">
+            <widget class="QPlainTextEdit" name="willMessageEdit">
+             <property name="maximumSize">
+              <size>
+               <width>16777215</width>
+               <height>300</height>
+              </size>
+             </property>
+             <property name="toolTip">
+              <string>Enter the last will message to be sent</string>
+             </property>
+            </widget>
+           </item>
+          </layout>
+         </widget>
+         <widget class="QWidget" name="tlsTab">
+          <attribute name="title">
+           <string>SSL/TLS</string>
+          </attribute>
+          <layout class="QVBoxLayout" name="verticalLayout_5">
+           <item>
+            <widget class="QGroupBox" name="tlsGroupBox">
+             <property name="toolTip">
+              <string>Select to enable SSL/TLS connections</string>
+             </property>
+             <property name="title">
+              <string>SSL/TLS Enabled</string>
+             </property>
+             <property name="checkable">
+              <bool>true</bool>
+             </property>
+             <property name="checked">
+              <bool>false</bool>
+             </property>
+             <layout class="QVBoxLayout" name="verticalLayout_4">
+              <item>
+               <widget class="QRadioButton" name="tlsDefaultCertsButton">
+                <property name="toolTip">
+                 <string>Select to use the default certificate file of the client</string>
+                </property>
+                <property name="text">
+                 <string>CA signed server certificate</string>
+                </property>
+                <property name="checked">
+                 <bool>true</bool>
+                </property>
+               </widget>
+              </item>
+              <item>
+               <widget class="QRadioButton" name="tlsCertsFileButton">
+                <property name="toolTip">
+                 <string>Select to use a specific certificate file</string>
+                </property>
+                <property name="text">
+                 <string>CA certificate file</string>
+                </property>
+               </widget>
+              </item>
+              <item>
+               <widget class="QWidget" name="tlsCertsFileWidget" native="true">
+                <property name="enabled">
+                 <bool>false</bool>
+                </property>
+                <layout class="QHBoxLayout" name="horizontalLayout_6">
+                 <property name="leftMargin">
+                  <number>0</number>
+                 </property>
+                 <property name="topMargin">
+                  <number>0</number>
+                 </property>
+                 <property name="rightMargin">
+                  <number>0</number>
+                 </property>
+                 <property name="bottomMargin">
+                  <number>0</number>
+                 </property>
+                 <item>
+                  <spacer name="horizontalSpacer_4">
+                   <property name="orientation">
+                    <enum>Qt::Horizontal</enum>
+                   </property>
+                   <property name="sizeType">
+                    <enum>QSizePolicy::Fixed</enum>
+                   </property>
+                   <property name="sizeHint" stdset="0">
+                    <size>
+                     <width>25</width>
+                     <height>20</height>
+                    </size>
+                   </property>
+                  </spacer>
+                 </item>
+                 <item>
+                  <widget class="QLabel" name="label_8">
+                   <property name="text">
+                    <string>CA File:</string>
+                   </property>
+                  </widget>
+                 </item>
+                 <item>
+                  <widget class="E5PathPicker" name="tlsCertsFilePicker" native="true">
+                   <property name="sizePolicy">
+                    <sizepolicy hsizetype="Expanding" vsizetype="Preferred">
+                     <horstretch>0</horstretch>
+                     <verstretch>0</verstretch>
+                    </sizepolicy>
+                   </property>
+                   <property name="focusPolicy">
+                    <enum>Qt::StrongFocus</enum>
+                   </property>
+                   <property name="toolTip">
+                    <string>Enter the full path to the CA certificate file</string>
+                   </property>
+                  </widget>
+                 </item>
+                </layout>
+               </widget>
+              </item>
+              <item>
+               <widget class="QRadioButton" name="tlsSelfSignedCertsButton">
+                <property name="toolTip">
+                 <string>Select to use a self signed client certificate</string>
+                </property>
+                <property name="text">
+                 <string>Self signed certificates</string>
+                </property>
+               </widget>
+              </item>
+              <item>
+               <widget class="QWidget" name="tlsSelfSignedFilesWidget" native="true">
+                <property name="enabled">
+                 <bool>false</bool>
+                </property>
+                <layout class="QGridLayout" name="gridLayout_5">
+                 <property name="leftMargin">
+                  <number>0</number>
+                 </property>
+                 <property name="topMargin">
+                  <number>0</number>
+                 </property>
+                 <property name="rightMargin">
+                  <number>0</number>
+                 </property>
+                 <property name="bottomMargin">
+                  <number>0</number>
+                 </property>
+                 <item row="0" column="0">
+                  <spacer name="horizontalSpacer_5">
+                   <property name="orientation">
+                    <enum>Qt::Horizontal</enum>
+                   </property>
+                   <property name="sizeType">
+                    <enum>QSizePolicy::Fixed</enum>
+                   </property>
+                   <property name="sizeHint" stdset="0">
+                    <size>
+                     <width>25</width>
+                     <height>20</height>
+                    </size>
+                   </property>
+                  </spacer>
+                 </item>
+                 <item row="0" column="1">
+                  <widget class="QLabel" name="label_9">
+                   <property name="text">
+                    <string>CA File:</string>
+                   </property>
+                  </widget>
+                 </item>
+                 <item row="0" column="2">
+                  <widget class="E5PathPicker" name="tlsSelfSignedCertsFilePicker" native="true">
+                   <property name="sizePolicy">
+                    <sizepolicy hsizetype="Expanding" vsizetype="Preferred">
+                     <horstretch>0</horstretch>
+                     <verstretch>0</verstretch>
+                    </sizepolicy>
+                   </property>
+                   <property name="focusPolicy">
+                    <enum>Qt::StrongFocus</enum>
+                   </property>
+                   <property name="toolTip">
+                    <string>Enter the full path to the CA certificate file</string>
+                   </property>
+                  </widget>
+                 </item>
+                 <item row="1" column="1">
+                  <widget class="QLabel" name="label_10">
+                   <property name="text">
+                    <string>Client Certificate File:</string>
+                   </property>
+                  </widget>
+                 </item>
+                 <item row="1" column="2">
+                  <widget class="E5PathPicker" name="tlsSelfSignedClientCertFilePicker" native="true">
+                   <property name="sizePolicy">
+                    <sizepolicy hsizetype="Expanding" vsizetype="Preferred">
+                     <horstretch>0</horstretch>
+                     <verstretch>0</verstretch>
+                    </sizepolicy>
+                   </property>
+                   <property name="focusPolicy">
+                    <enum>Qt::StrongFocus</enum>
+                   </property>
+                   <property name="toolTip">
+                    <string>Enter the full path to the client certificate file</string>
+                   </property>
+                  </widget>
+                 </item>
+                 <item row="2" column="1">
+                  <widget class="QLabel" name="label_11">
+                   <property name="text">
+                    <string>Client Key File:</string>
+                   </property>
+                  </widget>
+                 </item>
+                 <item row="2" column="2">
+                  <widget class="E5PathPicker" name="tlsSelfSignedClientKeyFilePicker" native="true">
+                   <property name="sizePolicy">
+                    <sizepolicy hsizetype="Expanding" vsizetype="Preferred">
+                     <horstretch>0</horstretch>
+                     <verstretch>0</verstretch>
+                    </sizepolicy>
+                   </property>
+                   <property name="focusPolicy">
+                    <enum>Qt::StrongFocus</enum>
+                   </property>
+                   <property name="toolTip">
+                    <string>Enter the full path to the client key file</string>
+                   </property>
+                  </widget>
+                 </item>
+                </layout>
+               </widget>
+              </item>
+             </layout>
+            </widget>
+           </item>
+           <item>
+            <spacer name="verticalSpacer_3">
+             <property name="orientation">
+              <enum>Qt::Vertical</enum>
+             </property>
+             <property name="sizeHint" stdset="0">
+              <size>
+               <width>20</width>
+               <height>128</height>
+              </size>
+             </property>
+            </spacer>
+           </item>
+          </layout>
+         </widget>
+        </widget>
+       </item>
+       <item>
+        <widget class="QDialogButtonBox" name="profileButtonBox">
+         <property name="standardButtons">
+          <set>QDialogButtonBox::Apply|QDialogButtonBox::Reset|QDialogButtonBox::RestoreDefaults</set>
+         </property>
+        </widget>
+       </item>
+      </layout>
+     </widget>
+    </widget>
+   </item>
+   <item>
+    <widget class="QDialogButtonBox" name="buttonBox">
+     <property name="orientation">
+      <enum>Qt::Horizontal</enum>
+     </property>
+     <property name="standardButtons">
+      <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
+     </property>
+    </widget>
+   </item>
+  </layout>
+ </widget>
+ <customwidgets>
+  <customwidget>
+   <class>E5ClearableLineEdit</class>
+   <extends>QLineEdit</extends>
+   <header>E5Gui/E5LineEdit.h</header>
+  </customwidget>
+  <customwidget>
+   <class>E5PathPicker</class>
+   <extends>QWidget</extends>
+   <header>E5Gui/E5PathPicker.h</header>
+   <container>1</container>
+  </customwidget>
+ </customwidgets>
+ <tabstops>
+  <tabstop>profilesList</tabstop>
+  <tabstop>plusButton</tabstop>
+  <tabstop>copyButton</tabstop>
+  <tabstop>minusButton</tabstop>
+  <tabstop>profileEdit</tabstop>
+  <tabstop>brokerAddressEdit</tabstop>
+  <tabstop>brokerPortSpinBox</tabstop>
+  <tabstop>clientIdEdit</tabstop>
+  <tabstop>generateIdButton</tabstop>
+  <tabstop>profileTabWidget</tabstop>
+  <tabstop>keepaliveSpinBox</tabstop>
+  <tabstop>cleanSessionCheckBox</tabstop>
+  <tabstop>usernameEdit</tabstop>
+  <tabstop>passwordEdit</tabstop>
+  <tabstop>willTopicEdit</tabstop>
+  <tabstop>willMessageEdit</tabstop>
+  <tabstop>willQosSpinBox</tabstop>
+  <tabstop>willRetainCheckBox</tabstop>
+  <tabstop>tlsGroupBox</tabstop>
+  <tabstop>tlsDefaultCertsButton</tabstop>
+  <tabstop>tlsCertsFileButton</tabstop>
+  <tabstop>tlsCertsFilePicker</tabstop>
+  <tabstop>tlsSelfSignedCertsButton</tabstop>
+  <tabstop>tlsSelfSignedCertsFilePicker</tabstop>
+  <tabstop>tlsSelfSignedClientCertFilePicker</tabstop>
+  <tabstop>tlsSelfSignedClientKeyFilePicker</tabstop>
+ </tabstops>
+ <resources/>
+ <connections>
+  <connection>
+   <sender>buttonBox</sender>
+   <signal>accepted()</signal>
+   <receiver>MqttConnectionProfilesDialog</receiver>
+   <slot>accept()</slot>
+   <hints>
+    <hint type="sourcelabel">
+     <x>227</x>
+     <y>579</y>
+    </hint>
+    <hint type="destinationlabel">
+     <x>157</x>
+     <y>274</y>
+    </hint>
+   </hints>
+  </connection>
+  <connection>
+   <sender>buttonBox</sender>
+   <signal>rejected()</signal>
+   <receiver>MqttConnectionProfilesDialog</receiver>
+   <slot>reject()</slot>
+   <hints>
+    <hint type="sourcelabel">
+     <x>295</x>
+     <y>585</y>
+    </hint>
+    <hint type="destinationlabel">
+     <x>286</x>
+     <y>274</y>
+    </hint>
+   </hints>
+  </connection>
+  <connection>
+   <sender>tlsCertsFileButton</sender>
+   <signal>toggled(bool)</signal>
+   <receiver>tlsCertsFileWidget</receiver>
+   <slot>setEnabled(bool)</slot>
+   <hints>
+    <hint type="sourcelabel">
+     <x>367</x>
+     <y>238</y>
+    </hint>
+    <hint type="destinationlabel">
+     <x>357</x>
+     <y>252</y>
+    </hint>
+   </hints>
+  </connection>
+  <connection>
+   <sender>tlsSelfSignedCertsButton</sender>
+   <signal>toggled(bool)</signal>
+   <receiver>tlsSelfSignedFilesWidget</receiver>
+   <slot>setEnabled(bool)</slot>
+   <hints>
+    <hint type="sourcelabel">
+     <x>387</x>
+     <y>287</y>
+    </hint>
+    <hint type="destinationlabel">
+     <x>466</x>
+     <y>305</y>
+    </hint>
+   </hints>
+  </connection>
+ </connections>
+</ui>
--- a/MqttMonitor/MqttMonitorWidget.py	Sat Sep 08 16:51:39 2018 +0200
+++ b/MqttMonitor/MqttMonitorWidget.py	Sat Sep 08 16:55:42 2018 +0200
@@ -16,6 +16,7 @@
 
 import os
 import collections
+import copy
 
 from PyQt5.QtCore import pyqtSlot, QTimer
 from PyQt5.QtGui import QTextCursor
@@ -61,6 +62,10 @@
         
         self.brokerWidget.setCurrentIndex(0)
         
+        self.__connectionModeProfile = True
+        self.__setConnectionMode(True)  # initial mode is 'profile connection'
+        self.__populateProfileComboBox()
+        
         self.connectButton.setIcon(UI.PixmapCache.getIcon("ircConnect.png"))
         self.brokerConnectionOptionsButton.setIcon(UI.PixmapCache.getIcon(
             os.path.join("MqttMonitor", "icons", "connectionOptions.png")))
@@ -283,6 +288,14 @@
         self.brokerStatusLabel.show()
         QTimer.singleShot(5000, self.brokerStatusLabel.hide)
     
+    @pyqtSlot()
+    def on_modeButton_clicked(self):
+        """
+        Private slot to switch between connection profiles and direct
+        connection mode.
+        """
+        self.__setConnectionMode(not self.__connectionModeProfile)
+    
     @pyqtSlot(str)
     def on_brokerComboBox_editTextChanged(self, host):
         """
@@ -291,21 +304,41 @@
         @param host host name of the broker
         @type str
         """
-        if not self.__connectedToBroker and not host:
-            self.connectButton.setEnabled(False)
-        else:
-            self.connectButton.setEnabled(True)
+        self.__setConnectButtonState()
     
     @pyqtSlot()
     def on_brokerConnectionOptionsButton_clicked(self):
         """
-        Private slot to show a dialog to modify connection options.
+        Private slot to show a dialog to modify connection options or a
+        dialog to edit connection profiles.
         """
-        from .MqttConnectionOptionsDialog import MqttConnectionOptionsDialog
-        dlg = MqttConnectionOptionsDialog(
-            self.__client, self.__connectionOptions, parent=self)
-        if dlg.exec_() == QDialog.Accepted:
-            self.__connectionOptions = dlg.getConnectionOptions()
+        if self.__connectionModeProfile:
+            from .MqttConnectionProfilesDialog import \
+                MqttConnectionProfilesDialog
+            dlg = MqttConnectionProfilesDialog(
+                self.__client, self.__plugin.getPreferences("BrokerProfiles"),
+                parent=self)
+            if dlg.exec_() == QDialog.Accepted:
+                profilesDict = dlg.getProfiles()
+                self.__plugin.setPreferences("BrokerProfiles", profilesDict)
+                self.__populateProfileComboBox()
+        else:
+            from .MqttConnectionOptionsDialog import \
+                MqttConnectionOptionsDialog
+            dlg = MqttConnectionOptionsDialog(
+                self.__client, self.__connectionOptions, parent=self)
+            if dlg.exec_() == QDialog.Accepted:
+                self.__connectionOptions = dlg.getConnectionOptions()
+                if self.__connectionOptions["TlsEnable"]:
+                    port = self.brokerPortComboBox.currentText().strip()
+                    if port == "1883":
+                        # it is default non-encrypted port => set to TLS port
+                        self.brokerPortComboBox.setEditText("8883")
+                else:
+                    port = self.brokerPortComboBox.currentText().strip()
+                    if port == "8883":
+                        # it is default TLS port => set to non-encrypted port
+                        self.brokerPortComboBox.setEditText("1883")
     
     @pyqtSlot()
     def on_connectButton_clicked(self):
@@ -315,20 +348,10 @@
         if self.__connectedToBroker:
             self.__client.disconnectFromServer()
         else:
-            host = self.brokerComboBox.currentText()
-            port = self.brokerPortComboBox.currentText().strip()
-            try:
-                port = int(port)
-            except ValueError:
-                # use standard port at 1883
-                port = 1883
-            if host:
-                self.__addBrokerToRecent(host, port)
-                if self.__connectionOptions is None:
-                    self.__client.connectToServer(host, port=port)
-                else:
-                    self.__client.connectToServerWithOptions(
-                        host, port=port, options=self.__connectionOptions)
+            if self.__connectionModeProfile:
+                self.__profileConnectToBroker()
+            else:
+                self.__directConnectToBroker()
     
     @pyqtSlot(str)
     def on_subscribeTopicEdit_textChanged(self, topic):
@@ -502,6 +525,8 @@
         # step 2a: populate the broker name list
         self.brokerComboBox.addItems([b[0].strip() for b in brokerList])
         
+        self.__setConnectButtonState()
+        
         # step 2b: populate the broker ports list
         if brokerList:
             currentPort = brokerList[0][1]
@@ -515,6 +540,17 @@
         index = self.brokerPortComboBox.findText(currentPortStr)
         self.brokerPortComboBox.setCurrentIndex(index)
     
+    def __populateProfileComboBox(self):
+        """
+        Private method to populate the profiles selection box.
+        """
+        profilesDict = self.__plugin.getPreferences("BrokerProfiles")
+        
+        self.profileComboBox.clear()
+        self.profileComboBox.addItems(sorted(profilesDict.keys()))
+        
+        self.__setConnectButtonState()
+    
     def __updateUnsubscribeTopicComboBox(self):
         """
         Private method to update the unsubcribe topic combo box.
@@ -630,3 +666,66 @@
             "5min": "-",
             "15min": "-",
         }
+    
+    def __setConnectionMode(self, profileMode):
+        """
+        Private method to set the connection mode.
+        
+        @param profileMode flag indicating the profile connection mode
+        @type bool
+        """
+        self.__connectionModeProfile = profileMode
+        if profileMode:
+            self.modeButton.setIcon(UI.PixmapCache.getIcon(
+                os.path.join("MqttMonitor", "icons", "profiles.png")))
+        else:
+            self.modeButton.setIcon(UI.PixmapCache.getIcon(
+                os.path.join("MqttMonitor", "icons", "quickopen.png")))
+        
+        self.profileComboBox.setVisible(profileMode)
+        self.brokerConnectionWidget.setVisible(not profileMode)
+        self.__setConnectButtonState()
+    
+    def __setConnectButtonState(self):
+        """
+        Private method to set the enabled state of the connect button.
+        """
+        if self.__connectionModeProfile:
+            self.connectButton.setEnabled(
+                bool(self.profileComboBox.currentText()))
+        else:
+            self.connectButton.setEnabled(
+                bool(self.brokerComboBox.currentText()))
+    
+    def __directConnectToBroker(self):
+        """
+        Private method to connect to the broker with entered data.
+        """
+        host = self.brokerComboBox.currentText()
+        port = self.brokerPortComboBox.currentText().strip()
+        try:
+            port = int(port)
+        except ValueError:
+            # use standard port at 1883
+            port = 1883
+        if host:
+            self.__addBrokerToRecent(host, port)
+            if self.__connectionOptions is None:
+                self.__client.connectToServer(host, port=port)
+            else:
+                self.__client.connectToServerWithOptions(
+                    host, port=port, options=self.__connectionOptions)
+    
+    def __profileConnectToBroker(self):
+        """
+        Private method to connect to the broker with selected profile.
+        """
+        profileName = self.profileComboBox.currentText()
+        if profileName:
+            profilesDict = self.__plugin.getPreferences("BrokerProfiles")
+            profile = copy.copy(profilesDict[profileName])      # play it save
+            host = profile["BrokerAddress"]
+            port = profile["BrokerPort"]
+            
+            self.__client.connectToServerWithOptions(host, port=port,
+                                                     options=profile)
--- a/MqttMonitor/MqttMonitorWidget.ui	Sat Sep 08 16:51:39 2018 +0200
+++ b/MqttMonitor/MqttMonitorWidget.ui	Sat Sep 08 16:55:42 2018 +0200
@@ -35,24 +35,89 @@
     </layout>
    </item>
    <item>
-    <widget class="QGroupBox" name="groupBox">
+    <widget class="QGroupBox" name="brokerGroupBox">
      <property name="title">
       <string>Broker</string>
      </property>
      <layout class="QGridLayout" name="gridLayout">
       <item row="0" column="0">
-       <widget class="E5ClearableComboBox" name="brokerComboBox">
-        <property name="sizePolicy">
-         <sizepolicy hsizetype="Expanding" vsizetype="Fixed">
-          <horstretch>0</horstretch>
-          <verstretch>0</verstretch>
-         </sizepolicy>
+       <widget class="QToolButton" name="modeButton">
+        <property name="toolTip">
+         <string>Press to switch the mode between profiles and direct connection</string>
+        </property>
+       </widget>
+      </item>
+      <item row="0" column="1">
+       <layout class="QHBoxLayout" name="horizontalLayout_15">
+        <property name="spacing">
+         <number>0</number>
         </property>
+        <item>
+         <widget class="QComboBox" name="profileComboBox">
+          <property name="sizePolicy">
+           <sizepolicy hsizetype="Expanding" vsizetype="Fixed">
+            <horstretch>0</horstretch>
+            <verstretch>0</verstretch>
+           </sizepolicy>
+          </property>
+          <property name="toolTip">
+           <string>Select the profile to be used to connect to the broker</string>
+          </property>
+         </widget>
+        </item>
+        <item>
+         <widget class="QWidget" name="brokerConnectionWidget" native="true">
+          <layout class="QHBoxLayout" name="horizontalLayout_14">
+           <property name="leftMargin">
+            <number>0</number>
+           </property>
+           <property name="topMargin">
+            <number>0</number>
+           </property>
+           <property name="rightMargin">
+            <number>0</number>
+           </property>
+           <property name="bottomMargin">
+            <number>0</number>
+           </property>
+           <item>
+            <widget class="E5ClearableComboBox" name="brokerComboBox">
+             <property name="sizePolicy">
+              <sizepolicy hsizetype="Expanding" vsizetype="Fixed">
+               <horstretch>0</horstretch>
+               <verstretch>0</verstretch>
+              </sizepolicy>
+             </property>
+             <property name="toolTip">
+              <string>Enter the host name of the broker</string>
+             </property>
+             <property name="editable">
+              <bool>true</bool>
+             </property>
+            </widget>
+           </item>
+           <item>
+            <widget class="QComboBox" name="brokerPortComboBox">
+             <property name="toolTip">
+              <string>Enter the broker port to connect to</string>
+             </property>
+             <property name="editable">
+              <bool>true</bool>
+             </property>
+             <property name="sizeAdjustPolicy">
+              <enum>QComboBox::AdjustToContents</enum>
+             </property>
+            </widget>
+           </item>
+          </layout>
+         </widget>
+        </item>
+       </layout>
+      </item>
+      <item row="0" column="2">
+       <widget class="QToolButton" name="brokerConnectionOptionsButton">
         <property name="toolTip">
-         <string>Enter the host name of the broker</string>
-        </property>
-        <property name="editable">
-         <bool>true</bool>
+         <string>Press to open a dialog to enter connection options</string>
         </property>
        </widget>
       </item>
@@ -73,26 +138,6 @@
         </property>
        </widget>
       </item>
-      <item row="0" column="1">
-       <widget class="QComboBox" name="brokerPortComboBox">
-        <property name="toolTip">
-         <string>Enter the broker port to connect to</string>
-        </property>
-        <property name="editable">
-         <bool>true</bool>
-        </property>
-        <property name="sizeAdjustPolicy">
-         <enum>QComboBox::AdjustToContents</enum>
-        </property>
-       </widget>
-      </item>
-      <item row="0" column="2">
-       <widget class="QToolButton" name="brokerConnectionOptionsButton">
-        <property name="toolTip">
-         <string>Press to open a dialog to enter connection options</string>
-        </property>
-       </widget>
-      </item>
      </layout>
     </widget>
    </item>
@@ -434,8 +479,8 @@
            <rect>
             <x>0</x>
             <y>0</y>
-            <width>344</width>
-            <height>840</height>
+            <width>339</width>
+            <height>670</height>
            </rect>
           </property>
           <layout class="QFormLayout" name="formLayout">
@@ -1169,6 +1214,8 @@
   </customwidget>
  </customwidgets>
  <tabstops>
+  <tabstop>modeButton</tabstop>
+  <tabstop>profileComboBox</tabstop>
   <tabstop>brokerComboBox</tabstop>
   <tabstop>brokerPortComboBox</tabstop>
   <tabstop>brokerConnectionOptionsButton</tabstop>
@@ -1180,9 +1227,10 @@
   <tabstop>unsubscribeTopicComboBox</tabstop>
   <tabstop>unsubscribeButton</tabstop>
   <tabstop>publishTopicComboBox</tabstop>
+  <tabstop>publishPayloadEdit</tabstop>
   <tabstop>publishQosSpinBox</tabstop>
   <tabstop>publishRetainCheckBox</tabstop>
-  <tabstop>publishPayloadEdit</tabstop>
+  <tabstop>publishClearButton</tabstop>
   <tabstop>publishButton</tabstop>
   <tabstop>messagesEdit</tabstop>
   <tabstop>messagesClearButton</tabstop>
Binary file MqttMonitor/icons/profiles.png has changed
Binary file MqttMonitor/icons/quickopen.png has changed
--- a/PluginMqttMonitor.e4p	Sat Sep 08 16:51:39 2018 +0200
+++ b/PluginMqttMonitor.e4p	Sat Sep 08 16:55:42 2018 +0200
@@ -18,6 +18,7 @@
   <Sources>
     <Source>MqttMonitor/MqttClient.py</Source>
     <Source>MqttMonitor/MqttConnectionOptionsDialog.py</Source>
+    <Source>MqttMonitor/MqttConnectionProfilesDialog.py</Source>
     <Source>MqttMonitor/MqttMonitorWidget.py</Source>
     <Source>MqttMonitor/__init__.py</Source>
     <Source>PluginMqttMonitor.py</Source>
@@ -25,6 +26,7 @@
   </Sources>
   <Forms>
     <Form>MqttMonitor/MqttConnectionOptionsDialog.ui</Form>
+    <Form>MqttMonitor/MqttConnectionProfilesDialog.ui</Form>
     <Form>MqttMonitor/MqttMonitorWidget.ui</Form>
   </Forms>
   <Translations>
--- a/PluginMqttMonitor.py	Sat Sep 08 16:51:39 2018 +0200
+++ b/PluginMqttMonitor.py	Sat Sep 08 16:55:42 2018 +0200
@@ -94,6 +94,8 @@
         
         self.__defaults = {
             "RecentBrokersWithPort": "[]",      # JSON formatted empty list
+            "BrokerProfiles": "{}",             # JSON formatted empty dict
+            # __IGNORE_WARNING_M613__
         }
         
         self.__translator = None
@@ -209,7 +211,7 @@
         @param key the key of the value to get
         @return the requested setting
         """
-        if key in ["RecentBrokersWithPort"]:
+        if key in ["RecentBrokersWithPort", "BrokerProfiles"]:
             return json.loads(Preferences.Prefs.settings.value(
                 self.PreferencesKey + "/" + key, self.__defaults[key]))
         else:
@@ -223,7 +225,7 @@
         @param key the key of the setting to be set (string)
         @param value the value to be set
         """
-        if key in ["RecentBrokersWithPort"]:
+        if key in ["RecentBrokersWithPort", "BrokerProfiles"]:
             Preferences.Prefs.settings.setValue(
                 self.PreferencesKey + "/" + key, json.dumps(value))
         else:

eric ide

mercurial