class Serafin:
    """!
    @brief A Serafin object corresponds to a single Serafin in file IO stream

    # Attributes:
    - language <str>: language for variables ('fr' or 'en')
    - mode <str>: file open mode
    - file <_io.BufferedReader>: input Serafin stream
    - filename <str>: path to file
    - file_size <int>: size of file
    """
    def __init__(self, filename, mode, language):
        self.language = language
        self.mode = mode

        self.file = None  # will be opened when called using `with` statement
        self.filename = filename
        self.file_size = None

    def __enter__(self):
        self.file = open(self.filename, self.mode)
        return self

    def __exit__(self, exc_type, exc_val, exc_tb):
        self.file.close()
        return False class LineMapCanvas(MapCanvas):
    def __init__(self):
        super().__init__()

    def reinitFigure(self, mesh, lines, line_names, line_colors):
        # draw the mesh
        self.initFigure(mesh)

        # add polyline labels
        for p, name, color in zip(lines, line_names, line_colors):
            x, y = p.polyline().xy
            line = mlines.Line2D(x, y, color=color, lw=1)
            self.axes.add_line(line)

            center = p.polyline().centroid
            cx, cy = center.x, center.y
            self.axes.annotate(name, (cx, cy), color=color, weight='bold',
                               fontsize=8, ha='center', va='center')
        self.draw() class LoadMeshDialog(OutputProgressDialog):
    def __init__(self, mesh_type, input_header):
        super().__init__('Processing the mesh. Please wait.', 'Processing the mesh...')
        thread = ConstructIndexThread(mesh_type, input_header)
        self.connectToThread(thread)

    def outputFinished(self):
        self.setValue(100)
        self.setCancelButtonText('OK')
        logging.info('Finished processing the mesh')

    def cancel(self):
        if self.cancelButton.text() == 'Cancel':
            if not self.thread.canceled:
                self.thread.canceled = True
                logging.info('Input canceled.')

    def run(self):
        self.thread.run()
        if not self.thread.canceled:
            self.outputFinished()
        self.exec_()
        return self.thread.mesh class MapViewer(QWidget):
    def __init__(self, canvas):
        super().__init__()
        self.canvas = canvas

        # add the the tool bar
        self.toolBar = NavigationToolbar2QT(self.canvas, self)
        self.toolBar.setToolButtonStyle(Qt.ToolButtonTextBesideIcon)

        self.scrollArea = QScrollArea()
        self.scrollArea.setBackgroundRole(QPalette.Dark)
        self.scrollArea.setWidget(self.canvas)

        self._setLayout()
        self.resize(800, 700)

    def _setLayout(self):
        vlayout = QVBoxLayout()
        vlayout.addWidget(self.toolBar)
        vlayout.addWidget(self.scrollArea)
        vlayout.setSpacing(0)
        vlayout.setContentsMargins(0, 0, 0, 0)
        self.setLayout(vlayout)

        self.setWindowTitle('Map Viewer')
        self.resize(self.sizeHint()) def open_polylines():
    filename, _ = QFileDialog.getOpenFileName(None, 'Open a .i2s or .shp file', '', 'Line sets (*.i2s *.shp)',
                                              options=QFileDialog.Options() | QFileDialog.DontUseNativeDialog)
    if not filename:
        return False, '', []
    if not test_open(filename):
        return False, '', []

    polylines = []
    if filename.endswith('.i2s'):
        with BlueKenue.Read(filename) as f:
            f.read_header()
            for i, poly in enumerate(f.get_open_polylines()):
                poly.set_id('Polyline %i' % i)
                polylines.append(poly)
    else:
        try:
            for i, poly in enumerate(Shapefile.get_open_polylines(filename)):
                poly.set_id('Polyline %i' % i)
                polylines.append(poly)
        except ShapefileException as e:
            QMessageBox.critical(None, 'Error', e, QMessageBox.Ok)
            return False, '', []
    if not polylines:
        QMessageBox.critical(None, 'Error', 'The file does not contain any open polyline.',
                             QMessageBox.Ok)
        return False, '', []
    return True, filename, polylines class OutputProgressDialog(QProgressDialog):
    def __init__(self, message='Output in progress', title='Writing the output...', parent=None):
        super().__init__(message, 'OK', 0, 100, parent)
        self.setMinimumDuration(0)

        self.cancelButton = QPushButton('Cancel')
        self.setCancelButton(self.cancelButton)
        self.canceled.connect(self.cancel)

        self.setAutoReset(False)
        self.setAutoClose(False)

        self.setWindowTitle(title)
        self.setWindowFlags(Qt.WindowTitleHint)
        self.setFixedSize(300, 150)
        self.thread = None

        self.open()
        self.setValue(0)
        QApplication.processEvents()

    def outputFinished(self):
        self.setValue(100)
        self.setCancelButtonText('OK')
        logging.info('Finished writing the output.')

    def connectToThread(self, thread):
        self.thread = thread
        thread.tick.connect(self.setValue)

    def cancel(self):
        if self.cancelButton.text() == 'Cancel':
            if not self.thread.canceled:
                self.thread.canceled = True
                logging.info('Output canceled.') class OutputThread(QThread):
    tick = pyqtSignal(int, name='changed')

    def __init__(self):
        super().__init__()
        self.canceled = False class ProgressBarIterator:
    def __init__(self, iterable, emit=lambda x: x, perc_range=(0, 100), length=None):
        """
        Update progress bar during an iterative
        @param iterable: iterator
        @param perc_range <(int, int)>: minimum and maximum percentage bounds
        @param emit: function to call at each significant progress
        """
        self.emit = emit
        self.iterable = iterable
        self.min, self.max = perc_range
        self.length = length

    def do_at_next(self, perc):
        """
        Call self.emit function with curent percentage
        @param perc <float>: percentage progress (within range [0, 100])
        """
        self.emit(perc)
        QApplication.processEvents()

    def __iter__(self):
        """Iterate and update progress bar at each new percentage"""
        length = len(self.iterable) if self.length is None else self.length
        if length != 0:
            ratio = (self.max - self.min) / length
            old_perc = -1
            for i, value in enumerate(self.iterable):
                yield value

                new_perc = int(self.min + (i + 1) * ratio)
                if new_perc > old_perc:
                    self.do_at_next(new_perc)
                old_perc = new_perc

    @staticmethod
    def prepare(*args, **kwargs):
        """
        Get a ready sub-class on which to iterate
        @return <SubProgressBarIterator>:  ProgressBarIterator sub-class with pre-defined arguments
        """
        class SubProgressBarIterator(ProgressBarIterator):
            def __init__(self, iterable, unit=None):
                """!
                @param iterable: iterable or generator to loop over
                @param unit: argument not used in GUI
                """
                super().__init__(iterable, *args, **kwargs)
        return SubProgressBarIterator class ProjectLinesPlotViewer(QWidget):
    def __init__(self):
        super().__init__()
        self.filename = ''
        self.language = 'fr'
        self.header = None
        self.time = []
        self.lines = []
        self.line_interpolators = []
        self.line_interpolators_internal = []

        self.var_table = {}
        self.current_vars = {}
        self.line_colors = {}
        self.current_linestyles = {}

        self.lineStyles = ['solid', 'dashed', 'dashdot', 'dotted']

        # set up a custom plot viewer
        self.editLineColorAct = QAction('Edit line colors', self,
                                        icon=self.style().standardIcon(QStyle.SP_FileDialogDetailedView),
                                        triggered=self.editColor)
        self.editLineStyleAct = QAction('Edit line type', self,
                                        icon=self.style().standardIcon(QStyle.SP_FileDialogDetailedView),
                                        triggered=self.editLineStyle)
        self.plotViewer = PlotViewer()
        self.plotViewer.exitAct.setEnabled(False)
        self.plotViewer.menuBar.setVisible(False)
        self.plotViewer.toolBar.addAction(self.editLineColorAct)
        self.plotViewer.toolBar.addAction(self.editLineStyleAct)
        self.plotViewer.toolBar.addSeparator()
        self.plotViewer.toolBar.addAction(self.plotViewer.xLabelAct)
        self.plotViewer.toolBar.addAction(self.plotViewer.yLabelAct)
        self.plotViewer.toolBar.addSeparator()
        self.plotViewer.toolBar.addAction(self.plotViewer.titleAct)
        self.plotViewer.canvas.figure.canvas.mpl_connect('motion_notify_event', self.plotViewer.mouseMove)

        # put it in a group box to get a nice border
        self.gb = QGroupBox()
        ly = QHBoxLayout()
        ly.addWidget(self.plotViewer)
        self.gb.setLayout(ly)
        self.gb.setStyleSheet('QGroupBox {border: 8px solid rgb(108, 122, 137); border-radius: 6px }')
        self.gb.setMinimumWidth(600)

        self.control = ProjectLinesImageControlPanel()
        self.control.btnCompute.clicked.connect(self.btnComputeEvent)
        self.control.unitBox.currentTextChanged.connect(self._updateTable)

        self.splitter = QSplitter()
        self.splitter.addWidget(self.control)
        self.splitter.addWidget(self.gb)
        handle = self.splitter.handle(1)
        layout = QVBoxLayout()
        layout.setSpacing(0)
        layout.setContentsMargins(0, 0, 0, 0)
        line = QFrame()
        line.setFrameShape(QFrame.VLine)
        line.setFrameShadow(QFrame.Sunken)
        layout.addWidget(line)
        handle.setLayout(layout)

        mainLayout = QHBoxLayout()
        mainLayout.addWidget(self.splitter)
        self.setLayout(mainLayout)
        self.setWindowTitle('Project Lines')

    def editColor(self):
        line_labels = {i: self.lines[i].id for i in self.line_colors}
        msg = ColumnColorEditor('Line', list(self.getSelection().keys()),
                                line_labels, self.line_colors,
                                self.plotViewer.defaultColors, self.plotViewer.colorToName)
        value = msg.exec_()
        if value == QDialog.Rejected:
            return
        msg.getColors(self.line_colors, line_labels, self.plotViewer.nameToColor)
        self.btnComputeEvent()

    def editLineStyle(self):
        msg = LineStyleEditor(self.lineStyles, self.current_linestyles)
        value = msg.exec_()
        if value == QDialog.Rejected:
            return
        msg.getLineStyles(self.current_linestyles)
        self.btnComputeEvent()

    def getSelection(self):
        self.current_vars = {}
        unit = self.control.unitBox.currentText().split(': ')[1]
        unit = '' if unit == 'None' else unit
        variables = self.var_table[unit]
        for row in range(self.control.varTable.rowCount()):
            for j in range(len(variables)):
                if self.control.varTable.item(row, j+1).checkState() == Qt.Checked:
                    line_id = self.control.varTable.item(row, 0).text()
                    line_index = [poly.id for poly in self.lines].index(line_id)
                    if line_index not in self.current_vars:
                        self.current_vars[line_index] = [variables[j]]
                    else:
                        self.current_vars[line_index].append(variables[j])
        return self.current_vars

    def _updateTable(self, text):
        self.current_linestyles = {}
        self.control.varTable.setRowCount(0)
        unit = text.split(': ')[1]
        unit = '' if unit == 'None' else unit
        vars = self.var_table[unit]

        # initialize default linestyles
        j = 0
        for var in vars:
            j %= len(self.lineStyles)
            self.current_linestyles[var] = self.lineStyles[j]
            j += 1

        nb_vars = len(vars)
        self.control.varTable.setColumnCount(nb_vars + 1)
        self.control.varTable.setHorizontalHeaderLabels(['Line'] + vars)

        for i, line in enumerate(self.lines):
            if self.line_interpolators[i][0]:
                offset = self.control.varTable.rowCount()
                self.control.varTable.insertRow(offset)
                self.control.varTable.setItem(offset, 0, QTableWidgetItem(line.id))
                for j, var in enumerate(vars):
                    var_item = QTableWidgetItem('')
                    var_item.setCheckState(Qt.Unchecked)
                    self.control.varTable.setItem(offset, j+1, var_item)

    def _compute(self, time_index, line_interpolators, reference, max_distance):
        distances = {}
        values = {}
        try:
            with Serafin.Read(self.filename, self.language) as input_stream:
                input_stream.header = self.header
                input_stream.time = self.time
                for line_id in self.current_vars:
                    distances[line_id] = []
                    values[line_id] = {}

                    for var in self.current_vars[line_id]:
                        values[line_id][var] = []

                    for x, y, (i, j, k), interpolator in line_interpolators[line_id]:
                        d = reference.project(x, y)
                        if d <= 0 or d >= max_distance:
                            continue
                        distances[line_id].append(d)

                        for var in self.current_vars[line_id]:
                            all_values = input_stream.read_var_in_frame(time_index, var)
                            values[line_id][var].append(interpolator.dot(all_values[[i, j, k]]))
                    distances[line_id] = np.array(distances[line_id])
            return distances, values
        except (Serafin.SerafinRequestError, Serafin.SerafinValidationError) as e:
            QMessageBox.critical(self, 'Serafin Error', e.message, QMessageBox.Ok)
            return {}, {}

    def btnComputeEvent(self):
        self.current_vars = self.getSelection()
        if not self.current_vars:
            QMessageBox.critical(self, 'Error', 'Select at least one variable to plot.',
                                 QMessageBox.Ok)
            return
        ref_id = self.control.lineBox.currentText()
        ref_index = [poly.id for poly in self.lines].index(ref_id)
        self.plotViewer.current_title = 'Values of variables along %s' % ref_id
        self.plotViewer.current_ylabel = 'Value (%s)' % (self.control.unitBox.currentText().split(': ')[1])

        line_interpolators = {}
        if self.control.intersection.isChecked():
            for line_id in self.current_vars:
                line_interpolator, distances = self.line_interpolators[line_id]
                line_interpolators[line_id] = line_interpolator
        else:
            for line_id in self.current_vars:
                line_interpolator, distances = self.line_interpolators_internal[line_id]
                line_interpolators[line_id] = line_interpolator

        reference = self.lines[ref_index]
        max_distance = reference.length()
        time_index = int(self.control.timeSelection.index.text()) - 1
        distances, values = self._compute(time_index, line_interpolators, reference, max_distance)

        self.plotViewer.canvas.axes.clear()

        if self.control.addInternal.isChecked():
            if self.control.intersection.isChecked():
                line_interpolators_internal = {}
                for line_id in self.current_vars:
                    line_interpolator, _ = self.line_interpolators_internal[line_id]
                    line_interpolators_internal[line_id] = line_interpolator
                distances_internal, values_internal = self._compute(time_index,
                                                                    line_interpolators_internal,
                                                                    reference, max_distance)
                for line_id, variables in self.current_vars.items():
                    for var in variables:
                        self.plotViewer.canvas.axes.plot(distances[line_id], values[line_id][var],
                                                         linestyle=self.current_linestyles[var],
                                                         color=self.line_colors[line_id],
                                                         linewidth=2, label='%s$_%d$' % (var, line_id+1))

                        self.plotViewer.canvas.axes.plot(distances_internal[line_id],
                                                         values_internal[line_id][var], 'o',
                                                         color=self.line_colors[line_id])
            else:
                for line_id, variables in self.current_vars.items():
                    for var in variables:
                        self.plotViewer.canvas.axes.plot(distances[line_id], values[line_id][var],
                                                         marker='o', linestyle=self.current_linestyles[var],
                                                         color=self.line_colors[line_id], linewidth=2,
                                                         label='%s$_%d$' % (var, line_id+1))

        else:
            for line_id, variables in self.current_vars.items():
                for var in variables:
                    self.plotViewer.canvas.axes.plot(distances[line_id], values[line_id][var],
                                                     linestyle=self.current_linestyles[var],
                                                     color=self.line_colors[line_id], linewidth=2,
                                                     label='%s$_%d$' % (var, line_id+1))

        self.plotViewer.canvas.axes.legend()
        self.plotViewer.canvas.axes.grid(linestyle='dotted')
        self.plotViewer.canvas.axes.set_xlabel(self.plotViewer.current_xlabel)
        self.plotViewer.canvas.axes.set_ylabel(self.plotViewer.current_ylabel)
        self.plotViewer.canvas.axes.set_title(self.plotViewer.current_title)
        self.plotViewer.canvas.draw()

    def reset(self):
        self.control.intersection.setChecked(True)
        self.control.addInternal.setChecked(False)
        self.control.lineBox.clear()
        self.control.varTable.setRowCount(0)
        self.current_vars = {}
        self.line_colors = {}
        self.current_linestyles = {}
        self.plotViewer.defaultPlot()
        self.plotViewer.current_title = ''
        self.plotViewer.current_xlabel = 'Cumulative distance (M)'
        self.plotViewer.current_ylabel = ''
        self.control.timeSelection.clearText()

    def getInput(self, input_data, lines, line_interpolators, line_interpolators_internal):
        self.filename = input_data.filename
        self.header = input_data.header
        self.time = input_data.time
        self.lines = lines
        self.line_interpolators = line_interpolators
        self.line_interpolators_internal = line_interpolators_internal

        if self.header.date is not None:
            year, month, day, hour, minute, second = self.header.date
            start_time = datetime.datetime(year, month, day, hour, minute, second)
        else:
            start_time = datetime.datetime(1900, 1, 1, 0, 0, 0)
        frames = list(map(lambda x: start_time + datetime.timedelta(seconds=x), self.time))
        self.control.timeSelection.initTime(self.time, frames)

        self.var_table = {}
        for var_ID, var_unit in zip(self.header.var_IDs, self.header.var_units):
            var_unit = var_unit.decode(Serafin.SLF_EIT).strip()
            if var_unit in self.var_table:
                self.var_table[var_unit].append(var_ID)
            else:
                self.var_table[var_unit] = [var_ID]

        for var_unit in self.var_table:
            if not var_unit:
                self.control.unitBox.addItem('Unit: None')
            else:
                self.control.unitBox.addItem('Unit: %s' % var_unit)
        if 'M' in self.var_table:
            self.control.unitBox.setCurrentIndex(list(self.var_table.keys()).index('M'))
        self._updateTable(self.control.unitBox.currentText())

        j = 0
        for i, line in enumerate(self.lines):
            if self.line_interpolators[i][0]:
                j %= len(self.plotViewer.defaultColors)
                self.control.lineBox.addItem(line.id)
                self.line_colors[i] = self.plotViewer.defaultColors[j]   # initialize default line colors
                j += 1 class PyTelToolWidget(QWidget):
    def __init__(self, parent):
        super().__init__()
        self.parent = parent
        self.input = None
        if parent is None:
            self.language = settings.LANG
            self.csv_separator = settings.CSV_SEPARATOR
            self.fmt_float = settings.FMT_FLOAT
            self.logging_level = settings.LOGGING_LEVEL
        else:
            self.language = parent.language
            self.csv_separator = parent.csv_separator
            self.fmt_float = parent.fmt_float
            self.logging_level = parent.logging_level
        self.setMinimumWidth(600)
        self.setWindowFlags(self.windowFlags() | Qt.CustomizeWindowHint)
        self.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)

    def inDialog(self):
        if self.parent is not None:
            self.parent.inDialog()
        else:
            self.setWindowFlags(self.windowFlags() & ~Qt.WindowCloseButtonHint)
            self.setEnabled(False)
            self.show()

    def outDialog(self):
        if self.parent is not None:
            self.parent.outDialog()
        else:
            self.setWindowFlags(self.windowFlags() | Qt.WindowCloseButtonHint)
            self.setEnabled(True)
            self.show()

    def switch_language(self, language):
        if language == 'fr':
            self.input.frenchButton.setChecked(True)
        else:
            self.input.englishButton.setChecked(True) class QPlainTextEditLogger(logging.Handler):
    """!
    @brief A text edit box displaying the message logs
    """
    def __init__(self, parent):
        super().__init__()
        self.widget = QPlainTextEdit(parent)
        self.widget.setReadOnly(True)

    def emit(self, record):
        msg = self.format(record)
        self.widget.appendPlainText(msg) def save_dialog(file_format, input_name='', input_names=None):
    # create the save file dialog
    options = QFileDialog.Options()
    options |= QFileDialog.DontUseNativeDialog
    options |= QFileDialog.DontConfirmOverwrite
    if file_format == 'Serafin':
        extensions = settings.SERAFIN_EXT
        file_filter = 'Serafin Files (%s)' % ' '.join(['*%s' % extension for extension in extensions])
    elif file_format == 'CSV':
        extensions = ['.csv']
        file_filter = 'CSV Files (*.csv)'
    else:
        raise NotImplementedError('File format %s is not supported' % file_format)
    filename, _ = QFileDialog.getSaveFileName(None, 'Choose the output file name', '', file_filter, options=options)

    # check the file name consistency
    if not filename:
        return True, ''

    # add default extension (taken as first) if missing
    if os.path.splitext(filename)[1] not in extensions:
        filename += extensions[0]

    # overwrite to the input file is forbidden
    if input_name:
        if filename == input_name:
            QMessageBox.critical(None, 'Error', 'Cannot overwrite to the input file.', QMessageBox.Ok)
            return True, ''
    elif input_names:
        for name in input_names:
            if filename == name:
                QMessageBox.critical(None, 'Error', 'Cannot overwrite to the input file.', QMessageBox.Ok)
                return True, ''

    # handle overwrite manually
    overwrite = handle_overwrite(filename)
    if overwrite is None:
        return True, ''

    return False, filename class SerafinInputTab(QWidget):
    def __init__(self, parent):
        super().__init__()
        self.parent = parent

        # create the button open
        self.btnOpen = QPushButton('Open', self, icon=self.style().standardIcon(QStyle.SP_DialogOpenButton))
        self.btnOpen.setToolTip('<b>Open</b> a Serafin file')
        self.btnOpen.setFixedSize(105, 50)

        # create some text fields displaying the IO files info
        self.inNameBox = QLineEdit()
        self.inNameBox.setReadOnly(True)
        self.summaryTextBox = QPlainTextEdit()
        self.summaryTextBox.setFixedHeight(50)
        self.summaryTextBox.setReadOnly(True)

        # create a checkbox for language selection
        self.langBox = QGroupBox('Input language')
        hlayout = QHBoxLayout()
        self.frenchButton = QRadioButton('French')
        self.englishButton = QRadioButton('English')
        hlayout.addWidget(self.frenchButton)
        hlayout.addWidget(self.englishButton)
        self.langBox.setLayout(hlayout)
        self.langBox.setMaximumHeight(80)
        if self.parent.language == 'fr':
            self.frenchButton.setChecked(True)
        else:
            self.englishButton.setChecked(True)

        # create the widget displaying message logs
        self.logTextBox = QPlainTextEditLogger(self)
        self.logTextBox.setFormatter(logging.Formatter(settings.LOGGING_FMT_GUI))
        logging.getLogger().addHandler(self.logTextBox)
        logging.getLogger().setLevel(self.parent.logging_level)

        self.input_layout = QVBoxLayout()
        hlayout = QHBoxLayout()
        hlayout.setAlignment(Qt.AlignLeft)
        hlayout.addItem(QSpacerItem(50, 1))
        hlayout.addWidget(self.btnOpen)
        hlayout.addItem(QSpacerItem(30, 1))
        hlayout.addWidget(self.langBox)
        self.input_layout.addLayout(hlayout)
        self.input_layout.addItem(QSpacerItem(10, 10))

        glayout = QGridLayout()
        glayout.addWidget(QLabel('Input file'), 1, 1)
        glayout.addWidget(self.inNameBox, 1, 2)
        glayout.addWidget(QLabel('Summary'), 2, 1)
        glayout.addWidget(self.summaryTextBox, 2, 2)
        glayout.setAlignment(Qt.AlignLeft)
        glayout.setSpacing(10)
        self.input_layout.addLayout(glayout)

    def current_language(self):
        if self.frenchButton.isChecked():
            return 'fr'
        return 'en'

    def reset(self):
        self.inNameBox.clear()
        self.summaryTextBox.clear()

    def open_event(self):
        filename, _ = QFileDialog.getOpenFileName(self, 'Open a Serafin file', '', 'Serafin Files (%s)' %
                                                  ' '.join(['*%s' % extension for extension in settings.SERAFIN_EXT]),
                                                  options=QFileDialog.Options() | QFileDialog.DontUseNativeDialog)
        if not filename:
            return True, ''

        if not test_open(filename):
            return True, ''

        return False, filename

    def read(self, filename):
        self.inNameBox.setText(filename)
        data = SerafinData('', filename, self.current_language())
        try:
            data.read()
        except (Serafin.SerafinRequestError, Serafin.SerafinValidationError) as e:
            QMessageBox.critical(None, 'Serafin Error', e.message, QMessageBox.Ok, QMessageBox.Ok)
            return False, None
        except PermissionError:
            QMessageBox.critical(None, 'Permission denied',
                                 'Permission denied. (Is the file opened by another application?).',
                                 QMessageBox.Ok, QMessageBox.Ok)
            return False, None
        self.summaryTextBox.appendPlainText(data.header.summary())
        logging.info('Finished reading the input file')
        return True, data

    def read_2d(self, filename, update=True):
        if update:
            self.inNameBox.setText(filename)
        data = SerafinData('', filename, self.current_language())

        try:
            is_2d = data.read()
        except (Serafin.SerafinRequestError, Serafin.SerafinValidationError) as e:
            QMessageBox.critical(None, 'Serafin Error', e.message, QMessageBox.Ok, QMessageBox.Ok)
            return False, None
        except PermissionError:
            QMessageBox.critical(None, 'Permission denied',
                                 'Permission denied. (Is the file opened by another application?).',
                                 QMessageBox.Ok, QMessageBox.Ok)
            return False, None

        if not is_2d:
            QMessageBox.critical(self, 'Error', 'The file type (TELEMAC 3D) is currently not supported.',
                                 QMessageBox.Ok)
            return False, None
        if update:
            self.summaryTextBox.appendPlainText(data.header.summary())
        logging.info('Finished reading the input file')
        return True, data class SimpleTimeDateSelection(QWidget):
    def __init__(self):
        super().__init__()

        self.frames = []
        self.dates = []

        self.index = QLineEdit('', self)
        self.slider = TimeSlider(self)
        self.value = QLineEdit('', self)
        self.date = QLineEdit('', self)

        self.value.setReadOnly(True)
        self.date.setReadOnly(True)

        self.index.setMaximumWidth(50)
        self.value.setMaximumWidth(100)
        self.date.setMaximumWidth(150)

        mainLayout = QVBoxLayout()
        mainLayout.addWidget(self.slider)
        hlayout = QHBoxLayout()
        hlayout.addWidget(QLabel('Frame index'))
        hlayout.addWidget(self.index)
        hlayout.addWidget(QLabel('value'))
        hlayout.addWidget(self.value)
        hlayout.addWidget(QLabel('date'))
        hlayout.addWidget(self.date)
        hlayout.addStretch()
        hlayout.setAlignment(Qt.AlignLeft)
        hlayout.setSpacing(10)
        mainLayout.addLayout(hlayout)
        self.setLayout(mainLayout)
        self.index.editingFinished.connect(self.slider.enterIndexEvent)

    def updateSelection(self):
        index = int(self.index.text()) - 1
        self.value.setText(str(self.frames[index]))
        self.date.setText(str(self.dates[index]))

    def initTime(self, frames, dates):
        self.frames = frames
        self.dates = dates
        self.slider.reinit(len(frames), 0)
        self.index.setText(str(1))
        self.updateSelection()

    def clearText(self):
        self.index.clear()
        self.value.clear()
        self.date.clear() class VariableTable(TableWidgetDragRows):
    def __init__(self):
        super().__init__()
        self.setColumnCount(3)
        self.setHorizontalHeaderLabels(['ID', 'Name', 'Unit'])
        vh = self.verticalHeader()
        vh.setSectionResizeMode(QHeaderView.Fixed)
        vh.setDefaultSectionSize(20)
        hh = self.horizontalHeader()
        hh.setDefaultSectionSize(110)
        self.setEditTriggers(QAbstractItemView.NoEditTriggers)
        self.setMaximumHeight(800)

    def fill(self, header):
        for i, (var_id, name, unit) in enumerate(zip(header.var_IDs, header.var_names, header.var_units)):
            self.insertRow(self.rowCount())
            id_item = QTableWidgetItem(var_id.strip())
            name_item = QTableWidgetItem(name.decode(Serafin.SLF_EIT).strip())
            unit_item = QTableWidgetItem(unit.decode(Serafin.SLF_EIT).strip())
            self.setItem(i, 0, id_item)
            self.setItem(i, 1, name_item)
            self.setItem(i, 2, unit_item)

    def get_selected(self):
        selected = []
        for i in range(self.rowCount()):
            selected.append(self.item(i, 0).text())
        return selected

    def get_selected_all(self):
        selected = []
        for i in range(self.rowCount()):
            selected.append((self.item(i, 0).text(),
                            bytes(self.item(i, 1).text(), Serafin.SLF_EIT).ljust(16),
                            bytes(self.item(i, 2).text(), Serafin.SLF_EIT).ljust(16)))
        return selected
