Ejemplo n.º 1
0
    def _setup_table_widget(self):
        """
        Make a table showing
        :return: A QTableWidget object which will contain plot widgets
        """
        table_widget = QTableWidget(3, 7, self)
        table_widget.setVerticalHeaderLabels(['u1', 'u2', 'u3'])
        col_headers = [
            'a*', 'b*', 'c*'
        ] if self.frame == SpecialCoordinateSystem.HKL else ['Qx', 'Qy', 'Qz']
        col_headers.extend(['start', 'stop', 'nbins', 'step'])
        table_widget.setHorizontalHeaderLabels(col_headers)
        table_widget.setFixedHeight(
            table_widget.verticalHeader().defaultSectionSize() *
            (table_widget.rowCount() + 1))  # +1 to include headers
        for icol in range(table_widget.columnCount()):
            table_widget.setColumnWidth(icol, 50)
        table_widget.horizontalHeader().setSectionResizeMode(
            QHeaderView.Stretch)
        table_widget.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff)

        self.table = table_widget
        self.layout.addWidget(self.table)
Ejemplo n.º 2
0
class PMGTableShow(BaseExtendedWidget):
    default_bg = QTableWidgetItem().background()
    default_fg = QTableWidgetItem().foreground()

    def __init__(self,
                 layout_dir: str,
                 title: List[str],
                 initial_value: List[List[Union[int, float, str]]],
                 size_restricted=False,
                 header_adaption_h=False,
                 header_adaption_v=False,
                 background_color: List[List[Union[str]]] = None,
                 foreground_color: List[List[Union[str]]] = None):
        super().__init__(layout_dir=layout_dir)

        self.maximum_rows = 100
        self.size_restricted = size_restricted
        self.header_adaption_h = header_adaption_h
        self.header_adaption_v = header_adaption_v
        self.background_color = background_color if background_color is not None else ''
        self.foreground_color = foreground_color if foreground_color is not None else ''
        self.char_width = 15
        self.on_check_callback = None
        self.title_list = title
        entryLayout = QHBoxLayout()
        entryLayout.setContentsMargins(0, 0, 0, 0)

        self.ctrl = QTableWidget()
        self.ctrl.verticalHeader().setVisible(False)
        self.set_params(size_restricted, header_adaption_h, header_adaption_v)
        self.ctrl.setColumnCount(len(title))

        self.ctrl.setHorizontalScrollBarPolicy(Qt.ScrollBarAsNeeded)
        for i, text in enumerate(title):
            self.ctrl.setColumnWidth(i, len(text) * self.char_width + 10)
            self.ctrl.setHorizontalHeaderItem(i, QTableWidgetItem(text))

        self.central_layout.addLayout(entryLayout)
        entryLayout.addWidget(self.ctrl)

        if initial_value is not None:
            for sublist in initial_value:
                assert len(sublist) == len(title), \
                    'title is not as long as sublist,%s,%s' % (repr(title), sublist)
                self.ctrl.setRowCount(len(initial_value))
                self.set_value(initial_value)

    def set_params(self,
                   size_restricted=False,
                   header_adaption_h=False,
                   header_adaption_v=False):
        self.size_restricted = size_restricted
        self.header_adaption_h = header_adaption_h
        self.header_adaption_v = header_adaption_v
        if header_adaption_h:
            self.ctrl.horizontalHeader().setSectionResizeMode(
                QHeaderView.Stretch)
        if header_adaption_v:
            self.ctrl.verticalHeader().setSectionResizeMode(
                QHeaderView.Stretch)

    def check_data(self, value: List[List[Union[int, float, str]]]):
        for sublist in value:
            assert len(sublist) == len(self.title_list),\
                '%s,%s' % (repr(sublist), repr(self.title_list))

    def set_value(self, value: List[List[Union[int, float, str]]]):
        self.check_data(value)
        self.ctrl.setRowCount(len(value))
        cols = len(value[0])
        if isinstance(self.foreground_color, str):
            fg = [[self.foreground_color for i in range(cols)]
                  for j in range(len(value))]

        else:
            fg = self.foreground_color
        if isinstance(self.background_color, str):
            bg = [[self.background_color for i in range(cols)]
                  for j in range(len(value))]
        else:
            bg = self.background_color
        for row, row_list in enumerate(value):
            for col, content in enumerate(row_list):
                if len(str(content)) * self.char_width > self.ctrl.columnWidth(
                        col):
                    self.ctrl.setColumnWidth(
                        col,
                        len(str(content)) * self.char_width + 10)
                table_item = QTableWidgetItem(str(content))
                table_item.setTextAlignment(Qt.AlignCenter)
                # 字体颜色(红色)
                if fg[row][col] == '':
                    table_item.setForeground(self.default_fg)
                else:
                    table_item.setForeground(
                        QBrush(QColor(*color_str2tup(fg[row][col]))))

                # 背景颜色(红色)
                if bg[row][col] == '':
                    table_item.setBackground(self.default_bg)
                else:
                    table_item.setBackground(
                        QBrush(QColor(*color_str2tup(bg[row][col]))))
                self.ctrl.setItem(row, col, table_item)

        if self.size_restricted:
            if self.header_adaption_h:
                self.ctrl.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
                scrollbar_area_width = 0
            else:
                self.ctrl.setHorizontalScrollBarPolicy(Qt.ScrollBarAsNeeded)
                scrollbar_area_width = 10
            self.ctrl.setMaximumHeight((self.ctrl.rowCount() + 1) * 30 +
                                       scrollbar_area_width)
            self.setMaximumHeight((self.ctrl.rowCount() + 1) * 30 +
                                  scrollbar_area_width)

    def alert(self, alert_level: int):
        self.ctrl.alert(alert_level)

    def add_row(self, row: List):
        assert len(row) == self.ctrl.columnCount()
        rc = self.ctrl.rowCount()
        self.ctrl.setRowCount(rc + 1)
        for i, val in enumerate(row):
            self.ctrl.setItem(rc, i, QTableWidgetItem(str(val)))
        if self.ctrl.rowCount() > self.maximum_rows:
            self.ctrl.removeRow(0)
Ejemplo n.º 3
0
class GroupsPostView(QDialog):
    """
    +------------------------+
    |  Groups : Post/Delete  |
    +------------------------+
    |                        |
    |   check1      Name1    |
    |   check2      Name2    |
    |   check3      Name3    |
    |                        |
    |       SetAsMain        |
    |   Apply   OK   Close   |
    +------------------------+
    """
    def __init__(self, data, win_parent=None):
        self.win_parent = win_parent
        #Init the base class

        groups = data['groups']
        inames = data['inames']
        self.imain = data['imain']
        self.names = [group.name for group in groups]
        self.white = (255, 255, 255)
        self.light_grey = (211, 211, 211)
        self.inames = inames
        self.shown_set = data['shown']
        self.deleted_groups = set()
        #self.inames = argsort(self.names)
        #print('inames =', inames)

        anames = array(self.names)
        for iname, name in enumerate(anames[self.inames]):
            print('name[%s] = %r' % (iname, name))

        # ignore these...
        #self._default_name = data['name']
        #self._default_coords = data['coords']
        #self._default_elements = data['elements']
        #self._default_color = data['color']

        #self.coords_pound = data['coords_pound']
        #self.elements_pound = data['elements_pound']

        #self._default_is_discrete = data['is_discrete']

        self.out_data = data

        QDialog.__init__(self, win_parent)
        #self.setupUi(self)
        self.setWindowTitle('Groups: Post/View')
        self.create_widgets()
        self.create_layout()
        self.set_connections()
        #self.show()

    def create_widgets(self):
        # main/delete/supergroup
        self.set_as_main_button = QPushButton("Set As Main")
        self.create_super_group_button = QPushButton("Create Super Group")
        self.delete_groups_button = QPushButton("Delete Groups")
        self.revert_groups_button = QPushButton("Revert Groups")

        self.show_groups_button = QPushButton("Show Groups")
        self.hide_groups_button = QPushButton("Hide Groups")

        # closing
        self.apply_button = QPushButton("Apply")
        self.ok_button = QPushButton("OK")
        self.cancel_button = QPushButton("Cancel")

        #table
        self.table = QTableWidget()
        self.checks = []
        self.names_text = []

        bold = QtGui.QFont()
        bold.setBold(True)
        bold.setItalic(True)
        bold.setWeight(75)
        anames = array(self.names)
        for iname, name in enumerate(anames[self.inames]):
            check = QTableWidgetItem()
            check.setCheckState(False)

            # TODO: create right click menu ???
            name_text = QTableWidgetItem(str(name))
            if iname == self.imain:
                name_text.setFont(bold)
                self.shown_set.add(iname)
                check.setCheckState(2)
                name_text.setBackground(QtGui.QColor(*self.light_grey))
            elif iname in self.shown_set:
                name_text.setBackground(QtGui.QColor(*self.light_grey))

            self.checks.append(check)
            self.names_text.append(name_text)

    def create_layout(self):
        nrows = len(self.names)
        table = self.table
        table.setRowCount(nrows)
        table.setColumnCount(2)
        headers = [QString('Operate On'), QString('Name')]
        table.setHorizontalHeaderLabels(headers)

        header = table.horizontalHeader()
        header.setStretchLastSection(True)

        #table.setAlternatingRowColors(True)

        #header = table.verticalHeader()
        #header.setStretchLastSection(True)
        #table.resize(400, 250)

        #heighti = table.rowHeight(0)
        #total_height = nrows * heighti
        #table.setMaximumHeight(total_height)
        #table.resize(total_height, None)

        #for iname, name in enumerate(self.names[self.inames]):
        #print('name[%s] = %r' % (iname, name))
        for iname in self.inames:
            check = self.checks[iname]
            name_text = self.names_text[iname]
            # row, col, value
            table.setItem(iname, 0, check)
            table.setItem(iname, 1, name_text)
        table.resizeRowsToContents()
        #table.horizontalHeaderItem(1).setTextAlignment(QtCore.AlignHCenter)

        #= QVBoxLayout()
        ok_cancel_box = QHBoxLayout()
        ok_cancel_box.addWidget(self.apply_button)
        ok_cancel_box.addWidget(self.ok_button)
        ok_cancel_box.addWidget(self.cancel_button)

        vbox = QVBoxLayout()
        vbox.addWidget(table)
        vbox.addWidget(self.set_as_main_button)
        #vbox.addWidget(self.create_super_group_button)

        vbox.addStretch()
        vbox.addWidget(self.show_groups_button)
        vbox.addWidget(self.hide_groups_button)
        vbox.addStretch()
        vbox.addWidget(self.delete_groups_button)
        vbox.addWidget(self.revert_groups_button)
        vbox.addStretch()

        vbox.addStretch()
        vbox.addLayout(ok_cancel_box)

        self.setLayout(vbox)

    def set_connections(self):
        """creates the actions for the menu"""
        self.set_as_main_button.clicked.connect(self.on_set_as_main)
        self.delete_groups_button.clicked.connect(self.on_delete_groups)
        self.revert_groups_button.clicked.connect(self.on_revert_groups)

        self.show_groups_button.clicked.connect(self.on_show_groups)
        self.hide_groups_button.clicked.connect(self.on_hide_groups)

        self.create_super_group_button.clicked.connect(
            self.on_create_super_group)

        self.apply_button.clicked.connect(self.on_apply)
        self.ok_button.clicked.connect(self.on_ok)
        self.cancel_button.clicked.connect(self.on_cancel)

    def closeEvent(self, event):
        event.accept()

    @property
    def nrows(self):
        return self.table.rowCount()

    def on_hide_groups(self):
        self._set_highlight(self.white)

    def on_show_groups(self):
        self._set_highlight(self.light_grey)

    def _set_highlight(self, color):
        for irow in range(self.nrows):
            check = self.checks[irow]
            is_checked = check.checkState()

            # 0 - unchecked
            # 1 - partially checked (invalid)
            # 2 - checked
            if is_checked:
                name_text = self.names_text[irow]
                name_text.setBackground(QtGui.QColor(*color))

    def on_delete_groups(self):
        for irow in range(self.nrows):
            check = self.checks[irow]
            is_checked = check.checkState()

            # 0 - unchecked
            # 1 - partially checked (invalid)
            # 2 - checked
            if irow == 0 and is_checked:
                # TODO: change this to a log
                print('error deleting group ALL...change this to a log')
                #self.window_parent.log
                return
            if is_checked:
                self.table.hideRow(irow)
                self.deleted_groups.add(irow)
                check.setCheckState(0)

        if self.imain > 0 and self.shown_set == set([0]):
            bold = QtGui.QFont()
            bold.setBold(True)
            bold.setItalic(True)

            self.imain = 0
            irow = 0
            check = self.checks[irow]
            name_text = self.names_texts[irow]
            name_text.setFont(bold)
            name_text.setBackground(QtGui.QColor(*self.light_grey))

    def on_revert_groups(self):
        for irow in range(self.nrows):
            self.table.showRow(irow)
        self.deleted_groups = set()

    def on_create_super_group(self):
        inames = [
            iname for iname, check in enumerate(self.checks)
            if bool(check.checkState())
        ]

        if not len(inames):
            # TODO: add logging
            print('nothing is checked...')
            return
        if inames[0] == 0:
            # TODO: add logging
            print("cannot include 'ALL' in supergroup...")
            return

        name = 'SuperGroup'
        # popup gui and get a name

        irow = self.table.rowCount()
        self.table.insertRow(irow)

        check = QTableWidgetItem()
        check.setCheckState(False)
        name_text = QTableWidgetItem(str(name))

        self.names.extend(name)
        self.names_text.append(name_text)
        self.checks.append(check)

        self.table.setItem(irow, 0, check)
        self.table.setItem(irow, 1, name_text)

    def on_set_as_main(self):
        bold = QtGui.QFont()
        bold.setBold(True)
        bold.setItalic(True)

        normal = QtGui.QFont()
        normal.setBold(False)
        normal.setItalic(False)

        imain = None
        imain_set = False
        for irow in range(self.nrows):
            check = self.checks[irow]
            name_text = self.names_text[irow]
            is_checked = check.checkState()

            # 0 - unchecked
            # 1 - partially checked (invalid)
            # 2 - checked
            if is_checked and not imain_set:
                # TODO: change this to a log
                #self.window_parent.log
                imain_set = True
                imain = irow
                name_text.setFont(bold)
                name_text.setBackground(QtGui.QColor(*self.light_grey))
                self.shown_set.add(irow)
            elif irow == self.imain:
                name_text.setFont(normal)
                if irow == 0:
                    name_text.setBackground(QtGui.QColor(*self.white))
                    if irow in self.shown_set:
                        self.shown_set.remove(irow)
                elif imain == 0:
                    name_text.setBackground(QtGui.QColor(*self.white))
                    self.shown_set.remove(imain)
        self.imain = imain

    def get_main_group(self):
        return self.imain

    def get_shown_group(self):
        return self.shown_set

    def get_deleted_groups(self):
        return self.deleted_groups

    def on_validate(self):
        flag0 = flag1 = flag2 = True
        main_group_id = self.get_main_group()
        shown_groups_ids = self.get_shown_group()
        deleted_group_ids = self.get_deleted_groups()

        if flag0 and flag1 and flag2:
            self.out_data['imain'] = main_group_id
            self.out_data['shown'] = shown_groups_ids
            self.out_data['remove'] = deleted_group_ids
            self.out_data['clicked_ok'] = True
            return True
        return False

    def on_apply(self):
        passed = self.on_validate()
        if passed:
            self.win_parent.on_post_group(self.out_data)

    def on_ok(self):
        passed = self.on_validate()
        if passed:
            self.close()
            #self.destroy()

    def on_cancel(self):
        self.close()
