# Online Python compiler (interpreter) to run Python online.
# Write Python 3 code in this online editor and run it.
# WeatherAppClient.py code: -
# This Python file uses the following encoding: utf-8
import sys
from PySide6.QtCore import Slot, QThreadPool, QSize, QResource, Qt, QMargins
from PySide6.QtGui import QIcon, QPainter, QFont, QFontDatabase
from PySide6.QtCharts import QChart, QChartView, QLineSeries, QValueAxis
from network import UDPReceiver
from ui_weather_app_ui import Ui_WeatherAppUi
from ui_parameter import Ui_parameter
from ui_settings_form import Ui_SettingsForm
from ui_parameter_settings import Ui_ParameterSettings
from SideMenu import SideMenu
from config import Config
from app_signals import AppSignals
from database import Database
import datetime


# Important:
# I need to run the following command to generate the ui_form.py file
#     pyside6-uic form.ui -o ui_form.py, or
#     pyside2-uic form.ui -o ui_form.py
# For compiling resources:
#     pyside6-rcc resources.qrc -o resources.py

class WeatherAppUI(Ui_WeatherAppUi):
    def __init__(self, mainWindow, config):
        super().__init__()
        self.mainWindow = mainWindow
        self.config = config
        self.parameters = {}
        # Create all required widgets
        self.setupUi(mainWindow)
        # Set up the pages
        self.pages = {
            "Home": {
                "icon": ":/icons/icons/home.svg",
                "slot": self.showPage,
                "page": self.home,

            },
            "Settings": {
                "icon": ":/icons/icons/settings-5-line.svg",
                "slot": self.showPage,
                "page": SettingsForm(self.config)
            },
        }
        # Create the side menu
        self.sideMenu = SideMenu(self.pages, 400)
        # Add the parameters to "Home" page
        for paramName, paramConfig in self.config["parameters"].items():
            self.parameters[paramName] = Parameter(paramConfig)
            self.parameterListLayout.addWidget(self.parameters[paramName])

        # Create settings page
        self.settingsForm = SettingsForm(self.config)
        self.widgetStack = QStackedLayout(self.bodyContentLayout)
        self.widgetStack.setStackingMode(QStackedLayout.StackAll)
        # Add the side menu to the top of the stack
        self.widgetStack.addWidget(self.sideMenu)
        # Add each page below it
        for pageInfo in self.pages.values():
            self.widgetStack.addWidget(pageInfo["page"])
        # Close the menu which was open by default
        self.sideMenu.toggle()
        self.setupSignalsAndSlots()

    def addParameter(self, parameter):
        self.parameterListLayout.addWidget(parameter)

    def showPage(self, pageName):
        self.widgetStack.setCurrentWidget(self.pages[pageName]["page"])
        self.widgetStack.setCurrentWidget(self.sideMenu)
        self.sideMenu.toggle()

    def setupSignalsAndSlots(self):
        self.menuButton.clicked.connect(self.sideMenu.toggle)

