Beispiel #1
0
class Editor(QDialog):
    """Basic scene editor."""
    def __init__(self, parent: MainWindow, renderers: List[Renderer]) -> None:
        """Initialize the Editor."""
        super().__init__(parent=parent)
        self.renderers = renderers

        self.tree_widget = QTreeWidget()
        self.tree_widget.setHeaderHidden(True)
        self.stacked_widget = QStackedWidget()
        self.layout = QHBoxLayout()
        self.layout.addWidget(self.tree_widget)
        self.layout.addWidget(self.stacked_widget)

        def _selection_callback() -> None:
            for item in self.tree_widget.selectedItems():
                widget_idx = item.data(0, Qt.ItemDataRole.UserRole)
                self.stacked_widget.setCurrentIndex(widget_idx)

        self.tree_widget.itemSelectionChanged.connect(_selection_callback)

        self.setLayout(self.layout)
        self.setWindowTitle("Editor")
        self.setModal(True)

        self.update()

    def update(self) -> None:
        """Update the internal widget list."""
        self.tree_widget.clear()
        for idx, renderer in enumerate(self.renderers):
            actors = renderer._actors  # pylint: disable=protected-access
            widget_idx = self.stacked_widget.addWidget(
                _get_renderer_widget(renderer))
            top_item = QTreeWidgetItem(self.tree_widget,
                                       ["Renderer {}".format(idx)])
            top_item.setData(0, Qt.ItemDataRole.UserRole, widget_idx)
            self.tree_widget.addTopLevelItem(top_item)
            for name, actor in actors.items():
                if actor is not None:
                    widget_idx = self.stacked_widget.addWidget(
                        _get_actor_widget(actor))
                    child_item = QTreeWidgetItem(top_item, [name])
                    child_item.setData(0, Qt.ItemDataRole.UserRole, widget_idx)
                    top_item.addChild(child_item)
        self.tree_widget.expandAll()

    def toggle(self) -> None:
        """Toggle the editor visibility."""
        self.update()
        if self.isVisible():
            self.hide()
        else:
            self.show()
class AlgorithmMonitorDialog(QDialog):
    """
    Displays progress of all running algorithms.
    """
    def __init__(self, parent, model):
        super(AlgorithmMonitorDialog, self).__init__(parent)
        self.tree = QTreeWidget(self)
        self.tree.setColumnCount(3)
        self.tree.setSelectionMode(QTreeWidget.NoSelection)
        self.tree.setColumnWidth(0, 220)
        self.tree.setHeaderLabels(['Algorithm', 'Progress', ''])
        header = self.tree.header()
        header.setSectionResizeMode(1, QHeaderView.Stretch)
        header.setSectionResizeMode(2, QHeaderView.Fixed)
        header.setStretchLastSection(False)

        button_layout = QHBoxLayout()
        self.close_button = QPushButton('Close')
        button_layout.addStretch()
        button_layout.addWidget(self.close_button)

        layout = QVBoxLayout()
        layout.addWidget(self.tree)
        layout.addLayout(button_layout)
        self.setLayout(layout)

        self.setWindowTitle('Mantid - Algorithm progress')
        self.setWindowIcon(QIcon(":/MantidPlot_Icon_32offset.png"))
        self.resize(500, 300)

        self.presenter = AlgorithmProgressDialogPresenter(self, model)
        self.presenter.update_gui()

    def update(self, data):
        """
        Update the gui elements.
        :param data: Data in format of AlgorithmProgressModel.get_running_algorithm_data()
        """
        self.tree.clear()
        for alg_data in data:
            name, id, properties = alg_data
            item = QTreeWidgetItem([name])
            self.tree.addTopLevelItem(item)
            progress_bar = QProgressBar()
            progress_bar.setAlignment(Qt.AlignHCenter)
            self.presenter.add_progress_bar(id, progress_bar)
            cancel_button = CancelButton(self.presenter, id)
            self.tree.setItemWidget(item, 1, progress_bar)
            self.tree.setItemWidget(item, 2, cancel_button)
            for prop in properties:
                item.addChild(QTreeWidgetItem(prop))