Ejemplo n.º 4
0
class CameraWindow(PyDialog):
    """defines the CameraWindow class"""
    def __init__(self, data, win_parent=None):
        """
        +--------+
        | Camera |
        +--------+---------------+
        |  Camera Name           |
        |  +-------------------+ |
        |  |                   | |
        |  |                   | |
        |  |                   | |
        |  |                   | |
        |  |                   | |
        |  +-------------------+ |
        |                        |
        | Name xxx       Save    |
        | Delete   Set           |
        |                        |
        |    Apply   OK  Cancel  |
        +--------+---------------+
        """
        PyDialog.__init__(self, data, win_parent)
        self.setWindowTitle('Camera Views')
        #self.setWindowIcon(view_icon)

        self._default_name = 'Camera'
        self.out_data['clicked_ok'] = False

        self.cameras = deepcopy(data['cameras'])
        self.names = sorted(self.cameras.keys())

        self.name = QLabel("Name:")
        self.name_edit = QLineEdit(str(self._default_name))

        self.delete_button = QPushButton("Delete")
        self.set_button = QPushButton("Set")
        self.save_button = QPushButton("Save")

        # closing
        self.apply_button = QPushButton("Apply")
        self.close_button = QPushButton("Close")
        self.cancel_button = QPushButton("Cancel")

        self.table = QTableWidget()
        names_text = []
        for name in self.names:
            name_text = QTableWidgetItem(str(name))
            names_text.append(name_text)
        self.create_layout(names_text)
        self.set_connections()

    def create_layout(self, names_text):
        nrows = len(self.names)
        table = self.table
        table.setRowCount(nrows)
        table.setColumnCount(1)
        headers = ['Camera Name']
        table.setHorizontalHeaderLabels(headers)

        header = table.horizontalHeader()
        header.setStretchLastSection(True)

        for iname, name_text in enumerate(names_text):
            # row, col, value
            table.setItem(iname, 0, name_text)
        table.resizeRowsToContents()

        ok_cancel_box = QHBoxLayout()
        ok_cancel_box.addWidget(self.apply_button)
        ok_cancel_box.addWidget(self.close_button)
        ok_cancel_box.addWidget(self.cancel_button)

        grid = QGridLayout()

        irow = 0
        grid.addWidget(self.name, irow, 0)
        grid.addWidget(self.name_edit, irow, 1)
        grid.addWidget(self.save_button, irow, 2)
        irow += 1

        grid.addWidget(self.delete_button, irow, 0)
        grid.addWidget(self.set_button, irow, 1)
        irow += 1

        vbox = QVBoxLayout()
        vbox.addWidget(self.table)

        vbox.addLayout(grid)
        vbox.addStretch()
        vbox.addLayout(ok_cancel_box)
        self.setLayout(vbox)

    def set_connections(self):
        """creates the actions for the menu"""
        #if qt_version == 4:
        #self.connect(self.ok_button, QtCore.SIGNAL('clicked()'), self.on_ok)
        self.set_button.clicked.connect(self.on_set)
        self.save_button.clicked.connect(self.on_save)
        self.delete_button.clicked.connect(self.on_delete)
        self.apply_button.clicked.connect(self.on_apply)
        self.close_button.clicked.connect(self.on_close)
        self.cancel_button.clicked.connect(self.on_cancel)

    def on_set(self):
        objs = self.table.selectedIndexes()
        if len(objs) == 1:
            obj = objs[0]
            irow = obj.row()
            name = self.names[irow]
            self.set_camera(name)
            return True
        return False

    def on_save(self):
        name = str(self.name_edit.text()).strip()
        if name in self.cameras:
            return
        irow = self.nrows
        if len(name):
            self.table.insertRow(irow)
            name_text = QTableWidgetItem(str(name))
            self.table.setItem(irow, 0, name_text)
            self.name_edit.setText('')
            self.save_camera(name)

    def set_camera(self, name):
        camera_data = self.cameras[name]
        if self.win_parent is None:
            return
        self.win_parent.on_set_camera_data(camera_data)

    def save_camera(self, name):
        self.names.append(name)
        if self.win_parent is None:
            self.cameras[name] = None
            return
        self.cameras[name] = self.win_parent.get_camera_data()

    @property
    def nrows(self):
        return self.table.rowCount()

    def on_delete(self):
        irows = []
        for obj in self.table.selectedIndexes():
            irow = obj.row()
            irows.append(irow)
        irows.sort()

        for irow in reversed(irows):
            self.table.removeRow(irow)
            name = self.names.pop(irow)
            del self.cameras[name]
            #print('  removing irow=%s name=%r' % (irow, name))

    def closeEvent(self, event):
        event.accept()

    def on_apply(self):
        passed = self.on_set()
        #if passed:
        #    self.win_parent.create_plane(self.out_data)
        return passed

    def on_close(self):
        self.out_data['clicked_ok'] = True
        self.out_data['cameras'] = self.cameras
        self.close()

    def on_ok(self):
        passed = self.on_apply()
        if passed:
            name = str(self.name_edit.text()).strip()
            self.out_data['name'] = name
            self.out_data['cameras'] = self.cameras
            self.out_data['clicked_ok'] = True
            self.close()
            #self.destroy()

    def on_cancel(self):
        self.close()
Ejemplo n.º 5
0
class MCSDialog(QDialog):
    """A dialog to perform minimal cut set computation"""

    def __init__(self, appdata: CnaData, centralwidget):
        QDialog.__init__(self)
        self.setWindowTitle("Minimal Cut Sets Computation")

        self.appdata = appdata
        self.centralwidget = centralwidget
        self.eng = appdata.engine
        self.out = io.StringIO()
        self.err = io.StringIO()

        self.layout = QVBoxLayout()
        l1 = QLabel("Target Region(s)")
        self.layout.addWidget(l1)
        s1 = QHBoxLayout()

        completer = QCompleter(
            self.appdata.project.cobra_py_model.reactions.list_attr("id"), self)
        completer.setCaseSensitivity(Qt.CaseInsensitive)

        self.target_list = QTableWidget(1, 4)
        self.target_list.setHorizontalHeaderLabels(
            ["region no", "T", "≥/≤", "t"])
        self.target_list.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch)
        self.target_list.horizontalHeader().setSectionResizeMode(0, QHeaderView.Fixed)
        self.target_list.horizontalHeader().resizeSection(0, 100)
        self.target_list.horizontalHeader().setSectionResizeMode(2, QHeaderView.Fixed)
        self.target_list.horizontalHeader().resizeSection(2, 50)
        item = QLineEdit("1")
        self.target_list.setCellWidget(0, 0, item)
        item2 = QLineEdit("")
        item2.setCompleter(completer)
        self.target_list.setCellWidget(0, 1, item2)
        combo = QComboBox(self.target_list)
        combo.insertItem(1, "≤")
        combo.insertItem(2, "≥")
        self.target_list.setCellWidget(0, 2, combo)
        item = QLineEdit("0")
        self.target_list.setCellWidget(0, 3, item)

        s1.addWidget(self.target_list)

        s11 = QVBoxLayout()
        self.add_target = QPushButton("+")
        self.add_target.clicked.connect(self.add_target_region)
        self.rem_target = QPushButton("-")
        self.rem_target.clicked.connect(self.rem_target_region)
        s11.addWidget(self.add_target)
        s11.addWidget(self.rem_target)
        s1.addItem(s11)
        self.layout.addItem(s1)

        l2 = QLabel("Desired Region(s)")
        self.layout.addWidget(l2)
        s2 = QHBoxLayout()
        self.desired_list = QTableWidget(1, 4)
        self.desired_list.setHorizontalHeaderLabels(
            ["region no", "D", "≥/≤", "d"])
        self.desired_list.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch)
        self.desired_list.horizontalHeader().setSectionResizeMode(0, QHeaderView.Fixed)
        self.desired_list.horizontalHeader().resizeSection(0, 100)
        self.desired_list.horizontalHeader().setSectionResizeMode(2, QHeaderView.Fixed)
        self.desired_list.horizontalHeader().resizeSection(2, 50)
        item = QLineEdit("1")
        self.desired_list.setCellWidget(0, 0, item)
        item2 = QLineEdit("")
        item2.setCompleter(completer)
        self.desired_list.setCellWidget(0, 1, item2)
        combo = QComboBox(self.desired_list)
        combo.insertItem(1, "≤")
        combo.insertItem(2, "≥")
        self.desired_list.setCellWidget(0, 2, combo)
        item = QLineEdit("0")
        self.desired_list.setCellWidget(0, 3, item)
        s2.addWidget(self.desired_list)

        s21 = QVBoxLayout()
        self.add_desire = QPushButton("+")
        self.add_desire.clicked.connect(self.add_desired_region)
        self.rem_desire = QPushButton("-")
        self.rem_desire.clicked.connect(self.rem_desired_region)
        s21.addWidget(self.add_desire)
        s21.addWidget(self.rem_desire)
        s2.addItem(s21)
        self.layout.addItem(s2)

        s3 = QHBoxLayout()

        sgx = QVBoxLayout()
        self.gen_kos = QCheckBox("Gene KOs")
        self.exclude_boundary = QCheckBox(
            "Exclude boundary\nreactions as cuts")
        sg1 = QHBoxLayout()
        s31 = QVBoxLayout()
        l = QLabel("Max. Solutions")
        s31.addWidget(l)
        l = QLabel("Max. Size")
        s31.addWidget(l)
        l = QLabel("Time Limit [sec]")
        s31.addWidget(l)

        sg1.addItem(s31)

        s32 = QVBoxLayout()
        self.max_solu = QLineEdit("inf")
        self.max_solu.setMaximumWidth(50)
        s32.addWidget(self.max_solu)
        self.max_size = QLineEdit("7")
        self.max_size.setMaximumWidth(50)
        s32.addWidget(self.max_size)
        self.time_limit = QLineEdit("inf")
        self.time_limit.setMaximumWidth(50)
        s32.addWidget(self.time_limit)

        sg1.addItem(s32)
        sgx.addWidget(self.gen_kos)
        sgx.addWidget(self.exclude_boundary)
        sgx.addItem(sg1)
        s3.addItem(sgx)

        g3 = QGroupBox("Solver")
        s33 = QVBoxLayout()
        self.bg1 = QButtonGroup()
        optlang_solver_name = interface_to_str(
            appdata.project.cobra_py_model.problem)
        self.solver_optlang = QRadioButton(f"{optlang_solver_name} (optlang)")
        self.solver_optlang.setToolTip(
            "Uses the solver specified by the current model.")
        s33.addWidget(self.solver_optlang)
        self.bg1.addButton(self.solver_optlang)
        self.solver_cplex_matlab = QRadioButton("CPLEX (MATLAB)")
        self.solver_cplex_matlab.setToolTip(
            "Only enabled with MATLAB and CPLEX")
        s33.addWidget(self.solver_cplex_matlab)
        self.bg1.addButton(self.solver_cplex_matlab)
        self.solver_cplex_java = QRadioButton("CPLEX (Octave)")
        self.solver_cplex_java.setToolTip("Only enabled with Octave and CPLEX")
        s33.addWidget(self.solver_cplex_java)
        self.bg1.addButton(self.solver_cplex_java)
        self.solver_intlinprog = QRadioButton("intlinprog (MATLAB)")
        self.solver_intlinprog.setToolTip("Only enabled with MATLAB")
        s33.addWidget(self.solver_intlinprog)
        self.bg1.addButton(self.solver_intlinprog)
        self.solver_glpk = QRadioButton("GLPK (Octave/MATLAB)")
        s33.addWidget(self.solver_glpk)
        self.bg1.addButton(self.solver_glpk)
        self.bg1.buttonClicked.connect(self.configure_solver_options)
        g3.setLayout(s33)
        s3.addWidget(g3)

        g4 = QGroupBox("MCS search")
        s34 = QVBoxLayout()
        self.bg2 = QButtonGroup()
        self.any_mcs = QRadioButton("any MCS (fast)")
        self.any_mcs.setChecked(True)
        s34.addWidget(self.any_mcs)
        self.bg2.addButton(self.any_mcs)

        # Search type: by cardinality only with CPLEX possible
        self.mcs_by_cardinality = QRadioButton("by cardinality")
        s34.addWidget(self.mcs_by_cardinality)
        self.bg2.addButton(self.mcs_by_cardinality)

        self.smalles_mcs_first = QRadioButton("smallest MCS first")
        s34.addWidget(self.smalles_mcs_first)
        self.bg2.addButton(self.smalles_mcs_first)
        g4.setLayout(s34)

        s3.addWidget(g4)
        self.layout.addItem(s3)

        # Disable incompatible combinations
        if appdata.selected_engine == 'None':
            self.solver_optlang.setChecked(True)
            self.solver_cplex_matlab.setEnabled(False)
            self.solver_cplex_java.setEnabled(False)
            self.solver_glpk.setEnabled(False)
            self.solver_intlinprog.setEnabled(False)
            if optlang_solver_name != 'cplex':
                self.mcs_by_cardinality.setEnabled(False)
        else:
            self.solver_glpk.setChecked(True)
            if not self.eng.is_cplex_matlab_ready():
                self.solver_cplex_matlab.setEnabled(False)
            if not self.eng.is_cplex_java_ready():
                self.solver_cplex_java.setEnabled(False)
            if self.appdata.is_matlab_set():
                self.solver_cplex_java.setEnabled(False)
            if not self.appdata.is_matlab_set():
                self.solver_cplex_matlab.setEnabled(False)
                self.solver_intlinprog.setEnabled(False)
        self.configure_solver_options()

        s4 = QVBoxLayout()
        self.consider_scenario = QCheckBox(
            "Consider constraint given by scenario")
        s4.addWidget(self.consider_scenario)
        self.advanced = QCheckBox(
            "Advanced: Define knockout/addition costs for genes/reactions")
        self.advanced.setEnabled(False)
        s4.addWidget(self.advanced)
        self.layout.addItem(s4)

        buttons = QHBoxLayout()
        # self.save = QPushButton("save")
        # buttons.addWidget(self.save)
        # self.load = QPushButton("load")
        # buttons.addWidget(self.load)
        self.compute_mcs = QPushButton("Compute MCS")
        buttons.addWidget(self.compute_mcs)
        # self.compute_mcs2 = QPushButton("Compute MCS2")
        # buttons.addWidget(self.compute_mcs2)
        self.cancel = QPushButton("Close")
        buttons.addWidget(self.cancel)
        self.layout.addItem(buttons)

        # max width for buttons
        self.add_target.setMaximumWidth(20)
        self.rem_target.setMaximumWidth(20)
        self.add_desire.setMaximumWidth(20)
        self.rem_desire.setMaximumWidth(20)

        self.setLayout(self.layout)

        # Connecting the signal
        self.cancel.clicked.connect(self.reject)
        self.compute_mcs.clicked.connect(self.compute)

    @Slot()
    def configure_solver_options(self):
        optlang_solver_name = interface_to_str(
            self.appdata.project.cobra_py_model.problem)
        if self.solver_optlang.isChecked():
            self.gen_kos.setChecked(False)
            self.gen_kos.setEnabled(False)
            self.exclude_boundary.setEnabled(True)
            if optlang_solver_name != 'cplex':
                if self.mcs_by_cardinality.isChecked():
                    self.mcs_by_cardinality.setChecked(False)
                    self.any_mcs.setChecked(True)
                self.mcs_by_cardinality.setEnabled(False)
                self.mcs_by_cardinality.setChecked(False)

        else:
            self.gen_kos.setEnabled(True)
            self.exclude_boundary.setChecked(False)
            self.exclude_boundary.setEnabled(False)
            self.mcs_by_cardinality.setEnabled(True)

    def add_target_region(self):
        i = self.target_list.rowCount()
        self.target_list.insertRow(i)

        completer = QCompleter(
            self.appdata.project.cobra_py_model.reactions.list_attr("id"), self)
        completer.setCaseSensitivity(Qt.CaseInsensitive)

        item = QLineEdit("1")
        self.target_list.setCellWidget(i, 0, item)
        item2 = QLineEdit("")
        item2.setCompleter(completer)
        self.target_list.setCellWidget(i, 1, item2)
        combo = QComboBox(self.target_list)
        combo.insertItem(1, "≤")
        combo.insertItem(2, "≥")
        self.target_list.setCellWidget(i, 2, combo)
        item = QLineEdit("0")
        self.target_list.setCellWidget(i, 3, item)

    def add_desired_region(self):
        i = self.desired_list.rowCount()
        self.desired_list.insertRow(i)

        completer = QCompleter(
            self.appdata.project.cobra_py_model.reactions.list_attr("id"), self)
        completer.setCaseSensitivity(Qt.CaseInsensitive)

        item = QLineEdit("1")
        self.desired_list.setCellWidget(i, 0, item)
        item2 = QLineEdit("")
        item2.setCompleter(completer)
        self.desired_list.setCellWidget(i, 1, item2)
        combo = QComboBox(self.desired_list)
        combo.insertItem(1, "≤")
        combo.insertItem(2, "≥")
        self.desired_list.setCellWidget(i, 2, combo)
        item = QLineEdit("0")
        self.desired_list.setCellWidget(i, 3, item)

    def rem_target_region(self):
        i = self.target_list.rowCount()
        self.target_list.removeRow(i-1)

    def rem_desired_region(self):
        i = self.desired_list.rowCount()
        self.desired_list.removeRow(i-1)

    def compute(self):
        if self.solver_optlang.isChecked():
            self.compute_optlang()
        else:
            self.compute_legacy()

    def compute_legacy(self):
        self.setCursor(Qt.BusyCursor)
        # create CobraModel for matlab
        with self.appdata.project.cobra_py_model as model:
            if self.consider_scenario.isChecked():  # integrate scenario into model bounds
                for r in self.appdata.project.scen_values.keys():
                    model.reactions.get_by_id(
                        r).bounds = self.appdata.project.scen_values[r]
            cobra.io.save_matlab_model(model, os.path.join(
                self.appdata.cna_path, "cobra_model.mat"), varname="cbmodel")
        self.eng.eval("load('cobra_model.mat')",
                      nargout=0)

        try:
            self.eng.eval("cnap = CNAcobra2cna(cbmodel);",
                          nargout=0,
                          stdout=self.out, stderr=self.err)
        except Exception:
            output = io.StringIO()
            traceback.print_exc(file=output)
            exstr = output.getvalue()
            print(exstr)
            QMessageBox.warning(self, 'Unknown exception occured!',
                                exstr+'\nPlease report the problem to:\n\
                                    \nhttps://github.com/cnapy-org/CNApy/issues')
            return

        self.eng.eval("genes = [];", nargout=0,
                      stdout=self.out, stderr=self.err)
        cmd = "maxSolutions = " + str(float(self.max_solu.text())) + ";"
        self.eng.eval(cmd, nargout=0, stdout=self.out, stderr=self.err)

        cmd = "maxSize = " + str(int(self.max_size.text())) + ";"
        self.eng.eval(cmd, nargout=0, stdout=self.out, stderr=self.err)

        cmd = "milp_time_limit = " + str(float(self.time_limit.text())) + ";"
        self.eng.eval(cmd, nargout=0, stdout=self.out, stderr=self.err)

        if self.gen_kos.isChecked():
            self.eng.eval("gKOs = 1;", nargout=0)
        else:
            self.eng.eval("gKOs = 0;", nargout=0)
        if self.advanced.isChecked():
            self.eng.eval("advanced_on = 1;", nargout=0)
        else:
            self.eng.eval("advanced_on = 0;", nargout=0)

        if self.solver_intlinprog.isChecked():
            self.eng.eval("solver = 'intlinprog';", nargout=0)
        if self.solver_cplex_java.isChecked():
            self.eng.eval("solver = 'java_cplex_new';", nargout=0)
        if self.solver_cplex_matlab.isChecked():
            self.eng.eval("solver = 'matlab_cplex';", nargout=0)
        if self.solver_glpk.isChecked():
            self.eng.eval("solver = 'glpk';", nargout=0)
        if self.any_mcs.isChecked():
            self.eng.eval("mcs_search_mode = 'search_1';", nargout=0)
        elif self.mcs_by_cardinality.isChecked():
            self.eng.eval("mcs_search_mode = 'search_2';", nargout=0)
        elif self.smalles_mcs_first.isChecked():
            self.eng.eval("mcs_search_mode = 'search_3';", nargout=0)

        rows = self.target_list.rowCount()
        for i in range(0, rows):
            p1 = self.target_list.cellWidget(i, 0).text()
            p2 = self.target_list.cellWidget(i, 1).text()
            if self.target_list.cellWidget(i, 2).currentText() == '≤':
                p3 = "<="
            else:
                p3 = ">="
            p4 = self.target_list.cellWidget(i, 3).text()
            cmd = "dg_T = {[" + p1+"], '" + p2 + \
                "', '" + p3 + "', [" + p4 + "']};"
            self.eng.eval(cmd, nargout=0,
                          stdout=self.out, stderr=self.err)

        rows = self.desired_list.rowCount()
        for i in range(0, rows):
            p1 = self.desired_list.cellWidget(i, 0).text()
            p2 = self.desired_list.cellWidget(i, 1).text()
            if self.desired_list.cellWidget(i, 2).currentText() == '≤':
                p3 = "<="
            else:
                p3 = ">="
            p4 = self.desired_list.cellWidget(i, 3).text()
            cmd = "dg_D = {[" + p1+"], '" + p2 + \
                "', '" + p3 + "', [" + p4 + "']};"
            self.eng.eval(cmd, nargout=0)

        # get some data
        self.eng.eval("reac_id = cellstr(cnap.reacID).';",
                      nargout=0, stdout=self.out, stderr=self.err)

        mcs = []
        values = []
        reactions = []
        reac_id = []
        if self.appdata.is_matlab_set():
            reac_id = self.eng.workspace['reac_id']
            try:
                self.eng.eval("[mcs] = cnapy_compute_mcs(cnap, genes, maxSolutions, maxSize, milp_time_limit, gKOs, advanced_on, solver, mcs_search_mode, dg_T,dg_D);",
                              nargout=0)
            except Exception:
                output = io.StringIO()
                traceback.print_exc(file=output)
                exstr = output.getvalue()
                print(exstr)
                QMessageBox.warning(self, 'Unknown exception occured!',
                                    exstr+'\nPlease report the problem to:\n\
                                    \nhttps://github.com/cnapy-org/CNApy/issues')
                return
            else:
                self.eng.eval("[reaction, mcs, value] = find(mcs);", nargout=0,
                              stdout=self.out, stderr=self.err)
                reactions = self.eng.workspace['reaction']
                mcs = self.eng.workspace['mcs']
                values = self.eng.workspace['value']
        elif self.appdata.is_octave_ready():
            reac_id = self.eng.pull('reac_id')
            reac_id = reac_id[0]
            try:
                self.eng.eval("[mcs] = cnapy_compute_mcs(cnap, genes, maxSolutions, maxSize, milp_time_limit, gKOs, advanced_on, solver, mcs_search_mode, dg_T,dg_D);",
                              nargout=0)
            except Exception:
                output = io.StringIO()
                traceback.print_exc(file=output)
                exstr = output.getvalue()
                print(exstr)
                QMessageBox.warning(self, 'Unknown exception occured!',
                                    exstr+'\nPlease report the problem to:\n\
                                    \nhttps://github.com/cnapy-org/CNApy/issues')
                return
            else:
                self.eng.eval("[reaction, mcs, value] = find(mcs);", nargout=0,
                              stdout=self.out, stderr=self.err)
                reactions = self.eng.pull('reaction')
                mcs = self.eng.pull('mcs')
                values = self.eng.pull('value')

        if len(mcs) == 0:
            QMessageBox.information(self, 'No cut sets',
                                          'Cut sets have not been calculated or do not exist.')
        else:
            last_mcs = 1
            omcs = []
            current_mcs = {}
            for i in range(0, len(reactions)):
                reacid = int(reactions[i][0])
                reaction = reac_id[reacid-1]
                c_mcs = int(mcs[i][0])
                c_value = int(values[i][0])
                if c_value == -1:  # -1 stands for removed which is 0 in the ui
                    c_value = 0
                if c_mcs > last_mcs:
                    omcs.append(current_mcs)
                    last_mcs = c_mcs
                    current_mcs = {}
                current_mcs[reaction] = c_value
            omcs.append(current_mcs)
            self.appdata.project.modes = omcs
            self.centralwidget.mode_navigator.current = 0
            QMessageBox.information(self, 'Cut sets found',
                                          str(len(omcs))+' Cut sets have been calculated.')

        self.centralwidget.update_mode()
        self.centralwidget.mode_navigator.title.setText("MCS Navigation")

        self.setCursor(Qt.ArrowCursor)

    def compute_optlang(self):

        self.setCursor(Qt.BusyCursor)
        max_mcs_num = float(self.max_solu.text())
        max_mcs_size = int(self.max_size.text())
        timeout = float(self.time_limit.text())
        if timeout is float('inf'):
            timeout = None

        # if self.gen_kos.isChecked():
        #     self.eng.eval("gKOs = 1;", nargout=0)
        # else:
        #     self.eng.eval("gKOs = 0;", nargout=0)
        # if self.advanced.isChecked():
        #     self.eng.eval("advanced_on = 1;", nargout=0)
        # else:
        #     self.eng.eval("advanced_on = 0;", nargout=0)

        if self.smalles_mcs_first.isChecked():
            enum_method = 1
        elif self.mcs_by_cardinality.isChecked():
            enum_method = 2
        elif self.any_mcs.isChecked():
            enum_method = 3

        with self.appdata.project.cobra_py_model as model:
            if self.consider_scenario.isChecked():  # integrate scenario into model bounds
                for r in self.appdata.project.scen_values.keys():
                    model.reactions.get_by_id(
                        r).bounds = self.appdata.project.scen_values[r]
            reac_id = model.reactions.list_attr("id")
            reac_id_symbols = cMCS_enumerator.get_reac_id_symbols(reac_id)
            rows = self.target_list.rowCount()
            targets = dict()
            for i in range(0, rows):
                p1 = self.target_list.cellWidget(i, 0).text()
                p2 = self.target_list.cellWidget(i, 1).text()
                if len(p1) > 0 and len(p2) > 0:
                    if self.target_list.cellWidget(i, 2).currentText() == '≤':
                        p3 = "<="
                    else:
                        p3 = ">="
                    p4 = float(self.target_list.cellWidget(i, 3).text())
                    targets.setdefault(p1, []).append((p2, p3, p4))
            targets = list(targets.values())
            targets = [cMCS_enumerator.relations2leq_matrix(cMCS_enumerator.parse_relations(
                t, reac_id_symbols=reac_id_symbols), reac_id) for t in targets]

            rows = self.desired_list.rowCount()
            desired = dict()
            for i in range(0, rows):
                p1 = self.desired_list.cellWidget(i, 0).text()
                p2 = self.desired_list.cellWidget(i, 1).text()
                if len(p1) > 0 and len(p2) > 0:
                    if self.desired_list.cellWidget(i, 2).currentText() == '≤':
                        p3 = "<="
                    else:
                        p3 = ">="
                    p4 = float(self.desired_list.cellWidget(i, 3).text())
                    desired.setdefault(p1, []).append((p2, p3, p4))

            desired = list(desired.values())
            desired = [cMCS_enumerator.relations2leq_matrix(cMCS_enumerator.parse_relations(
                d, reac_id_symbols=reac_id_symbols), reac_id) for d in desired]

            try:
                mcs = cMCS_enumerator.compute_mcs(model,
                                                  targets=targets,
                                                  desired=desired,
                                                  enum_method=enum_method,
                                                  max_mcs_size=max_mcs_size,
                                                  max_mcs_num=max_mcs_num,
                                                  timeout=timeout,
                                                  exclude_boundary_reactions_as_cuts=self.exclude_boundary.isChecked())
            except cMCS_enumerator.InfeasibleRegion as e:
                QMessageBox.warning(self, 'Cannot calculate MCS', str(e))
                return targets, desired
            except Exception:
                output = io.StringIO()
                traceback.print_exc(file=output)
                exstr = output.getvalue()
                print(exstr)
                QMessageBox.warning(self, 'An exception has occured!',
                                    exstr+'\nPlease report the problem to:\n\
                                    \nhttps://github.com/cnapy-org/CNApy/issues')
                return targets, desired
            finally:
                self.setCursor(Qt.ArrowCursor)

        if len(mcs) == 0:
            QMessageBox.information(self, 'No cut sets',
                                          'Cut sets have not been calculated or do not exist.')
            return targets, desired

        omcs = [{reac_id[i]: 0 for i in m} for m in mcs]
        self.appdata.project.modes = omcs
        self.centralwidget.mode_navigator.current = 0
        QMessageBox.information(self, 'Cut sets found',
                                      str(len(omcs))+' Cut sets have been calculated.')

        self.centralwidget.update_mode()
        self.centralwidget.mode_navigator.title.setText("MCS Navigation")