class Graph(QChart):
    def __init__(self, xWidth, yMin, yMax, name):
        super().__init__()
        self.name = name
        self.series = QLineSeries()
        self.xWidth = xWidth
        self.xMin, self.xMax = 0, xWidth - 1
        self.xSeries = QLineSeries()

        # Set up the axes and their appearence
        self.xAxis = QValueAxis()
        self.yAxis = QValueAxis()
        self.xAxis.setRange(self.xMin, self.xMax)
        self.yMin, self.yMax = yMin, yMax
        self.yAxis.setRange(yMin, yMax)
        self.addAxis(self.xAxis, Qt.AlignBottom)
        self.addAxis(self.yAxis, Qt.AlignLeft)
        self.addSeries(self.series)
        self.series.attachAxis(self.xAxis)
        self.series.attachAxis(self.yAxis)
        self.xAxis.setTickCount(6)
        self.yAxis.setTickCount(3)

        robotoRegularId = QFontDatabase.addApplicationFont(":/fonts/fonts/Roboto-Black.ttf")
        robotoRegularFamily = QFontDatabase.applicationFontFamilies(robotoRegularId)[0]
        RobotoRegular = QFont(robotoRegularFamily)

        self.yAxis.setLabelsFont(RobotoRegular)
        self.xAxis.setLabelsFont(RobotoRegular)

        self.legend().hide()
        self.setMargins(QMargins(0, 0, 0, 0))
        self.chartView = QChartView(self)
        self.chartView.setRenderHint(QPainter.Antialiasing)

    def setValue(self, x, y):
        if x >= 0: # x axis negative overflow is ignored
            # Handle x axis positive overflow
            if x > self.xMax:
                self.xMin += (x - self.xMax)
                self.xMax = x
                self.xAxis.setRange(self.xMin, self.xMax)
            # Handle y axis overflow
            if y < self.yMin:
                self.yMin = y
            if y > self.yMax:
                self.yMax = y + ((y-self.yMin) * 0.1) # Leave some margin up top
            self.yAxis.setRange(self.yMin, self.yMax)
            self.series.append(x, y)
            newSeries = QLineSeries()
            for point in self.series.points():
                newSeries.append(point)
            self.removeAllSeries()
            self.addSeries(newSeries)
            self.series = newSeries
            self.series.attachAxis(self.xAxis)
            self.series.attachAxis(self.yAxis)

    def view(self):
        return self.chartView

class Parameter(Ui_parameter, QWidget):
    def __init__(self, paramConfig):
        super().__init__()
        self.setupUi(self)
        self.paramConfig = paramConfig
        self.loadConfig()
        self.setupGraph()
        AppSignals.dataReceived.connect(self.displayData)
        AppSignals.reloadApp.connect(self.loadConfig)

    @Slot()
    def loadConfig(self):
        paramConfig = self.paramConfig
        self.icon = QIcon(paramConfig['icon'])
        self.paramName = paramConfig["display_name"]
        self.paramUnit = paramConfig["unit"]
        self.paramNameButton.setIcon(self.icon)
        self.paramNameButton.setIconSize(QSize(32, 32))
        self.paramNameButton.setText(self.paramName)
        self.paramUnitLabel.setText(self.paramUnit)
        self.paramConfig = paramConfig

    def setupGraph(self):
        self.graph = Graph(
                            50, \
                            float(self.paramConfig.min_limit), \
                            # float(self.paramConfig.max_limit), \
                            1.0, # Because the graph will self adjust
                            self.paramName
                            )
        self.paramGraphLayout.addWidget(self.graph.view())

    @Slot()
    def displayData(self, dataDict):
        if self.paramConfig.file_key in dataDict:
            value = float(dataDict[self.paramConfig.file_key])
            # Display the value in LCD Display
            self.paramValueDisplay.display(value)
            # Display the value in the Graph
            self.graph.setValue(x=dataDict["recordNum"], y = value)
            

