1
0
forked from IPKM/nmreval

crude updater added; write_state uses plain text, not pickle; disallow invalid characters; T246

This commit is contained in:
Dominik Demuth 2023-01-05 15:27:33 +01:00
parent 2ed390ccae
commit 04eb83a19d
11 changed files with 273 additions and 131 deletions

View File

@ -36,18 +36,21 @@ AppDir:
# for /usr/bin/env # for /usr/bin/env
- coreutils - coreutils
- dash - dash
- tango-icon-theme - zsync
- hicolor-icon-theme
- libatlas3-base - libatlas3-base
- python3.9-minimal - python3.9-minimal
- python3-pyqt5
- python3-numpy - python3-numpy
#- python3-matplotlib
#- python-matplotlib-data
- python3-scipy - python3-scipy
# - python3-matplotlib
# - python-matplotlib-data
- python3-bsddb3 - python3-bsddb3
- python3-h5py - python3-h5py
- python3-pyqt5
- python3-pyqtgraph - python3-pyqtgraph
- python3-tk - python3-requests
- python3-urllib3
# - python3-tk
exclude: exclude:
- libavahi-client3 - libavahi-client3
- libavahi-common-data - libavahi-common-data

View File

@ -16,9 +16,10 @@ from nmreval.lib.logger import handle_exception
sys.excepthook = handle_exception sys.excepthook = handle_exception
from gui_qt import App from gui_qt import App
from gui_qt.main.mainwindow import NMRMainWindow
app = App([]) app = App(['Team Rocket FTW!'])
from gui_qt.main.mainwindow import NMRMainWindow
mplQt = NMRMainWindow() mplQt = NMRMainWindow()
mplQt.show() mplQt.show()

View File

@ -5,4 +5,4 @@ PyQt5
h5py h5py
pyqtgraph pyqtgraph
bsddb3 bsddb3
requests

View File

