crude updater added; write_state uses plain text, not pickle; disallow invalid characters; T246
This commit is contained in:
parent
2ed390ccae
commit
04eb83a19d
@ -36,18 +36,21 @@ AppDir:
|
||||
# for /usr/bin/env
|
||||
- coreutils
|
||||
- dash
|
||||
- tango-icon-theme
|
||||
- zsync
|
||||
- hicolor-icon-theme
|
||||
- libatlas3-base
|
||||
- python3.9-minimal
|
||||
- python3-pyqt5
|
||||
- python3-numpy
|
||||
#- python3-matplotlib
|
||||
#- python-matplotlib-data
|
||||
- python3-scipy
|
||||
# - python3-matplotlib
|
||||
# - python-matplotlib-data
|
||||
- python3-bsddb3
|
||||
- python3-h5py
|
||||
- python3-pyqt5
|
||||
- python3-pyqtgraph
|
||||
- python3-tk
|
||||
- python3-requests
|
||||
- python3-urllib3
|
||||
# - python3-tk
|
||||
exclude:
|
||||
- libavahi-client3
|
||||
- libavahi-common-data
|
||||
|
@ -16,9 +16,10 @@ from nmreval.lib.logger import handle_exception
|
||||
sys.excepthook = handle_exception
|
||||
|
||||
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.show()
|
||||
|
@ -5,4 +5,4 @@ PyQt5
|
||||
h5py
|
||||
pyqtgraph
|
||||
bsddb3
|
||||
|
||||
requests
|
||||
|
@ -1,8 +1,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
|
||||
# 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)
|
||||
BaseWindow.setCentralWidget(self.centralwidget)
|
||||
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.menuFile = QtWidgets.QMenu(self.menubar)
|
||||
self.menuFile.setObjectName("menuFile")
|
||||
@ -179,8 +179,6 @@ class Ui_BaseWindow(object):
|
||||
self.toolBar_data.setObjectName("toolBar_data")
|
||||
BaseWindow.addToolBar(QtCore.Qt.TopToolBarArea, self.toolBar_data)
|
||||
self.action_close = QtWidgets.QAction(BaseWindow)
|
||||
icon = QtGui.QIcon.fromTheme("window-close")
|
||||
self.action_close.setIcon(icon)
|
||||
self.action_close.setObjectName("action_close")
|
||||
self.actionExportGraphic = QtWidgets.QAction(BaseWindow)
|
||||
self.actionExportGraphic.setObjectName("actionExportGraphic")
|
||||
@ -191,20 +189,14 @@ class Ui_BaseWindow(object):
|
||||
self.action_calc = QtWidgets.QAction(BaseWindow)
|
||||
self.action_calc.setObjectName("action_calc")
|
||||
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_save_fit_parameter = QtWidgets.QAction(BaseWindow)
|
||||
self.action_save_fit_parameter.setObjectName("action_save_fit_parameter")
|
||||
self.action_sort_pts = QtWidgets.QAction(BaseWindow)
|
||||
self.action_sort_pts.setObjectName("action_sort_pts")
|
||||
self.action_reset = QtWidgets.QAction(BaseWindow)
|
||||
icon = QtGui.QIcon.fromTheme("edit-clear")
|
||||
self.action_reset.setIcon(icon)
|
||||
self.action_reset.setObjectName("action_reset")
|
||||
self.actionDocumentation = QtWidgets.QAction(BaseWindow)
|
||||
icon = QtGui.QIcon.fromTheme("help-about")
|
||||
self.actionDocumentation.setIcon(icon)
|
||||
self.actionDocumentation.setObjectName("actionDocumentation")
|
||||
self.action_FitWidget = QtWidgets.QAction(BaseWindow)
|
||||
self.action_FitWidget.setObjectName("action_FitWidget")
|
||||
@ -250,8 +242,6 @@ class Ui_BaseWindow(object):
|
||||
self.actionConfiguration = QtWidgets.QAction(BaseWindow)
|
||||
self.actionConfiguration.setObjectName("actionConfiguration")
|
||||
self.actionRefresh = QtWidgets.QAction(BaseWindow)
|
||||
icon = QtGui.QIcon.fromTheme("view-refresh")
|
||||
self.actionRefresh.setIcon(icon)
|
||||
self.actionRefresh.setObjectName("actionRefresh")
|
||||
self.actionInterpolation = QtWidgets.QAction(BaseWindow)
|
||||
self.actionInterpolation.setObjectName("actionInterpolation")
|
||||
@ -278,8 +268,6 @@ class Ui_BaseWindow(object):
|
||||
self.actionNew_window = QtWidgets.QAction(BaseWindow)
|
||||
self.actionNew_window.setObjectName("actionNew_window")
|
||||
self.actionDelete_window = QtWidgets.QAction(BaseWindow)
|
||||
icon = QtGui.QIcon.fromTheme("edit-delete")
|
||||
self.actionDelete_window.setIcon(icon)
|
||||
self.actionDelete_window.setObjectName("actionDelete_window")
|
||||
self.actionCascade_windows = QtWidgets.QAction(BaseWindow)
|
||||
self.actionCascade_windows.setObjectName("actionCascade_windows")
|
||||
@ -330,8 +318,6 @@ class Ui_BaseWindow(object):
|
||||
self.actionChange_datatypes = QtWidgets.QAction(BaseWindow)
|
||||
self.actionChange_datatypes.setObjectName("actionChange_datatypes")
|
||||
self.actionPrint = QtWidgets.QAction(BaseWindow)
|
||||
icon = QtGui.QIcon.fromTheme("document-print")
|
||||
self.actionPrint.setIcon(icon)
|
||||
self.actionPrint.setObjectName("actionPrint")
|
||||
self.action_lm_fit = QtWidgets.QAction(BaseWindow)
|
||||
self.action_lm_fit.setCheckable(True)
|
||||
@ -494,7 +480,7 @@ class Ui_BaseWindow(object):
|
||||
|
||||
self.retranslateUi(BaseWindow)
|
||||
self.tabWidget.setCurrentIndex(0)
|
||||
self.action_close.triggered.connect(BaseWindow.close)
|
||||
self.action_close.triggered.connect(BaseWindow.close) # type: ignore
|
||||
QtCore.QMetaObject.connectSlotsByName(BaseWindow)
|
||||
|
||||
def retranslateUi(self, BaseWindow):
|
||||
|
@ -8,15 +8,18 @@ from ..Qt import QtWidgets, QtCore
|
||||
class FileDialog(QtWidgets.QFileDialog):
|
||||
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)
|
||||
|
||||
self.setOption(QtWidgets.QFileDialog.DontUseNativeDialog, True)
|
||||
|
||||
self.setWindowTitle(caption)
|
||||
if directory is not None:
|
||||
if 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.setSortingEnabled(True)
|
||||
@ -35,13 +38,30 @@ class FileDialog(QtWidgets.QFileDialog):
|
||||
def save_file(self) -> pathlib.Path | None:
|
||||
outfile = self.selectedFiles()
|
||||
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
|
||||
|
||||
@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):
|
||||
def __init__(self, directory=None, caption=None, filters='', parent=None):
|
||||
super().__init__(directory=directory, caption=caption, filters=filters, parent=parent)
|
||||
def __init__(self, **kwargs):
|
||||
super().__init__(**kwargs)
|
||||
|
||||
self.setFileMode(QtWidgets.QFileDialog.ExistingFiles)
|
||||
|
||||
@ -81,8 +101,8 @@ class OpenFileDialog(FileDialog):
|
||||
|
||||
|
||||
class SaveDirectoryDialog(FileDialog):
|
||||
def __init__(self, directory=None, filters='', parent=None):
|
||||
super().__init__(directory=directory, filters=filters, parent=parent)
|
||||
def __init__(self, **kwargs):
|
||||
super().__init__(**kwargs)
|
||||
|
||||
self.setOption(QtWidgets.QFileDialog.DontConfirmOverwrite, False)
|
||||
self.setAcceptMode(QtWidgets.QFileDialog.AcceptSave)
|
||||
@ -123,4 +143,3 @@ class SaveDirectoryDialog(FileDialog):
|
||||
self.setWindowTitle('Save')
|
||||
self.setNameFilters(['All files (*.*)', 'Session file (*.nmr)', 'Text file (*.dat)',
|
||||
'HDF file (*.h5)', 'Grace files (*.agr)'])
|
||||
|
||||
|
@ -37,7 +37,6 @@ class GraceMsgBox(QtWidgets.QDialog):
|
||||
agr.parse(fname)
|
||||
|
||||
layout = QtWidgets.QGridLayout()
|
||||
layout.setContentsMargins(13, 13, 13, 13)
|
||||
self.setLayout(layout)
|
||||
|
||||
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):
|
||||
label = QtWidgets.QLabel(key.capitalize(), self)
|
||||
layout2.addWidget(label, row, 0)
|
||||
if (sec, key) in allowed_values:
|
||||
if (sec, key) in ALLOWED_VALUE:
|
||||
edit = QtWidgets.QComboBox(self)
|
||||
edit.addItems(allowed_values[(sec, key)])
|
||||
edit.addItems(ALLOWED_VALUE[(sec, key)])
|
||||
edit.setCurrentIndex(edit.findText(value))
|
||||
else:
|
||||
edit = QtWidgets.QLineEdit(self)
|
||||
|
@ -1,8 +1,15 @@
|
||||
import sys
|
||||
from os import getenv, stat
|
||||
import hashlib
|
||||
import subprocess
|
||||
from datetime import datetime
|
||||
from contextlib import contextmanager
|
||||
|
||||
import requests
|
||||
from numpy import linspace
|
||||
from scipy.interpolate import interp1d
|
||||
|
||||
from ..Qt import QtGui, QtWidgets, QtCore
|
||||
from gui_qt.Qt import QtGui, QtWidgets, QtCore
|
||||
|
||||
|
||||
@contextmanager
|
||||
@ -48,3 +55,162 @@ class RdBuCMap:
|
||||
col = QtGui.QColor.fromRgb(*(float(self.spline[i](val)) for i in range(3)))
|
||||
|
||||
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())
|
||||
|
@ -5,10 +5,9 @@ import re
|
||||
from pathlib import Path
|
||||
|
||||
from numpy import geomspace, linspace
|
||||
from pyqtgraph import ViewBox, PlotDataItem
|
||||
from pyqtgraph import ViewBox
|
||||
|
||||
from nmreval.configs import *
|
||||
from nmreval.lib.utils import open_bug_report
|
||||
|
||||
from .management import UpperManagement
|
||||
from ..Qt import QtCore, QtGui, QtPrintSupport, QtWidgets
|
||||
@ -28,6 +27,7 @@ from ..math.smooth import QSmooth
|
||||
from ..nmr.coupling_calc import QCoupCalcDialog
|
||||
from ..nmr.t1_from_tau import QRelaxCalc
|
||||
from .._py.basewindow import Ui_BaseWindow
|
||||
from ..lib.utils import UpdateDialog, open_bug_report
|
||||
|
||||
|
||||
class NMRMainWindow(QtWidgets.QMainWindow, Ui_BaseWindow):
|
||||
@ -218,13 +218,13 @@ class NMRMainWindow(QtWidgets.QMainWindow, Ui_BaseWindow):
|
||||
@QtCore.pyqtSlot(name='on_action_open_triggered')
|
||||
def open(self):
|
||||
filedialog = OpenFileDialog(directory=self.path, caption='Open files',
|
||||
filters='All files (*.*);;'
|
||||
'Program session (*.nmr);;'
|
||||
'HDF files (*.h5);;'
|
||||
'Text files (*.txt *.dat);;'
|
||||
'Novocontrol Alpha (*.EPS);;'
|
||||
'TecMag files (*.tnt);;'
|
||||
'Grace files (*.agr)')
|
||||
filter='All files (*.*);;'
|
||||
'Program session (*.nmr);;'
|
||||
'HDF files (*.h5);;'
|
||||
'Text files (*.txt *.dat);;'
|
||||
'Novocontrol Alpha (*.EPS);;'
|
||||
'TecMag files (*.tnt);;'
|
||||
'Grace files (*.agr)')
|
||||
|
||||
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_actionSave_triggered')
|
||||
def save(self):
|
||||
save_dialog = SaveDirectoryDialog(
|
||||
directory=str(self.path), parent=self,
|
||||
)
|
||||
save_dialog = SaveDirectoryDialog(directory=str(self.path), parent=self)
|
||||
|
||||
mode = save_dialog.exec()
|
||||
if mode == QtWidgets.QDialog.Accepted:
|
||||
@ -274,12 +272,14 @@ class NMRMainWindow(QtWidgets.QMainWindow, Ui_BaseWindow):
|
||||
@QtCore.pyqtSlot()
|
||||
@QtCore.pyqtSlot(list)
|
||||
def save_fit_parameter(self, fit_sets: list[str] = None):
|
||||
fname, _ = QtWidgets.QFileDialog.getSaveFileName(self, 'Save fit parameter', directory=str(self.path),
|
||||
filter='All files(*, *);;Text files(*.dat *.txt)',
|
||||
options=QtWidgets.QFileDialog.DontConfirmOverwrite)
|
||||
save_dialog = SaveDirectoryDialog(parent=self, caption='Save fit parameter', directory=str(self.path),
|
||||
filter='All files(*, *);;Text files(*.dat *.txt)')
|
||||
|
||||
if fname:
|
||||
self.management.save_fit_parameter(fname, fit_sets=fit_sets)
|
||||
mode = save_dialog.exec()
|
||||
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')
|
||||
def export_graphic(self):
|
||||
@ -988,14 +988,19 @@ class NMRMainWindow(QtWidgets.QMainWindow, Ui_BaseWindow):
|
||||
dialog.show()
|
||||
|
||||
def close(self):
|
||||
write_state({'recent_path': str(self.path)})
|
||||
write_state({'History': {'recent path': str(self.path)}})
|
||||
|
||||
super().close()
|
||||
|
||||
def read_state(self):
|
||||
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')
|
||||
def report_bug(self):
|
||||
open_bug_report()
|
||||
|
||||
@QtCore.pyqtSlot(name='on_actionUpdate_triggered')
|
||||
def look_for_update(self):
|
||||
w = UpdateDialog(parent=self)
|
||||
w.show()
|
||||
|
@ -5,7 +5,7 @@ from shutil import copyfile
|
||||
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):
|
||||
@ -18,14 +18,13 @@ def check_for_config(make=True):
|
||||
cwd = pathlib.Path(__file__).parent
|
||||
copyfile(cwd / 'models' / 'usermodels.py', conf_path / 'usermodels.py')
|
||||
with resource_path('resources', 'Default.agr') as fp:
|
||||
copyfile(fp, conf_path / 'Default.agr')
|
||||
copyfile(fp, conf_path / 'Default.agr')
|
||||
|
||||
else:
|
||||
raise e
|
||||
|
||||
|
||||
def config_paths() -> pathlib.Path:
|
||||
# TODO adjust for different OS
|
||||
searchpaths = ['~/.config/nmreval', '~/.auswerten', '/usr/share/nmreval']
|
||||
conf_path = None
|
||||
|
||||
@ -46,8 +45,6 @@ def read_configuration() -> configparser.ConfigParser:
|
||||
config_file = config_paths() / 'nmreval.cfg'
|
||||
if not config_file.exists():
|
||||
write_configuration({'GUI': {'theme': 'normal', 'color': 'light'}})
|
||||
# raise FileNotFoundError('Configuration file not found')
|
||||
#
|
||||
except FileNotFoundError as e:
|
||||
raise e
|
||||
|
||||
@ -67,24 +64,38 @@ def write_configuration(opts: dict):
|
||||
parser.write(f)
|
||||
|
||||
|
||||
allowed_values = {
|
||||
ALLOWED_VALUE = {
|
||||
('GUI', 'theme'): ['normal', 'pokemon'],
|
||||
('GUI', 'color'): ['light', 'dark'],
|
||||
}
|
||||
|
||||
|
||||
def write_state(opts: dict):
|
||||
def write_state(new_opts: dict):
|
||||
config_file = config_paths() / 'guistate.ini'
|
||||
old_opts = read_state()
|
||||
old_opts.update(opts)
|
||||
with config_file.open('wb') as f:
|
||||
pickle.dump(old_opts, f)
|
||||
opts = read_state()
|
||||
opts.update(new_opts)
|
||||
|
||||
parser = configparser.ConfigParser()
|
||||
parser.read_dict(opts)
|
||||
|
||||
with config_file.open('w') as f:
|
||||
parser.write(f)
|
||||
|
||||
|
||||
def read_state() -> dict:
|
||||
config_file = config_paths() / 'guistate.ini'
|
||||
if not config_file.exists():
|
||||
return {}
|
||||
return {'History': {'recent path': pathlib.Path.home()}}
|
||||
|
||||
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
|
||||
|
||||
|
@ -5,34 +5,14 @@ from ..math.mittagleffler import mlf
|
||||
|
||||
ArrayLike = TypeVar('ArrayLike')
|
||||
|
||||
|
||||
def valid_function(expr: str, extra_namespace: dict = None):
|
||||
|
||||
local = {'mlf': mlf}
|
||||
if extra_namespace is not None:
|
||||
local.update(extra_namespace)
|
||||
|
||||
try:
|
||||
return ne.evaluate(expr, {}, local), True
|
||||
except:
|
||||
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)
|
||||
#
|
||||
# def valid_function(expr: str, extra_namespace: dict = None):
|
||||
#
|
||||
# local = {'mlf': mlf}
|
||||
# if extra_namespace is not None:
|
||||
# local.update(extra_namespace)
|
||||
#
|
||||
# try:
|
||||
# return ne.evaluate(expr, {}, local), True
|
||||
# except:
|
||||
# return None, False
|
||||
|
@ -136,7 +136,7 @@
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>1386</width>
|
||||
<height>30</height>
|
||||
<height>20</height>
|
||||
</rect>
|
||||
</property>
|
||||
<widget class="QMenu" name="menuFile">
|
||||
@ -501,10 +501,6 @@
|
||||
<addaction name="action_sort_pts"/>
|
||||
</widget>
|
||||
<action name="action_close">
|
||||
<property name="icon">
|
||||
<iconset theme="window-close">
|
||||
<normaloff>../../../../../.designer/backup</normaloff>../../../../../.designer/backup</iconset>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>&Quit</string>
|
||||
</property>
|
||||
@ -542,10 +538,6 @@
|
||||
</property>
|
||||
</action>
|
||||
<action name="action_delete_sets">
|
||||
<property name="icon">
|
||||
<iconset theme="edit-delete">
|
||||
<normaloff>../../../../../.designer/backup</normaloff>../../../../../.designer/backup</iconset>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>&Delete Set</string>
|
||||
</property>
|
||||
@ -564,10 +556,6 @@
|
||||
</property>
|
||||
</action>
|
||||
<action name="action_reset">
|
||||
<property name="icon">
|
||||
<iconset theme="edit-clear">
|
||||
<normaloff>../../../../../../../../.designer/backup</normaloff>../../../../../../../../.designer/backup</iconset>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>&Reset</string>
|
||||
</property>
|
||||
@ -576,10 +564,6 @@
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionDocumentation">
|
||||
<property name="icon">
|
||||
<iconset theme="help-about">
|
||||
<normaloff>../../../../../../../../.designer/backup</normaloff>../../../../../../../../.designer/backup</iconset>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>&Documentation</string>
|
||||
</property>
|
||||
@ -705,10 +689,6 @@
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionRefresh">
|
||||
<property name="icon">
|
||||
<iconset theme="view-refresh">
|
||||
<normaloff>../../../../../.designer/backup</normaloff>../../../../../.designer/backup</iconset>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Refresh</string>
|
||||
</property>
|
||||
@ -779,10 +759,6 @@
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionDelete_window">
|
||||
<property name="icon">
|
||||
<iconset theme="edit-delete">
|
||||
<normaloff>../../../../../.designer/backup</normaloff>../../../../../.designer/backup</iconset>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Delete graph</string>
|
||||
</property>
|
||||
@ -914,10 +890,6 @@
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionPrint">
|
||||
<property name="icon">
|
||||
<iconset theme="document-print">
|
||||
<normaloff>../../../.designer/backup</normaloff>../../../.designer/backup</iconset>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Print...</string>
|
||||
</property>
|
||||
|
Loading…
Reference in New Issue
Block a user