From cc26b337c246c6d9e8b10e3e36aed4ff920a2e7d Mon Sep 17 00:00:00 2001 From: Yannik Schmidt Date: Sat, 15 Aug 2020 21:46:05 +0200 Subject: [PATCH] Implement GUI --- .gitignore | 1 + .../main/python/config_parse.py | 2 + constants.py => src/main/python/constants.py | 0 frontend.py => src/main/python/frontend.py | 0 .../main/python/frontend_utils.py | 0 src/main/python/gui.py | 104 ++++++++ init.py => src/main/python/init.py | 0 .../main/python/input_backend.py | 8 +- language.py => src/main/python/language.py | 0 src/main/python/localization/de.py | 17 +- src/main/python/main.py | 241 +++++++++++++++--- plot.py => src/main/python/plot.py | 0 .../main/python/plot_graphutils.py | 0 .../main/python/plot_imageutils.py | 0 plot_main.py => src/main/python/plot_main.py | 19 +- .../main/python/plot_timeutils.py | 0 .../main/python/test_cases}/__init__.py | 0 .../python/test_cases}/graphutils_test.py | 0 .../main/python/test_cases}/timeutils_test.py | 0 19 files changed, 343 insertions(+), 49 deletions(-) rename config_parse.py => src/main/python/config_parse.py (94%) rename constants.py => src/main/python/constants.py (100%) rename frontend.py => src/main/python/frontend.py (100%) rename frontend_utils.py => src/main/python/frontend_utils.py (100%) create mode 100644 src/main/python/gui.py rename init.py => src/main/python/init.py (100%) rename input_backend.py => src/main/python/input_backend.py (97%) rename language.py => src/main/python/language.py (100%) rename plot.py => src/main/python/plot.py (100%) rename plot_graphutils.py => src/main/python/plot_graphutils.py (100%) rename plot_imageutils.py => src/main/python/plot_imageutils.py (100%) rename plot_main.py => src/main/python/plot_main.py (89%) rename plot_timeutils.py => src/main/python/plot_timeutils.py (100%) rename {test_cases => src/main/python/test_cases}/__init__.py (100%) rename {test_cases => src/main/python/test_cases}/graphutils_test.py (100%) rename {test_cases => src/main/python/test_cases}/timeutils_test.py (100%) diff --git a/.gitignore b/.gitignore index b61119b..c937d1f 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ *.png +target/ *.bak cache/ *.dbf diff --git a/config_parse.py b/src/main/python/config_parse.py similarity index 94% rename from config_parse.py rename to src/main/python/config_parse.py index a4351f4..bd4d09e 100644 --- a/config_parse.py +++ b/src/main/python/config_parse.py @@ -28,6 +28,8 @@ def get_keys(like=None): def change_cfg(key,value): global conf + if conf == None: + parse_config() confs = conf["plot"] v = str(value) key = str(key) diff --git a/constants.py b/src/main/python/constants.py similarity index 100% rename from constants.py rename to src/main/python/constants.py diff --git a/frontend.py b/src/main/python/frontend.py similarity index 100% rename from frontend.py rename to src/main/python/frontend.py diff --git a/frontend_utils.py b/src/main/python/frontend_utils.py similarity index 100% rename from frontend_utils.py rename to src/main/python/frontend_utils.py diff --git a/src/main/python/gui.py b/src/main/python/gui.py new file mode 100644 index 0000000..dde55a1 --- /dev/null +++ b/src/main/python/gui.py @@ -0,0 +1,104 @@ +#!/usr/bin/python3 + +from fbs_runtime.application_context.PyQt5 import ApplicationContext +from PyQt5.QtCore import QDateTime, Qt, QTimer +from PyQt5.QtWidgets import (QApplication, QCheckBox, QComboBox, QDateTimeEdit, + QDial, QDialog, QGridLayout, QGroupBox, QHBoxLayout, QLabel, QLineEdit, + QProgressBar, QPushButton, QRadioButton, QScrollBar, QSizePolicy, + QSlider, QSpinBox, QStyleFactory, QTableWidget, QTabWidget, QTextEdit, + QVBoxLayout, QWidget) + +import localization.de as de + +class WidgetGallery(QDialog): + + def __init__(self, parent=None): + super(WidgetGallery, self).__init__(parent) + + self.localization = de + self.srcFileString = "" + + styleComboBox = QComboBox() + styleComboBox.addItems(QStyleFactory.keys()) + + self.createFileSelection() + self.createDateSelection() + self.createCheckboxArea() + + mainLayout = QGridLayout() + mainLayout.addLayout(topLayout, 0, 0, 1, 2) + mainLayout.addWidget(self.fileSelectionGroup, 1, 0) + mainLayout.addWidget(self.dateSelectionGroupBox, 1, 1) + mainLayout.addWidget(self.checkboxGroup, 2, 0) + + mainLayout.setRowStretch(1, 1) + mainLayout.setRowStretch(2, 1) + mainLayout.setColumnStretch(0, 1) + mainLayout.setColumnStretch(1, 1) + + self.setLayout(mainLayout) + + self.setWindowTitle(self.localization.window_title) + + def createFileSelection(self): + '''Generate the area containing the file selectors and go button''' + + self.fileSelectionGroup = QGroupBox(self.localization.file_selection) + + # basic object # + buttonGo = QPushButton(self.localization.button_go) + buttonSrcFile = QPushButton(self.localization.button_set_src_file) + srcFileName = QLabel(self.srcFileString) + + buttonTargetFile = QPushButton(self.target_file) + buttonUseSrcDir = QCheckBox(self.localization.button_use_src_dir) + + # connectors # + buttonGo.connect(self.run) + buttonSrcFile.connect(self.selectSrcFile) + buttonTargetFile.connect(self.selectTargetFile) + buttonUseSrcDir.connect(self.useSrcDir) + + # layout # + layout = QVBoxLayout() + + layout.addWidget(buttonGo) + layout.addWidget(buttonSrcFile) + layout.addWidget(srcFileName) + + layout.addWidget(buttonTargetFile) + layout.addWidget(buttonUseSrcDir) + + layout.addStretch(1) + self.dateSelectionGroupBox.setLayout(layout) + + def createDateSelection(self): + '''Generate the area containing the date selectors''' + + self.dateSelectionGroupBox = QGroupBox(self.localization.date_selection) + + layout = QVBoxLayout() + layout.addWidget() # TODO + layout.addStretch(1) + self.topRightGroupBox.setLayout(layout) + + def createCheckboxArea(self): + '''Generate area with configuration options''' + + self.checkboxGroup = QGroupBox(self.localization.options) + + buttonOTemp = QCheckBox(self.localization.button_otemp) + buttonOHumidity = QCheckBox(self.localization.button_ohumidity) + + layout = QVBoxLayout() + layout.addWidget(buttonOTemp) + layout.addWidget(buttonOHumidity) + layout.addStretch(1) + self.topRightGroupBox.setLayout(layout) + + +if __name__ == '__main__': + appctxt = ApplicationContext() + gallery = WidgetGallery() + gallery.show() + sys.exit(appctxt.app.exec_()) diff --git a/init.py b/src/main/python/init.py similarity index 100% rename from init.py rename to src/main/python/init.py diff --git a/input_backend.py b/src/main/python/input_backend.py similarity index 97% rename from input_backend.py rename to src/main/python/input_backend.py index a1ba830..024c7a8 100644 --- a/input_backend.py +++ b/src/main/python/input_backend.py @@ -31,7 +31,7 @@ class Data: '''Get time of last timestamp''' return max(self.times) - def get_timeframe(self, callback,date1=None,date2=None): + def get_timeframe(self, callback, date1=None, date2=None): out_x = [] out_y = [] i = 0 @@ -151,7 +151,7 @@ def processExternalData(datapoints, plotNameKey, fromTime, toTime, dtype): raise e -def read_in_file(path, backend=None, outsideData=False): +def read_in_file(path, backend=None, outsideData=False, plotOutsideTemp=True, plotOutsideHum=True): '''Read in a file, add outside data if requested''' datapoints = dict() @@ -179,8 +179,8 @@ def read_in_file(path, backend=None, outsideData=False): plotSettings = [ CFG("plot_temperatur"), CFG("plot_humidity"), CFG("plot_dewcels"), - outsideData, - outsideData ] + plotOutsideTemp, + plotOutsideHum ] assert(len(names) == len(colors) == len(identifiers) == len(plotSettings)) diff --git a/language.py b/src/main/python/language.py similarity index 100% rename from language.py rename to src/main/python/language.py diff --git a/src/main/python/localization/de.py b/src/main/python/localization/de.py index 8957b30..0c0307b 100644 --- a/src/main/python/localization/de.py +++ b/src/main/python/localization/de.py @@ -1,9 +1,22 @@ window_title = "Datenlogger Tool" file_selection = "Dateiauswahl" button_go = "Start" +button_go_wait = "Bitte Warten.." button_set_src_file = "Datei.." button_use_src_dir = "Gleicher Ordner" date_selection = "Zeitbereich" options = "Optionen" -button_otemp = "Ausßentemperatur" -button_ohumidity = "Außenluftfeuchtigkeit" +button_otemp = "Ausßentemperatur einzeichnen" +button_ohumidity = "Außenluftfeuchtigkeit einzeichnen" +output_file = "Ausgabe" +output_file_placeholder = "Datei auswählen.." +src_file_dialog = "Datei auswählen" +src_file_extensions = "Logger Dateien (*.txt *.csv *.dbf);; Alle *.*" +save_file_dialog = "Zieldatei" +wait_dialog_text = "Datei wird gelesen.." +error_read_in = "Fehler beim Einlesen der Datei." +done_text = "Fertig" +start_section = "Ausführen" +close = "Schließen" +open_pic = "Bild öffnen.." +bad_time = "Fehlerhafte Zeitangabe!" diff --git a/src/main/python/main.py b/src/main/python/main.py index e3c2064..849a796 100755 --- a/src/main/python/main.py +++ b/src/main/python/main.py @@ -1,14 +1,22 @@ #!/usr/bin/python3 from fbs_runtime.application_context.PyQt5 import ApplicationContext -from PyQt5.QtCore import QDateTime, Qt, QTimer +from PyQt5.QtCore import QDateTime, Qt, QTimer, QUrl +import PyQt5.QtCore +import PyQt5.QtGui from PyQt5.QtWidgets import (QApplication, QCheckBox, QComboBox, QDateTimeEdit, QDial, QDialog, QGridLayout, QGroupBox, QHBoxLayout, QLabel, QLineEdit, QProgressBar, QPushButton, QRadioButton, QScrollBar, QSizePolicy, QSlider, QSpinBox, QStyleFactory, QTableWidget, QTabWidget, QTextEdit, - QVBoxLayout, QWidget) + QVBoxLayout, QWidget, QFileDialog, QDateEdit, QMessageBox) import localization.de as de +import sys +import datetime as dt + +import input_backend +import plot_main +import config_parse as cp class WidgetGallery(QDialog): @@ -18,85 +26,250 @@ class WidgetGallery(QDialog): self.localization = de self.srcFileString = "" self.targetFileString = "" + self.truePath = None styleComboBox = QComboBox() styleComboBox.addItems(QStyleFactory.keys()) + self.createStartSection() self.createFileSelection() self.createDateSelection() self.createCheckboxArea() mainLayout = QGridLayout() - mainLayout.addLayout(topLayout, 0, 0, 1, 2) mainLayout.addWidget(self.fileSelectionGroup, 1, 0) - mainLayout.addWidget(self.dateSelectionGroupBox, 1, 1) - mainLayout.addWidget(self.checkboxGroup, 2, 0) - - mainLayout.setRowStretch(1, 1) - mainLayout.setRowStretch(2, 1) - mainLayout.setColumnStretch(0, 1) - mainLayout.setColumnStretch(1, 1) + mainLayout.addWidget(self.dateSelectionGroupBox, 2, 0) + mainLayout.addWidget(self.checkboxGroup, 3, 0) + mainLayout.addWidget(self.startSection, 4, 0) self.setLayout(mainLayout) self.setWindowTitle(self.localization.window_title) + def createStartSection(self): + '''Generate Aread containing the start button''' + + self.startSection = QGroupBox(self.localization.start_section) + self.buttonGo = QPushButton(self.localization.button_go) + self.buttonGo.setDisabled(True) + self.buttonGo.clicked.connect(self.run) + + layout = QVBoxLayout() + layout.addWidget(self.buttonGo) + + self.startSection.setLayout(layout) + def createFileSelection(self): '''Generate the area containing the file selectors and go button''' self.fileSelectionGroup = QGroupBox(self.localization.file_selection) # basic object # - buttonGo = QPushButton(self.localization.button_go) - buttonSrcFile = QPushButton(self.localization.button_set_src_file) - srcFileName = QLabel(self.srcFileString) + self.buttonSrcFile = QPushButton(self.localization.button_set_src_file) + self.srcFileName = QLabel(self.localization.output_file) - buttonTargetFile = QPushButton(self.targetFileString) - buttonUseSrcDir = QCheckBox(self.localization.button_use_src_dir) + self.buttonTargetFile = QPushButton(self.localization.output_file_placeholder) + self.boxUseSrcDir = QCheckBox(self.localization.button_use_src_dir) # connectors # - buttonGo.connect(self.run) - buttonSrcFile.connect(self.selectSrcFile) - buttonTargetFile.connect(self.selectTargetFile) - buttonUseSrcDir.connect(self.useSrcDir) + self.buttonSrcFile.clicked.connect(self.selectSrcFile) + self.buttonTargetFile.clicked.connect(self.selectTargetFile) + self.boxUseSrcDir.stateChanged.connect(self.useSrcDir) + self.boxUseSrcDir.setChecked(True) # layout # layout = QVBoxLayout() - layout.addWidget(buttonGo) - layout.addWidget(buttonSrcFile) - layout.addWidget(srcFileName) + layout.addWidget(self.buttonSrcFile) + layout.addWidget(self.srcFileName) - layout.addWidget(buttonTargetFile) - layout.addWidget(buttonUseSrcDir) + layout.addWidget(self.buttonTargetFile) + layout.addWidget(self.boxUseSrcDir) layout.addStretch(1) - self.dateSelectionGroupBox.setLayout(layout) + self.fileSelectionGroup.setLayout(layout) def createDateSelection(self): '''Generate the area containing the date selectors''' self.dateSelectionGroupBox = QGroupBox(self.localization.date_selection) - layout = QVBoxLayout() - #layout.addWidget() # TODO - layout.addStretch(1) - self.topRightGroupBox.setLayout(layout) + layout = QGridLayout() + + self.startDateEdit = QDateEdit(calendarPopup=True) + self.startDateEdit.setDisplayFormat("dd.MM.yyyy") + self.startDateEdit.setReadOnly(True) + self.startDateEdit.lineEdit().setDisabled(True) + + self.endDateEdit = QDateEdit(calendarPopup=True) + self.endDateEdit.setDisplayFormat("dd.MM.yyyy") + self.endDateEdit.setReadOnly(True) + self.endDateEdit.lineEdit().setDisabled(True) + + self.startTimeEdit = QLineEdit("00:00") + self.endTimeEdit = QLineEdit("23:59") + self.startTimeEdit.setDisabled(True) + self.endTimeEdit.setDisabled(True) + + layout.addWidget(self.startDateEdit, 0, 0) + layout.addWidget(self.startTimeEdit, 0, 1) + + layout.addWidget(self.endDateEdit, 1, 0) + layout.addWidget(self.endTimeEdit, 1, 1) + + layout.setColumnStretch(0, 1) + layout.setColumnStretch(1, 1) + + self.dateSelectionGroupBox.setLayout(layout) def createCheckboxArea(self): '''Generate area with configuration options''' self.checkboxGroup = QGroupBox(self.localization.options) - buttonOTemp = QCheckBox(self.localization.button_otemp) - buttonOHumidity = QCheckBox(self.localization.button_ohumidity) + self.boxOTemp = QCheckBox(self.localization.button_otemp) + self.boxOHumidity = QCheckBox(self.localization.button_ohumidity) layout = QVBoxLayout() - layout.addWidget(buttonOTemp) - layout.addWidget(buttonOHumidity) + layout.addWidget(self.boxOTemp) + layout.addWidget(self.boxOHumidity) layout.addStretch(1) - self.topRightGroupBox.setLayout(layout) + self.checkboxGroup.setLayout(layout) + def run(self): + '''Run generation with selected file and options''' + + # set save target if nessesary # + self.buttonGo.setText(self.localization.button_go_wait) + self.buttonGo.setDisabled(True) + self.repaint() + + if self.boxUseSrcDir.isChecked(): + target = self.srcFileString + forcePath = False + else: + target = self.targetFileString + forcePath = True + + # workaround for checkboxes changed # + self.datapoints = input_backend.read_in_file(self.srcFileString, + outsideData=True, + plotOutsideTemp=self.boxOTemp.isChecked(), + plotOutsideHum=self.boxOHumidity.isChecked()) + + # build dates # + try: + startTimeHelper = dt.datetime.strptime(self.startTimeEdit.text(),"%H:%M") + endTimeHelper = dt.datetime.strptime(self.endTimeEdit.text(),"%H:%M") + except ValueError as e: + errorBox = QMessageBox(self) + errorBox.setAttribute(PyQt5.QtCore.Qt.WA_DeleteOnClose) + errorBox.setText(self.localization.bad_time) + errorBox.setDetailedText(str(e)) + errorBox.show() + self.buttonGo.setText(self.localization.button_go) + self.buttonGo.setDisabled(False) + return + + startTimeOffset = dt.timedelta(hours=startTimeHelper.hour, minutes=startTimeHelper.minute) + endTimeOffset = dt.timedelta(hours=endTimeHelper.hour, minutes=endTimeHelper.minute) + + zeroTime = dt.time(0, 0) + startDateTime = dt.datetime.combine(self.startDateEdit.date().toPyDate(), zeroTime) + startDateTime += startTimeOffset + endDateTime = dt.datetime.combine(self.endDateEdit.date().toPyDate(), zeroTime) + endDateTime += endTimeOffset + + self.truePath = plot_main.plot(self.datapoints, path=target, + date1=startDateTime, + date2=endDateTime, + forcePath=forcePath) + + self.buttonGo.setText(self.localization.button_go) + self.buttonGo.setDisabled(False) + + doneDialog = QMessageBox(self) + doneDialog.setAttribute(PyQt5.QtCore.Qt.WA_DeleteOnClose) + doneDialog.setText(self.localization.done_text) + doneDialog.addButton(self.localization.open_pic, QMessageBox.YesRole) + doneDialog.addButton(self.localization.close, QMessageBox.NoRole) + doneDialog.buttonClicked.connect(self.openFile) + doneDialog.show() + + def selectSrcFile(self): + '''Function to select a src-file''' + + self.srcFileString = QFileDialog.getOpenFileName(self, self.localization.src_file_dialog, + "", "Data-Files (*.txt *.csv *.dbf)")[0] + self.srcFileName.setText(self.srcFileString) + + if not self.srcFileString: + return + + waitDialog = QMessageBox(self) + waitDialog.setAttribute(PyQt5.QtCore.Qt.WA_DeleteOnClose) + waitDialog.setText(self.localization.wait_dialog_text) + waitDialog.show() + try: + self.datapoints = input_backend.read_in_file(self.srcFileString, + outsideData=False, + plotOutsideTemp=False, + plotOutsideHum=False) + except Exception as e: + waitDialog.close() + errorBox = QMessageBox(self) + errorBox.setAttribute(PyQt5.QtCore.Qt.WA_DeleteOnClose) + errorBox.setText(self.localization.error_read_in) + errorBox.setDetailedText(str(e)) + errorBox.show() + return + + waitDialog.close() + + start = self.datapoints[cp.CFG("plot_temperatur_key")].getFirstTime() + self.startDateEdit.setDateTime(start) + + end = self.datapoints[cp.CFG("plot_temperatur_key")].getLastTime() + self.endDateEdit.setDateTime(end) + + self.buttonGo.setDisabled(False) + self.endDateEdit.setReadOnly(False) + self.startDateEdit.setReadOnly(False) + self.startDateEdit.lineEdit().setDisabled(False) + self.endDateEdit.lineEdit().setDisabled(False) + self.startTimeEdit.setDisabled(False) + self.endTimeEdit.setDisabled(False) + + def selectTargetFile(self): + '''Function to select a target-file''' + self.targetFileString = QFileDialog.getSaveFileName(self, + self.localization.save_file_dialog)[0] + if not self.targetFileString: + return + + self.buttonTargetFile.setText(self.targetFileString) + self.buttonGo.setDisabled(False) + + def useSrcDir(self): + '''Function to handle use src dir checkbox''' + if self.boxUseSrcDir.isChecked(): + self.buttonTargetFile.setDisabled(True) + if self.srcFileString: + self.buttonGo.setDisabled(False) + self.srcFileName.setText(self.srcFileString) + else: + self.buttonTargetFile.setDisabled(False) + if self.targetFileString: + self.buttonTargetFile.setText(self.targetFileString) + else: + self.buttonGo.setDisabled(True) + + def openFile(self, button): + if button.text() == self.localization.open_pic and self.truePath: + print("JES") + PyQt5.QtGui.QDesktopServices.openUrl(QUrl.fromLocalFile(self.truePath)); + else: + pass if __name__ == '__main__': appctxt = ApplicationContext() diff --git a/plot.py b/src/main/python/plot.py similarity index 100% rename from plot.py rename to src/main/python/plot.py diff --git a/plot_graphutils.py b/src/main/python/plot_graphutils.py similarity index 100% rename from plot_graphutils.py rename to src/main/python/plot_graphutils.py diff --git a/plot_imageutils.py b/src/main/python/plot_imageutils.py similarity index 100% rename from plot_imageutils.py rename to src/main/python/plot_imageutils.py diff --git a/plot_main.py b/src/main/python/plot_main.py similarity index 89% rename from plot_main.py rename to src/main/python/plot_main.py index aec8b73..d6aded6 100644 --- a/plot_main.py +++ b/src/main/python/plot_main.py @@ -19,16 +19,12 @@ import plot_imageutils import plot_timeutils -def plot(datapoints,path=None,date1=None,date2=None): +def plot(datapoints, path=None, date1=None, date2=None, forcePath=False): plotname = "" if CFG("name_of_plot") == "None" else CFG("name_of_plot") tup = [None,None,plot_timeutils.between_dates,plotname] - if CFG("enable_multicore_support"): - thread = Process(target=__plot,args=(tup,datapoints,date1,date2)) - thread.start() - else: - __plot(tup,datapoints,path,date1,date2) + return __plot(tup, datapoints, path, date1, date2, forcePath) -def __plot(tup,datapoints,path,date1=None,date2=None): +def __plot(tup, datapoints, path, date1=None, date2=None, forcePath=False): NO_SERIES = True x,y,ymin,ymax,unix_x,major_xticks = ( [] , [], -1 , -1 , [], [] ) lw = CFG("plot_line_width") @@ -67,8 +63,11 @@ def __plot(tup,datapoints,path,date1=None,date2=None): ## using unix_x relys on unix_x to be the same for all plots ## if path == None: path = open_file() - - pic_path = output_path(path,date1,date2) + + if not forcePath: + pic_path = output_path(path,date1,date2) + else: + pic_path = path ## set resoltuion ## @@ -83,6 +82,8 @@ def __plot(tup,datapoints,path,date1=None,date2=None): ### do operations on the finished png ### plot_imageutils.check_and_rotate(pic_path) + return pic_path + def output_path(path,date1,date2): if date1 != None and date2 == None: pic_path = path + "-nach-%s"%date1.strftime("%d.%m.%y") + ".png" diff --git a/plot_timeutils.py b/src/main/python/plot_timeutils.py similarity index 100% rename from plot_timeutils.py rename to src/main/python/plot_timeutils.py diff --git a/test_cases/__init__.py b/src/main/python/test_cases/__init__.py similarity index 100% rename from test_cases/__init__.py rename to src/main/python/test_cases/__init__.py diff --git a/test_cases/graphutils_test.py b/src/main/python/test_cases/graphutils_test.py similarity index 100% rename from test_cases/graphutils_test.py rename to src/main/python/test_cases/graphutils_test.py diff --git a/test_cases/timeutils_test.py b/src/main/python/test_cases/timeutils_test.py similarity index 100% rename from test_cases/timeutils_test.py rename to src/main/python/test_cases/timeutils_test.py