Ejemplo n.º 6
0
class Extension2ReaderTable(QWidget):
    """Table showing extension to reader mappings with removal button.

    Widget presented in preferences-plugin dialog."""

    valueChanged = Signal(int)

    def __init__(self, parent=None):
        super().__init__(parent=parent)

        self._table = QTableWidget()
        self._table.setShowGrid(False)
        self._populate_table()

        layout = QVBoxLayout()
        layout.addWidget(self._table)
        self.setLayout(layout)

    def _populate_table(self):
        """Add row for each extension to reader mapping in settings"""
        self._extension_col = 0
        self._reader_col = 1

        header_strs = [trans._('Extension'), trans._('Reader Plugin')]

        self._table.setColumnCount(2)
        self._table.setColumnWidth(self._extension_col, 100)
        self._table.setColumnWidth(self._reader_col, 150)
        self._table.verticalHeader().setVisible(False)
        self._table.setMinimumHeight(120)

        extension2reader = get_settings().plugins.extension2reader
        if len(extension2reader) > 0:
            self._table.setRowCount(len(extension2reader))
            self._table.horizontalHeader().setStretchLastSection(True)
            self._table.horizontalHeader().setStyleSheet(
                'border-bottom: 2px solid white;')
            self._table.setHorizontalHeaderLabels(header_strs)

            for row, (extension,
                      plugin_name) in enumerate(extension2reader.items()):
                item = QTableWidgetItem(extension)
                item.setFlags(Qt.NoItemFlags)
                self._table.setItem(row, self._extension_col, item)

                plugin_widg = QWidget()
                # need object name to easily find row
                plugin_widg.setObjectName(f'{extension}')
                plugin_widg.setLayout(QHBoxLayout())
                plugin_widg.layout().setContentsMargins(0, 0, 0, 0)

                plugin_label = QLabel(plugin_name)
                # need object name to easily work out which button was clicked
                remove_btn = QPushButton('x', objectName=f'{extension}')
                remove_btn.setFixedWidth(30)
                remove_btn.setStyleSheet('margin: 4px;')
                remove_btn.setToolTip(
                    trans._('Remove this extension to reader association'))
                remove_btn.clicked.connect(self._remove_extension_assignment)

                plugin_widg.layout().addWidget(plugin_label)
                plugin_widg.layout().addWidget(remove_btn)
                self._table.setCellWidget(row, self._reader_col, plugin_widg)
        else:
            # Display that there are no extensions with reader associations
            self._table.setRowCount(1)
            self._table.setHorizontalHeaderLabels(header_strs)

            self._table.setColumnHidden(self._reader_col, True)
            self._table.setColumnWidth(self._extension_col, 200)
            item = QTableWidgetItem(trans._('No extensions found.'))
            item.setFlags(Qt.NoItemFlags)
            self._table.setItem(0, 0, item)

    def _remove_extension_assignment(self, event):
        """Delete extension to reader mapping setting and remove table row"""
        extension_to_remove = self.sender().objectName()
        current_settings = get_settings().plugins.extension2reader
        # need explicit assignment to new object here for persistence
        get_settings().plugins.extension2reader = {
            k: v
            for k, v in current_settings.items() if k != extension_to_remove
        }

        for i in range(self._table.rowCount()):
            row_widg_name = self._table.cellWidget(
                i, self._reader_col).objectName()
            if row_widg_name == extension_to_remove:
                self._table.removeRow(i)
                return
