commit 7d28568e1bebaf6f71d5a78a6089a22ba7d90b6c
Author: MFori <forejt.martin97@gmail.com>
Date:   Mon May 17 17:06:49 2021 +0200

    Re: #8921 - settings dialog

diff --git a/aswi2021vochomurka/service/mqtt/mqtt_subscriber.py b/aswi2021vochomurka/service/mqtt/mqtt_subscriber.py
index be7b979..7afccac 100644
--- a/aswi2021vochomurka/service/mqtt/mqtt_subscriber.py
+++ b/aswi2021vochomurka/service/mqtt/mqtt_subscriber.py
@@ -7,6 +7,7 @@ from aswi2021vochomurka.service.subscriber import Subscriber
 
 
 class MQTTSubscriber(Subscriber):
+    client: mqtt.Client = None
 
     # The callback for when the client receives a CONNACK response from the server.
     def on_connect(self, client, userdata, flags, rc, properties=None):
@@ -40,6 +41,7 @@ class MQTTSubscriber(Subscriber):
     def start(self):
         super().start()
         client = mqtt.Client()
+        self.client = client
         client.on_connect = self.on_connect
         client.on_message = self.on_message
         client.on_disconnect = self.on_disconnect
@@ -63,3 +65,11 @@ class MQTTSubscriber(Subscriber):
             return
 
         client.loop_forever()
+
+    def stop(self):
+        super().stop()
+        if self.client is not None:
+            logging.info("Disconnecting from broker")
+            client = self.client
+            self.client = None
+            client.disconnect()
diff --git a/aswi2021vochomurka/service/subscriber.py b/aswi2021vochomurka/service/subscriber.py
index 53c4fe1..25ce50b 100644
--- a/aswi2021vochomurka/service/subscriber.py
+++ b/aswi2021vochomurka/service/subscriber.py
@@ -1,19 +1,20 @@
 import time
+from typing import Dict
 
 from apscheduler.schedulers.background import BackgroundScheduler
+from apscheduler.schedulers.base import STATE_STOPPED
 
 from aswi2021vochomurka.model.Message import Message
 from aswi2021vochomurka.service.file_manager import FileManager
 from aswi2021vochomurka.service.subscriber_callback import SubscriberCallback
 from aswi2021vochomurka.service.subscriber_params import SubscriberParams
-from typing import Dict
 
 
 class Subscriber:
     callback: SubscriberCallback
     params: SubscriberParams
 
-    scheduler = BackgroundScheduler()
+    scheduler: BackgroundScheduler
     files: Dict[str, FileManager] = {}
 
     def __init__(self, callback: SubscriberCallback, params: SubscriberParams):
@@ -22,17 +23,19 @@ class Subscriber:
 
     def start(self):
         # start scheduler to check closed topics
+        self.scheduler = BackgroundScheduler()
         self.scheduler.add_job(self.check_closed_topics, 'interval', seconds=self.params.closeLimit)
         self.scheduler.start()
 
     def stop(self):
-        self.scheduler.shutdown()
+        if self.scheduler.state != STATE_STOPPED:
+            self.scheduler.shutdown()
         self.close_files()
 
     def close_files(self):
         for topic in self.files:
             self.files.get(topic).close()
-        self.files = {}
+        self.files.clear()
 
     def check_closed_topics(self):
         t = time.time()
diff --git a/aswi2021vochomurka/view/main_view.py b/aswi2021vochomurka/view/main_view.py
index cab9175..eef4738 100644
--- a/aswi2021vochomurka/view/main_view.py
+++ b/aswi2021vochomurka/view/main_view.py
@@ -2,6 +2,7 @@ import logging
 import math
 
 import matplotlib.pyplot as plt
+from PyQt5 import QtGui
 from PyQt5.QtCore import QSize, QThread, QObject, pyqtSignal
 from PyQt5.QtWidgets import QMainWindow, QWidget, QMenuBar, QAction
 from PyQt5.QtWidgets import QVBoxLayout