@ -1,8 +1,8 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# Form implementation generated from reading ui file '/autohome/dominik/nmreval/src/resources/_ui/basewindow.ui' # Form implementation generated from reading ui file 'src/resources/_ui/basewindow.ui'
# #
# Created by: PyQt5 UI code generator 5.15.4 # Created by: PyQt5 UI code generator 5.15.7
# #
# WARNING: Any manual changes made to this file will be lost when pyuic5 is # WARNING: Any manual changes made to this file will be lost when pyuic5 is
# run again. Do not edit this file unless you know what you are doing. # run again. Do not edit this file unless you know what you are doing.
@ -75,7 +75,7 @@ class Ui_BaseWindow(object):
self.horizontalLayout.addWidget(self.splitter) self.horizontalLayout.addWidget(self.splitter)
BaseWindow.setCentralWidget(self.centralwidget) BaseWindow.setCentralWidget(self.centralwidget)
self.menubar = QtWidgets.QMenuBar(BaseWindow) self.menubar = QtWidgets.QMenuBar(BaseWindow)
self.menubar.setGeometry(QtCore.QRect(0, 0, 1386, 30)) self.menubar.setGeometry(QtCore.QRect(0, 0, 1386, 20))
self.menubar.setObjectName("menubar") self.menubar.setObjectName("menubar")
self.menuFile = QtWidgets.QMenu(self.menubar) self.menuFile = QtWidgets.QMenu(self.menubar)
self.menuFile.setObjectName("menuFile") self.menuFile.setObjectName("menuFile")
@ -179,8 +179,6 @@ class Ui_BaseWindow(object):
self.toolBar_data.setObjectName("toolBar_data") self.toolBar_data.setObjectName("toolBar_data")
BaseWindow.addToolBar(QtCore.Qt.TopToolBarArea, self.toolBar_data) BaseWindow.addToolBar(QtCore.Qt.TopToolBarArea, self.toolBar_data)
self.action_close = QtWidgets.QAction(BaseWindow) self.action_close = QtWidgets.QAction(BaseWindow)
icon = QtGui.QIcon.fromTheme("window-close")
self.action_close.setIcon(icon)
self.action_close.setObjectName("action_close") self.action_close.setObjectName("action_close")
self.actionExportGraphic = QtWidgets.QAction(BaseWindow) self.actionExportGraphic = QtWidgets.QAction(BaseWindow)
self.actionExportGraphic.setObjectName("actionExportGraphic") self.actionExportGraphic.setObjectName("actionExportGraphic")
@ -191,20 +189,14 @@ class Ui_BaseWindow(object):
self.action_calc = QtWidgets.QAction(BaseWindow) self.action_calc = QtWidgets.QAction(BaseWindow)
self.action_calc.setObjectName("action_calc") self.action_calc.setObjectName("action_calc")
self.action_delete_sets = QtWidgets.QAction(BaseWindow) self.action_delete_sets = QtWidgets.QAction(BaseWindow)
icon = QtGui.QIcon.fromTheme("edit-delete")
self.action_delete_sets.setIcon(icon)
self.action_delete_sets.setObjectName("action_delete_sets") self.action_delete_sets.setObjectName("action_delete_sets")
self.action_save_fit_parameter = QtWidgets.QAction(BaseWindow) self.action_save_fit_parameter = QtWidgets.QAction(BaseWindow)
self.action_save_fit_parameter.setObjectName("action_save_fit_parameter") self.action_save_fit_parameter.setObjectName("action_save_fit_parameter")
self.action_sort_pts = QtWidgets.QAction(BaseWindow) self.action_sort_pts = QtWidgets.QAction(BaseWindow)
self.action_sort_pts.setObjectName("action_sort_pts") self.action_sort_pts.setObjectName("action_sort_pts")
self.action_reset = QtWidgets.QAction(BaseWindow) self.action_reset = QtWidgets.QAction(BaseWindow)
icon = QtGui.QIcon.fromTheme("edit-clear")
self.action_reset.setIcon(icon)
self.action_reset.setObjectName("action_reset") self.action_reset.setObjectName("action_reset")
self.actionDocumentation = QtWidgets.QAction(BaseWindow) self.actionDocumentation = QtWidgets.QAction(BaseWindow)
icon = QtGui.QIcon.fromTheme("help-about")
self.actionDocumentation.setIcon(icon)
self.actionDocumentation.setObjectName("actionDocumentation") self.actionDocumentation.setObjectName("actionDocumentation")
self.action_FitWidget = QtWidgets.QAction(BaseWindow) self.action_FitWidget = QtWidgets.QAction(BaseWindow)
self.action_FitWidget.setObjectName("action_FitWidget") self.action_FitWidget.setObjectName("action_FitWidget")
@ -250,8 +242,6 @@ class Ui_BaseWindow(object):
self.actionConfiguration = QtWidgets.QAction(BaseWindow) self.actionConfiguration = QtWidgets.QAction(BaseWindow)
self.actionConfiguration.setObjectName("actionConfiguration") self.actionConfiguration.setObjectName("actionConfiguration")
self.actionRefresh = QtWidgets.QAction(BaseWindow) self.actionRefresh = QtWidgets.QAction(BaseWindow)
icon = QtGui.QIcon.fromTheme("view-refresh")
self.actionRefresh.setIcon(icon)
self.actionRefresh.setObjectName("actionRefresh") self.actionRefresh.setObjectName("actionRefresh")
self.actionInterpolation = QtWidgets.QAction(BaseWindow) self.actionInterpolation = QtWidgets.QAction(BaseWindow)
self.actionInterpolation.setObjectName("actionInterpolation") self.actionInterpolation.setObjectName("actionInterpolation")
@ -278,8 +268,6 @@ class Ui_BaseWindow(object):
self.actionNew_window = QtWidgets.QAction(BaseWindow) self.actionNew_window = QtWidgets.QAction(BaseWindow)
self.actionNew_window.setObjectName("actionNew_window") self.actionNew_window.setObjectName("actionNew_window")
self.actionDelete_window = QtWidgets.QAction(BaseWindow) self.actionDelete_window = QtWidgets.QAction(BaseWindow)
icon = QtGui.QIcon.fromTheme("edit-delete")
self.actionDelete_window.setIcon(icon)
self.actionDelete_window.setObjectName("actionDelete_window") self.actionDelete_window.setObjectName("actionDelete_window")
self.actionCascade_windows = QtWidgets.QAction(BaseWindow) self.actionCascade_windows = QtWidgets.QAction(BaseWindow)
self.actionCascade_windows.setObjectName("actionCascade_windows") self.actionCascade_windows.setObjectName("actionCascade_windows")
@ -330,8 +318,6 @@ class Ui_BaseWindow(object):
self.actionChange_datatypes = QtWidgets.QAction(BaseWindow) self.actionChange_datatypes = QtWidgets.QAction(BaseWindow)
self.actionChange_datatypes.setObjectName("actionChange_datatypes") self.actionChange_datatypes.setObjectName("actionChange_datatypes")
self.actionPrint = QtWidgets.QAction(BaseWindow) self.actionPrint = QtWidgets.QAction(BaseWindow)
icon = QtGui.QIcon.fromTheme("document-print")
self.actionPrint.setIcon(icon)
self.actionPrint.setObjectName("actionPrint") self.actionPrint.setObjectName("actionPrint")
self.action_lm_fit = QtWidgets.QAction(BaseWindow) self.action_lm_fit = QtWidgets.QAction(BaseWindow)
self.action_lm_fit.setCheckable(True) self.action_lm_fit.setCheckable(True)
@ -494,7 +480,7 @@ class Ui_BaseWindow(object):
self.retranslateUi(BaseWindow) self.retranslateUi(BaseWindow)
self.tabWidget.setCurrentIndex(0) self.tabWidget.setCurrentIndex(0)
self.action_close.triggered.connect(BaseWindow.close) self.action_close.triggered.connect(BaseWindow.close) # type: ignore
QtCore.QMetaObject.connectSlotsByName(BaseWindow) QtCore.QMetaObject.connectSlotsByName(BaseWindow)
def retranslateUi(self, BaseWindow): def retranslateUi(self, BaseWindow):

