forked from IPKM/nmreval
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
|
# 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-scipy
|
||||||
# - python3-matplotlib
|
# - python3-matplotlib
|
||||||
# - python-matplotlib-data
|
# - python-matplotlib-data
|
||||||
- python3-scipy
|
|
||||||
- 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
|
||||||
|
@ -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()
|
||||||
|
@ -5,4 +5,4 @@ PyQt5
|
|||||||
h5py
|
h5py
|
||||||
pyqtgraph
|
pyqtgraph
|
||||||
bsddb3
|
bsddb3
|
||||||
|
requests
|
||||||
|
@ -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):
|
||||||
|
@ -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:
|
||||||
|
if self.is_valid(outfile[0]):
|
||||||
return pathlib.Path(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)'])
|
||||||
|
|
||||||
|
@ -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)
|
||||||
|
@ -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())
|
||||||
|
@ -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,7 +218,7 @@ 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);;'
|
||||||
@ -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()
|
||||||
|
@ -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):
|
||||||
@ -25,7 +25,6 @@ def check_for_config(make=True):
|
|||||||
|
|
||||||
|
|
||||||
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
|
||||||
|
|
||||||
|
@ -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)
|
|
||||||
|
@ -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>&Quit</string>
|
<string>&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>&Delete Set</string>
|
<string>&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>&Reset</string>
|
<string>&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>&Documentation</string>
|
<string>&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>
|
||||||
|
Loading…
Reference in New Issue
Block a user