Beispiel #3
0
class AlgorithmMonitorDialog(QDialog):
    """
    Displays progress of all running algorithms.
    """
    def __init__(self, parent, model):
        super(AlgorithmMonitorDialog, self).__init__(parent)
        self.tree = QTreeWidget(self)
        self.tree.setColumnCount(3)
        self.tree.setSelectionMode(QTreeWidget.NoSelection)
        self.tree.setColumnWidth(0, 220)
        self.tree.setHeaderLabels(['Algorithm', 'Progress', ''])
        header = self.tree.header()
        header.setSectionResizeMode(1, QHeaderView.Stretch)
        header.setSectionResizeMode(2, QHeaderView.Fixed)
        header.setStretchLastSection(False)

        button_layout = QHBoxLayout()
        self.close_button = QPushButton('Close')
        button_layout.addStretch()
        button_layout.addWidget(self.close_button)

        layout = QVBoxLayout()
        layout.addWidget(self.tree)
        layout.addLayout(button_layout)
        self.setLayout(layout)

        self.setWindowTitle('Mantid - Algorithm progress')
        self.setWindowIcon(QIcon(":/MantidPlot_Icon_32offset.png"))
        self.resize(500, 300)

        self.presenter = AlgorithmProgressDialogPresenter(self, model)
        self.presenter.update_gui()

    def update(self, data):
        """
        Update the gui elements.
        :param data: Data in format of AlgorithmProgressModel.get_running_algorithm_data()
        """
        self.tree.clear()
        for alg_data in data:
            name, id, properties = alg_data
            item = QTreeWidgetItem([name])
            self.tree.addTopLevelItem(item)
            progress_bar = QProgressBar()
            progress_bar.setAlignment(Qt.AlignHCenter)
            self.presenter.add_progress_bar(id, progress_bar)
            cancel_button = CancelButton(self.presenter, id)
            self.tree.setItemWidget(item, 1, progress_bar)
            self.tree.setItemWidget(item, 2, cancel_button)
            for prop in properties:
                item.addChild(QTreeWidgetItem(prop))
Beispiel #4
0
class SystemLog(PyDMFrame):
    def __init__(self, parent=None, init_channel=None):
        PyDMFrame.__init__(self, parent, init_channel)

        self._systemLog = None
        self._node = None

    def connection_changed(self, connected):
        build = (self._node is None) and (self._connected != connected
                                          and connected is True)
        super(SystemLog, self).connection_changed(connected)

        if not build:
            return

        self._node = nodeFromAddress(self.channel)
        self._path = self.channel.replace('SystemLog', 'ClearLog')

        vb = QVBoxLayout()
        self.setLayout(vb)

        gb = QGroupBox('System Log (20 most recent entries)')
        vb.addWidget(gb)

        vb = QVBoxLayout()
        gb.setLayout(vb)

        self._systemLog = QTreeWidget()
        vb.addWidget(self._systemLog)

        self._systemLog.setColumnCount(2)
        self._systemLog.setHeaderLabels(['Field', 'Value'])
        self._systemLog.setColumnWidth(0, 200)

        self._pb = PyDMPushButton(label='Clear Log',
                                  pressValue=1,
                                  init_channel=self._path)
        vb.addWidget(self._pb)

    def value_changed(self, new_val):
        lst = json.loads(new_val)

        self._systemLog.clear()

        # Show only the last 20 entries
        # In the future we can consider a pop up dialog with the full log.
        # Unclear if that is neccessary.
        for ent in lst[-20:]:
            widget = QTreeWidgetItem(self._systemLog)
            widget.setText(
                0,
                time.strftime("%Y-%m-%d %H:%M:%S %Z",
                              time.localtime(ent['created'])))

            widget.setText(1, ent['message'])
            widget.setExpanded(False)
            widget.setTextAlignment(0, Qt.AlignTop)

            temp = QTreeWidgetItem(widget)
            temp.setText(0, 'Name')
            temp.setText(1, str(ent['name']))
            temp.setTextAlignment(0, Qt.AlignRight)

            temp = QTreeWidgetItem(widget)
            temp.setText(0, 'Level')
            temp.setText(
                1, '{} ({})'.format(ent['levelName'], ent['levelNumber']))
            temp.setTextAlignment(0, Qt.AlignRight)

            if ent['exception'] is not None:
                exc = QTreeWidgetItem(widget)
                exc.setText(0, 'exception')
                exc.setText(1, str(ent['exception']))
                exc.setExpanded(False)
                exc.setTextAlignment(0, Qt.AlignRight)

                for v in ent['traceBack']:
                    temp = QTreeWidgetItem(exc)
                    temp.setText(0, '')
                    temp.setText(1, v)