class ParameterSettings(Ui_ParameterSettings, QWidget):

    def __init__(self, config):
        self.config = config
        super(ParameterSettings, self).__init__()
        self.iconPrefix = ":/icons/icons"
        self.setupUi()

    def setupUi(self):
        super().setupUi(self)
        table = self.parameterSettingsTable
        paramConfig = self.config.parameters
        # Add the column names
        firstParam, firstParamSettings = list(paramConfig.items())[0]
        colHeaders = [ key.replace("_", " ").title() for key in firstParamSettings.keys()] + ["Remove"]
        table.setColumnCount(len(colHeaders))
        table.setHorizontalHeaderLabels(colHeaders)
        header = table.horizontalHeader()
        header.setSectionResizeMode(QHeaderView.ResizeToContents)
        header.setSectionResizeMode(7, QHeaderView.Stretch)
        # The dict which holds the widget items containing the settings values, populated by calling self.load
        self.settings = {}
        self.loadConfig()

    def loadConfig(self):

        paramConfig = self.config.parameters
        table = self.parameterSettingsTable

        # Clear the table
        table.clearContents()
        table.setRowCount(0)
        self.settings = {}


        # Create a list of available icons in resource file
        iconList = []
        iconResource = QResource(self.iconPrefix)
        for icon in iconResource.children():
            iconList.append(icon)

        for paramName, paramSettings in paramConfig.items():
            self.settings[paramName] = {}
            self.settings[paramName]['display_name'] = QTableWidgetItem(str(paramSettings.display_name))

            # Create a combobox for choosing icons
            iconChooser = QComboBox()
            for icon in iconList:
                iconImage = QIcon(self.iconPrefix + "/" + icon)
                iconChooser.addItem(iconImage, icon[:-4])
            # Set the chosen icon in the dropdown as that given in config
            iconChooser.setCurrentIndex(iconChooser.findText(paramSettings.icon[len(self.iconPrefix)+1:-4]))
            self.settings[paramName]['icon'] = iconChooser
            self.settings[paramName]['unit'] = QTableWidgetItem(str(paramSettings.unit))
            self.settings[paramName]['unit'].setTextAlignment(Qt.AlignCenter)
            self.settings[paramName]['min_limit'] = QTableWidgetItem(str(paramSettings.min_limit))
            self.settings[paramName]['min_limit'].setTextAlignment(Qt.AlignRight)
            self.settings[paramName]['max_limit'] = QTableWidgetItem(str(paramSettings.max_limit))
            self.settings[paramName]['max_limit'].setTextAlignment(Qt.AlignRight)
            self.settings[paramName]['threshold_limit'] = QTableWidgetItem(str(paramSettings.threshold_limit))
            self.settings[paramName]['threshold_limit'].setTextAlignment(Qt.AlignRight)
            self.settings[paramName]['file_key'] = QTableWidgetItem(str(paramSettings.file_key))
            self.settings[paramName]['file_key'].setTextAlignment(Qt.AlignCenter)
            self.settings[paramName]['conversion_function'] = QTableWidgetItem(paramSettings.conversion_function)
            self.settings[paramName]['conversion_function'].setTextAlignment(Qt.AlignCenter)
        row = col = 0
        # Add the widgets to the table
        table.setRowCount(len(self.settings))
        for paramName, paramSettings in self.settings.items():
            col = 0
            for widget in paramSettings.values():
                if type(widget) is QTableWidgetItem:
                    table.setItem(row, col, widget)
                else:
                    table.setCellWidget(row, col, widget)
                col += 1
            row += 1

    def updateConfig(self):
        if self.settings:
            for paramName, paramSettings in self.settings.items():
                for settingName, settingWidget in paramSettings.items():
                    if type(settingWidget) is QTableWidgetItem:
                        value = settingWidget.text()
                    elif type(settingWidget) is QComboBox: # Icon path
                        value = self.iconPrefix + "/" + settingWidget.currentText() + ".svg"
                    self.config.parameters[paramName][settingName] = value


class SettingsForm(Ui_SettingsForm, QWidget):

    def __init__(self, config):
        super(SettingsForm, self).__init__()
        self.config = config
        self.setupUi()
        self.loadConfig()

    def setupUi(self):
        super().setupUi(self)
        self.saveButton.clicked.connect(self.saveSettings)
        self.revertButton.clicked.connect(self.revertSettings)

    def loadConfig(self):
        self.appTitleLineEdit.setText(str(self.config.app_title))
        self.uDPIPLineEdit.setText(str(self.config.network.udp_ip))
        self.uDPPortLineEdit.setText(str(self.config.network.udp_port))
        self.bufferSizeLineEdit.setText(str(self.config.network.buffer_size))

    def updateConfig(self):
        self.config.app_title = self.appTitleLineEdit.text()
        self.config.network.udp_ip = self.uDPIPLineEdit.text()
        self.config.network.udp_port = self.uDPPortLineEdit.text()
        self.config.network.buffer_size = self.bufferSizeLineEdit.text()

    @Slot()
    def saveSettings(self):
        saveDialog = QMessageBox(self)
        saveDialog.setWindowTitle("Save the settings")
        saveDialog.setText("Are you sure you want to save the settings?")
        saveDialog.setStandardButtons(QMessageBox.Yes | QMessageBox.No)
        saveDialog.setIcon(QMessageBox.Question)
        button = saveDialog.exec()
        if button == QMessageBox.Yes:
            self.updateConfig() # Update master settings config
            self.config.save() # Persist the config in file
            AppSignals.reloadApp.emit() # Signal the reload of application

    @Slot()
    def revertSettings(self):
        saveDialog = QMessageBox(self)
        saveDialog.setWindowTitle("Revert the settings")
        saveDialog.setText("Are you sure you want to revert to previous settings? All changes made by you will be lost!")
        saveDialog.setStandardButtons(QMessageBox.Yes | QMessageBox.No)
        saveDialog.setIcon(QMessageBox.Question)
        button = saveDialog.exec()
        if button == QMessageBox.Yes:
            self.loadConfig() # Load existing config values