View File

@ -8,15 +8,18 @@ from ..Qt import QtWidgets, QtCore
class FileDialog(QtWidgets.QFileDialog): class FileDialog(QtWidgets.QFileDialog):
last_path = None last_path = None
def __init__(self, directory=None, caption=None, filters='', parent=None): def __init__(self, directory=None, caption=None, filter='', parent=None):
super().__init__(parent=parent) super().__init__(parent=parent)
self.setOption(QtWidgets.QFileDialog.DontUseNativeDialog, True) self.setOption(QtWidgets.QFileDialog.DontUseNativeDialog, True)
self.setWindowTitle(caption) self.setWindowTitle(caption)
if directory is not None: if directory:
self.setDirectory(str(directory)) self.setDirectory(str(directory))
self.setNameFilters(filters.split(';;')) elif self.last_path is not None:
self.setDirectory(str(FileDialog.last_path))
self.setNameFilters(filter.split(';;'))
file_tree = self.findChild(QtWidgets.QTreeView, 'treeView') file_tree = self.findChild(QtWidgets.QTreeView, 'treeView')
file_tree.setSortingEnabled(True) file_tree.setSortingEnabled(True)
@ -35,13 +38,30 @@ class FileDialog(QtWidgets.QFileDialog):
def save_file(self) -> pathlib.Path | None: def save_file(self) -> pathlib.Path | None:
outfile = self.selectedFiles() outfile = self.selectedFiles()
if outfile: if outfile:
return pathlib.Path(outfile[0]) if self.is_valid(outfile[0]):
return pathlib.Path(outfile[0])
else:
_ = QtWidgets.QMessageBox.warning(self, 'Save file',
'Filename contains one or more invalid character: / * < > \\ | : "')
return return
@staticmethod
def is_valid(filename: str):
bad_character = r'/*<>\|:"'
for c in bad_character:
if c in filename:
return False
return True
def close(self):
FileDialog.last_path = self.directory()
super().close()
class OpenFileDialog(FileDialog): class OpenFileDialog(FileDialog):
def __init__(self, directory=None, caption=None, filters='', parent=None): def __init__(self, **kwargs):
super().__init__(directory=directory, caption=caption, filters=filters, parent=parent) super().__init__(**kwargs)
self.setFileMode(QtWidgets.QFileDialog.ExistingFiles) self.setFileMode(QtWidgets.QFileDialog.ExistingFiles)
@ -81,8 +101,8 @@ class OpenFileDialog(FileDialog):
class SaveDirectoryDialog(FileDialog): class SaveDirectoryDialog(FileDialog):
def __init__(self, directory=None, filters='', parent=None): def __init__(self, **kwargs):
super().__init__(directory=directory, filters=filters, parent=parent) super().__init__(**kwargs)
self.setOption(QtWidgets.QFileDialog.DontConfirmOverwrite, False) self.setOption(QtWidgets.QFileDialog.DontConfirmOverwrite, False)
self.setAcceptMode(QtWidgets.QFileDialog.AcceptSave) self.setAcceptMode(QtWidgets.QFileDialog.AcceptSave)
@ -123,4 +143,3 @@ class SaveDirectoryDialog(FileDialog):
self.setWindowTitle('Save') self.setWindowTitle('Save')
self.setNameFilters(['All files (*.*)', 'Session file (*.nmr)', 'Text file (*.dat)', self.setNameFilters(['All files (*.*)', 'Session file (*.nmr)', 'Text file (*.dat)',
'HDF file (*.h5)', 'Grace files (*.agr)']) 'HDF file (*.h5)', 'Grace files (*.agr)'])