Ejemplo n.º 7
0
class WndManageEmissionLines(SecondaryWindow):

    signal_selected_element_changed = Signal(str)
    signal_update_element_selection_list = Signal()
    signal_update_add_remove_btn_state = Signal(bool, bool)
    signal_marker_state_changed = Signal(bool)

    signal_parameters_changed = Signal()

    def __init__(self, *, gpc, gui_vars):
        super().__init__()

        # Global processing classes
        self.gpc = gpc
        # Global GUI variables (used for control of GUI state)
        self.gui_vars = gui_vars

        # Threshold used for peak removal (displayed in lineedit)
        self._remove_peak_threshold = self.gpc.get_peak_threshold()

        self._enable_events = False

        self._eline_list = [
        ]  # List of emission lines (used in the line selection combo)
        self._table_contents = [
        ]  # Keep a copy of table contents (list of dict)
        self._selected_eline = ""

        self.initialize()

        self._enable_events = True

        # Marker state is reported by Matplotlib plot in 'line_plot' model
        def cb(marker_state):
            self.signal_marker_state_changed.emit(marker_state)

        self.gpc.set_marker_reporter(cb)
        self.signal_marker_state_changed.connect(
            self.slot_marker_state_changed)

        # Update button states
        self._update_add_remove_btn_state()
        self._update_add_edit_userpeak_btn_state()
        self._update_add_edit_pileup_peak_btn_state()

    def initialize(self):
        self.setWindowTitle("PyXRF: Add/Remove Emission Lines")
        self.resize(600, 600)

        top_buttons = self._setup_select_elines()
        self._setup_elines_table()
        bottom_buttons = self._setup_action_buttons()

        vbox = QVBoxLayout()

        # Group of buttons above the table
        vbox.addLayout(top_buttons)

        # Tables
        hbox = QHBoxLayout()
        hbox.addWidget(self.tbl_elines)
        vbox.addLayout(hbox)

        vbox.addLayout(bottom_buttons)

        self.setLayout(vbox)

        self._set_tooltips()

    def _setup_select_elines(self):

        self.cb_select_all = QCheckBox("All")
        self.cb_select_all.setChecked(True)
        self.cb_select_all.toggled.connect(self.cb_select_all_toggled)

        self.element_selection = ElementSelection()

        # The following field should switched to 'editable' state from when needed
        self.le_peak_intensity = LineEditReadOnly()

        self.pb_add_eline = QPushButton("Add")
        self.pb_add_eline.clicked.connect(self.pb_add_eline_clicked)

        self.pb_remove_eline = QPushButton("Remove")
        self.pb_remove_eline.clicked.connect(self.pb_remove_eline_clicked)

        self.pb_user_peaks = QPushButton("New User Peak ...")
        self.pb_user_peaks.clicked.connect(self.pb_user_peaks_clicked)
        self.pb_pileup_peaks = QPushButton("New Pileup Peak ...")
        self.pb_pileup_peaks.clicked.connect(self.pb_pileup_peaks_clicked)

        self.element_selection.signal_current_item_changed.connect(
            self.element_selection_item_changed)

        vbox = QVBoxLayout()

        hbox = QHBoxLayout()
        hbox.addWidget(self.element_selection)
        hbox.addWidget(self.le_peak_intensity)
        hbox.addWidget(self.pb_add_eline)
        hbox.addWidget(self.pb_remove_eline)
        vbox.addLayout(hbox)

        hbox = QHBoxLayout()
        hbox.addWidget(self.cb_select_all)
        hbox.addStretch(1)
        hbox.addWidget(self.pb_user_peaks)
        hbox.addWidget(self.pb_pileup_peaks)
        vbox.addLayout(hbox)

        # Wrap vbox into hbox, because it will be inserted into vbox
        hbox = QHBoxLayout()
        hbox.addLayout(vbox)
        hbox.addStretch(1)
        return hbox

    def _setup_elines_table(self):
        """The table has only functionality necessary to demonstrate how it is going
        to look. A lot more code is needed to actually make it run."""

        self._validator_peak_height = QDoubleValidator()
        self._validator_peak_height.setBottom(0.01)

        self.tbl_elines = QTableWidget()
        self.tbl_elines.setStyleSheet(
            "QTableWidget::item{color: black;}"
            "QTableWidget::item:selected{background-color: red;}"
            "QTableWidget::item:selected{color: white;}")

        self.tbl_labels = [
            "", "Z", "Line", "E, keV", "Peak Int.", "Rel. Int.(%)", "CS"
        ]
        self.tbl_cols_editable = ["Peak Int."]
        self.tbl_value_min = {"Rel. Int.(%)": 0.1}
        tbl_cols_resize_to_content = ["", "Z", "Line"]

        self.tbl_elines.setColumnCount(len(self.tbl_labels))
        self.tbl_elines.verticalHeader().hide()
        self.tbl_elines.setHorizontalHeaderLabels(self.tbl_labels)

        self.tbl_elines.setSelectionBehavior(QTableWidget.SelectRows)
        self.tbl_elines.setSelectionMode(QTableWidget.SingleSelection)
        self.tbl_elines.itemSelectionChanged.connect(
            self.tbl_elines_item_selection_changed)
        self.tbl_elines.itemChanged.connect(self.tbl_elines_item_changed)

        header = self.tbl_elines.horizontalHeader()
        for n, lbl in enumerate(self.tbl_labels):
            # Set stretching for the columns
            if lbl in tbl_cols_resize_to_content:
                header.setSectionResizeMode(n, QHeaderView.ResizeToContents)
            else:
                header.setSectionResizeMode(n, QHeaderView.Stretch)
            # Set alignment for the columns headers (HEADERS only)
            if n == 0:
                header.setDefaultAlignment(Qt.AlignCenter)
            else:
                header.setDefaultAlignment(Qt.AlignRight)

        self.cb_sel_list = []  # List of checkboxes

    def _setup_action_buttons(self):
        self.pb_remove_rel = QPushButton("Remove Rel.Int.(%) <")
        self.pb_remove_rel.clicked.connect(self.pb_remove_rel_clicked)

        self.le_remove_rel = LineEditExtended("")
        self._validator_le_remove_rel = QDoubleValidator()
        self._validator_le_remove_rel.setBottom(0.01)  # Some small number
        self._validator_le_remove_rel.setTop(100.0)
        self.le_remove_rel.setText(
            self._format_threshold(self._remove_peak_threshold))
        self._update_le_remove_rel_state()
        self.le_remove_rel.textChanged.connect(self.le_remove_rel_text_changed)
        self.le_remove_rel.editingFinished.connect(
            self.le_remove_rel_editing_finished)

        self.pb_remove_unchecked = QPushButton("Remove Unchecked Lines")
        self.pb_remove_unchecked.clicked.connect(
            self.pb_remove_unchecked_clicked)

        hbox = QHBoxLayout()
        hbox.addWidget(self.pb_remove_rel)
        hbox.addWidget(self.le_remove_rel)
        hbox.addStretch(1)
        hbox.addWidget(self.pb_remove_unchecked)

        return hbox

    def _set_tooltips(self):
        set_tooltip(self.cb_select_all,
                    "<b>Select/Deselect All</b> emission lines in the list")
        set_tooltip(self.element_selection, "<b>Set active</b> emission line")
        set_tooltip(self.le_peak_intensity,
                    "Set or modify <b>intensity</b> of the active peak.")
        set_tooltip(self.pb_add_eline, "<b>Add</b> emission line to the list.")
        set_tooltip(self.pb_remove_eline,
                    "<b>Remove</b> emission line from the list.")
        set_tooltip(
            self.pb_user_peaks,
            "Open dialog box to add or modify parameters of the <b>user-defined peak</b>"
        )
        set_tooltip(
            self.pb_pileup_peaks,
            "Open dialog box to add or modify parameters of the <b>pileup peak</b>"
        )

        set_tooltip(self.tbl_elines,
                    "The list of the selected <b>emission lines</b>")

        # set_tooltip(self.pb_update,
        #             "Update the internally stored list of selected emission lines "
        #             "and their parameters. This button is <b>deprecated</b>, but still may be "
        #             "needed in some situations. In future releases it will be <b>removed</b> or replaced "
        #             "with 'Accept' button. Substantial changes to the computational code is needed before "
        #             "it happens.")
        # set_tooltip(self.pb_undo,
        #             "<b>Undo</b> changes to the table of selected emission lines. Doesn't always work.")
        set_tooltip(
            self.pb_remove_rel,
            "<b>Remove emission lines</b> from the list if their relative intensity is less "
            "then specified threshold.",
        )
        set_tooltip(
            self.le_remove_rel,
            "<b>Threshold</b> that controls which emission lines are removed "
            "when <b>Remove Rel.Int.(%)</b> button is pressed.",
        )
        set_tooltip(self.pb_remove_unchecked,
                    "Remove <b>unchecked</b> emission lines from the list.")

    def update_widget_state(self, condition=None):
        # Update the state of the menu bar
        state = not self.gui_vars["gui_state"]["running_computations"]
        self.setEnabled(state)

        # Hide the window if required by the program state
        state_file_loaded = self.gui_vars["gui_state"]["state_file_loaded"]
        state_model_exist = self.gui_vars["gui_state"]["state_model_exists"]
        if not state_file_loaded or not state_model_exist:
            self.hide()

        if condition == "tooltips":
            self._set_tooltips()

    def fill_eline_table(self, table_contents):
        self._table_contents = copy.deepcopy(table_contents)

        self._enable_events = False

        self.tbl_elines.clearContents()

        # Clear the list of checkboxes
        for cb in self.cb_sel_list:
            cb.stateChanged.connect(self.cb_eline_state_changed)
        self.cb_sel_list = []

        self.tbl_elines.setRowCount(len(table_contents))
        for nr, row in enumerate(table_contents):
            sel_status = row["sel_status"]
            row_data = [
                None, row["z"], row["eline"], row["energy"], row["peak_int"],
                row["rel_int"], row["cs"]
            ]

            for nc, entry in enumerate(row_data):

                label = self.tbl_labels[nc]

                # Set alternating background colors for the table rows
                #   Make background for editable items a little brighter
                brightness = 240 if label in self.tbl_cols_editable else 220
                if nr % 2:
                    rgb_bckg = (255, brightness, brightness)  # Light-red
                else:
                    rgb_bckg = (brightness, 255, brightness)  # Light-green

                if nc == 0:
                    item = QWidget()
                    cb = CheckBoxNamed(name=f"{nr}")
                    item_hbox = QHBoxLayout(item)
                    item_hbox.addWidget(cb)
                    item_hbox.setAlignment(Qt.AlignCenter)
                    item_hbox.setContentsMargins(0, 0, 0, 0)

                    css1 = get_background_css(rgb_bckg,
                                              widget="QCheckbox",
                                              editable=False)
                    css2 = get_background_css(rgb_bckg,
                                              widget="QWidget",
                                              editable=False)
                    item.setStyleSheet(css2 + css1)

                    cb.setChecked(Qt.Checked if sel_status else Qt.Unchecked)
                    cb.stateChanged.connect(self.cb_eline_state_changed)
                    cb.setStyleSheet("QCheckBox {color: black;}")
                    self.cb_sel_list.append(cb)

                    self.tbl_elines.setCellWidget(nr, nc, item)
                else:

                    s = None
                    # The case when the value (Rel. Int.) is limited from the bottom
                    #   We don't want to print very small numbers here
                    if label in self.tbl_value_min:
                        v = self.tbl_value_min[label]
                        if isinstance(entry,
                                      (float, np.float64)) and (entry < v):
                            s = f"<{v:.2f}"
                    if s is None:
                        if isinstance(entry, (float, np.float64)):
                            s = f"{entry:.2f}" if entry else "-"
                        else:
                            s = f"{entry}" if entry else "-"

                    item = QTableWidgetItem(s)

                    item.setTextAlignment(Qt.AlignRight | Qt.AlignVCenter)

                    # Set all columns not editable (unless needed)
                    if label not in self.tbl_cols_editable:
                        item.setFlags(item.flags() & ~Qt.ItemIsEditable)

                    brush = QBrush(QColor(*rgb_bckg))
                    item.setBackground(brush)

                    self.tbl_elines.setItem(nr, nc, item)

        self._enable_events = True
        # Update the rest of the widgets
        self._update_widgets_based_on_table_state()

    @Slot()
    def update_widget_data(self):
        # This is typically a new set of emission lines. Clear the selection both
        #   in the table and in the element selection tool.
        self.element_selection.set_current_item("")
        self.tbl_elines.clearSelection()
        self._set_selected_eline("")
        # Now update the tables
        self._update_eline_selection_list()
        self.update_eline_table()
        self._update_add_remove_btn_state()

    def pb_pileup_peaks_clicked(self):
        data = {}

        eline = self._selected_eline
        if self.gpc.get_eline_name_category(eline) == "pileup":
            logger.error(
                f"Attempt to add pileup peak '{eline}' while another pileup peak is selected."
            )
            return

        energy, marker_visible = self.gpc.get_suggested_manual_peak_energy()
        best_guess = self.gpc.get_guessed_pileup_peak_components(energy=energy,
                                                                 tolerance=0.1)
        if best_guess is not None:
            el1, el2, energy = best_guess
        else:
            # No peaks were found, enter peaks manually
            el1, el2, energy = "", "", 0
        data["element1"] = el1
        data["element2"] = el2
        data["energy"] = energy
        data["range_low"], data[
            "range_high"] = self.gpc.get_selected_energy_range()

        if not marker_visible:
            # We shouldn't end up here, but this will protect from crashing in case
            #   the button was not disabled (a bug).
            msg = "Select location of the new peak center (energy)\nby clicking on the plot in 'Fit Model' tab"
            msgbox = QMessageBox(QMessageBox.Information,
                                 "User Input Required",
                                 msg,
                                 QMessageBox.Ok,
                                 parent=self)
            msgbox.exec()
        else:
            dlg = DialogPileupPeakParameters()

            def func():
                def f(e1, e2):
                    try:
                        name = self.gpc.generate_pileup_peak_name(e1, e2)
                        e = self.gpc.get_pileup_peak_energy(name)
                    except Exception:
                        e = 0
                    return e

                return f

            dlg.set_compute_energy_function(func())
            dlg.set_parameters(data)
            if dlg.exec():
                print("Pileup peak is added")
                try:
                    data = dlg.get_parameters()
                    eline1, eline2 = data["element1"], data["element2"]
                    eline = self.gpc.generate_pileup_peak_name(eline1, eline2)
                    self.gpc.add_peak_manual(eline)
                    self.update_eline_table()  # Update the table
                    self.tbl_elines_set_selection(
                        eline)  # Select new emission line
                    self._set_selected_eline(eline)
                    self._set_fit_status(False)
                    logger.info(f"New pileup peak {eline} was added")
                except RuntimeError as ex:
                    msg = str(ex)
                    msgbox = QMessageBox(QMessageBox.Critical,
                                         "Error",
                                         msg,
                                         QMessageBox.Ok,
                                         parent=self)
                    msgbox.exec()
                    # Reload the table anyway (nothing is going to be selected)
                    self.update_eline_table()

    def pb_user_peaks_clicked(self):
        eline = self._selected_eline
        # If current peak is user_defined peak
        is_userpeak = self.gpc.get_eline_name_category(eline) == "userpeak"

        if is_userpeak:
            data = {}
            data["enabled"] = True
            data["name"] = eline
            data["maxv"] = self.gpc.get_eline_intensity(eline)
            data["energy"], data[
                "fwhm"] = self.gpc.get_current_userpeak_energy_fwhm()

            dlg = DialogEditUserPeakParameters()
            dlg.set_parameters(data=data)
            if dlg.exec():
                print("Editing of user defined peak is completed")
                try:
                    eline = data["name"]
                    data = dlg.get_parameters()
                    self.gpc.update_userpeak(data["name"], data["energy"],
                                             data["maxv"], data["fwhm"])
                    self._set_fit_status(False)
                    logger.info(f"User defined peak {eline} was updated.")
                except Exception as ex:
                    msg = str(ex)
                    msgbox = QMessageBox(QMessageBox.Critical,
                                         "Error",
                                         msg,
                                         QMessageBox.Ok,
                                         parent=self)
                    msgbox.exec()
                # Reload the table anyway (nothing is going to be selected)
                self.update_eline_table()

        else:
            data = {}
            data["name"] = self.gpc.get_unused_userpeak_name()
            data[
                "energy"], marker_visible = self.gpc.get_suggested_manual_peak_energy(
                )
            if marker_visible:
                dlg = DialogNewUserPeak()
                dlg.set_parameters(data=data)
                if dlg.exec():
                    try:
                        eline = data["name"]
                        self.gpc.add_peak_manual(eline)
                        self.update_eline_table()  # Update the table
                        self.tbl_elines_set_selection(
                            eline)  # Select new emission line
                        self._set_selected_eline(eline)
                        self._set_fit_status(False)
                        logger.info(f"New user defined peak {eline} is added")
                    except RuntimeError as ex:
                        msg = str(ex)
                        msgbox = QMessageBox(QMessageBox.Critical,
                                             "Error",
                                             msg,
                                             QMessageBox.Ok,
                                             parent=self)
                        msgbox.exec()
                        # Reload the table anyway (nothing is going to be selected)
                        self.update_eline_table()
            else:
                msg = ("Select location of the new peak center (energy)\n"
                       "by clicking on the plot in 'Fit Model' tab")
                msgbox = QMessageBox(QMessageBox.Information,
                                     "User Input Required",
                                     msg,
                                     QMessageBox.Ok,
                                     parent=self)
                msgbox.exec()

    @Slot()
    def pb_add_eline_clicked(self):
        logger.debug("'Add line' clicked")
        # It is assumed that this button is active only if an element is selected from the list
        #   of available emission lines. It can't be used to add user-defined peaks or pileup peaks.
        eline = self._selected_eline
        if eline:
            try:
                self.gpc.add_peak_manual(eline)
                self.update_eline_table()  # Update the table
                self.tbl_elines_set_selection(
                    eline)  # Select new emission line
                self._set_fit_status(False)
            except RuntimeError as ex:
                msg = str(ex)
                msgbox = QMessageBox(QMessageBox.Critical,
                                     "Error",
                                     msg,
                                     QMessageBox.Ok,
                                     parent=self)
                msgbox.exec()
                # Reload the table anyway (nothing is going to be selected)
                self.update_eline_table()

    @Slot()
    def pb_remove_eline_clicked(self):
        logger.debug("'Remove line' clicked")
        eline = self._selected_eline
        if eline:
            # If currently selected line is the emission line (like Ca_K), we want
            # it to remain selected after it is deleted. This means that nothing is selected
            # in the table. For other lines, nothing should remain selected.
            self.tbl_elines.clearSelection()
            self.gpc.remove_peak_manual(eline)
            self.update_eline_table()  # Update the table
            if self.gpc.get_eline_name_category(eline) != "eline":
                eline = ""
            # This will update widgets
            self._set_selected_eline(eline)
            self._set_fit_status(False)

    def cb_select_all_toggled(self, state):
        self._enable_events = False

        eline_list, state_list = [], []

        for n_row in range(self.tbl_elines.rowCount()):
            eline = self._table_contents[n_row]["eline"]
            # Do not deselect lines in category 'other'. They probably never to be deleted.
            # They also could be deselected manually.
            if self.gpc.get_eline_name_category(
                    eline) == "other" and not state:
                to_check = True
            else:
                to_check = state
            self.cb_sel_list[n_row].setChecked(
                Qt.Checked if to_check else Qt.Unchecked)
            eline_list.append(eline)
            state_list.append(to_check)

        self.gpc.set_checked_emission_lines(eline_list, state_list)
        self._set_fit_status(False)
        self._enable_events = True

    def cb_eline_state_changed(self, name, state):
        if self._enable_events:
            n_row = int(name)
            state = state == Qt.Checked
            eline = self._table_contents[n_row]["eline"]
            self.gpc.set_checked_emission_lines([eline], [state])
            self._set_fit_status(False)

    def tbl_elines_item_changed(self, item):
        if self._enable_events:
            n_row, n_col = self.tbl_elines.row(item), self.tbl_elines.column(
                item)
            # Value was changed
            if n_col == 4:
                text = item.text()
                eline = self._table_contents[n_row]["eline"]
                if self._validator_peak_height.validate(
                        text, 0)[0] != QDoubleValidator.Acceptable:
                    val = self._table_contents[n_row]["peak_int"]
                    self._enable_events = False
                    item.setText(f"{val:.2f}")
                    self._enable_events = True
                    self._set_fit_status(False)
                else:
                    self.gpc.update_eline_peak_height(eline, float(text))
                    self.update_eline_table()

    def tbl_elines_item_selection_changed(self):
        sel_ranges = self.tbl_elines.selectedRanges()
        # The table is configured to have one or no selected ranges
        # 'Open' button should be enabled only if a range (row) is selected
        if sel_ranges:
            index = sel_ranges[0].topRow()
            eline = self._table_contents[index]["eline"]
            if self._enable_events:
                self._enable_events = False
                self._set_selected_eline(eline)
                self.element_selection.set_current_item(eline)
                self._enable_events = True

    def tbl_elines_set_selection(self, eline):
        """
        Select the row with emission line `eline` in the table. Deselect everything if
        the emission line does not exist.
        """
        index = self._get_eline_index_in_table(eline)
        self.tbl_elines.clearSelection()
        if index >= 0:
            self.tbl_elines.selectRow(index)

    def element_selection_item_changed(self, index, eline):
        self.signal_selected_element_changed.emit(eline)

        if self._enable_events:
            self._enable_events = False
            self._set_selected_eline(eline)
            self.tbl_elines_set_selection(eline)
            self._enable_events = True

    def pb_remove_rel_clicked(self):
        try:
            self.gpc.remove_peaks_below_threshold(self._remove_peak_threshold)
        except Exception as ex:
            msg = str(ex)
            msgbox = QMessageBox(QMessageBox.Critical,
                                 "Error",
                                 msg,
                                 QMessageBox.Ok,
                                 parent=self)
            msgbox.exec()
        self.update_eline_table()
        # Update the displayed estimated peak amplitude value 'le_peak_intensity'
        self._set_selected_eline(self._selected_eline)
        self._set_fit_status(False)

    def le_remove_rel_text_changed(self, text):
        self._update_le_remove_rel_state(text)

    def le_remove_rel_editing_finished(self):
        text = self.le_remove_rel.text()
        if self._validator_le_remove_rel.validate(
                text, 0)[0] == QDoubleValidator.Acceptable:
            self._remove_peak_threshold = float(text)
        else:
            self.le_remove_rel.setText(
                self._format_threshold(self._remove_peak_threshold))

    def pb_remove_unchecked_clicked(self):
        try:
            self.gpc.remove_unchecked_peaks()
        except Exception as ex:
            msg = str(ex)
            msgbox = QMessageBox(QMessageBox.Critical,
                                 "Error",
                                 msg,
                                 QMessageBox.Ok,
                                 parent=self)
            msgbox.exec()
        # Reload the table
        self.update_eline_table()
        # Update the displayed estimated peak amplitude value 'le_peak_intensity'
        self._set_selected_eline(self._selected_eline)
        self._set_fit_status(False)

    def _display_peak_intensity(self, eline):
        v = self.gpc.get_eline_intensity(eline)
        s = f"{v:.10g}" if v is not None else ""
        self.le_peak_intensity.setText(s)

    def _update_le_remove_rel_state(self, text=None):
        if text is None:
            text = self.le_remove_rel.text()
        state = self._validator_le_remove_rel.validate(
            text, 0)[0] == QDoubleValidator.Acceptable
        self.le_remove_rel.setValid(state)
        self.pb_remove_rel.setEnabled(state)

    @Slot(str)
    def slot_selection_item_changed(self, eline):
        self.element_selection.set_current_item(eline)

    @Slot(bool)
    def slot_marker_state_changed(self, state):
        # If userpeak is selected and plot is clicked (marker is set), then user
        #   should be allowed to add userpeak at a new location. So deselect the userpeak
        #   from the table (if it is selected)
        logger.debug(
            f"Vertical marker on the fit plot changed state to {state}.")
        if state:
            self._deselect_special_peak_in_table()
        # Now update state of all buttons
        self._update_add_remove_btn_state()
        self._update_add_edit_userpeak_btn_state()
        self._update_add_edit_pileup_peak_btn_state()

    def _format_threshold(self, value):
        return f"{value:.2f}"

    def _deselect_special_peak_in_table(self):
        """Deselect userpeak if a userpeak is selected"""
        if self.gpc.get_eline_name_category(
                self._selected_eline) in ("userpeak", "pileup"):
            # Clear all selections
            self.tbl_elines_set_selection("")
            self._set_selected_eline("")
            # We also want to show marker at the new position
            self.gpc.show_marker_at_current_position()

    def _update_widgets_based_on_table_state(self):
        index, eline = self._get_current_index_in_table()
        if index >= 0:
            # Selection exists. Update the state of element selection widget.
            self.element_selection.set_current_item(eline)
        else:
            # No selection, update the state based on element selection widget.
            eline = self._selected_eline
            self.tbl_elines_set_selection(eline)
        self._update_add_remove_btn_state(eline)
        self._update_add_edit_userpeak_btn_state()
        self._update_add_edit_pileup_peak_btn_state()

    def _update_eline_selection_list(self):
        self._eline_list = self.gpc.get_full_eline_list()
        self.element_selection.set_item_list(self._eline_list)
        self.signal_update_element_selection_list.emit()

    @Slot()
    def update_eline_table(self):
        """Update table of emission lines without changing anything else"""
        eline_table = self.gpc.get_selected_eline_table()
        self.fill_eline_table(eline_table)

    def _get_eline_index_in_table(self, eline):
        try:
            index = [_["eline"] for _ in self._table_contents].index(eline)
        except ValueError:
            index = -1
        return index

    def _get_eline_index_in_list(self, eline):
        try:
            index = self._eline_list.index(eline)
        except ValueError:
            index = -1
        return index

    def _get_current_index_in_table(self):
        sel_ranges = self.tbl_elines.selectedRanges()
        # The table is configured to have one or no selected ranges
        # 'Open' button should be enabled only if a range (row) is selected
        if sel_ranges:
            index = sel_ranges[0].topRow()
            eline = self._table_contents[index]["eline"]
        else:
            index, eline = -1, ""
        return index, eline

    def _get_current_index_in_list(self):
        index, eline = self.element_selection.get_current_item()
        return index, eline

    def _update_add_remove_btn_state(self, eline=None):
        if eline is None:
            index_in_table, eline = self._get_current_index_in_table()
            index_in_list, eline = self._get_current_index_in_list()
        else:
            index_in_table = self._get_eline_index_in_table(eline)
            index_in_list = self._get_eline_index_in_list(eline)
        add_enabled, remove_enabled = True, True
        if index_in_list < 0 and index_in_table < 0:
            add_enabled, remove_enabled = False, False
        else:
            if index_in_table >= 0:
                if self.gpc.get_eline_name_category(eline) != "other":
                    add_enabled = False
                else:
                    add_enabled, remove_enabled = False, False
            else:
                remove_enabled = False
        self.pb_add_eline.setEnabled(add_enabled)
        self.pb_remove_eline.setEnabled(remove_enabled)
        self.signal_update_add_remove_btn_state.emit(add_enabled,
                                                     remove_enabled)

    def _update_add_edit_userpeak_btn_state(self):

        enabled = True
        add_peak = True
        if self.gpc.get_eline_name_category(
                self._selected_eline) == "userpeak":
            add_peak = False

        # Finally check if marker is set (you need it for adding peaks)
        _, marker_set = self.gpc.get_suggested_manual_peak_energy()

        if not marker_set and add_peak:
            enabled = False

        if add_peak:
            btn_text = "New User Peak ..."
        else:
            btn_text = "Edit User Peak ..."
        self.pb_user_peaks.setText(btn_text)
        self.pb_user_peaks.setEnabled(enabled)

    def _update_add_edit_pileup_peak_btn_state(self):

        enabled = True
        if self.gpc.get_eline_name_category(self._selected_eline) == "pileup":
            enabled = False

        # Finally check if marker is set (you need it for adding peaks)
        _, marker_set = self.gpc.get_suggested_manual_peak_energy()
        # Ignore set marker for userpeaks (marker is used to display location of userpeaks)
        if self.gpc.get_eline_name_category(
                self._selected_eline) == "userpeak":
            marker_set = False

        if not marker_set:
            enabled = False

        self.pb_pileup_peaks.setEnabled(enabled)

    def _set_selected_eline(self, eline):
        self._update_add_remove_btn_state(eline)
        if eline != self._selected_eline:
            self._selected_eline = eline
            self.gpc.set_selected_eline(eline)
            self._display_peak_intensity(eline)
        else:
            # Peak intensity may change in some circumstances, so renew the displayed value.
            self._display_peak_intensity(eline)
        # Update button states after 'self._selected_eline' is set
        self._update_add_edit_userpeak_btn_state()
        self._update_add_edit_pileup_peak_btn_state()

    def _set_fit_status(self, status):
        self.gui_vars["gui_state"]["state_model_fit_exists"] = status
        self.signal_parameters_changed.emit()