class WeatherAppClient(QMainWindow):

    def __init__(self, parent=None):
        self.config = Config("config.json")
        self.db = Database("weather_data_dump.db", list(self.config.parameters.keys()))
        super().__init__(parent)
        self.loadConfig()
        AppSignals.reloadApp.connect(self.loadConfig)
        AppSignals.dataReceived.connect(self.saveToDB)

    def saveToDB(self, data):
        timestamp = str(datetime.datetime.today())
        dataToBeSaved = {
            "TIMESTAMP": timestamp,
            "RECORD_TYPE": data["recordType"],
        }
        for key, value in data.items():
            for paramName, paramProps in self.config.parameters.items():
                if paramProps.file_key == key:
                    dataToBeSaved[paramName] = value
                    break
        print("About to save: ", dataToBeSaved)
        self.db.addRow(dataToBeSaved)

    @Slot()
    def loadConfig(self):
        # Remove old thread pool if created
        if hasattr(self, "threadPool"):
            self.threadPool.clear()
        # Delete old UDP Receiver thread
        if hasattr(self, "udpReceiver"):
            self.udpReceiver.endReception()
            del self.udpReceiver
        # Create a UDP receiver program
        self.udpReceiver = UDPReceiver(self.config.network.udp_ip, \
                                       int(self.config.network.udp_port), \
                                       int(self.config.network.buffer_size))
        self.threadPool = QThreadPool()
        self.threadPool.start(self.udpReceiver)
        # Create the main UI if not created already
        if not hasattr(self, "ui"):
            self.ui = WeatherAppUI(self, self.config)
        self.ui.appTitle.setText(self.config.app_title)

    @Slot()
    def closeEvent(self, event):
        self.udpReceiver.endReception()
        del self.udpReceiver
        self.threadPool.clear()
        print("Closed app")

if __name__ == "__main__":
    app = QApplication(sys.argv)
    widget = WeatherAppClient()
    widget.show()
    ret = app.exec()
    sys.exit(ret)
 


# WeatherDataSimulator.py code: -
# This Python file uses the following encoding: utf-8
import sys, time, json, re, os

from PySide6.QtWidgets import QApplication, QMainWindow, QFileDialog, QWidget, QMessageBox
from PySide6.QtGui import QIcon, QCloseEvent
from PySide6.QtCore import QObject, Signal, Slot, QRunnable, QThreadPool
from network import UDPSender

# Important:
# I need to run the following command to generate the ui_form.py file
#     pyside6-uic form.ui -o ui_form.py, or
#     pyside2-uic form.ui -o ui_form.py
from ui_weather_data_simulator import Ui_WeatherDataSimulator

class Modes:
    PLAYING = "Play"
    PAUSED = "Pause"
    STEP = "Step"

# Create custom signals
class Signals(QObject):
    TransmissionOver = Signal(int)
    Transmit = Signal(int, int) # Record number and Total Records

signals = Signals()