View File

@ -37,7 +37,6 @@ class GraceMsgBox(QtWidgets.QDialog):
agr.parse(fname) agr.parse(fname)
layout = QtWidgets.QGridLayout() layout = QtWidgets.QGridLayout()
layout.setContentsMargins(13, 13, 13, 13)
self.setLayout(layout) self.setLayout(layout)
label = QtWidgets.QLabel('%s already exists. Select one of the options or cancel:' % os.path.split(fname)[1]) label = QtWidgets.QLabel('%s already exists. Select one of the options or cancel:' % os.path.split(fname)[1])
@ -100,9 +99,9 @@ class GeneralConfiguration(QtWidgets.QDialog):
for key, value in parser.items(sec): for key, value in parser.items(sec):
label = QtWidgets.QLabel(key.capitalize(), self) label = QtWidgets.QLabel(key.capitalize(), self)
layout2.addWidget(label, row, 0) layout2.addWidget(label, row, 0)
if (sec, key) in allowed_values: if (sec, key) in ALLOWED_VALUE:
edit = QtWidgets.QComboBox(self) edit = QtWidgets.QComboBox(self)
edit.addItems(allowed_values[(sec, key)]) edit.addItems(ALLOWED_VALUE[(sec, key)])
edit.setCurrentIndex(edit.findText(value)) edit.setCurrentIndex(edit.findText(value))
else: else:
edit = QtWidgets.QLineEdit(self) edit = QtWidgets.QLineEdit(self)

View File

