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()]}{fname[m.start():m.end()]}{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