# Create a Sender Thread class
class SenderThread(QRunnable):

    def __init__(self, tInfo):
        super(SenderThread, self).__init__()
        self.tInfo = tInfo

    @Slot()
    def run(self):
        while self.tInfo["transmit"]:
            if self.tInfo["currentMode"] == Modes.PLAYING and \
            "fileLines" in self.tInfo:
                if self.tInfo["curRecordNum"] >= self.tInfo["numRecords"]:
                    self.tInfo["currentMode"] = Modes.PAUSED # Prevent this loop's if block from being processed until re-play
                    signals.TransmissionOver.emit(self.tInfo["curRecordNum"]) # Signal that the last record has been sent
                    continue
                # Take the current line and send it
                curRecord = self.tInfo["fileLines"][self.tInfo["curRecordNum"]].strip()
                self.tInfo["sender"].send(curRecord, self.tInfo["IP"], int(self.tInfo["Port"]))
                print("Sent record:", curRecord)
                # Notify whoever is concerned that current record has been sent
                signals.Transmit.emit(self.tInfo["curRecordNum"]+1, self.tInfo["numRecords"])
                print("Sleeping for {} msec".format(round(1000 / self.tInfo["freq"], 2)))
                time.sleep(1 / self.tInfo["freq"])
                self.tInfo["curRecordNum"] += 1