@ -1,8 +1,15 @@
import sys
from os import getenv, stat
import hashlib
import subprocess
from datetime import datetime
from contextlib import contextmanager from contextlib import contextmanager
import requests
from numpy import linspace from numpy import linspace
from scipy.interpolate import interp1d from scipy.interpolate import interp1d
from ..Qt import QtGui, QtWidgets, QtCore from gui_qt.Qt import QtGui, QtWidgets, QtCore
@contextmanager @contextmanager
@ -48,3 +55,162 @@ class RdBuCMap:
col = QtGui.QColor.fromRgb(*(float(self.spline[i](val)) for i in range(3))) col = QtGui.QColor.fromRgb(*(float(self.spline[i](val)) for i in range(3)))
return col return col
class UpdateDialog(QtWidgets.QDialog):
host = 'mirror.infra.pkm'
bucket = 'nmreval'
version = 'NMReval-latest-x86_64'
def __init__(self, filename: str = None, parent=None):
super().__init__(parent=parent)
self._init_ui()
if filename is None:
filename = getenv('APPIMAGE')
self._appfile = filename
self._url_zsync = f'http://{self.host}/{self.bucket}/{self.version}.AppImage.zsync'
self.process = QtCore.QProcess(self)
self.look_for_updates()
def _init_ui(self):
self.setWindowTitle('Updates')
layout = QtWidgets.QVBoxLayout()
self.label = QtWidgets.QLabel()
layout.addWidget(self.label)
layout.addSpacing(10)
self.dialog_button = QtWidgets.QDialogButtonBox()
self.dialog_button.accepted.connect(self.accept)
self.dialog_button.rejected.connect(self.reject)
layout.addWidget(self.dialog_button)
self.setLayout(layout)
def look_for_updates(self):
# Download zsync file of latest Appimage, look for SHA-1 hash and compare with hash of AppImage
m_time_zsync, checksum_zsync = self.get_zsync()
m_time_file, checksum_file = self.get_appimage_info()
if m_time_zsync is None:
label_text = '<p>Retrieval of version information failed.</p>' \
'<p>Please try later (or complain to people that it does not work).</p>'
dialog_bttns = QtWidgets.QDialogButtonBox.Close
else:
label_text = f'<p>Found most recent update: {m_time_zsync.strftime("%d %B %Y %H:%M")}</p>'
if m_time_file is None:
label_text += 'No AppImage file found, press Ok to downlaod latest version.'
dialog_bttns = QtWidgets.QDialogButtonBox.Ok|QtWidgets.QDialogButtonBox.Close
else:
label_text += f'<p>Date of used AppImage: {m_time_file.strftime("%d %B %Y %H:%M")}</p>'
if not ((checksum_file is not None) and (checksum_zsync is not None)):
label_text += 'Could not determine if this version is newer, please update manually (if necessary).'
dialog_bttns = QtWidgets.QDialogButtonBox.Close
elif checksum_file != checksum_zsync:
label_text += f'<p>Newer version available. <b>Update?</b></p>'
dialog_bttns = QtWidgets.QDialogButtonBox.Ok|QtWidgets.QDialogButtonBox.Cancel
else:
label_text += f'<p>Version is already the newest</p>'
dialog_bttns = QtWidgets.QDialogButtonBox.Close
self.label.setText(label_text)
self.dialog_button.setStandardButtons(dialog_bttns)
def get_zsync(self):
m_time_zsync = None
checksum_zsync = None
zsync_file = None
try:
response = requests.get(self._url_zsync)
if response.status_code == requests.codes['\o/']:
zsync_file = response.content
except Exception as e:
pass
if zsync_file is not None:
for line in zsync_file.split(b'\n'):
try:
kw, val = line.split(b': ')
if kw == b'MTime':
m_time_zsync = datetime.strptime(str(val, encoding='utf-8'), '%a, %d %b %Y %H:%M:%S %z').astimezone(None)
elif kw == b'SHA-1':
checksum_zsync = str(val, encoding='utf-8')
except ValueError:
# stop when empty line is reached
break
return m_time_zsync, checksum_zsync
def get_appimage_info(self):
if self._appfile is None:
return None, None
stat_mtime = stat(self._appfile).st_mtime
m_time_file = datetime.fromtimestamp(stat_mtime).replace(microsecond=0)
with open(self._appfile, 'rb') as f:
checksum_file = hashlib.sha1(f.read()).hexdigest()
return m_time_file, checksum_file
def update_appimage(self):
if self._appfile is None:
args = [self._url_zsync]
else:
args = ['-i', self._appfile, self._url_zsync]
self.process.readyReadStandardOutput.connect(self.onReadyReadStandardOutput)
self.process.readyReadStandardError.connect(self.onReadyReadStandardOutput)
self.process.start('zsync', args)
if not self.process.waitForFinished():
return False
return True
def onReadyReadStandardOutput(self):
result = self.process.readAllStandardOutput().data().decode()
self.label.setText(result)
def accept(self):
if self.update_appimage():
_ = QtWidgets.QMessageBox.information(self, 'Updates', 'Download finished. Execute AppImage for new version.')
super().accept()
def open_bug_report():
form_entries = {
'description': 'Please state the nature of the medical emergency.',
'title': 'Everything is awesome?',
'assign[0]': 'dominik',
'subscribers[0]': 'dominik',
'tag': 'nmreval',
'priority': 'normal',
'status': 'open',
}
full_url = 'https://chaos3.fkp.physik.tu-darmstadt.de/maniphest/task/edit/?'
for k, v in form_entries.items():
full_url += f'{k}={v}&'
full_url.replace(' ', '+')
import webbrowser
webbrowser.open(full_url)
if __name__ == '__main__':
app = QtWidgets.QApplication([])
w = UpdateDialog()
w.show()
sys.exit(app.exec())

View File