Ejemplo n.º 8
0
class ReactionMask(QWidget):
    """The input mask for a reaction"""

    def __init__(self, parent: ReactionList):
        QWidget.__init__(self)

        self.parent = parent
        self.reaction = None
        self.is_valid = True
        self.changed = False
        self.setAcceptDrops(False)

        layout = QVBoxLayout()
        l = QHBoxLayout()
        self.delete_button = QPushButton("Delete reaction")
        self.delete_button.setIcon(QIcon.fromTheme("edit-delete"))
        policy = QSizePolicy()
        policy.ShrinkFlag = True
        self.delete_button.setSizePolicy(policy)
        l.addWidget(self.delete_button)
        layout.addItem(l)

        l = QHBoxLayout()
        label = QLabel("Id:")
        self.id = QLineEdit()
        l.addWidget(label)
        l.addWidget(self.id)
        layout.addItem(l)

        l = QHBoxLayout()
        label = QLabel("Name:")
        self.name = QLineEdit()
        l.addWidget(label)
        l.addWidget(self.name)
        layout.addItem(l)

        l = QHBoxLayout()
        label = QLabel("Equation:")
        self.equation = QLineEdit()
        l.addWidget(label)
        l.addWidget(self.equation)
        layout.addItem(l)

        l = QHBoxLayout()
        label = QLabel("Rate min:")
        self.lower_bound = QLineEdit()
        l.addWidget(label)
        l.addWidget(self.lower_bound)
        layout.addItem(l)

        l = QHBoxLayout()
        label = QLabel("Rate max:")
        self.upper_bound = QLineEdit()
        l.addWidget(label)
        l.addWidget(self.upper_bound)
        layout.addItem(l)

        l = QHBoxLayout()
        label = QLabel("Coefficient in obj. function:")
        self.coefficent = QLineEdit()
        l.addWidget(label)
        l.addWidget(self.coefficent)
        layout.addItem(l)

        l = QHBoxLayout()
        label = QLabel("Gene reaction rule:")
        self.gene_reaction_rule = QLineEdit()
        l.addWidget(label)
        l.addWidget(self.gene_reaction_rule)
        layout.addItem(l)

        l = QVBoxLayout()
        label = QLabel("Annotations:")
        l.addWidget(label)
        l2 = QHBoxLayout()
        self.annotation = QTableWidget(0, 2)
        self.annotation.setHorizontalHeaderLabels(
            ["key", "value"])
        self.annotation.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch)
        l2.addWidget(self.annotation)

        self.add_anno = QPushButton("+")
        self.add_anno.clicked.connect(self.add_anno_row)
        l2.addWidget(self.add_anno)
        l.addItem(l2)
        layout.addItem(l)

        l = QVBoxLayout()
        label = QLabel("Metabolites involved in this reaction:")
        l.addWidget(label)
        l2 = QHBoxLayout()
        self.metabolites = QTreeWidget()
        self.metabolites.setHeaderLabels(["Id"])
        self.metabolites.setSortingEnabled(True)
        l2.addWidget(self.metabolites)
        l.addItem(l2)
        self.metabolites.itemDoubleClicked.connect(
            self.emit_jump_to_metabolite)
        layout.addItem(l)

        self.jump_list = JumpList(self)
        layout.addWidget(self.jump_list)

        self.setLayout(layout)

        self.delete_button.clicked.connect(self.delete_reaction)

        self.throttler = SignalThrottler(500)
        self.throttler.triggered.connect(self.reaction_data_changed)

        self.id.textEdited.connect(self.throttler.throttle)
        self.name.textEdited.connect(self.throttler.throttle)
        self.equation.textEdited.connect(self.throttler.throttle)
        self.lower_bound.textEdited.connect(self.throttler.throttle)
        self.upper_bound.textEdited.connect(self.throttler.throttle)
        self.coefficent.textEdited.connect(self.throttler.throttle)
        self.gene_reaction_rule.textEdited.connect(self.throttler.throttle)
        self.annotation.itemChanged.connect(self.throttler.throttle)

        self.validate_mask()

    def add_anno_row(self):
        i = self.annotation.rowCount()
        self.annotation.insertRow(i)
        self.changed = True

    def apply(self):
        try:
            self.reaction.id = self.id.text()
        except ValueError:
            turn_red(self.id)
            QMessageBox.information(
                self, 'Invalid id', 'Could not apply changes identifier ' +
                self.id.text() + ' already used.')
        else:
            self.reaction.name = self.name.text()
            self.reaction.build_reaction_from_string(self.equation.text())
            self.reaction.lower_bound = float(self.lower_bound.text())
            self.reaction.upper_bound = float(self.upper_bound.text())
            self.reaction.objective_coefficient = float(self.coefficent.text())
            self.reaction.gene_reaction_rule = self.gene_reaction_rule.text()
            self.reaction.annotation = {}
            rows = self.annotation.rowCount()
            for i in range(0, rows):
                key = self.annotation.item(i, 0).text()
                if self.annotation.item(i, 1) is None:
                    value = ""
                else:
                    value = self.annotation.item(i, 1).text()

                self.reaction.annotation[key] = value

            self.changed = False
            self.reactionChanged.emit(self.reaction)

    def delete_reaction(self):
        self.hide()
        self.reactionDeleted.emit(self.reaction)

    def validate_id(self):

        with self.parent.appdata.project.cobra_py_model as model:
            try:
                r = cobra.Reaction(id=self.id.text())
                model.add_reaction(r)
            except ValueError:
                turn_red(self.id)
                return False
            else:
                turn_white(self.id)
                return True

    def validate_name(self):
        with self.parent.appdata.project.cobra_py_model as model:
            try:
                r = cobra.Reaction(id="testid", name=self.name.text())
                model.add_reaction(r)
            except ValueError:
                turn_red(self.name)
                return False
            else:
                turn_white(self.name)
                return True

    def validate_equation(self):
        ok = False
        test_reaction = cobra.Reaction(
            "xxxx_cnapy_test_reaction", name="cnapy test reaction")
        with self.parent.appdata.project.cobra_py_model as model:
            model.add_reaction(test_reaction)

            try:
                eqtxt = self.equation.text().rstrip()
                if len(eqtxt) > 0 and eqtxt[-1] == '+':
                    turn_red(self.equation)
                else:
                    test_reaction.build_reaction_from_string(eqtxt)
                    turn_white(self.equation)
                    ok = True
            except ValueError:
                turn_red(self.equation)

        try:
            test_reaction = self.parent.appdata.project.cobra_py_model.reactions.get_by_id(
                "xxxx_cnapy_test_reaction")
            self.parent.appdata.project.cobra_py_model.remove_reactions(
                [test_reaction], remove_orphans=True)
        except KeyError:
            pass

        return ok

    def validate_lowerbound(self):
        try:
            _x = float(self.lower_bound.text())
        except ValueError:
            turn_red(self.lower_bound)
            return False
        else:
            turn_white(self.lower_bound)
            return True

    def validate_upperbound(self):
        try:
            _x = float(self.upper_bound.text())
        except ValueError:
            turn_red(self.upper_bound)
            return False
        else:
            turn_white(self.upper_bound)
            return True

    def validate_coefficient(self):
        try:
            _x = float(self.coefficent.text())
        except ValueError:
            turn_red(self.coefficent)
            return False
        else:
            turn_white(self.coefficent)
            return True

    def validate_gene_reaction_rule(self):
        try:
            _x = float(self.gene_reaction_rule.text())
        except ValueError:
            turn_red(self.gene_reaction_rule)
            return False
        else:
            turn_white(self.gene_reaction_rule)
            return True

    def validate_mask(self):

        valid_id = self.validate_id()
        valid_name = self.validate_name()
        valid_equation = self.validate_equation()
        valid_lb = self.validate_lowerbound()
        valid_ub = self.validate_upperbound()
        valid_coefficient = self.validate_coefficient()
        if valid_id & valid_name & valid_equation & valid_lb & valid_ub & valid_coefficient:
            self.is_valid = True
        else:
            self.is_valid = False

    def reaction_data_changed(self):
        self.changed = True
        self.validate_mask()
        if self.is_valid:
            self.apply()
            self.update_state()

    def update_state(self):
        self.jump_list.clear()
        for name, m in self.parent.appdata.project.maps.items():
            if self.id.text() in m["boxes"]:
                self.jump_list.add(name)

        self.metabolites.clear()
        if self.parent.appdata.project.cobra_py_model.reactions.has_id(self.id.text()):
            reaction = self.parent.appdata.project.cobra_py_model.reactions.get_by_id(
                self.id.text())
            for m in reaction.metabolites:
                item = QTreeWidgetItem(self.metabolites)
                item.setText(0, m.id)
                item.setText(1, m.name)
                item.setData(2, 0, m)
                text = "Id: " + m.id + "\nName: " + m.name
                item.setToolTip(1, text)

    def emit_jump_to_map(self, name):
        self.jumpToMap.emit(name, self.id.text())

    def emit_jump_to_metabolite(self, metabolite):
        self.jumpToMetabolite.emit(str(metabolite.data(2, 0)))

    jumpToMap = Signal(str, str)
    jumpToMetabolite = Signal(str)
    reactionChanged = Signal(cobra.Reaction)
    reactionDeleted = Signal(cobra.Reaction)