class WeatherDataSimulator(Ui_WeatherDataSimulator, QWidget):

    def __init__(self, MainWindow, config):
        super().__init__()
        self.simFileTypes = "Text file (*.txt, *.TXT)"
        self.setupUi(MainWindow)
        self.config = config
        self.loadConfig()
        self.setSignalsAndSlots()
        self.loadSender()

    def setupUi(self, MainWindow):
        super().setupUi(MainWindow)

        # Set up the rest of the UI
        self.playIcon = QIcon(":/icons/res/icons/play.svg")
        self.pauseIcon = QIcon(":/icons/res/icons/pause.svg")

    def loadConfig(self):
        self.transmissionInfo = {}
        self.transmissionInfo["transmit"] = True # Flag to Sender thread
        self.transmissionInfo["sender"] = UDPSender()
        self.transmissionInfo["currentMode"] = Modes.PAUSED
        self.setTransmissionFrequency(int(self.config["freq"]))
        self.transmissionIPLineEdit.setText(self.config["udp_ip"])
        self.transmissionPortLineEdit.setText(self.config["udp_port"])
        self.speedSelector.setValue(int(self.config["freq"]))
        # Read the file content and store in transmissionInfo
        if not self.setFileContent(self.config["file_last_opened"]):
            self.fileChooserButton.setText("Choose file...")

    def setSignalsAndSlots(self):
        self.playPauseButton.clicked.connect(self.playPause)
        self.fileChooserButton.clicked.connect(self.chooseFile)
        self.speedSelector.valueChanged.connect(lambda freq: self.setTransmissionFrequency(freq))
        self.speedSelector.valueChanged.connect(lambda freq: self.updateConfig(freq=freq))
        self.transmissionIPLineEdit.textChanged.connect(lambda ip: self.updateConfig(udp_ip=ip))
        self.transmissionPortLineEdit.textChanged.connect(lambda ip: self.updateConfig(udp_port=ip))
        signals.Transmit.connect(self.moveSlider)
        signals.Transmit.connect(self.setTransmittedCount)
        signals.TransmissionOver.connect(self.showTransmissionOverMessage)
        self.progressSlider.sliderMoved.connect(self.setNextRecordNumToBeSent)
        self.progressSlider.sliderMoved.connect(self.setTransmittedCount)

    def loadSender(self):
        # Create a sender thread and start it
        self.senderThread = SenderThread(self.transmissionInfo)
        self.threadpool = QThreadPool()
        self.threadpool.start(self.senderThread)

    def updateConfig(self, **configValue):
        for key, value in configValue.items():
            self.config[key] = value

    @Slot()
    def showTransmissionOverMessage (self, numRecords):
        self.pause()
        transmissionEndDlg = QMessageBox(self)
        transmissionEndDlg.setWindowTitle("Transmission Ended")
        transmissionEndDlg.setText(f"All {numRecords} records have been sent. You may rewind using the slider to send again.")
        transmissionEndDlg.setStandardButtons(QMessageBox.Ok)
        transmissionEndDlg.exec()

    @Slot()
    def moveSlider(self, value, _):
        self.progressSlider.setValue(value)

    @Slot()
    def setNextRecordNumToBeSent(self, recordNum):
        """Use the slider to set the next record number to be sent"""
        self.transmissionInfo["curRecordNum"] = recordNum

    @Slot()
    def setTransmittedCount(self, currentRecord):
        self.sentCount.setText(f"Sent: {currentRecord}/{self.transmissionInfo['numRecords']} {'records' if currentRecord != 1 else 'record'}")

    def setTransmissionFrequency(self, freq):
        self.transmissionInfo["freq"] = freq
        self.currentFreqLabel.setText(f"Freq: {freq} {'records' if int(freq) != 1 else 'record'}/s")

    def validateConfigValues(self):
        ipIsValid = re.compile("^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$").match(self.config["udp_ip"])
        portIsValid = re.compile("^\d{4}").match(self.config["udp_port"])
        filePathIsValid = os.path.isfile(self.config["file_last_opened"])
        freqIsValid = (1 <= int(self.config["freq"]) <= 100)
        return (ipIsValid and portIsValid and filePathIsValid and freqIsValid)

    def play(self):
        if not self.validateConfigValues():
            saveDialog = QMessageBox(self)
            saveDialog.setWindowTitle("Error in settings")
            saveDialog.setText("There are errors in your configuration. Please correct!")
            saveDialog.setStandardButtons(QMessageBox.Ok)
            saveDialog.setIcon(QMessageBox.Warning)
            saveDialog.exec()
            return
        self.transmissionInfo["currentMode"] = Modes.PLAYING
        self.transmissionInfo["IP"] = self.transmissionIPLineEdit.text()
        self.transmissionInfo["Port"] = self.transmissionPortLineEdit.text()
        self.playPauseButton.setIcon(self.pauseIcon)
        self.transmissionIPLineEdit.setEnabled(False)
        self.transmissionPortLineEdit.setEnabled(False)
        self.fileChooserButton.setEnabled(False)
        self.progressSlider.setEnabled(False)

    def pause(self):
        self.transmissionInfo["currentMode"] = Modes.PAUSED
        self.playPauseButton.setIcon(self.playIcon)
        self.transmissionIPLineEdit.setEnabled(True)
        self.transmissionPortLineEdit.setEnabled(True)
        self.fileChooserButton.setEnabled(True)
        self.progressSlider.setEnabled(True)

    @Slot()
    def playPause(self):
        if self.transmissionInfo["currentMode"] == Modes.PAUSED:
            self.play()

        elif self.transmissionInfo["currentMode"] == Modes.PLAYING:
            self.pause()

    @Slot()
    def chooseFile(self):
        file = QFileDialog.getOpenFileName(self,
                        "Open Simulation File",
                        "", self.simFileTypes)
        self.setFileContent(file[0])

    def setFileContent(self, filePath):
        """This function does n things:
            1. Set the file chooser button's text to the file path
            2. Read the file content and store it in memory.
            3. Calculate the total number of records, a.k.a lines in the file
            4. Set current record number to 1 (start of transmission)
            5. Set the minimum and maximum value of the progress slider."""
        if filePath and filePath.lower().endswith(".txt") and os.path.exists(filePath) and os.path.isfile(filePath):
            self.filePath = filePath
            self.fileChooserButton.setText(filePath)
            self.updateConfig(file_last_opened = filePath)
            with open(filePath) as simFile:
                self.transmissionInfo["fileLines"] = simFile.readlines()
                self.transmissionInfo["numRecords"] = len(self.transmissionInfo["fileLines"])
                self.transmissionInfo["curRecordNum"] = 0
                self.progressSlider.setMinimum(1)
                self.progressSlider.setMaximum(self.transmissionInfo["numRecords"])
                self.sentCount.setText(f"Sent: 0/{self.transmissionInfo['numRecords']} records")
            return True
        else:
            return False

    def stopTransmission(self):
        self.transmissionInfo['transmit'] = False

class MainWindow(QMainWindow):
    def __init__(self, parent=None):
        super().__init__(parent)
        with open("config.json") as configFile:
            self.config = json.loads(configFile.read())
        self.ui = WeatherDataSimulator(self, self.config)
        self.setFixedSize(800, 438)

    def closeEvent(self, event: QCloseEvent) -> None:
        # Store the config in config.json
        with open("config.json", "w") as configFile:
            configFile.write(json.dumps(self.config, indent = 4))
            self.ui.stopTransmission()
        print("Bye!")