@ -5,10 +5,9 @@ import re
from pathlib import Path from pathlib import Path
from numpy import geomspace, linspace from numpy import geomspace, linspace
from pyqtgraph import ViewBox, PlotDataItem from pyqtgraph import ViewBox
from nmreval.configs import * from nmreval.configs import *
from nmreval.lib.utils import open_bug_report
from .management import UpperManagement from .management import UpperManagement
from ..Qt import QtCore, QtGui, QtPrintSupport, QtWidgets from ..Qt import QtCore, QtGui, QtPrintSupport, QtWidgets
@ -28,6 +27,7 @@ from ..math.smooth import QSmooth
from ..nmr.coupling_calc import QCoupCalcDialog from ..nmr.coupling_calc import QCoupCalcDialog
from ..nmr.t1_from_tau import QRelaxCalc from ..nmr.t1_from_tau import QRelaxCalc
from .._py.basewindow import Ui_BaseWindow from .._py.basewindow import Ui_BaseWindow
from ..lib.utils import UpdateDialog, open_bug_report
class NMRMainWindow(QtWidgets.QMainWindow, Ui_BaseWindow): class NMRMainWindow(QtWidgets.QMainWindow, Ui_BaseWindow):
@ -218,13 +218,13 @@ class NMRMainWindow(QtWidgets.QMainWindow, Ui_BaseWindow):
@QtCore.pyqtSlot(name='on_action_open_triggered') @QtCore.pyqtSlot(name='on_action_open_triggered')
def open(self): def open(self):
filedialog = OpenFileDialog(directory=self.path, caption='Open files', filedialog = OpenFileDialog(directory=self.path, caption='Open files',
filters='All files (*.*);;' filter='All files (*.*);;'
'Program session (*.nmr);;' 'Program session (*.nmr);;'
'HDF files (*.h5);;' 'HDF files (*.h5);;'
'Text files (*.txt *.dat);;' 'Text files (*.txt *.dat);;'
'Novocontrol Alpha (*.EPS);;' 'Novocontrol Alpha (*.EPS);;'
'TecMag files (*.tnt);;' 'TecMag files (*.tnt);;'
'Grace files (*.agr)') 'Grace files (*.agr)')
filedialog.set_graphs(self.management.graphs.list()) filedialog.set_graphs(self.management.graphs.list())
@ -250,9 +250,7 @@ class NMRMainWindow(QtWidgets.QMainWindow, Ui_BaseWindow):
@QtCore.pyqtSlot(name='on_actionExportData_triggered') @QtCore.pyqtSlot(name='on_actionExportData_triggered')
@QtCore.pyqtSlot(name='on_actionSave_triggered') @QtCore.pyqtSlot(name='on_actionSave_triggered')
def save(self): def save(self):
save_dialog = SaveDirectoryDialog( save_dialog = SaveDirectoryDialog(directory=str(self.path), parent=self)
directory=str(self.path), parent=self,
)
mode = save_dialog.exec() mode = save_dialog.exec()
if mode == QtWidgets.QDialog.Accepted: if mode == QtWidgets.QDialog.Accepted:
@ -274,12 +272,14 @@ class NMRMainWindow(QtWidgets.QMainWindow, Ui_BaseWindow):
@QtCore.pyqtSlot() @QtCore.pyqtSlot()
@QtCore.pyqtSlot(list) @QtCore.pyqtSlot(list)
def save_fit_parameter(self, fit_sets: list[str] = None): def save_fit_parameter(self, fit_sets: list[str] = None):
fname, _ = QtWidgets.QFileDialog.getSaveFileName(self, 'Save fit parameter', directory=str(self.path), save_dialog = SaveDirectoryDialog(parent=self, caption='Save fit parameter', directory=str(self.path),
filter='All files(*, *);;Text files(*.dat *.txt)', filter='All files(*, *);;Text files(*.dat *.txt)')
options=QtWidgets.QFileDialog.DontConfirmOverwrite)
if fname: mode = save_dialog.exec()
self.management.save_fit_parameter(fname, fit_sets=fit_sets) if mode == QtWidgets.QDialog.Accepted:
savefile = save_dialog.save_file()
if savefile:
self.management.save_fit_parameter(savefile, fit_sets=fit_sets)
@QtCore.pyqtSlot(name='on_actionExportGraphic_triggered') @QtCore.pyqtSlot(name='on_actionExportGraphic_triggered')
def export_graphic(self): def export_graphic(self):
@ -988,14 +988,19 @@ class NMRMainWindow(QtWidgets.QMainWindow, Ui_BaseWindow):
dialog.show() dialog.show()
def close(self): def close(self):
write_state({'recent_path': str(self.path)}) write_state({'History': {'recent path': str(self.path)}})
super().close() super().close()
def read_state(self): def read_state(self):
opts = read_state() opts = read_state()
self.path = pathlib.Path(opts.get('recent_path', Path.home())) self.path = pathlib.Path(opts['History'].get('recent path', Path.home()))
@QtCore.pyqtSlot(name='on_actionBugs_triggered') @QtCore.pyqtSlot(name='on_actionBugs_triggered')
def report_bug(self): def report_bug(self):
open_bug_report() open_bug_report()
@QtCore.pyqtSlot(name='on_actionUpdate_triggered')
def look_for_update(self):
w = UpdateDialog(parent=self)
w.show()