@@ -23,7 +24,7 @@ class Worker(QObject, SubscriberCallback):
     connected = pyqtSignal()
     disconnected = pyqtSignal()
     error = pyqtSignal(Exception)
-    newMessage = pyqtSignal(str)
+    newMessage = pyqtSignal(Message)
     subscriber: Subscriber = None
 
     params = SubscriberParams(
@@ -37,6 +38,9 @@ class Worker(QObject, SubscriberCallback):
         self.subscriber = MQTTSubscriber(self, self.params)
         self.subscriber.start()
 
+    def stop(self):
+        self.subscriber.stop()
+
     def onConnected(self):
         self.connected.emit()
 
@@ -47,8 +51,8 @@ class Worker(QObject, SubscriberCallback):
         self.error.emit()
 
     def onMessage(self, message: Message):
-        self.newMessage.emit(message.topic)
-        self.window.plot(message)
+        self.newMessage.emit(message)
+        pass
 
     def onCloseTopic(self, topic: str):
         pass
@@ -128,9 +132,13 @@ class MainView(QMainWindow):
 
         self.canvas.draw()
 
+    def closeEvent(self, a0: QtGui.QCloseEvent) -> None:
+        self.worker.stop()
+
     def settings(self):
         dialog = SettingsDialog()
-        dialog.exec_()
+        if dialog.exec_():
+            self.reconnect()
 
     def help(self):
         dialog = HelpDialog()
@@ -141,13 +149,21 @@ class MainView(QMainWindow):
         dialog = AboutDialog()
         dialog.exec_()
 
+    def disconnect(self):
+        self.worker.stop()
+        self.workerThread.quit()
+        self.workerThread.wait()
+
+    def reconnect(self):
+        self.disconnect()
+        self.workerThread.start()
+
     def initSubscriber(self):
         self.workerThread = QThread()
         self.worker = Worker()
         self.worker.moveToThread(self.workerThread)
         self.workerThread.started.connect(self.worker.start)
-        # self.worker.newMessage.connect(
-        #    lambda message: self.b.insertPlainText(message + "\n")
-        # )
-        self.worker.window = self
+        self.worker.newMessage.connect(
+            lambda message: self.plot(message)
+        )
         self.workerThread.start()
diff --git a/aswi2021vochomurka/view/settings.py b/aswi2021vochomurka/view/settings.py
index cc09372..742cb44 100644
--- a/aswi2021vochomurka/view/settings.py
+++ b/aswi2021vochomurka/view/settings.py
@@ -1,8 +1,106 @@
-from PyQt5.QtWidgets import QDialog
+from PyQt5 import QtCore
+from PyQt5.QtCore import QSettings, QSize
+from PyQt5.QtGui import QStandardItemModel, QStandardItem
+from PyQt5.QtWidgets import QDialog, QVBoxLayout, QDialogButtonBox, QGroupBox, QFormLayout, QLabel, QLineEdit, QSpinBox, \
+    QCheckBox, QListView, QWidget, QPushButton, QHBoxLayout, QListWidget, QListWidgetItem, QTableWidget
 
 
 class SettingsDialog(QDialog):
+    topics = ['/home/1', '/home/2']
+
     def __init__(self):
         super(SettingsDialog, self).__init__()
-
+        self.settings = QSettings("Vochomurka", "MQTTClient")
         self.setWindowTitle("Settings")
+        self.setMinimumSize(QSize(600, 500))
+
+        connectionGroupBox = QGroupBox("Connection")
+        connectionLayout = QFormLayout()
+        self.hostInput = QLineEdit(self.settings.value("connection_host", "localhost", str))
+        connectionLayout.addRow(QLabel("Host:"), self.hostInput)
+        self.portInput = QSpinBox()
+        self.portInput.setMaximum(65535)
+        self.portInput.setValue(self.settings.value("connection_port", 1883, int))
+        connectionLayout.addRow(QLabel("Port:"), self.portInput)
+        self.keepaliveInput = QSpinBox()
+        self.keepaliveInput.setMaximum(1000)
+        self.keepaliveInput.setValue(self.settings.value("connection_keepalive", 60, int))
+        connectionLayout.addRow(QLabel("Keepalive(s):"), self.keepaliveInput)
+        self.anonymousInput = QCheckBox()
+        self.anonymousInput.setChecked(self.settings.value("connection_anonymous", True, bool))
+        self.anonymousInput.stateChanged.connect(self.anonymousChanged)
+        connectionLayout.addRow(QLabel("Anonymous:"), self.anonymousInput)
+        self.usernameInput = QLineEdit(self.settings.value("connection_username", "", str))
+        connectionLayout.addRow(QLabel("Username:"), self.usernameInput)
+        self.passwordInput = QLineEdit(self.settings.value("connection_password", "", str))
+        connectionLayout.addRow(QLabel("Password:"), self.passwordInput)
+        self.anonymousChanged()
+        connectionGroupBox.setLayout(connectionLayout)
+
+        topicsGroupBox = QGroupBox("Topics")
+        topicsLayout = QFormLayout()
+
+        self.topicsListWidget = QListWidget()
+        for topic in self.topics:
+            item = QListWidgetItem()
+            item.setText(topic)
+            item.setFlags(item.flags() | QtCore.Qt.ItemIsEditable)
+            self.topicsListWidget.addItem(item)
+
+        topicsLayout.addRow(self.topicsListWidget)
+        add = QPushButton("Add")
+        add.setFixedWidth(60)
+        add.clicked.connect(self.addTopic)
+        remove = QPushButton("Remove")
+        remove.setFixedWidth(60)
+        remove.clicked.connect(self.removeTopic)
+        topicsLayout.addRow(add, remove)
+
+        self.timeoutInput = QSpinBox()
+        self.timeoutInput.setMaximum(1000)
+        self.timeoutInput.setToolTip("Unsubscribe topic and close file when there is not new message after this "
+                                     "timeout (in seconds) expires")
+        timeoutLabel = QLabel("Topic timeout(s):")
+        timeoutLabel.setToolTip("Unsubscribe topic and close file when there is not new message after this "
+                                "timeout (in seconds) expires")
+        self.timeoutInput.setValue(self.settings.value("topic_timeout", 60, int))
+        topicsLayout.addRow(timeoutLabel, self.timeoutInput)
+
+        topicsGroupBox.setLayout(topicsLayout)
+
+        buttonBox = QDialogButtonBox()
+        buttonBox.addButton("Save and Reconnect", QDialogButtonBox.AcceptRole)
+        buttonBox.addButton("Cancel", QDialogButtonBox.RejectRole)
+        buttonBox.accepted.connect(self.accept)
+        buttonBox.rejected.connect(self.reject)
+
+        mainLayout = QVBoxLayout()
+        mainLayout.addWidget(connectionGroupBox)
+        mainLayout.addWidget(topicsGroupBox)
+        mainLayout.addWidget(buttonBox)
+        self.setLayout(mainLayout)
+
+    def addTopic(self):
+        item = QListWidgetItem()
+        item.setText("/topic")
+        item.setFlags(item.flags() | QtCore.Qt.ItemIsEditable)
+        self.topicsListWidget.addItem(item)
+
+    def removeTopic(self):
+        for item in self.topicsListWidget.selectedItems():
+            self.topicsListWidget.takeItem(self.topicsListWidget.row(item))
+
+    def anonymousChanged(self):
+        self.usernameInput.setEnabled(not self.anonymousInput.isChecked())
+        self.passwordInput.setEnabled(not self.anonymousInput.isChecked())
+
+    def accept(self) -> None:
+        super().accept()
+        self.topics = self.topicsListWidget.item(0).text()
+        print(self.topics)
+        self.settings.setValue("connection_host", self.hostInput.text())
+        self.settings.setValue("connection_port", self.portInput.value())
+        self.settings.setValue("connection_keepalive", self.keepaliveInput.value())
+        self.settings.setValue("connection_anonymous", self.anonymousInput.isChecked())
+        self.settings.setValue("connection_username", self.usernameInput.text())
+        self.settings.setValue("connection_password", self.passwordInput.text())