if __name__ == "__main__":
    app = QApplication(sys.argv)
    # apply_stylesheet(app, theme='light_red.xml')
    widget = MainWindow()
    widget.show()

#Sidemenu.py code: -
# This Python file uses the following encoding: utf-8
from PySide6.QtCore import QPropertyAnimation, QEasingCurve, QSize
from PySide6.QtGui import QIcon, QFont
from PySide6.QtWidgets import QVBoxLayout, QPushButton, QSpacerItem, QSizePolicy, QWidget, QFrame
import functools

class SideMenu(QFrame):
    def __init__(self, menuItems, maxWidth):
        super(SideMenu, self).__init__()
        self.maxWidth = maxWidth
        self.isOpen = True # Starts as open despite me trying to set its width to 0
        self._setupUI(menuItems)

    def _setupUI(self, buttonsAndSlots):
        self.setAutoFillBackground(True)
        self.setMaximumWidth(self.maxWidth)
        self.menuLayout = QVBoxLayout(self)
        for buttonName, buttonProperties in buttonsAndSlots.items():
            menuButton = QPushButton(self)
            menuButton.setIcon(QIcon(buttonProperties['icon']))
            menuButton.clicked.connect(functools.partial(buttonProperties['slot'], buttonName))
            menuButton.setText(buttonName)
            font1 = QFont()
            font1.setPointSize(16)
            menuButton.setFont(font1)
            menuButton.setIconSize(QSize(32, 32))
            menuButton.setMinimumHeight(50)
            self.menuLayout.addWidget(menuButton)
        menuSpacer = QSpacerItem(20, 40, QSizePolicy.Minimum, QSizePolicy.Expanding)
        self.menuLayout.addItem(menuSpacer)

        # Create the animation for opening and closing
        self.animation = QPropertyAnimation(self, b"maximumWidth")
        self.animation.setEasingCurve(QEasingCurve.InOutCubic)
        # Create open and closed states for feeding the animation
        self.openState = self.maxWidth
        self.closedState = 0
        self.resize(self.maxWidth, self.height())
        self.animation.setDuration(200)

    def toggle(self):
        if not self.isOpen:
            self.animation.setStartValue(self.closedState)
            self.animation.setEndValue(self.openState)
            self.animation.start()
            self.isOpen = True
        else:
            self.animation.setStartValue(self.openState)
            self.animation.setEndValue(self.closedState)
            self.animation.start()
            self.isOpen = False


















# Database.py code for client: -
import sqlite3

class Database:

    def __init__(self, dbName, parameterNames):
        self.dbName = dbName
        if not dbName.endswith(".db"):
            raise Exception("SQLite Database name must end with .db!")
        self.dbCon = sqlite3.connect(dbName)
        self.cursor = self.dbCon.cursor()
        self.tableName = "WeatherData"
        self.createTable(parameterNames)

    def createTable(self, parameterNames):
        res = self.cursor.execute("SELECT name FROM sqlite_master")
        tableNames = res.fetchall()

        if not tableNames: # Table doesn't exist. Create
            query = "CREATE TABLE WeatherData(ID INTEGER PRIMARY KEY, TIMESTAMP TEXT NOT NULL, RECORD_TYPE TEXT NOT NULL, "
            for paramName in parameterNames:
                query += (paramName + " REAL, ")
            query = query[:-2] + ")" # Remove the last comma and close the paranthesis
            self.cursor.execute(query)

        elif len(tableNames) == 1 and tableNames[0][0] == self.tableName:
            # Table already exists. Fetch the existing columns
            existingColumns = [row[1] for row in self.cursor.execute("PRAGMA table_info(WeatherData)").fetchall() if (row[1] != "ID" and row[1] != "TIMESTAMP")]

            # Find the symmetric difference between both
            missingCols = list(set(parameterNames).difference(set(existingColumns)))

            # Add the missing columns to the existing table
            for colName in missingCols:
                self.cursor.execute("ALTER TABLE {} ADD {} REAL".format(self.tableName, colName))
                self.dbCon.commit()

    def addRow(self, paramsAndValues):
        """paramsAndValues: a dictionary where keys are parameter names and
        values represent their corresponding value. """

        query = "INSERT INTO " + self.tableName + "(" + \
                ",".join(str(paramName) for paramName in paramsAndValues.keys()) + ") VALUES (" + \
                ",".join("?" * len(paramsAndValues)) + ")"
        self.cursor.execute(query, tuple(paramsAndValues.values()))
        self.dbCon.commit()

    def describe(self):
        print("Table:", self.tableName)
        print("Columns: ", [row[1] for row in self.cursor.execute("PRAGMA table_info(WeatherData)").fetchall()])

    def show(self):
        res = self.cursor.execute("SELECT * FROM " + self.tableName)
        for row in res.fetchall():
            print(row)

    def __del__(self):
        self.dbCon.commit()
        self.dbCon.close()







 

