nmreval/src/gui_qt/io/asciireader.py
Dominik Demuth af0e0fc76f
All checks were successful
Build AppImage / Explore-Gitea-Actions (push) Successful in 2m3s
#177 (#178)
closes issue #177
2023-12-13 19:10:01 +00:00

281 lines
10 KiB
Python

from __future__ import annotations
import re
from nmreval.io.asciireader import AsciiReader
from nmreval.utils import NUMBER_RE
from ..Qt import QtGui, QtCore, QtWidgets
from .._py.asciidialog import Ui_ascii_reader
class QAsciiReader(QtWidgets.QDialog, Ui_ascii_reader):
data_read = QtCore.pyqtSignal(list)
file_ext = ['.dat', '.txt']
def __init__(self, fname=None, parent=None):
super().__init__(parent=parent)
self.setupUi(self)
self.reader = None
self._matches = []
self.regex_input.setText(NUMBER_RE.pattern)
self.custom_input.setValidator(QtGui.QDoubleValidator())
self.comment_textfield = QtWidgets.QPlainTextEdit(self.header_widget)
self.comment_textfield.setReadOnly(True)
pal = QtWidgets.QApplication.instance().palette()
rgb = pal.color(pal.Base).getRgb()[:3]
rgb2 = pal.color(pal.Text).getRgb()[:3]
self.comment_textfield.setStyleSheet(f'background-color: rgb{rgb}; color: rgb{rgb2};')
self.header_widget.setText('Header')
self.header_widget.addWidget(self.comment_textfield)
self.ascii_table.horizontalHeader().setStretchLastSection(True)
self.buttonbox.button(QtWidgets.QDialogButtonBox.Apply).clicked.connect(self.apply)
self.buttonbox.button(QtWidgets.QDialogButtonBox.Ok).clicked.connect(self.accept)
self.changestaggeredrange(0)
self.ascii_table.contextMenuEvent = self.ctx_table
self.ascii_table.horizontalHeader().setContextMenuPolicy(QtCore.Qt.CustomContextMenu)
self.ascii_table.horizontalHeader().customContextMenuRequested.connect(self.ctx_table)
self.skip = False
if fname is not None:
self.__call__(fname)
def __call__(self, fname, *args, **kwargs):
self.reader = AsciiReader(fname)
self.check_filename(self.regex_input.text())
if self.skip:
self.accept()
return
for i in [self.stag_lineEdit, self.start_lineedit, self.end_lineedit,
self.comment_textfield, self.ascii_table, self.delay_lineedit]:
i.clear()
self.staggered_checkBox.setChecked(False)
self.log_checkBox.setChecked(False)
self.set_gui()
self.set_column_names(1)
self.skippy_checkbox.blockSignals(True)
self.skippy_checkbox.setCheckState(QtCore.Qt.Unchecked)
self.skippy_checkbox.blockSignals(False)
return self
def set_gui(self):
for text in self.reader.header:
self.comment_textfield.appendPlainText(text)
tmp = self.line_spinBox.value()
if self.reader.header:
self.line_spinBox.setMaximum(len(self.reader.header))
else:
self.line_spinBox.setValue(0)
self.line_spinBox.setEnabled(False)
self.show_preview(10)
self.line_spinBox.setValue(tmp)
if self.reader.delays is not None:
set_string = ''.join(str(d) + '\n' for d in self.reader.delays)
self.delay_textfield.setPlainText(set_string)
self.delay_lineedit.setText(str(len(self.reader.delays)))
def ctx_table(self, _):
menu = QtWidgets.QMenu(self)
x_action = QtWidgets.QAction('Set as x', self)
x_action.triggered.connect(lambda: self.set_columns('x'))
y_action = QtWidgets.QAction('Set as y', self)
y_action.triggered.connect(lambda: self.set_columns('y'))
menu.addActions([x_action, y_action])
if self.deltay_lineEdit.isEnabled():
yerr_action = QtWidgets.QAction('Set as \u0394y', self)
yerr_action.triggered.connect(lambda: self.set_columns('yerr'))
menu.addAction(yerr_action)
menu.popup(QtGui.QCursor.pos())
def set_columns(self, mode: str):
cols = ' '.join([str(s.column()+1) for s in self.ascii_table.selectionModel().selectedColumns()])
try:
lineedit = {'x': self.x_lineedit, 'y': self.y_lineedit, 'yerr': self.deltay_lineEdit}[mode]
lineedit.setText(cols)
except KeyError:
pass
@QtCore.pyqtSlot(int, name='on_preview_spinBox_valueChanged')
def show_preview(self, line_no: int):
preview, width, comments = self.reader.make_preview(line_no)
self.ascii_table.setRowCount(min(line_no, len(preview)))
self.ascii_table.setColumnCount(width + 1)
for i, line in enumerate(preview):
comment_line = comments[i]
for j, field in enumerate(line):
it = QtWidgets.QTableWidgetItem(field)
self.ascii_table.setItem(i, j, it)
it = QtWidgets.QTableWidgetItem(comment_line)
self.ascii_table.setItem(i, len(line), it)
self.ascii_table.resizeColumnsToContents()
@QtCore.pyqtSlot(int, name='on_column_checkBox_stateChanged')
@QtCore.pyqtSlot(int, name='on_line_spinBox_valueChanged')
def set_column_names(self, _):
self.ascii_table.setHorizontalHeaderLabels(map(str, range(1, self.ascii_table.columnCount() + 1)))
if self.column_checkBox.isChecked() and self.line_spinBox.isEnabled():
header_line = self.reader.header[self.line_spinBox.value()-1]
self.ascii_table.setHorizontalHeaderLabels(header_line.split())
@QtCore.pyqtSlot(int, name='on_staggered_checkBox_stateChanged')
def changestaggeredrange(self, state: int):
self.stag_lineEdit.setEnabled(state)
@QtCore.pyqtSlot(name='on_pushButton_clicked')
def calc_delays(self):
try:
start = float(self.start_lineedit.text())
stop = float(self.end_lineedit.text())
num_delays = int(self.delay_lineedit.text())
is_staggered = self.staggered_checkBox.isChecked()
if is_staggered:
staggered_range = int(self.stag_lineEdit.text())
else:
staggered_range = 0
except ValueError:
_ = QtWidgets.QMessageBox.information(self, 'No delays',
'Delays cannot be calculated: Not enough or wrong arguments.')
return
self.reader.calc_delays(start, stop, num_delays, log=self.log_checkBox.isChecked(),
stagg=staggered_range, stag_size=staggered_range)
if self.reader.delays is not None:
set_string = ''.join(str(d) + '\n' for d in self.reader.delays)
self.delay_textfield.setPlainText(set_string)
@QtCore.pyqtSlot(name='on_delay_textfield_textChanged')
def delays_changed(self):
new_delays = str(self.delay_textfield.toPlainText()).rstrip('\n').split('\n')
self.delay_lineedit.setText(str(len(new_delays)))
if new_delays[0] == '':
new_delays = None
else:
for k, v in enumerate(new_delays):
try:
new_delays[k] = float(v)
except ValueError:
new_delays[k] = -1
self.reader.delays = new_delays
@QtCore.pyqtSlot()
def accept(self):
if self.apply():
self.close()
def apply(self):
# default row for x is the first row, it will be superseded if an integer number is given.
x = self.x_lineedit.text()
if x:
try:
x = int(x)-1
except ValueError:
pass
else:
x = None
try:
y = [int(t)-1 for t in self.y_lineedit.text().split(' ')]
except ValueError:
y = None
try:
y_err = [int(t)-1 for t in self.deltay_lineEdit.text().split(' ')]
except ValueError:
y_err = None
col_header = None
if self.column_checkBox.isChecked():
col_header = []
for i in range(self.ascii_table.columnCount()):
if self.ascii_table.horizontalHeaderItem(i) is not None:
col_header.append(self.ascii_table.horizontalHeaderItem(i).text())
else:
col_header.append(i)
if y is not None and col_header is not None:
col_header = [col_header[i] for i in range(len(col_header))]
try:
ret_dic = self.reader.export(
x=x,
y=y,
yerr=y_err,
mode=self.buttonGroup.checkedButton().text(),
col_names=col_header,
num_value=self.get_numerical_value(),
)
self.data_read.emit(ret_dic)
except ImportError as e:
_ = QtWidgets.QMessageBox.information(self, 'Reading failed',
f'Import data failed with {e.args}')
return True
@QtCore.pyqtSlot(int, name='on_buttonGroup_buttonClicked')
def show_error(self, val: int):
self.deltay_lineEdit.setEnabled(val == -3)
@QtCore.pyqtSlot(int, name='on_skippy_checkbox_stateChanged')
def skip_next_dial(self, _: int):
self.skip = self.skippy_checkbox.isChecked()
@QtCore.pyqtSlot(str, name='on_regex_input_textChanged')
def check_filename(self, pattern: str = NUMBER_RE.pattern):
if self.reader is None:
return
try:
pattern = re.compile(pattern)
self.regex_input.setStyleSheet('color: rgb(0, 0, 0)')
self._matches = [m for m in pattern.finditer(str(self.reader.fname.stem))]
except re.error:
self._matches = []
if self._matches:
self.re_match_index.blockSignals(True)
self.re_match_index.setMaximum(len(self._matches))
self.re_match_index.blockSignals(False)
else:
self.regex_input.setStyleSheet('color: rgb(255, 0, 0)')
self.show_match(self.re_match_index.value())
@QtCore.pyqtSlot(int, name='on_re_match_index_valueChanged')
def show_match(self, idx: int = 0):
fname = str(self.reader.fname.stem)
if self._matches:
m = self._matches[idx-1]
self.label_8.setText(f'{fname[:m.start()]}<b>{fname[m.start():m.end()]}</b>{fname[m.end():]}')
else:
self.label_8.setText(fname)
def get_numerical_value(self):
val = 0
if self.re_button.isChecked() and self._matches:
m = self._matches[self.re_match_index.value()-1]
val = float(NUMBER_RE.search(m.group()).group().replace('p', '.'))
elif self.custom_button.isChecked():
val = float(self.custom_input.text())
return val