Ejemplo n.º 9
0
class MeasurementWidget(QWidget):
    """
    :type settings: Settings
    :type segment: Segment
    """
    def __init__(self, settings: PartSettings, segment=None):
        super().__init__()
        self.settings = settings
        self.segment = segment
        self.measurements_storage = MeasurementsStorage()
        self.recalculate_button = QPushButton(
            "Recalculate and\n replace measurement", self)
        self.recalculate_button.clicked.connect(
            self.replace_measurement_result)
        self.recalculate_append_button = QPushButton(
            "Recalculate and\n append measurement", self)
        self.recalculate_append_button.clicked.connect(
            self.append_measurement_result)
        self.copy_button = QPushButton("Copy to clipboard", self)
        self.copy_button.setToolTip(
            "You cacn copy also with 'Ctrl+C'. To get raw copy copy with 'Ctrl+Shit+C'"
        )
        self.horizontal_measurement_present = QCheckBox(
            "Horizontal view", self)
        self.no_header = QCheckBox("No header", self)
        self.no_units = QCheckBox("No units", self)
        self.no_units.setChecked(True)
        self.no_units.clicked.connect(self.refresh_view)
        self.expand_mode = QCheckBox("Expand", self)
        self.expand_mode.setToolTip(
            "Shows results for each component in separate entry")
        self.file_names = EnumComboBox(FileNamesEnum)
        self.file_names_label = QLabel("Add file name:")
        self.file_names.currentIndexChanged.connect(self.refresh_view)
        self.horizontal_measurement_present.stateChanged.connect(
            self.refresh_view)
        self.expand_mode.stateChanged.connect(self.refresh_view)
        self.copy_button.clicked.connect(self.copy_to_clipboard)
        self.measurement_type = SearchCombBox(self)
        # noinspection PyUnresolvedReferences
        self.measurement_type.currentIndexChanged.connect(
            self.measurement_profile_selection_changed)
        self.measurement_type.addItem("<none>")
        self.measurement_type.addItems(
            list(sorted(self.settings.measurement_profiles.keys())))
        self.measurement_type.setToolTip(
            'You can create new measurement profile in advanced window, in tab "Measurement settings"'
        )
        self.channels_chose = ChannelComboBox()
        self.units_choose = EnumComboBox(Units)
        self.units_choose.set_value(self.settings.get("units_value", Units.nm))
        self.info_field = QTableWidget(self)
        self.info_field.setColumnCount(3)
        self.info_field.setHorizontalHeaderLabels(["Name", "Value", "Units"])
        self.measurement_add_shift = 0
        layout = QVBoxLayout()
        # layout.addWidget(self.recalculate_button)
        v_butt_layout = QVBoxLayout()
        v_butt_layout.setSpacing(1)
        self.up_butt_layout = QHBoxLayout()
        self.up_butt_layout.addWidget(self.recalculate_button)
        self.up_butt_layout.addWidget(self.recalculate_append_button)
        self.butt_layout = QHBoxLayout()
        # self.butt_layout.setMargin(0)
        # self.butt_layout.setSpacing(10)
        self.butt_layout.addWidget(self.horizontal_measurement_present, 1)
        self.butt_layout.addWidget(self.no_header, 1)
        self.butt_layout.addWidget(self.no_units, 1)
        self.butt_layout.addWidget(self.expand_mode, 1)
        self.butt_layout.addWidget(self.file_names_label)
        self.butt_layout.addWidget(self.file_names, 1)
        self.butt_layout.addWidget(self.copy_button, 2)
        self.butt_layout2 = QHBoxLayout()
        self.butt_layout3 = QHBoxLayout()
        self.butt_layout3.addWidget(QLabel("Channel:"))
        self.butt_layout3.addWidget(self.channels_chose)
        self.butt_layout3.addWidget(QLabel("Units:"))
        self.butt_layout3.addWidget(self.units_choose)
        # self.butt_layout3.addWidget(QLabel("Noise removal:"))
        # self.butt_layout3.addWidget(self.noise_removal_method)
        self.butt_layout3.addWidget(QLabel("Measurement set:"))
        self.butt_layout3.addWidget(self.measurement_type, 2)
        v_butt_layout.addLayout(self.up_butt_layout)
        v_butt_layout.addLayout(self.butt_layout)
        v_butt_layout.addLayout(self.butt_layout2)
        v_butt_layout.addLayout(self.butt_layout3)
        layout.addLayout(v_butt_layout)
        # layout.addLayout(self.butt_layout)
        layout.addWidget(self.info_field)
        self.setLayout(layout)
        # noinspection PyArgumentList
        self.clip = QApplication.clipboard()
        self.settings.image_changed[int].connect(self.image_changed)
        self.previous_profile = None

    def check_if_measurement_can_be_calculated(self, name):
        if name == "<none>":
            return "<none>"
        profile: MeasurementProfile = self.settings.measurement_profiles.get(
            name)
        if profile.is_any_mask_measurement() and self.settings.mask is None:
            QMessageBox.information(
                self, "Need mask",
                "To use this measurement set please use data with mask loaded",
                QMessageBox.Ok)
            self.measurement_type.setCurrentIndex(0)
            return "<none>"
        if self.settings.roi is None:
            QMessageBox.information(
                self,
                "Need segmentation",
                'Before calculating please create segmentation ("Execute" button)',
                QMessageBox.Ok,
            )
            self.measurement_type.setCurrentIndex(0)
            return "<none>"
        return name

    def image_changed(self, channels_num):
        self.channels_chose.change_channels_num(channels_num)

    def measurement_profile_selection_changed(self, index):
        text = self.measurement_type.itemText(index)
        text = self.check_if_measurement_can_be_calculated(text)
        try:
            stat = self.settings.measurement_profiles[text]
            is_mask = stat.is_any_mask_measurement()
            disable = is_mask and (self.settings.mask is None)
        except KeyError:
            disable = True
        self.recalculate_button.setDisabled(disable)
        self.recalculate_append_button.setDisabled(disable)
        if disable:
            self.recalculate_button.setToolTip(
                "Measurement profile contains mask measurements when mask is not loaded"
            )
            self.recalculate_append_button.setToolTip(
                "Measurement profile contains mask measurements when mask is not loaded"
            )
        else:
            self.recalculate_button.setToolTip("")
            self.recalculate_append_button.setToolTip("")

    def copy_to_clipboard(self):
        s = ""
        for r in range(self.info_field.rowCount()):
            for c in range(self.info_field.columnCount()):
                try:
                    s += str(self.info_field.item(r, c).text()) + "\t"
                except AttributeError:
                    s += "\t"
            s = s[:-1] + "\n"  # eliminate last '\t'
        self.clip.setText(s)

    def replace_measurement_result(self):
        self.measurements_storage.clear()
        self.previous_profile = ""
        self.append_measurement_result()

    def refresh_view(self):
        self.measurements_storage.set_expand(self.expand_mode.isChecked())
        self.measurements_storage.set_show_units(not self.no_units.isChecked())
        self.info_field.clear()
        save_orientation = self.horizontal_measurement_present.isChecked()
        columns, rows = self.measurements_storage.get_size(save_orientation)
        self.info_field.setColumnCount(columns)
        self.info_field.setRowCount(rows)
        self.info_field.setHorizontalHeaderLabels(
            self.measurements_storage.get_header(save_orientation))
        self.info_field.setVerticalHeaderLabels(
            self.measurements_storage.get_rows(save_orientation))
        for x in range(rows):
            for y in range(columns):
                self.info_field.setItem(
                    x, y,
                    QTableWidgetItem(
                        self.measurements_storage.get_val_as_str(
                            x, y, save_orientation)))
        if self.file_names.get_value() == FileNamesEnum.No:
            if save_orientation:
                self.info_field.removeColumn(0)
            else:
                self.info_field.removeRow(0)
        elif self.file_names.get_value() == FileNamesEnum.Short:
            if save_orientation:
                columns = 1
            else:
                rows = 1
            for x in range(rows):
                for y in range(columns):
                    item = self.info_field.item(x, y)
                    item.setText(os.path.basename(item.text()))

        self.info_field.setEditTriggers(QAbstractItemView.NoEditTriggers)

    def append_measurement_result(self):
        try:
            compute_class = self.settings.measurement_profiles[
                self.measurement_type.currentText()]
        except KeyError:
            QMessageBox.warning(
                self,
                "Measurement profile not found",
                f"Measurement profile '{self.measurement_type.currentText()}' not found'",
            )
            return

        if self.settings.roi is None:
            return
        units = self.units_choose.get_value()

        # FIXME find which errors should be displayed as warning
        # def exception_hook(exception):
        #    QMessageBox.warning(self, "Calculation error", f"Error during calculation: {exception}")

        for num in compute_class.get_channels_num():
            if num >= self.settings.image.channels:
                QMessageBox.warning(
                    self,
                    "Measurement error",
                    "Cannot calculate this measurement because "
                    f"image do not have channel {num+1}",
                )
                return

        thread = ExecuteFunctionThread(
            compute_class.calculate,
            [
                self.settings.image,
                self.channels_chose.currentIndex(), self.settings.roi_info,
                units
            ],
        )
        dial = WaitingDialog(
            thread,
            "Measurement calculation")  # , exception_hook=exception_hook)
        dial.exec()
        stat: MeasurementResult = thread.result
        if stat is None:
            return
        stat.set_filename(self.settings.image_path)
        self.measurements_storage.add_measurements(stat)
        self.previous_profile = compute_class.name
        self.refresh_view()

    def keyPressEvent(self, e: QKeyEvent):
        if e.modifiers() & Qt.ControlModifier:
            selected = self.info_field.selectedRanges()

            if e.key() == Qt.Key_C:  # copy
                s = ""

                for r in range(selected[0].topRow(),
                               selected[0].bottomRow() + 1):
                    for c in range(selected[0].leftColumn(),
                                   selected[0].rightColumn() + 1):
                        try:
                            s += str(self.info_field.item(r, c).text()) + "\t"
                        except AttributeError:
                            s += "\t"
                    s = s[:-1] + "\n"  # eliminate last '\t'
                self.clip.setText(s)

    def update_measurement_list(self):
        self.measurement_type.blockSignals(True)
        available = list(sorted(self.settings.measurement_profiles.keys()))
        text = self.measurement_type.currentText()
        try:
            index = available.index(text) + 1
        except ValueError:
            index = 0
        self.measurement_type.clear()
        self.measurement_type.addItem("<none>")
        self.measurement_type.addItems(available)
        self.measurement_type.setCurrentIndex(index)
        self.measurement_type.blockSignals(False)

    def showEvent(self, _):
        self.update_measurement_list()

    def event(self, event: QEvent):
        if event.type() == QEvent.WindowActivate:
            self.update_measurement_list()
        return super().event(event)

    @staticmethod
    def _move_widgets(widgets_list: List[Tuple[QWidget, int]],
                      layout1: QBoxLayout, layout2: QBoxLayout):
        for el in widgets_list:
            layout1.removeWidget(el[0])
            layout2.addWidget(el[0], el[1])

    def resizeEvent(self, _event: QResizeEvent) -> None:
        if self.width() < 800 and self.butt_layout2.count() == 0:
            self._move_widgets(
                [(self.file_names_label, 1), (self.file_names, 1),
                 (self.copy_button, 2)],
                self.butt_layout,
                self.butt_layout2,
            )
        elif self.width() > 800 and self.butt_layout2.count() != 0:
            self._move_widgets(
                [(self.file_names_label, 1), (self.file_names, 1),
                 (self.copy_button, 2)],
                self.butt_layout2,
                self.butt_layout,
            )
Ejemplo n.º 10
0
class MetabolitesMask(QWidget):
    """The input mask for a metabolites"""

    def __init__(self, appdata):
        QWidget.__init__(self)
        self.appdata = appdata
        self.metabolite = None
        self.is_valid = True
        self.changed = False
        self.setAcceptDrops(False)

        layout = QVBoxLayout()
        l = QHBoxLayout()
        label = QLabel("Id:")
        self.id = QLineEdit()
        l.addWidget(label)
        l.addWidget(self.id)
        layout.addItem(l)

        l = QHBoxLayout()
        label = QLabel("Name:")
        self.name = QLineEdit()
        l.addWidget(label)
        l.addWidget(self.name)
        layout.addItem(l)

        l = QHBoxLayout()
        label = QLabel("Formula:")
        self.formula = QLineEdit()
        l.addWidget(label)
        l.addWidget(self.formula)
        layout.addItem(l)

        l = QHBoxLayout()
        label = QLabel("Charge:")
        self.charge = QLineEdit()
        l.addWidget(label)
        l.addWidget(self.charge)
        layout.addItem(l)

        l = QHBoxLayout()
        label = QLabel("Compartment:")
        self.compartment = QLineEdit()
        l.addWidget(label)
        l.addWidget(self.compartment)
        layout.addItem(l)

        l = QVBoxLayout()
        label = QLabel("Annotations:")
        l.addWidget(label)
        l2 = QHBoxLayout()
        self.annotation = QTableWidget(0, 2)
        self.annotation.setHorizontalHeaderLabels(
            ["key", "value"])
        self.annotation.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch)
        l2.addWidget(self.annotation)

        self.add_anno = QPushButton("+")
        self.add_anno.clicked.connect(self.add_anno_row)
        l2.addWidget(self.add_anno)
        l.addItem(l2)
        layout.addItem(l)

        l = QVBoxLayout()
        label = QLabel("Reactions using this metabolite:")
        l.addWidget(label)
        l2 = QHBoxLayout()
        self.reactions = QTreeWidget()
        self.reactions.setHeaderLabels(["Id"])
        self.reactions.setSortingEnabled(True)
        l2.addWidget(self.reactions)
        l.addItem(l2)
        self.reactions.itemDoubleClicked.connect(self.emit_jump_to_reaction)
        layout.addItem(l)

        self.setLayout(layout)

        self.throttler = SignalThrottler(500)
        self.throttler.triggered.connect(self.metabolites_data_changed)

        self.id.textEdited.connect(self.throttler.throttle)
        self.name.textEdited.connect(self.throttler.throttle)
        self.formula.textEdited.connect(self.throttler.throttle)
        self.charge.textEdited.connect(self.throttler.throttle)
        self.compartment.textEdited.connect(self.throttler.throttle)
        self.annotation.itemChanged.connect(self.throttler.throttle)
        self.validate_mask()

    def add_anno_row(self):
        i = self.annotation.rowCount()
        self.annotation.insertRow(i)
        self.changed = True

    def apply(self):
        try:
            self.metabolite.id = self.id.text()
        except ValueError:
            turn_red(self.id)
            QMessageBox.information(
                self, 'Invalid id', 'Could not apply changes identifier ' +
                self.id.text()+' already used.')
        else:
            self.metabolite.name = self.name.text()
            self.metabolite.formula = self.formula.text()
            if self.charge.text() == "":
                self.metabolite.charge = None
            else:
                self.metabolite.charge = int(self.charge.text())
            self.metabolite.compartment = self.compartment.text()
            self.metabolite.annotation = {}
            rows = self.annotation.rowCount()
            for i in range(0, rows):
                key = self.annotation.item(i, 0).text()
                if self.annotation.item(i, 1) is None:
                    value = ""
                else:
                    value = self.annotation.item(i, 1).text()

                self.metabolite.annotation[key] = value

            self.changed = False
            self.metaboliteChanged.emit(self.metabolite)

    def validate_id(self):
        with self.appdata.project.cobra_py_model as model:
            text = self.id.text()
            if text == "":
                turn_red(self.id)
                return False
            if ' ' in text:
                turn_red(self.id)
                return False
            try:
                m = cobra.Metabolite(id=self.id.text())
                model.add_metabolites([m])
            except ValueError:
                turn_red(self.id)
                return False
            else:
                turn_white(self.id)
                return True

    def validate_name(self):
        with self.appdata.project.cobra_py_model as model:
            try:
                m = cobra.Metabolite(id="test_id", name=self.name.text())
                model.add_metabolites([m])
            except ValueError:
                turn_red(self.name)
                return False
            else:
                turn_white(self.name)
                return True

    def validate_formula(self):
        return True

    def validate_charge(self):
        try:
            if self.charge.text() != "":
                _x = int(self.charge.text())
        except ValueError:
            turn_red(self.charge)
            return False
        else:
            turn_white(self.charge)
            return True

    def validate_compartment(self):
        try:
            if ' ' in self.compartment.text():
                turn_red(self.compartment)
                return False
            if '-' in self.compartment.text():
                turn_red(self.compartment)
                return False
            _m = cobra.Metabolite(id="test_id", name=self.compartment.text())
        except ValueError:
            turn_red(self.compartment)
            return False
        else:
            turn_white(self.compartment)
            return True

    def validate_mask(self):
        valid_id = self.validate_id()
        valid_name = self.validate_name()
        valid_formula = self.validate_formula()
        valid_charge = self.validate_charge()
        valid_compartment = self.validate_compartment()
        if valid_id & valid_name & valid_formula & valid_charge & valid_compartment:
            self.is_valid = True
        else:
            self.is_valid = False

    def metabolites_data_changed(self):
        self.changed = True
        self.validate_mask()
        if self.is_valid:
            self.apply()
            self.update_state()

    def update_state(self):
        self.reactions.clear()
        if self.appdata.project.cobra_py_model.metabolites.has_id(self.id.text()):
            metabolite = self.appdata.project.cobra_py_model.metabolites.get_by_id(
                self.id.text())
            for r in metabolite.reactions:
                item = QTreeWidgetItem(self.reactions)
                item.setText(0, r.id)
                item.setText(1, r.name)
                item.setData(2, 0, r)
                text = "Id: " + r.id + "\nName: " + r.name
                item.setToolTip(1, text)

    def emit_jump_to_reaction(self, reaction):
        self.jumpToReaction.emit(reaction.data(2, 0).id)

    jumpToReaction = Signal(str)
    metaboliteChanged = Signal(cobra.Metabolite)