Beispiel #5
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)
Beispiel #6
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)
Beispiel #7
0
class MetaboliteList(QWidget):
    """A list of metabolites"""

    def __init__(self, appdata: CnaData):
        QWidget.__init__(self)
        self.appdata = appdata
        self.last_selected = None

        self.metabolite_list = QTreeWidget()
        self.metabolite_list.setHeaderLabels(["Id", "Name"])
        self.metabolite_list.setSortingEnabled(True)

        for m in self.appdata.project.cobra_py_model.metabolites:
            self.add_metabolite(m)
        self.metabolite_list.setContextMenuPolicy(Qt.CustomContextMenu)
        self.metabolite_list.customContextMenuRequested.connect(
            self.on_context_menu)

        # create context menu
        self.pop_menu = QMenu(self.metabolite_list)
        in_out_fluxes_action = QAction(
            'compute in/out fluxes for this metabolite', self.metabolite_list)
        self.pop_menu.addAction(in_out_fluxes_action)
        in_out_fluxes_action.triggered.connect(self.emit_in_out_fluxes_action)

        self.metabolite_mask = MetabolitesMask(appdata)
        self.metabolite_mask.hide()

        self.layout = QVBoxLayout()
        self.layout.setContentsMargins(0, 0, 0, 0)

        self.splitter = QSplitter()
        self.splitter.setOrientation(Qt.Vertical)
        self.splitter.addWidget(self.metabolite_list)
        self.splitter.addWidget(self.metabolite_mask)
        self.layout.addWidget(self.splitter)
        self.setLayout(self.layout)

        self.metabolite_list.currentItemChanged.connect(
            self.metabolite_selected)
        self.metabolite_mask.metaboliteChanged.connect(
            self.handle_changed_metabolite)
        self.metabolite_mask.jumpToReaction.connect(
            self.emit_jump_to_reaction)

    def clear(self):
        self.metabolite_list.clear()
        self.metabolite_mask.hide()

    def add_metabolite(self, metabolite):
        item = QTreeWidgetItem(self.metabolite_list)
        item.setText(0, metabolite.id)
        item.setText(1, metabolite.name)
        item.setData(2, 0, metabolite)

    def on_context_menu(self, point):
        if len(self.appdata.project.cobra_py_model.metabolites) > 0:
            self.pop_menu.exec_(self.mapToGlobal(point))

    def update_annotations(self, annotation):

        self.metabolite_mask.annotation.itemChanged.disconnect(
            self.metabolite_mask.throttler.throttle)
        c = self.metabolite_mask.annotation.rowCount()
        for i in range(0, c):
            self.metabolite_mask.annotation.removeRow(0)
        i = 0
        for key in annotation:
            self.metabolite_mask.annotation.insertRow(i)
            keyl = QTableWidgetItem(key)
            iteml = QTableWidgetItem(str(annotation[key]))
            self.metabolite_mask.annotation.setItem(i, 0, keyl)
            self.metabolite_mask.annotation.setItem(i, 1, iteml)
            i += 1

        self.metabolite_mask.annotation.itemChanged.connect(
            self.metabolite_mask.throttler.throttle)

    def handle_changed_metabolite(self, metabolite: cobra.Metabolite):
        # Update metabolite item in list
        root = self.metabolite_list.invisibleRootItem()
        child_count = root.childCount()
        for i in range(child_count):
            item = root.child(i)
            if item.data(2, 0) == metabolite:
                old_id = item.text(0)
                item.setText(0, metabolite.id)
                item.setText(1, metabolite.name)
                break

        self.last_selected = self.metabolite_mask.id.text()
        self.metaboliteChanged.emit(old_id, metabolite)

    def update_selected(self, string):
        root = self.metabolite_list.invisibleRootItem()
        child_count = root.childCount()
        for i in range(child_count):
            item = root.child(i)
            item.setHidden(True)

        for item in self.metabolite_list.findItems(string, Qt.MatchContains, 0):
            item.setHidden(False)
        for item in self.metabolite_list.findItems(string, Qt.MatchContains, 1):
            item.setHidden(False)

    def metabolite_selected(self, item, _column):
        if item is None:
            self.metabolite_mask.hide()
        else:
            self.metabolite_mask.show()
            metabolite: cobra.Metabolite = item.data(2, 0)

            self.metabolite_mask.metabolite = metabolite

            self.metabolite_mask.id.setText(metabolite.id)
            self.metabolite_mask.name.setText(metabolite.name)
            self.metabolite_mask.formula.setText(metabolite.formula)
            if metabolite.charge is None:
                pass
            else:
                self.metabolite_mask.charge.setText(str(metabolite.charge))
            self.metabolite_mask.compartment.setText(metabolite.compartment)
            self.update_annotations(metabolite.annotation)
            self.metabolite_mask.changed = False

            turn_white(self.metabolite_mask.id)
            turn_white(self.metabolite_mask.name)
            turn_white(self.metabolite_mask.formula)
            turn_white(self.metabolite_mask.charge)
            turn_white(self.metabolite_mask.compartment)
            self.metabolite_mask.is_valid = True
            self.metabolite_mask.update_state()

    def update(self):
        self.metabolite_list.clear()
        for m in self.appdata.project.cobra_py_model.metabolites:
            self.add_metabolite(m)

        if self.last_selected is None:
            pass
        else:
            items = self.metabolite_list.findItems(
                self.last_selected, Qt.MatchExactly)

            for i in items:
                self.metabolite_list.setCurrentItem(i)
                break

    def set_current_item(self, key):
        self.last_selected = key
        self.update()

    def emit_jump_to_reaction(self, reaction):
        self.jumpToReaction.emit(reaction)

    def emit_in_out_fluxes_action(self):
        self.computeInOutFlux.emit(self.metabolite_list.currentItem().text(0))

    itemActivated = Signal(str)
    metaboliteChanged = Signal(str, cobra.Metabolite)
    jumpToReaction = Signal(str)
    computeInOutFlux = Signal(str)