import datetime
import logging
import sys
from itertools import islice, cycle
from PyQt5.QtCore import Qt
from PyQt5.QtWidgets import (QApplication, QCheckBox, QComboBox, QGridLayout, QHBoxLayout,
                             QLabel, QLineEdit, QMessageBox, QPlainTextEdit, QPushButton,
                             QSpacerItem, QStyle, QTabWidget,
                             QVBoxLayout, QWidget)
from pyteltools.conf import settings
from pyteltools.slf import Serafin
from .util import LineMapCanvas, LoadMeshDialog, MapViewer, open_polylines, OutputProgressDialog, OutputThread, \
    ProgressBarIterator, ProjectLinesPlotViewer, PyTelToolWidget, QPlainTextEditLogger, save_dialog, SerafinInputTab, \
    SimpleTimeDateSelection, VariableTable
    def _reinitInput(self):
        self.reset()
        self.data = None
        self.has_map = False
        self.btnMap.setEnabled(False)
        self.mesh = None
        self.btnOpenLines.setEnabled(False)
        self.parent.reset()

    def _resetDefaultOptions(self):
        nb_nonempty = 0

        self.line_interpolators = []
        self.line_interpolators_internal = []

        for line in self.lines:
            line_interpolators, distances, \
                line_interpolators_internal, distances_internal = self.mesh.get_line_interpolators(line)
            if line_interpolators:
                nb_nonempty += 1
            self.line_interpolators.append((line_interpolators, distances))
            self.line_interpolators_internal.append((line_interpolators_internal, distances_internal))

        if nb_nonempty == 0:
            self.lines = []
            self.line_interpolators = []
            self.line_interpolators_internal = []

            self.linesNameBox.clear()
            self.parent.reset()

        else:
            old_filename = self.linesNameBox.toPlainText().split('\n')[0]
            self.linesNameBox.clear()
            self.linesNameBox.appendPlainText(old_filename + '\n' + 'The file contains {} open polyline{}.'
                                              '{} line{} the mesh continuously.'.format(
                                                  len(self.lines), 's' if len(self.lines) > 1 else '',
                                                  nb_nonempty, 's intersect' if nb_nonempty > 1 else ' intersects'))

            self.has_map = False
            self.btnMap.setEnabled(True)
            self.parent.getInput()

    def btnOpenSerafinEvent(self):
        canceled, filename = super().open_event()
        if canceled:
            return

        self._reinitInput()
        success, data = self.read_2d(filename)
        if not success:
            return

        # record the mesh for future visualization and calculations
        self.parent.inDialog()
        meshLoader = LoadMeshDialog('interpolation', data.header)
        self.mesh = meshLoader.run()
        self.parent.outDialog()
        if meshLoader.thread.canceled:
            self.linesNameBox.clear()
            self.parent.reset()
            return

        self.data = data
        self.btnOpenLines.setEnabled(True)
        self._resetDefaultOptions()

    def btnOpenLinesEvent(self):
        success, filename, polylines = open_polylines()
        if not success:
            return
        self.lines = polylines
        logging.info('Finished reading the lines file %s' % filename)

        nb_nonempty, indices_nonempty, \
            self.line_interpolators, self.line_interpolators_internal = self.mesh.get_line_interpolators(self.lines)

        if nb_nonempty == 0:
            QMessageBox.critical(self, 'Error', 'No line intersects the mesh continuously.',
                                 QMessageBox.Ok)
            return

        logging.info('Finished reading the lines file %s' % filename)

        self.linesNameBox.clear()
        self.linesNameBox.appendPlainText(filename + '\n' + 'The file contains {} open polyline{}.'
                                          '{} line{} the mesh continuously.'.format(
                                          len(self.lines), 's' if len(self.lines) > 1 else '',
                                          nb_nonempty, 's intersect' if nb_nonempty > 1 else ' intersects'))

        self.has_map = False
        self.btnMap.setEnabled(True)
        self.parent.getInput()

    def btnMapEvent(self):
        if not self.has_map:
            self.map.canvas.reinitFigure(self.mesh, self.lines, [poly.id for poly in self.lines],
                                         list(islice(cycle(['b', 'r', 'g', 'y', 'k', 'c', '#F28AD6', 'm']),
                                              len(self.lines))))

            self.has_map = True

            self.map.canvas.draw()
            self.has_map = True
        self.map.show()


class CSVTab(QWidget):
    def __init__(self, inputTab, parent):
        super().__init__()
        self.input = inputTab
        self.parent = parent

        self._initWidget()
        self._setLayout()
        self.btnSubmit.clicked.connect(self.btnSubmitEvent)

    def _initWidget(self):
        # create two 3-column tables for variables selection
        self.firstTable = VariableTable()

        self.secondTable = VariableTable()