Ejemplo n.º 11
0
class RgbSelectionWidget(QWidget):

    signal_update_map_selections = Signal()

    def __init__(self):
        super().__init__()

        self._range_table = []
        self._limit_table = []
        self._rgb_keys = ["red", "green", "blue"]
        self._rgb_dict = {_: None for _ in self._rgb_keys}

        widget_layout = self._setup_rgb_widget()
        self.setLayout(widget_layout)

        sp = QSizePolicy()
        sp.setControlType(QSizePolicy.PushButton)
        sp.setHorizontalPolicy(QSizePolicy.Expanding)
        sp.setVerticalPolicy(QSizePolicy.Fixed)
        self.setSizePolicy(sp)

    def _setup_rgb_element(self, n_row, *, rb_check=0):
        """
        Parameters
        ----------
        rb_check: int
            The number of QRadioButton to check. Typically this would be the row number.
        """
        combo_elements = ComboBoxNamed(name=f"{n_row}")
        # combo_elements.setSizeAdjustPolicy(QComboBox.AdjustToContents)

        # Set text color for QComboBox widget (necessary if the program is used with Dark theme)
        pal = combo_elements.palette()
        pal.setColor(QPalette.ButtonText, Qt.black)
        combo_elements.setPalette(pal)
        # Set text color for drop-down view (necessary if the program is used with Dark theme)
        pal = combo_elements.view().palette()
        pal.setColor(QPalette.Text, Qt.black)
        combo_elements.view().setPalette(pal)

        btns = [QRadioButton(), QRadioButton(), QRadioButton()]
        if 0 <= rb_check < len(btns):
            btns[rb_check].setChecked(True)

        # Color is set for operation with Dark theme
        for btn in btns:
            pal = btn.palette()
            pal.setColor(QPalette.Text, Qt.black)
            btn.setPalette(pal)

        btn_group = QButtonGroup()
        for btn in btns:
            btn_group.addButton(btn)

        rng = RangeManager(name=f"{n_row}", add_sliders=True)
        rng.setTextColor([0, 0, 0])  # Set text color to 'black'
        # Set some text in edit boxes (just to demonstrate how the controls will look like)
        rng.le_min_value.setText("0.0")
        rng.le_max_value.setText("1.0")

        rng.setAlignment(Qt.AlignCenter)
        return combo_elements, btns, rng, btn_group

    def _enable_selection_events(self, enable):
        if enable:
            if not self.elements_btn_groups_events_enabled:
                for btn_group in self.elements_btn_groups:
                    btn_group.buttonToggled.connect(self.rb_toggled)
                for el_combo in self.elements_combo:
                    el_combo.currentIndexChanged.connect(
                        self.combo_element_current_index_changed)
                for el_range in self.elements_range:
                    el_range.selection_changed.connect(
                        self.range_selection_changed)
                self.elements_btn_groups_events_enabled = True
        else:
            if self.elements_btn_groups_events_enabled:
                for btn_group in self.elements_btn_groups:
                    btn_group.buttonToggled.disconnect(self.rb_toggled)
                for el_combo in self.elements_combo:
                    el_combo.currentIndexChanged.disconnect(
                        self.combo_element_current_index_changed)
                # Disconnecting the Range Manager signals is not necessary, but let's do it for consistency
                for el_range in self.elements_range:
                    el_range.selection_changed.disconnect(
                        self.range_selection_changed)
                self.elements_btn_groups_events_enabled = False

    def _setup_rgb_widget(self):

        self.elements_combo = []
        self.elements_rb_color = []
        self.elements_range = []
        self.elements_btn_groups = []
        self.elements_btn_groups_events_enabled = False
        self.row_colors = []

        self.table = QTableWidget()
        # Horizontal header entries
        tbl_labels = ["Element", "Red", "Green", "Blue", "Range"]
        # The list of columns that stretch with the table
        self.tbl_cols_stretch = ("Range", )

        self.table.setColumnCount(len(tbl_labels))
        self.table.setRowCount(3)
        self.table.setHorizontalHeaderLabels(tbl_labels)
        self.table.verticalHeader().hide()
        self.table.setSelectionMode(QTableWidget.NoSelection)

        header = self.table.horizontalHeader()
        for n, lbl in enumerate(tbl_labels):
            # Set stretching for the columns
            if lbl in self.tbl_cols_stretch:
                header.setSectionResizeMode(n, QHeaderView.Stretch)
            else:
                header.setSectionResizeMode(n, QHeaderView.ResizeToContents)

        vheader = self.table.verticalHeader()
        vheader.setSectionResizeMode(QHeaderView.Stretch)  # ResizeToContents)

        for n_row in range(3):
            combo_elements, btns, rng, btn_group = self._setup_rgb_element(
                n_row, rb_check=n_row)

            combo_elements.setMinimumWidth(180)
            self.table.setCellWidget(n_row, 0, combo_elements)
            for i, btn in enumerate(btns):
                item = QWidget()
                item_hbox = QHBoxLayout(item)
                item_hbox.addWidget(btn)
                item_hbox.setAlignment(Qt.AlignCenter)
                item_hbox.setContentsMargins(0, 0, 0, 0)
                item.setMinimumWidth(70)

                self.table.setCellWidget(n_row, i + 1, item)

            rng.setMinimumWidth(200)
            rng.setMaximumWidth(400)
            self.table.setCellWidget(n_row, 4, rng)

            self.elements_combo.append(combo_elements)
            self.elements_rb_color.append(btns)
            self.elements_range.append(rng)
            self.elements_btn_groups.append(btn_group)
            self.row_colors.append(self._rgb_keys[n_row])

        # Colors that are used to paint rows of the table in RGB colors
        br = 150
        self._rgb_row_colors = {
            "red": (255, br, br),
            "green": (br, 255, br),
            "blue": (br, br, 255)
        }
        self._rgb_color_keys = ["red", "green", "blue"]

        # Set initial colors
        for n_row in range(self.table.rowCount()):
            self._set_row_color(n_row)

        self._enable_selection_events(True)

        self.table.resizeRowsToContents()

        # Table height is computed based on content. It doesn't seem
        #   to account for the height of custom widgets, but the table
        #   looks good enough
        table_height = 0
        for n_row in range(self.table.rowCount()):
            table_height += self.table.rowHeight(n_row)
        self.table.setMaximumHeight(table_height)

        table_width = 650
        self.table.setMinimumWidth(table_width)
        self.table.setMaximumWidth(800)

        hbox = QHBoxLayout()
        hbox.addWidget(self.table)

        return hbox

    def combo_element_current_index_changed(self, name, index):
        if index < 0 or index >= len(self._range_table):
            return
        n_row = int(name)
        sel_eline = self._range_table[index][0]
        row_color = self.row_colors[n_row]
        self._rgb_dict[row_color] = sel_eline

        self.elements_range[n_row].set_range(self._range_table[index][1],
                                             self._range_table[index][2])
        self.elements_range[n_row].set_selection(
            value_low=self._limit_table[index][1],
            value_high=self._limit_table[index][2])
        self._update_map_selections()

    def range_selection_changed(self, v_low, v_high, name):
        n_row = int(name)
        row_color = self.row_colors[n_row]
        sel_eline = self._rgb_dict[row_color]
        ind = None
        try:
            ind = [_[0] for _ in self._limit_table].index(sel_eline)
        except ValueError:
            pass
        if ind is not None:
            self._limit_table[ind][1] = v_low
            self._limit_table[ind][2] = v_high
            self._update_map_selections()
        # We are not preventing users to select the same emission line in to rows.
        #   Update the selected limits in other rows where the same element is selected.
        for nr, el_range in enumerate(self.elements_range):
            if (nr != n_row) and (self._rgb_dict[self.row_colors[nr]]
                                  == sel_eline):
                el_range.set_selection(value_low=v_low, value_high=v_high)

    def _get_selected_row_color(self, n_row):

        color_key = None

        btns = self.elements_rb_color[n_row]

        for n, btn in enumerate(btns):
            if btn.isChecked():
                color_key = self._rgb_color_keys[n]
                break

        return color_key

    def _set_row_color(self, n_row, *, color_key=None):
        """
        Parameters
        ----------
        n_row: int
            The row number that needs background color change (0..2 if table has 3 rows)
        color_key: int
            Color key: "red", "green" or "blue"
        """

        if color_key is None:
            color_key = self._get_selected_row_color(n_row)
        if color_key is None:
            return

        self.row_colors[n_row] = color_key
        rgb = self._rgb_row_colors[color_key]

        # The following code is based on the arrangement of the widgets in the table
        #   Modify the code if widgets are arranged differently or the table structure
        #   is changed
        for n_col in range(self.table.columnCount()):
            wd = self.table.cellWidget(n_row, n_col)
            if n_col == 0:
                # Combo box: update both QComboBox and QWidget backgrounds
                #   QWidget - background of the drop-down selection list
                css1 = get_background_css(rgb,
                                          widget="QComboBox",
                                          editable=False)
                css2 = get_background_css(rgb, widget="QWidget", editable=True)
                wd.setStyleSheet(css2 + css1)
            elif n_col <= 3:
                # 3 QRadioButton's. The buttons are inserted into QWidget objects,
                #   and we need to change backgrounds of QWidgets, not only buttons.
                wd.setStyleSheet(
                    get_background_css(rgb, widget="QWidget", editable=False))
            elif n_col == 4:
                # Custom RangeManager widget, color is updated using custom method
                wd.setBackground(rgb)

        n_col = self._rgb_color_keys.index(color_key)
        for n, n_btn in enumerate(self.elements_rb_color[n_row]):
            check_status = True if n == n_col else False
            n_btn.setChecked(check_status)

    def _fill_table(self):
        self._enable_selection_events(False)

        eline_list = [_[0] for _ in self._range_table]
        for n_row in range(self.table.rowCount()):
            self.elements_combo[n_row].clear()
            self.elements_combo[n_row].addItems(eline_list)

        for n_row, color in enumerate(self._rgb_color_keys):
            # Initially set colors in order
            self._set_row_color(n_row, color_key=color)
            eline_key = self._rgb_dict[color]
            if eline_key is not None:
                try:
                    ind = eline_list.index(eline_key)
                    self.elements_combo[n_row].setCurrentIndex(ind)
                    range_low, range_high = self._range_table[ind][1:]
                    self.elements_range[n_row].set_range(range_low, range_high)
                    sel_low, sel_high = self._limit_table[ind][1:]
                    self.elements_range[n_row].set_selection(
                        value_low=sel_low, value_high=sel_high)
                except ValueError:
                    pass
            else:
                self.elements_combo[n_row].setCurrentIndex(-1)  # Deselect all
                self.elements_range[n_row].set_range(0, 1)
                self.elements_range[n_row].set_selection(value_low=0,
                                                         value_high=1)

        self._enable_selection_events(True)

    def _find_rbutton(self, button):
        for nr, btns in enumerate(self.elements_rb_color):
            for nc, btn in enumerate(btns):
                if btn == button:
                    # Return tuple (nr, nc)
                    return nr, nc
        # Return None if the button is not found (this shouldn't happen)
        return None

    def rb_toggled(self, button, state):
        if state:  # Ignore signals from unchecked buttons
            nr, nc = self._find_rbutton(button)

            color_current = self.row_colors[nr]
            color_to_set = self._rgb_color_keys[nc]
            nr_switch = self.row_colors.index(color_to_set)

            self._enable_selection_events(False)

            self._set_row_color(nr, color_key=color_to_set)
            self._set_row_color(nr_switch, color_key=color_current)
            # Swap selected maps
            tmp = self._rgb_dict[color_to_set]
            self._rgb_dict[color_to_set] = self._rgb_dict[color_current]
            self._rgb_dict[color_current] = tmp

            self._enable_selection_events(True)

            self._update_map_selections()

    def set_ranges_and_limits(self,
                              *,
                              range_table=None,
                              limit_table=None,
                              rgb_dict=None):
        if range_table is not None:
            self._range_table = copy.deepcopy(range_table)
        if limit_table is not None:
            self._limit_table = copy.deepcopy(limit_table)
        if rgb_dict is not None:
            self._rgb_dict = rgb_dict.copy()
        self._fill_table()

    def _update_map_selections(self):
        """Upload the selections (limit table) and update plot"""
        self.signal_update_map_selections.emit()
Ejemplo n.º 12
0
class PMGRuleCtrl(BaseExtendedWidget):
    """
    rules:
    {'name':'regex',
    'text':'匹配正则表达式',
    'init':False
    }
    """
    def __init__(self,
                 layout_dir='v',
                 title='',
                 rules: List[Dict[str, Union[bool, int, float, str]]] = None):
        super().__init__(layout_dir)
        self.table_h_headers = []
        self.table_keys = []
        self.initial_values = []
        for rule in rules:
            self.table_h_headers.append(rule['text'])
            self.table_keys.append(rule['name'])
            self.initial_values.append(rule['init'])
        self.regulations_table = QTableWidget(0, len(self.table_h_headers))
        self.regulations_table.horizontalHeader().setSectionResizeMode(
            QHeaderView.Stretch)
        self.regulations_table.setHorizontalHeaderLabels(self.table_h_headers)

        self.layout().addWidget(self.regulations_table)
        self.set_layout = QHBoxLayout()
        self.layout().addLayout(self.set_layout)
        self.button_add = QPushButton('Add')
        self.button_remove = QPushButton('Remove')
        self.set_layout.addWidget(self.button_add)
        self.set_layout.addWidget(self.button_remove)
        self.button_add.clicked.connect(self.add_regulation)
        self.button_remove.clicked.connect(self.remove_regulation)

    def load_regulations(self, regulations: List[Dict[str,
                                                      Union[int, str, float,
                                                            bool]]]):
        row_count = len(regulations)
        self.regulations_table.setRowCount(row_count)
        for i, regulation in enumerate(regulations):
            l = [regulation[k] for k in self.table_keys]
            for j, obj in enumerate(l):
                item = QTableWidgetItem()
                item.setData(0, obj)
                self.regulations_table.setItem(i, j, item)

    def add_regulation(self):
        rc = self.regulations_table.rowCount()
        self.regulations_table.setRowCount(rc + 1)
        for i, obj in enumerate(self.initial_values):
            item = QTableWidgetItem()
            item.setData(0, obj)
            self.regulations_table.setItem(rc, i, item)

    def remove_regulation(self):
        self.regulations_table.removeRow(self.regulations_table.currentRow())

    def get_value(self) -> List[Dict]:
        l = []
        for i in range(self.regulations_table.rowCount()):
            dic = {}
            for j in range(self.regulations_table.columnCount()):
                item = self.regulations_table.item(i, j)
                dic[self.table_keys[j]] = item.data(0)
            l.append(dic)
        return l

    def set_value(self, value):
        self.load_regulations(value)
Ejemplo n.º 13
0
class AddDataTableMixin(object):
    """ Mixin providing validation and type casting for a table for adding cycle data.
    
        Should be used as part of a widget that has a `table` attribute. If it
        also has a `okButton`, this will be enabled/diabled when validation 
        is performed.
    """
    
    invalid = Signal(int, int)
    """ **signal** invalid(int `row`, int `col`)
    
        Emitted if the data in cell `row`,`col` is invalid.
    """
    
    def __init__(self, *args, emptyDateValid=True, **kwargs):
        super().__init__(*args, **kwargs)
        
        self.headerLabels = ['Date', 'Time', 'Distance (km)', 'Calories', 'Gear']
        self.headerLabelColumnOffset = 0
        self.table = QTableWidget(0, len(self.headerLabels))
        self.table.setHorizontalHeaderLabels(self.headerLabels)
        self.table.verticalHeader().setVisible(False)
        
        # dict of methods to validate and cast types for input data in each column
        isDatePartial = partial(isDate, allowEmpty=emptyDateValid)
        validateMethods = [isDatePartial, isDuration, isFloat, 
                           isFloat, isInt]
        parseDatePartial = partial(parseDate, pd_timestamp=True)
        castMethods = [parseDatePartial, parseDuration, float, float, int]
        self.mthds = {name:{'validate':validateMethods[i], 'cast':castMethods[i]}
                      for i, name in enumerate(self.headerLabels)}
        
        self.validateTimer = QTimer()
        self.validateTimer.setInterval(100)
        self.validateTimer.setSingleShot(True)
        self.validateTimer.timeout.connect(self._validate)
        self.table.cellChanged.connect(self.validateTimer.start)
        
        self.invalid.connect(self._invalid)
        
    @property
    def defaultBrush(self):
        # made this a property rather than setting in constructor as the mixin
        # doesn't have a `table` attribute when __init__ is called
        return self.table.item(0,0).background()
    
    @property
    def invalidBrush(self):
        return QBrush(QColor("#910404"))
    
    @Slot(int, int)
    def _invalid(self, row, col):
        """ Set the background of cell `row`,`col` to the `invalidBrush` and 
            disable the 'Ok' button.
        """
        self.table.item(row, col).setBackground(self.invalidBrush)
        if hasattr(self, "okButton"):
            self.okButton.setEnabled(False)
        
    @Slot()
    def _validate(self):
        """ Validate all data in the table. 
        
            If data is valid and the widget has an `okButton`, it will be enabled.
        """
        # it would be more efficient to only validate a single cell, after its
        # text has been changed, but this table is unlikely to ever be more 
        # than a few rows long, so this isn't too inefficient
        allValid = True
        for row in range(self.table.rowCount()):
            for col, name in enumerate(self.headerLabels):
                col += self.headerLabelColumnOffset
                item = self.table.item(row, col)
                value = item.text()
                mthd = self.mthds[name]['validate']
                valid = mthd(value)
                if not valid:
                    if hasattr(self, "_clicked"):
                        if (row, col) not in self._clicked:
                            continue
                    self.invalid.emit(row, col)
                    allValid = False
                elif valid and self.table.item(row, col).background() == self.invalidBrush:
                    self.table.item(row, col).setBackground(self.defaultBrush) 
                    
        if allValid and hasattr(self, "okButton"):
            self.okButton.setEnabled(True)
            
        return allValid
    
    
    def _getValues(self):
        
        values = {name:[] for name in self.headerLabels}
        
        self.table.sortItems(0, Qt.AscendingOrder)

        for row in range(self.table.rowCount()):
            for col, name in enumerate(self.headerLabels):
                item = self.table.item(row, col)
                value = item.text()
                mthd = self.mthds[name]['cast']
                value = mthd(value)
                values[name].append(value)
                
        return values