View File

@ -5,7 +5,7 @@ from shutil import copyfile
from importlib.resources import path as resource_path from importlib.resources import path as resource_path
__all__ = ['config_paths', 'check_for_config', 'read_configuration', 'write_configuration', 'allowed_values', 'write_state', 'read_state'] __all__ = ['config_paths', 'check_for_config', 'read_configuration', 'write_configuration', 'ALLOWED_VALUE', 'write_state', 'read_state']
def check_for_config(make=True): def check_for_config(make=True):
@ -18,14 +18,13 @@ def check_for_config(make=True):
cwd = pathlib.Path(__file__).parent cwd = pathlib.Path(__file__).parent
copyfile(cwd / 'models' / 'usermodels.py', conf_path / 'usermodels.py') copyfile(cwd / 'models' / 'usermodels.py', conf_path / 'usermodels.py')
with resource_path('resources', 'Default.agr') as fp: with resource_path('resources', 'Default.agr') as fp:
copyfile(fp, conf_path / 'Default.agr') copyfile(fp, conf_path / 'Default.agr')
else: else:
raise e raise e
def config_paths() -> pathlib.Path: def config_paths() -> pathlib.Path:
# TODO adjust for different OS
searchpaths = ['~/.config/nmreval', '~/.auswerten', '/usr/share/nmreval'] searchpaths = ['~/.config/nmreval', '~/.auswerten', '/usr/share/nmreval']
conf_path = None conf_path = None
@ -46,8 +45,6 @@ def read_configuration() -> configparser.ConfigParser:
config_file = config_paths() / 'nmreval.cfg' config_file = config_paths() / 'nmreval.cfg'
if not config_file.exists(): if not config_file.exists():
write_configuration({'GUI': {'theme': 'normal', 'color': 'light'}}) write_configuration({'GUI': {'theme': 'normal', 'color': 'light'}})
# raise FileNotFoundError('Configuration file not found')
#
except FileNotFoundError as e: except FileNotFoundError as e:
raise e raise e
@ -67,24 +64,38 @@ def write_configuration(opts: dict):
parser.write(f) parser.write(f)
allowed_values = { ALLOWED_VALUE = {
('GUI', 'theme'): ['normal', 'pokemon'], ('GUI', 'theme'): ['normal', 'pokemon'],
('GUI', 'color'): ['light', 'dark'], ('GUI', 'color'): ['light', 'dark'],
} }
def write_state(opts: dict): def write_state(new_opts: dict):
config_file = config_paths() / 'guistate.ini' config_file = config_paths() / 'guistate.ini'
old_opts = read_state() opts = read_state()
old_opts.update(opts) opts.update(new_opts)
with config_file.open('wb') as f:
pickle.dump(old_opts, f) parser = configparser.ConfigParser()
parser.read_dict(opts)
with config_file.open('w') as f:
parser.write(f)
def read_state() -> dict: def read_state() -> dict:
config_file = config_paths() / 'guistate.ini' config_file = config_paths() / 'guistate.ini'
if not config_file.exists(): if not config_file.exists():
return {} return {'History': {'recent path': pathlib.Path.home()}}
with config_file.open('rb') as f: with config_file.open('rb') as f:
return pickle.load(f) try:
opts = pickle.load(f)
opts['recent path'] = opts.get('recent_path', pathlib.Path.home())
return {'History': opts}
except pickle.UnpicklingError:
parser = configparser.ConfigParser()
parser.read(config_file)
return parser

View File