Python Online Compiler

Write, Run & Share Python code online using OneCompiler's Python online compiler for free. It's one of the robust, feature-rich online compilers for python language, supporting both the versions which are Python 3 and Python 2.7. Getting started with the OneCompiler's Python editor is easy and fast. The editor shows sample boilerplate code when you choose language as Python or Python2 and start coding.

Taking inputs (stdin)

OneCompiler's python online editor supports stdin and users can give inputs to programs using the STDIN textbox under the I/O tab. Following is a sample python program which takes name as input and print your name with hello.

import sys
name = sys.stdin.readline()
print("Hello "+ name)

About Python

Python is a very popular general-purpose programming language which was created by Guido van Rossum, and released in 1991. It is very popular for web development and you can build almost anything like mobile apps, web apps, tools, data analytics, machine learning etc. It is designed to be simple and easy like english language. It's is highly productive and efficient making it a very popular language.

Tutorial & Syntax help

Loops

1. If-Else:

When ever you want to perform a set of operations based on a condition IF-ELSE is used.

if conditional-expression
    #code
elif conditional-expression
    #code
else:
    #code

Note:

Indentation is very important in Python, make sure the indentation is followed correctly

2. For:

For loop is used to iterate over arrays(list, tuple, set, dictionary) or strings.

Example:

mylist=("Iphone","Pixel","Samsung")
for i in mylist:
    print(i)

3. While:

While is also used to iterate a set of statements based on a condition. Usually while is preferred when number of iterations are not known in advance.

while condition  
    #code 

Collections

There are four types of collections in Python.

1. List:

List is a collection which is ordered and can be changed. Lists are specified in square brackets.

Example:

mylist=["iPhone","Pixel","Samsung"]
print(mylist)

2. Tuple:

Tuple is a collection which is ordered and can not be changed. Tuples are specified in round brackets.

Example:

myTuple=("iPhone","Pixel","Samsung")
print(myTuple)

Below throws an error if you assign another value to tuple again.

myTuple=("iPhone","Pixel","Samsung")
print(myTuple)
myTuple[1]="onePlus"
print(myTuple)

3. Set:

Set is a collection which is unordered and unindexed. Sets are specified in curly brackets.

Example:

myset = {"iPhone","Pixel","Samsung"}
print(myset)

4. Dictionary:

Dictionary is a collection of key value pairs which is unordered, can be changed, and indexed. They are written in curly brackets with key - value pairs.

Example:

mydict = {
    "brand" :"iPhone",
    "model": "iPhone 11"
}
print(mydict)

Supported Libraries

Following are the libraries supported by OneCompiler's Python compiler

NameDescription
NumPyNumPy python library helps users to work on arrays with ease
SciPySciPy is a scientific computation library which depends on NumPy for convenient and fast N-dimensional array manipulation
SKLearn/Scikit-learnScikit-learn or Scikit-learn is the most useful library for machine learning in Python
PandasPandas is the most efficient Python library for data manipulation and analysis
DOcplexDOcplex is IBM Decision Optimization CPLEX Modeling for Python, is a library composed of Mathematical Programming Modeling and Constraint Programming Modeling