Ejemplo n.º 14
0
class D0(QGroupBox):
    def __init__(self, parent=None):
        self._parent = parent
        super().__init__(parent)
        self.setTitle("Define d₀")
        layout = QVBoxLayout()
        self.d0_grid_switch = QComboBox()
        self.d0_grid_switch.addItems(["Constant", "Field"])
        self.d0_grid_switch.currentTextChanged.connect(self.set_case)
        layout.addWidget(self.d0_grid_switch)
        self.d0_box = QWidget()
        d0_box_layout = QHBoxLayout()
        d0_box_layout.addWidget(QLabel("d₀"))
        validator = QDoubleValidator()
        validator.setBottom(0)
        self.d0 = QLineEdit()
        self.d0.setValidator(validator)
        self.d0.editingFinished.connect(self.update_d0)
        d0_box_layout.addWidget(self.d0)
        d0_box_layout.addWidget(QLabel("Δd₀"))
        self.d0e = QLineEdit()
        self.d0e.setValidator(validator)
        self.d0e.editingFinished.connect(self.update_d0)
        d0_box_layout.addWidget(self.d0e)
        self.d0_box.setLayout(d0_box_layout)
        layout.addWidget(self.d0_box)

        load_save = QWidget()
        load_save_layout = QHBoxLayout()
        self.load_grid = QPushButton("Load d₀ Grid")
        self.load_grid.clicked.connect(self.load_d0_field)
        load_save_layout.addWidget(self.load_grid)
        self.save_grid = QPushButton("Save d₀ Grid")
        self.save_grid.clicked.connect(self.save_d0_field)
        load_save_layout.addWidget(self.save_grid)
        load_save.setLayout(load_save_layout)
        layout.addWidget(load_save)
        self.d0_grid = QTableWidget()
        self.d0_grid.setColumnCount(5)
        self.d0_grid.setColumnWidth(0, 60)
        self.d0_grid.setColumnWidth(1, 60)
        self.d0_grid.setColumnWidth(2, 60)
        self.d0_grid.setColumnWidth(3, 60)
        self.d0_grid.verticalHeader().setVisible(False)
        self.d0_grid.horizontalHeader().setStretchLastSection(True)
        self.d0_grid.setHorizontalHeaderLabels(['vx', 'vy', 'vz', "d₀", "Δd₀"])
        spinBoxDelegate = SpinBoxDelegate()
        self.d0_grid.setItemDelegateForColumn(3, spinBoxDelegate)
        self.d0_grid.setItemDelegateForColumn(4, spinBoxDelegate)
        layout.addWidget(self.d0_grid)

        self.setLayout(layout)

        self.set_case('Constant')

    def set_case(self, case):
        if case == "Constant":
            self.d0_box.setEnabled(True)
            self.load_grid.setEnabled(False)
            self.save_grid.setEnabled(False)
            self.d0_grid.setEnabled(False)
        else:
            self.d0_box.setEnabled(False)
            self.load_grid.setEnabled(True)
            self.save_grid.setEnabled(True)
            self.d0_grid.setEnabled(True)

    def update_d0(self):
        self._parent.update_plot()

    def set_d0(self, d0, d0e):
        if d0 is None:
            self.d0.clear()
            self.d0e.clear()
        else:
            self.d0.setText(str(d0))
            self.d0e.setText(str(d0e))

    def set_d0_field(self, x, y, z, d0, d0e):
        if x is None:
            self.d0_grid.clearContents()
        else:
            self.d0_grid.setRowCount(len(x))

            for n in range(len(x)):
                x_item = QTableWidgetItem(f'{x[n]: 7.2f}')
                x_item.setFlags(x_item.flags() ^ Qt.ItemIsEditable)
                y_item = QTableWidgetItem(f'{y[n]: 7.2f}')
                y_item.setFlags(y_item.flags() ^ Qt.ItemIsEditable)
                z_item = QTableWidgetItem(f'{z[n]: 7.2f}')
                z_item.setFlags(z_item.flags() ^ Qt.ItemIsEditable)
                d0_item = QTableWidgetItem()
                d0_item.setData(Qt.EditRole, float(d0[n]))
                d0e_item = QTableWidgetItem()
                d0e_item.setData(Qt.EditRole, float(d0e[n]))
                self.d0_grid.setItem(n, 0, QTableWidgetItem(x_item))
                self.d0_grid.setItem(n, 1, QTableWidgetItem(y_item))
                self.d0_grid.setItem(n, 2, QTableWidgetItem(z_item))
                self.d0_grid.setItem(n, 3, QTableWidgetItem(d0_item))
                self.d0_grid.setItem(n, 4, QTableWidgetItem(d0e_item))

    def get_d0_field(self):
        if self.d0_grid.rowCount() == 0:
            return None
        else:
            x = [
                float(self.d0_grid.item(row, 0).text())
                for row in range(self.d0_grid.rowCount())
            ]
            y = [
                float(self.d0_grid.item(row, 1).text())
                for row in range(self.d0_grid.rowCount())
            ]
            z = [
                float(self.d0_grid.item(row, 2).text())
                for row in range(self.d0_grid.rowCount())
            ]
            d0 = [
                float(self.d0_grid.item(row, 3).text())
                for row in range(self.d0_grid.rowCount())
            ]
            d0e = [
                float(self.d0_grid.item(row, 4).text())
                for row in range(self.d0_grid.rowCount())
            ]
            return (d0, d0e, x, y, z)

    def save_d0_field(self):
        filename, _ = QFileDialog.getSaveFileName(
            self, "Save d0 Grid", "", "CSV (*.csv);;All Files (*)")
        if filename:
            d0, d0e, x, y, z = self.get_d0_field()
            np.savetxt(filename,
                       np.array([x, y, z, d0, d0e]).T,
                       fmt=['%.4g', '%.4g', '%.4g', '%.9g', '%.9g'],
                       header="vx, vy, vz, d0, d0_error",
                       delimiter=',')

    def load_d0_field(self):
        filename, _ = QFileDialog.getOpenFileName(
            self, "Load d0 Grid", "", "CSV (*.csv);;All Files (*)")
        if filename:
            x, y, z, d0, d0e = np.loadtxt(filename, delimiter=',', unpack=True)
            self.set_d0_field(x, y, z, d0, d0e)

    def get_d0(self):
        if self.d0_grid_switch.currentText() == "Constant":
            try:
                return (float(self.d0.text()), float(self.d0e.text()))
            except ValueError:
                return None
        else:
            return self.get_d0_field()
Ejemplo n.º 15
0
class Extension2ReaderTable(QWidget):
    """Table showing extension to reader mappings with removal button.

    Widget presented in preferences-plugin dialog."""

    valueChanged = Signal(int)

    def __init__(self, parent=None, npe2_readers=None, npe1_readers=None):
        super().__init__(parent=parent)

        npe2, npe1 = get_all_readers()
        if npe2_readers is None:
            npe2_readers = npe2
        if npe1_readers is None:
            npe1_readers = npe1

        self._npe2_readers = npe2_readers
        self._npe1_readers = npe1_readers

        self._table = QTableWidget()
        self._table.setShowGrid(False)
        self._set_up_table()
        self._edit_row = self._make_new_preference_row()
        self._populate_table()

        instructions = QLabel(
            trans.
            _('Enter a filename pattern to associate with a reader e.g. "*.tif" for all TIFF files.'
              ) + trans.
            _('Available readers will be filtered to those compatible with your pattern. Hover over a reader to see what patterns it accepts.'
              ) + trans.
            _('\n\nPreference saving for folder readers is not supported, so these readers are not shown.'
              ) + trans.
            _('\n\nFor documentation on valid filename patterns, see https://docs.python.org/3/library/fnmatch.html'
              ))
        instructions.setWordWrap(True)
        instructions.setOpenExternalLinks(True)

        layout = QVBoxLayout()
        instructions.setSizePolicy(QSizePolicy.MinimumExpanding,
                                   QSizePolicy.Expanding)
        layout.addWidget(instructions)
        layout.addWidget(self._edit_row)
        layout.addWidget(self._table)
        self.setLayout(layout)

    def _set_up_table(self):
        """Add table columns and headers, define styling"""
        self._fn_pattern_col = 0
        self._reader_col = 1

        header_strs = [trans._('Filename Pattern'), trans._('Reader Plugin')]

        self._table.setColumnCount(2)
        self._table.setColumnWidth(self._fn_pattern_col, 200)
        self._table.setColumnWidth(self._reader_col, 200)
        self._table.verticalHeader().setVisible(False)
        self._table.setMinimumHeight(120)
        self._table.horizontalHeader().setStyleSheet(
            'border-bottom: 2px solid white;')
        self._table.setHorizontalHeaderLabels(header_strs)
        self._table.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)

    def _populate_table(self):
        """Add row for each extension to reader mapping in settings"""

        fnpattern2reader = get_settings().plugins.extension2reader
        if len(fnpattern2reader) > 0:
            for fn_pattern, plugin_name in fnpattern2reader.items():
                self._add_new_row(fn_pattern, plugin_name)
        else:
            # Display that there are no filename patterns with reader associations
            self._display_no_preferences_found()

    def _make_new_preference_row(self):
        """Make row for user to add a new filename pattern assignment"""
        edit_row_widget = QWidget()
        edit_row_widget.setLayout(QGridLayout())
        edit_row_widget.layout().setContentsMargins(0, 0, 0, 0)

        self._fn_pattern_edit = QLineEdit()
        self._fn_pattern_edit.setPlaceholderText(
            "Start typing filename pattern...")
        self._fn_pattern_edit.textChanged.connect(
            self._filter_compatible_readers)

        add_reader_widg = QWidget()
        add_reader_widg.setLayout(QHBoxLayout())
        add_reader_widg.layout().setContentsMargins(0, 0, 0, 0)

        self._new_reader_dropdown = QComboBox()
        for i, (plugin_name, display_name) in enumerate(
                sorted(dict(self._npe2_readers,
                            **self._npe1_readers).items())):
            self._add_reader_choice(i, plugin_name, display_name)

        add_btn = QPushButton('Add')
        add_btn.setToolTip(trans._('Save reader preference for pattern'))
        add_btn.clicked.connect(self._save_new_preference)

        add_reader_widg.layout().addWidget(self._new_reader_dropdown)
        add_reader_widg.layout().addWidget(add_btn)

        edit_row_widget.layout().addWidget(
            self._fn_pattern_edit,
            0,
            0,
        )
        edit_row_widget.layout().addWidget(add_reader_widg, 0, 1)

        return edit_row_widget

    def _display_no_preferences_found(self):
        self._table.setRowCount(1)
        item = QTableWidgetItem(trans._('No filename preferences found.'))
        item.setFlags(Qt.NoItemFlags)
        self._table.setItem(self._fn_pattern_col, 0, item)

    def _add_reader_choice(self, i, plugin_name, display_name):
        """Add dropdown item for plugin_name with reader pattern tooltip"""
        reader_patterns = get_filename_patterns_for_reader(plugin_name)
        # TODO: no reader_patterns means directory reader,
        # we don't support preference association yet
        if not reader_patterns:
            return

        self._new_reader_dropdown.addItem(display_name, plugin_name)
        if '*' in reader_patterns:
            tooltip_text = 'Accepts all'
        else:
            reader_patterns_formatted = ', '.join(sorted(
                list(reader_patterns)))
            tooltip_text = f'Accepts: {reader_patterns_formatted}'
        self._new_reader_dropdown.setItemData(i,
                                              tooltip_text,
                                              role=Qt.ToolTipRole)

    def _filter_compatible_readers(self, new_pattern):
        """Filter reader dropwdown items to those that accept `new_extension`"""
        self._new_reader_dropdown.clear()

        readers = self._npe2_readers.copy()
        to_delete = []

        compatible_readers = get_potential_readers(new_pattern)
        for plugin_name, display_name in readers.items():
            if plugin_name not in compatible_readers:
                to_delete.append(plugin_name)

        for reader in to_delete:
            del readers[reader]
        readers.update(self._npe1_readers)

        if not readers:
            self._new_reader_dropdown.addItem("None available")
        else:
            for i, (plugin_name,
                    display_name) in enumerate(sorted(readers.items())):
                self._add_reader_choice(i, plugin_name, display_name)

    def _save_new_preference(self, event):
        """Save current preference to settings and show in table"""
        fn_pattern = self._fn_pattern_edit.text()
        reader = self._new_reader_dropdown.currentData()

        if not fn_pattern or not reader:
            return

        # if user types pattern that starts with a . it's probably a file extension so prepend the *
        if fn_pattern.startswith('.'):
            fn_pattern = f'*{fn_pattern}'

        if fn_pattern in get_settings().plugins.extension2reader:
            self._edit_existing_preference(fn_pattern, reader)
        else:
            self._add_new_row(fn_pattern, reader)
        get_settings().plugins.extension2reader = {
            **get_settings().plugins.extension2reader,
            fn_pattern: reader,
        }

    def _edit_existing_preference(self, fn_pattern, reader):
        """Edit existing extension preference"""
        current_reader_label = self.findChild(QLabel, fn_pattern)
        if reader in self._npe2_readers:
            reader = self._npe2_readers[reader]
        current_reader_label.setText(reader)

    def _add_new_row(self, fn_pattern, reader):
        """Add new reader preference to table"""
        last_row = self._table.rowCount()

        if (last_row == 1 and 'No filename preferences found'
                in self._table.item(0, 0).text()):
            self._table.removeRow(0)
            last_row = 0

        self._table.insertRow(last_row)
        item = QTableWidgetItem(fn_pattern)
        item.setFlags(Qt.NoItemFlags)
        self._table.setItem(last_row, self._fn_pattern_col, item)

        plugin_widg = QWidget()
        # need object name to easily find row
        plugin_widg.setObjectName(f'{fn_pattern}')
        plugin_widg.setLayout(QHBoxLayout())
        plugin_widg.layout().setContentsMargins(0, 0, 0, 0)

        if reader in self._npe2_readers:
            reader = self._npe2_readers[reader]
        plugin_label = QLabel(reader, objectName=fn_pattern)
        # need object name to easily work out which button was clicked
        remove_btn = QPushButton('X', objectName=fn_pattern)
        remove_btn.setFixedWidth(30)
        remove_btn.setStyleSheet('margin: 4px;')
        remove_btn.setToolTip(
            trans._('Remove this filename pattern to reader association'))
        remove_btn.clicked.connect(self.remove_existing_preference)

        plugin_widg.layout().addWidget(plugin_label)
        plugin_widg.layout().addWidget(remove_btn)
        self._table.setCellWidget(last_row, self._reader_col, plugin_widg)

    def remove_existing_preference(self, event):
        """Delete extension to reader mapping setting and remove table row"""
        pattern_to_remove = self.sender().objectName()
        current_settings = get_settings().plugins.extension2reader
        # need explicit assignment to new object here for persistence
        get_settings().plugins.extension2reader = {
            k: v
            for k, v in current_settings.items() if k != pattern_to_remove
        }

        for i in range(self._table.rowCount()):
            row_widg_name = self._table.cellWidget(
                i, self._reader_col).objectName()
            if row_widg_name == pattern_to_remove:
                self._table.removeRow(i)
                break

        if self._table.rowCount() == 0:
            self._display_no_preferences_found()

    def value(self):
        """Return extension:reader mapping from settings.

        Returns
        -------
        Dict[str, str]
            mapping of extension to reader plugin display name
        """
        return get_settings().plugins.extension2reader
Ejemplo n.º 16
0
class DialogSelectQuantStandard(QDialog):
    def __init__(self, parent=None):

        super().__init__(parent)

        self._qe_param_built_in = []
        self._qe_param_custom = []
        self._qe_standard_selected = None
        self._qe_param = []  # The list of all standards
        self._custom_label = []  # The list of booleans: True - the standard is custom, False -built-in

        self.setWindowTitle("Load Quantitative Standard")

        self.setMinimumHeight(500)
        self.setMinimumWidth(600)
        self.resize(600, 500)

        self.selected_standard_index = -1

        labels = ("C", "Serial #", "Name", "Description")
        col_stretch = (
            QHeaderView.ResizeToContents,
            QHeaderView.ResizeToContents,
            QHeaderView.ResizeToContents,
            QHeaderView.Stretch,
        )
        self.table = QTableWidget()
        set_tooltip(
            self.table,
            "To <b> load the sta"
            "ndard</b>, double-click on the table row or "
            "select the table row and then click <b>Ok</b> button.",
        )
        self.table.setMinimumHeight(200)
        self.table.setColumnCount(len(labels))
        self.table.verticalHeader().hide()
        self.table.setHorizontalHeaderLabels(labels)
        self.table.setSelectionBehavior(QTableWidget.SelectRows)
        self.table.setSelectionMode(QTableWidget.SingleSelection)
        self.table.itemSelectionChanged.connect(self.item_selection_changed)
        self.table.itemDoubleClicked.connect(self.item_double_clicked)

        self.table.setStyleSheet(
            "QTableWidget::item{color: black;}"
            "QTableWidget::item:selected{background-color: red;}"
            "QTableWidget::item:selected{color: white;}"
        )

        header = self.table.horizontalHeader()
        for n, col_stretch in enumerate(col_stretch):
            # Set stretching for the columns
            header.setSectionResizeMode(n, col_stretch)

        self.lb_info = QLabel()
        self.lb_info.setText("Column 'C': * means that the standard is user-defined.")

        button_box = QDialogButtonBox(QDialogButtonBox.Open | QDialogButtonBox.Cancel)
        button_box.button(QDialogButtonBox.Cancel).setDefault(True)
        button_box.accepted.connect(self.accept)
        button_box.rejected.connect(self.reject)

        self.pb_open = button_box.button(QDialogButtonBox.Open)
        self.pb_open.setEnabled(False)

        vbox = QVBoxLayout()
        vbox.addWidget(self.table)
        vbox.addWidget(self.lb_info)
        vbox.addWidget(button_box)
        self.setLayout(vbox)

    def _fill_table(self, table_contents):
        self.table.setRowCount(len(table_contents))

        for nr, row in enumerate(table_contents):
            for nc, entry in enumerate(row):
                s = textwrap.fill(entry, width=40)
                item = QTableWidgetItem(s)
                item.setFlags(item.flags() & ~Qt.ItemIsEditable)
                if not nc:
                    item.setTextAlignment(Qt.AlignHCenter | Qt.AlignVCenter)
                self.table.setItem(nr, nc, item)
        self.table.resizeRowsToContents()

        brightness = 220
        for nr in range(self.table.rowCount()):
            for nc in range(self.table.columnCount()):
                self.table.item(nr, nc)
                if nr % 2:
                    color = QColor(255, brightness, brightness)
                else:
                    color = QColor(brightness, 255, brightness)
                self.table.item(nr, nc).setBackground(QBrush(color))

        try:
            index = self._qe_param.index(self._qe_standard_selected)
            self.selected_standard_index = index
            n_columns = self.table.columnCount()
            self.table.setRangeSelected(QTableWidgetSelectionRange(index, 0, index, n_columns - 1), True)
        except ValueError:
            pass

    def item_selection_changed(self):
        sel_ranges = self.table.selectedRanges()
        # The table is configured to have one or no selected ranges
        # 'Open' button should be enabled only if a range (row) is selected
        if sel_ranges:
            self.selected_standard_index = sel_ranges[0].topRow()
            self.pb_open.setEnabled(True)
        else:
            self.selected_standard_index = -1
            self.pb_open.setEnabled(False)

    def item_double_clicked(self):
        self.accept()

    def set_standards(self, qe_param_built_in, qe_param_custom, qe_standard_selected):
        self._qe_standard_selected = qe_standard_selected
        self._qe_param = qe_param_custom + qe_param_built_in
        custom_label = [True] * len(qe_param_custom) + [False] * len(qe_param_built_in)

        table_contents = []
        for n, param in enumerate(self._qe_param):
            custom = "*" if custom_label[n] else ""
            serial = param["serial"]
            name = param["name"]
            description = param["description"]
            table_contents.append([custom, serial, name, description])

        self._fill_table(table_contents)

    def get_selected_standard(self):
        if self.selected_standard_index >= 0:
            return self._qe_param[self.selected_standard_index]
        else:
            return None