@ -5,34 +5,14 @@ from ..math.mittagleffler import mlf
ArrayLike = TypeVar('ArrayLike') ArrayLike = TypeVar('ArrayLike')
#
def valid_function(expr: str, extra_namespace: dict = None): # def valid_function(expr: str, extra_namespace: dict = None):
#
local = {'mlf': mlf} # local = {'mlf': mlf}
if extra_namespace is not None: # if extra_namespace is not None:
local.update(extra_namespace) # local.update(extra_namespace)
#
try: # try:
return ne.evaluate(expr, {}, local), True # return ne.evaluate(expr, {}, local), True
except: # except:
return None, False # return None, False
def open_bug_report():
form_entries = {
'description': 'Please state the nature of the medical emergency.',
'title': 'Everything is awesome?',
'assign[0]': 'dominik',
'subscribers[0]': 'dominik',
'tag': 'nmreval',
'priority': 'normal',
'status': 'open',
}
full_url = 'https://chaos3.fkp.physik.tu-darmstadt.de/maniphest/task/edit/?'
for k, v in form_entries.items():
full_url += f'{k}={v}&'
full_url.replace(' ', '+')
import webbrowser
webbrowser.open(full_url)

View File

@ -136,7 +136,7 @@
<x>0</x> <x>0</x>
<y>0</y> <y>0</y>
<width>1386</width> <width>1386</width>
<height>30</height> <height>20</height>
</rect> </rect>
</property> </property>
<widget class="QMenu" name="menuFile"> <widget class="QMenu" name="menuFile">
@ -501,10 +501,6 @@
<addaction name="action_sort_pts"/> <addaction name="action_sort_pts"/>
</widget> </widget>
<action name="action_close"> <action name="action_close">
<property name="icon">
<iconset theme="window-close">
<normaloff>../../../../../.designer/backup</normaloff>../../../../../.designer/backup</iconset>
</property>
<property name="text"> <property name="text">
<string>&amp;Quit</string> <string>&amp;Quit</string>
</property> </property>
@ -542,10 +538,6 @@
</property> </property>
</action> </action>
<action name="action_delete_sets"> <action name="action_delete_sets">
<property name="icon">
<iconset theme="edit-delete">
<normaloff>../../../../../.designer/backup</normaloff>../../../../../.designer/backup</iconset>
</property>
<property name="text"> <property name="text">
<string>&amp;Delete Set</string> <string>&amp;Delete Set</string>
</property> </property>
@ -564,10 +556,6 @@
</property> </property>
</action> </action>
<action name="action_reset"> <action name="action_reset">
<property name="icon">
<iconset theme="edit-clear">
<normaloff>../../../../../../../../.designer/backup</normaloff>../../../../../../../../.designer/backup</iconset>
</property>
<property name="text"> <property name="text">
<string>&amp;Reset</string> <string>&amp;Reset</string>
</property> </property>
@ -576,10 +564,6 @@
</property> </property>
</action> </action>
<action name="actionDocumentation"> <action name="actionDocumentation">
<property name="icon">
<iconset theme="help-about">
<normaloff>../../../../../../../../.designer/backup</normaloff>../../../../../../../../.designer/backup</iconset>
</property>
<property name="text"> <property name="text">
<string>&amp;Documentation</string> <string>&amp;Documentation</string>
</property> </property>
@ -705,10 +689,6 @@
</property> </property>
</action> </action>
<action name="actionRefresh"> <action name="actionRefresh">
<property name="icon">
<iconset theme="view-refresh">
<normaloff>../../../../../.designer/backup</normaloff>../../../../../.designer/backup</iconset>
</property>
<property name="text"> <property name="text">
<string>Refresh</string> <string>Refresh</string>
</property> </property>
@ -779,10 +759,6 @@
</property> </property>
</action> </action>
<action name="actionDelete_window"> <action name="actionDelete_window">
<property name="icon">
<iconset theme="edit-delete">
<normaloff>../../../../../.designer/backup</normaloff>../../../../../.designer/backup</iconset>
</property>
<property name="text"> <property name="text">
<string>Delete graph</string> <string>Delete graph</string>
</property> </property>
@ -914,10 +890,6 @@
</property> </property>
</action> </action>
<action name="actionPrint"> <action name="actionPrint">
<property name="icon">
<iconset theme="document-print">
<normaloff>../../../.designer/backup</normaloff>../../../.designer/backup</iconset>
</property>
<property name="text"> <property name="text">
<string>Print...</string> <string>Print...</string>
</property> </property>