コード例 #1
0
class TestRunner(ToolInstance):
    def __init__(self, session, name):
        super().__init__(session, name)
        self.tool_window = MainToolWindow(self)

        self._build_ui()

    def _build_ui(self):
        """
        ui should have:
            * table with a list of available tests and show results after they are done
            * way to filter tests
            * button to run tests
        """
        layout = QFormLayout()

        # table to list test classes and the results
        self.table = QTableWidget()
        self.table.setColumnCount(2)
        self.table.setHorizontalHeaderLabels(["test", "result"])
        self.table.horizontalHeader().setSectionResizeMode(
            0,
            self.table.horizontalHeader().Interactive)
        self.table.setEditTriggers(QTableWidget.NoEditTriggers)
        self.table.horizontalHeader().setSectionResizeMode(
            1,
            self.table.horizontalHeader().Stretch)
        self.table.setSizePolicy(QSizePolicy.MinimumExpanding,
                                 QSizePolicy.MinimumExpanding)
        self.table.setSelectionBehavior(self.table.SelectRows)
        layout.addRow(self.table)

        self.filter = QLineEdit()
        self.filter.setPlaceholderText("filter test names")
        self.filter.setClearButtonEnabled(True)
        self.filter.textChanged.connect(self.apply_filter)
        layout.addRow(self.filter)

        self.profile = QCheckBox()
        self.profile.setToolTip("profile functions called during testing "
                                "using cProfile")
        layout.addRow("profile calls:", self.profile)

        self.run_button = QPushButton("run tests")
        self.run_button.clicked.connect(self.run_tests)
        self.run_button.setToolTip(
            "if no tests are selected on the table, run all tests\n" +
            "otherwise, run selected tests")
        layout.addRow(self.run_button)

        self.fill_table()
        self.table.resizeColumnToContents(0)

        self.tool_window.ui_area.setLayout(layout)

        self.tool_window.manage(None)

    def apply_filter(self, text=None):
        """filter table to only show tests matching text"""
        if text is None:
            text = self.filter.text()

        if text:
            text = text.replace("(", "\(")
            text = text.replace(")", "\)")
            m = QRegularExpression(text)
            m.setPatternOptions(QRegularExpression.CaseInsensitiveOption)
            if m.isValid():
                m.optimize()
                filter = lambda row_num: m.match(
                    self.table.item(row_num, 0).text()).hasMatch()
            else:
                return

        else:
            filter = lambda row: True

        for i in range(0, self.table.rowCount()):
            self.table.setRowHidden(i, not filter(i))

    def fill_table(self):
        """adds test names to the table"""
        mgr = self.session.test_manager

        for name in mgr.tests.keys():
            row = self.table.rowCount()
            self.table.insertRow(row)
            test_name = QTableWidgetItem()
            test_name.setData(Qt.DisplayRole, name)
            self.table.setItem(row, 0, test_name)

    def run_tests(self):
        """run the tests selected on the table and show the results"""
        from TestManager.commands.test import test

        test_list = []

        use_selected = True
        for row in self.table.selectionModel().selectedRows():
            if self.table.isRowHidden(row.row()):
                continue

            test_name = self.table.item(row.row(), 0).text()
            test_list.append(test_name)

        if not test_list:
            use_selected = False
            for i in range(0, self.table.rowCount()):
                if self.table.isRowHidden(i):
                    continue

                test_name = self.table.item(i, 0).text()
                test_list.append(test_name)

            if not test_list:
                test_list = ["all"]

        results, stats = test(self.session,
                              test_list,
                              profile=self.profile.checkState() == Qt.Checked)

        cell_widgets = []

        for name in results:
            widget = QWidget()
            widget_layout = QHBoxLayout(widget)
            widget_layout.setContentsMargins(0, 0, 0, 0)

            success_button = get_button("success")
            fail_button = get_button("fail")
            skip_button = get_button("skip")
            error_button = get_button("error")
            expected_fail_button = get_button("expected fail")
            unexpected_success_button = get_button("unexpected success")

            success_count = 0
            fail_count = 0
            error_count = 0
            unexpected_success_count = 0
            expected_fail_count = 0
            skip_count = 0

            success_tooltip = "Successes:\n"
            fail_tooltip = "Failed tests:\n"
            error_tooltip = "Errors during test:\n"
            unexpected_success_tooltip = "Unexpected successes:\n"
            expected_fail_tooltip = "Expected fails:\n"
            skip_tooltip = "Skipped tests:\n"

            for case in results[name]:
                result, msg = results[name][case]
                if result == "success":
                    success_count += 1
                    success_tooltip += "%s.%s: %s\n" % (
                        case.__class__.__qualname__, case._testMethodName, msg)

                elif result == "fail":
                    fail_count += 1
                    fail_tooltip += "%s.%s failed: %s\n" % (
                        case.__class__.__qualname__, case._testMethodName, msg)

                elif result == "error":
                    error_count += 1
                    error_tooltip += "error during %s.%s: %s\n" % (
                        case.__class__.__qualname__, case._testMethodName, msg)

                elif result == "expected_failure":
                    expected_fail_count += 1
                    expected_fail_tooltip += "intended failure during %s.%s: %s\n" % (
                        case.__class__.__qualname__, case._testMethodName, msg)

                elif result == "skip":
                    skip_count += 1
                    skip_tooltip += "%s.%s\n" % (case.__class__.__qualname__,
                                                 case._testMethodName)

                elif result == "unexpected_success":
                    unexpected_success_count += 1
                    unexpected_success_tooltip += "%s.%s should not have worked, but did\n" % (
                        case.__class__.__qualname__, case._testMethodName)

            success_tooltip = success_tooltip.strip()
            fail_tooltip = fail_tooltip.strip()
            error_tooltip = error_tooltip.strip()
            expected_fail_tooltip = expected_fail_tooltip.strip()
            skip_tooltip = skip_tooltip.strip()
            unexpected_success_tooltip = unexpected_success_tooltip.strip()

            icon_count = 0
            if success_count:
                success_button.setText("%i" % success_count)
                success_button.setToolTip(success_tooltip)
                success_button.clicked.connect(
                    lambda *args, t_name=name, res=success_tooltip: self.
                    tool_window.create_child_window(
                        "successes for %s" % t_name,
                        text=res,
                        window_class=ResultsWindow,
                    ))
                widget_layout.insertWidget(icon_count, success_button, 1)
                icon_count += 1

            if fail_count:
                fail_button.setText("%i" % fail_count)
                fail_button.setToolTip(fail_tooltip)
                fail_button.clicked.connect(
                    lambda *args, res=fail_tooltip: self.tool_window.
                    create_child_window(
                        "failures for %s" % name,
                        text=res,
                        window_class=ResultsWindow,
                    ))
                widget_layout.insertWidget(icon_count, fail_button, 1)
                icon_count += 1

            if error_count:
                error_button.setText("%i" % error_count)
                error_button.setToolTip(error_tooltip)
                error_button.clicked.connect(
                    lambda *args, res=error_tooltip: self.tool_window.
                    create_child_window(
                        "errors for %s" % name,
                        text=res,
                        window_class=ResultsWindow,
                    ))
                widget_layout.insertWidget(icon_count, error_button, 1)
                icon_count += 1

            if unexpected_success_count:
                unexpected_success_button.setText("%i" %
                                                  unexpected_success_count)
                unexpected_success_button.setToolTip(
                    unexpected_success_tooltip)
                unexpected_success_button.clicked.connect(
                    lambda *args, res=unexpected_success_tooltip: self.
                    tool_window.create_child_window(
                        "unexpected successes for %s" % name,
                        text=res,
                        window_class=ResultsWindow,
                    ))
                widget_layout.insertWidget(icon_count,
                                           unexpected_success_button, 1)
                icon_count += 1

            if expected_fail_count:
                expected_fail_button.setText("%i" % expected_fail_count)
                expected_fail_button.setToolTip(expected_fail_tooltip)
                expected_fail_button.clicked.connect(
                    lambda *args, res=expected_fail_tooltip: self.tool_window.
                    create_child_window(
                        "expected failures for %s" % name,
                        text=res,
                        window_class=ResultsWindow,
                    ))
                widget_layout.insertWidget(icon_count, expected_fail_button, 1)
                icon_count += 1

            if skip_count:
                skip_button.setText("%i" % skip_count)
                skip_button.setToolTip(skip_tooltip)
                skip_button.clicked.connect(
                    lambda *args, res=skip_tooltip: self.tool_window.
                    create_child_window(
                        "skipped tests for %s" % name,
                        text=res,
                        window_class=ResultsWindow,
                    ))
                widget_layout.insertWidget(icon_count, skip_button, 1)

            cell_widgets.append(widget)

        widget_count = 0
        if use_selected:
            for row in self.table.selectionModel().selectedRows():
                if self.table.isRowHidden(row.row()):
                    continue

                self.table.setCellWidget(row.row(), 1,
                                         cell_widgets[widget_count])
                self.table.resizeRowToContents(row.row())
                widget_count += 1

        else:
            for i in range(0, self.table.rowCount()):
                if self.table.isRowHidden(i):
                    continue

                self.table.setCellWidget(i, 1, cell_widgets[widget_count])
                # self.table.resizeRowToContents(i)
                widget_count += 1

        if self.profile.checkState() == Qt.Checked:
            self.tool_window.create_child_window("stats",
                                                 text=stats,
                                                 window_class=ResultsWindow)
コード例 #2
0
ファイル: mouse_modes.py プロジェクト: QChASM/SEQCROW
class _SubstituentSelector(ToolInstance):
    def __init__(self, session, name):
        super().__init__(session, name)
        self.tool_window = MainToolWindow(self)
        self._build_ui()

    def _build_ui(self):
        layout = QFormLayout()

        self.substituent_table = SubstituentTable(singleSelect=True)
        layout.addRow(self.substituent_table)

        self.new_residue = QCheckBox()
        self.new_residue.setCheckState(
            Qt.Checked if SubstituteMouseMode.newRes else Qt.Unchecked)
        layout.addRow("new residue:", self.new_residue)

        self.res_name = QLineEdit()
        self.res_name.setPlaceholderText("leave blank to keep current")
        layout.addRow("set residue name:", self.res_name)

        self.distance_names = QCheckBox()
        self.distance_names.setCheckState(
            Qt.Checked if SubstituteMouseMode.useRemoteness else Qt.Unchecked)
        layout.addRow("distance atom names:", self.distance_names)

        self.keep_open = QCheckBox()
        layout.addRow("keep list open:", self.keep_open)

        do_it = QPushButton("set substituent")
        do_it.clicked.connect(self.set_sub)
        layout.addRow(do_it)

        self.keep_open.stateChanged.connect(
            lambda state: do_it.setVisible(state != Qt.Checked))
        self.keep_open.stateChanged.connect(self.sub_changed)

        self.substituent_table.table.itemSelectionChanged.connect(
            self.sub_changed)
        self.new_residue.stateChanged.connect(self.sub_changed)
        self.res_name.textChanged.connect(self.sub_changed)
        self.distance_names.stateChanged.connect(self.sub_changed)

        self.tool_window.ui_area.setLayout(layout)

        self.tool_window.manage(None)

    def sub_changed(self):
        if self.keep_open.checkState() == Qt.Checked:
            self.set_sub()

    def set_sub(self):
        sub_names = []
        for row in self.substituent_table.table.selectionModel().selectedRows(
        ):
            if self.substituent_table.table.isRowHidden(row.row()):
                continue

            sub_names.append(row.data())

        if sub_names:
            SubstituteMouseMode.substituent = sub_names[0]
            SubstituteMouseMode.newRes = self.new_residue.checkState(
            ) == Qt.Checked
            SubstituteMouseMode.useRemoteness = self.distance_names.checkState(
            ) == Qt.Checked
            sub_name = self.res_name.text()
            SubstituteMouseMode.resName = sub_name

        if self.keep_open.checkState() != Qt.Checked:
            self.delete()

    def close(self):
        sub_names = []
        for row in self.substituent_table.table.selectionModel().selectedRows(
        ):
            if self.substituent_table.table.isRowHidden(row.row()):
                continue

            sub_names.append(row.data())

        if sub_names:
            SubstituteMouseMode.substituent = sub_names[0]
            SubstituteMouseMode.newRes = self.new_residue.checkState(
            ) == Qt.Checked
            SubstituteMouseMode.useRemoteness = self.distance_names.checkState(
            ) == Qt.Checked
            sub_name = self.res_name.text()
            SubstituteMouseMode.resName = sub_name

        super().close()
コード例 #3
0
class Info(ToolInstance):

    help = "https://github.com/QChASM/SEQCROW/wiki/File-Info-Tool"

    def __init__(self, session, name):
        super().__init__(session, name)

        self.tool_window = MainToolWindow(self)

        self.settings = _InfoSettings(self.session, name)

        self._build_ui()

    def _build_ui(self):
        layout = QVBoxLayout()

        self.file_selector = FilereaderComboBox(self.session)
        self.file_selector.currentIndexChanged.connect(self.fill_table)
        layout.insertWidget(0, self.file_selector, 0)

        tabs = QTabWidget()
        self.tabs = tabs
        layout.insertWidget(1, self.tabs, 1)

        general_info = QWidget()
        general_layout = QVBoxLayout(general_info)
        tabs.addTab(general_info, "general")

        self.table = QTableWidget()
        self.table.setColumnCount(2)
        self.table.setHorizontalHeaderLabels(['Data', 'Value'])
        self.table.horizontalHeader().setStretchLastSection(False)
        self.table.horizontalHeader().setSectionResizeMode(
            0, QHeaderView.Interactive)
        self.table.setEditTriggers(QTableWidget.NoEditTriggers)
        self.table.horizontalHeader().setSectionResizeMode(
            1, QHeaderView.Stretch)
        self.table.setSizePolicy(QSizePolicy.MinimumExpanding,
                                 QSizePolicy.MinimumExpanding)
        general_layout.insertWidget(1, self.table, 1)

        self.filter = QLineEdit()
        self.filter.setPlaceholderText("filter data")
        self.filter.textChanged.connect(self.apply_filter)
        self.filter.setClearButtonEnabled(True)
        general_layout.insertWidget(2, self.filter, 0)

        freq_info = QWidget()
        freq_layout = QVBoxLayout(freq_info)
        tabs.addTab(freq_info, "harmonic frequencies")

        self.freq_table = QTableWidget()
        self.freq_table.setColumnCount(4)
        self.freq_table.setHorizontalHeaderLabels([
            "Frequency (cm\u207b\u00b9)",
            "symmetry",
            "IR intensity",
            "force constant (mDyne/\u212B)",
        ])
        self.freq_table.setSortingEnabled(True)
        self.freq_table.setEditTriggers(QTableWidget.NoEditTriggers)
        for i in range(0, 4):
            self.freq_table.resizeColumnToContents(i)

        self.freq_table.horizontalHeader().setStretchLastSection(False)
        self.freq_table.horizontalHeader().setSectionResizeMode(
            0, QHeaderView.Fixed)
        self.freq_table.horizontalHeader().setSectionResizeMode(
            1, QHeaderView.Fixed)
        self.freq_table.horizontalHeader().setSectionResizeMode(
            2, QHeaderView.Fixed)
        self.freq_table.horizontalHeader().setSectionResizeMode(
            3, QHeaderView.Stretch)

        self.freq_table.setSizePolicy(QSizePolicy.MinimumExpanding,
                                      QSizePolicy.MinimumExpanding)
        freq_layout.insertWidget(1, self.freq_table, 1)

        anharm_info = QWidget()
        anharm_layout = QVBoxLayout(anharm_info)
        tabs.addTab(anharm_info, "anharmonic frequencies")

        anharm_layout.insertWidget(0, QLabel("fundamentals:"), 0)

        self.fundamental_table = QTableWidget()
        self.fundamental_table.setColumnCount(3)
        self.fundamental_table.setHorizontalHeaderLabels([
            "Fundamental (cm\u207b\u00b9)",
            "Δ\u2090\u2099\u2095 (cm\u207b\u00b9)",
            "IR intensity",
        ])
        self.fundamental_table.setSortingEnabled(True)
        self.fundamental_table.setEditTriggers(QTableWidget.NoEditTriggers)
        for i in range(0, 3):
            self.fundamental_table.resizeColumnToContents(i)

        self.fundamental_table.horizontalHeader().setStretchLastSection(False)
        self.fundamental_table.horizontalHeader().setSectionResizeMode(
            0, QHeaderView.Fixed)
        self.fundamental_table.horizontalHeader().setSectionResizeMode(
            1, QHeaderView.Fixed)
        self.fundamental_table.horizontalHeader().setSectionResizeMode(
            2, QHeaderView.Stretch)

        self.fundamental_table.setSizePolicy(QSizePolicy.MinimumExpanding,
                                             QSizePolicy.MinimumExpanding)
        anharm_layout.insertWidget(1, self.fundamental_table, 1)

        # self.overtone_table = QTableWidget()
        # self.overtone_table.setColumnCount(3)
        # self.overtone_table.setHorizontalHeaderLabels(
        #     [
        #         "Fundamental (cm\u207b\u00b9)",
        #         "Overtone (cm\u207b\u00b9)",
        #         "IR intensity",
        #     ]
        # )
        # self.overtone_table.setSortingEnabled(True)
        # self.overtone_table.setEditTriggers(QTableWidget.NoEditTriggers)
        # for i in range(0, 3):
        #     self.overtone_table.resizeColumnToContents(i)
        #
        # self.overtone_table.horizontalHeader().setStretchLastSection(False)
        # self.overtone_table.horizontalHeader().setSectionResizeMode(0, QHeaderView.Fixed)
        # self.overtone_table.horizontalHeader().setSectionResizeMode(1, QHeaderView.Fixed)
        # self.overtone_table.horizontalHeader().setSectionResizeMode(2, QHeaderView.Stretch)
        #
        # self.overtone_table.setSizePolicy(QSizePolicy.MinimumExpanding, QSizePolicy.MinimumExpanding)
        # anharm_layout.insertWidget(2, self.overtone_table, 1)

        anharm_layout.insertWidget(2, QLabel("combinations and overtones:"), 0)

        self.combo_table = QTableWidget()
        self.combo_table.setColumnCount(4)
        self.combo_table.setHorizontalHeaderLabels([
            "Fundamental (cm\u207b\u00b9)",
            "Fundamental (cm\u207b\u00b9)",
            "Combination (cm\u207b\u00b9)",
            "IR intensity",
        ])
        self.combo_table.setSortingEnabled(True)
        self.combo_table.setEditTriggers(QTableWidget.NoEditTriggers)
        for i in range(0, 3):
            self.combo_table.resizeColumnToContents(i)

        self.combo_table.horizontalHeader().setStretchLastSection(False)
        self.combo_table.horizontalHeader().setSectionResizeMode(
            0, QHeaderView.Fixed)
        self.combo_table.horizontalHeader().setSectionResizeMode(
            1, QHeaderView.Fixed)
        self.combo_table.horizontalHeader().setSectionResizeMode(
            2, QHeaderView.Fixed)
        self.combo_table.horizontalHeader().setSectionResizeMode(
            3, QHeaderView.Stretch)

        self.combo_table.setSizePolicy(QSizePolicy.MinimumExpanding,
                                       QSizePolicy.MinimumExpanding)
        anharm_layout.insertWidget(3, self.combo_table, 1)

        menu = QMenuBar()

        export = menu.addMenu("&Export")
        copy = QAction("&Copy CSV to clipboard", self.tool_window.ui_area)
        copy.triggered.connect(self.copy_csv)
        shortcut = QKeySequence(Qt.CTRL + Qt.Key_C)
        copy.setShortcut(shortcut)
        export.addAction(copy)
        self.copy = copy

        save = QAction("&Save CSV...", self.tool_window.ui_area)
        save.triggered.connect(self.save_csv)
        export.addAction(save)

        delimiter = export.addMenu("Delimiter")

        comma = QAction("comma", self.tool_window.ui_area, checkable=True)
        comma.setChecked(self.settings.delimiter == "comma")
        comma.triggered.connect(lambda *args, delim="comma": self.settings.
                                __setattr__("delimiter", delim))
        delimiter.addAction(comma)

        tab = QAction("tab", self.tool_window.ui_area, checkable=True)
        tab.setChecked(self.settings.delimiter == "tab")
        tab.triggered.connect(lambda *args, delim="tab": self.settings.
                              __setattr__("delimiter", delim))
        delimiter.addAction(tab)

        # space = QAction("space", self.tool_window.ui_area, checkable=True)
        # space.setChecked(self.settings.delimiter == "space")
        # space.triggered.connect(lambda *args, delim="space": self.settings.__setattr__("delimiter", delim))
        # delimiter.addAction(space)

        semicolon = QAction("semicolon",
                            self.tool_window.ui_area,
                            checkable=True)
        semicolon.setChecked(self.settings.delimiter == "semicolon")
        semicolon.triggered.connect(lambda *args, delim="semicolon": self.
                                    settings.__setattr__("delimiter", delim))
        delimiter.addAction(semicolon)

        add_header = QAction("&Include CSV header",
                             self.tool_window.ui_area,
                             checkable=True)
        add_header.setChecked(self.settings.include_header)
        add_header.triggered.connect(self.header_check)
        export.addAction(add_header)

        tab.triggered.connect(
            lambda *args, action=semicolon: action.setChecked(False))
        semicolon.triggered.connect(
            lambda *args, action=tab: action.setChecked(False))

        archive = QAction("Include archive if present",
                          self.tool_window.ui_area,
                          checkable=True)
        archive.triggered.connect(
            lambda checked: setattr(self.settings, "archive", checked))
        archive.triggered.connect(
            lambda *args: self.fill_table(self.file_selector.count() - 1))
        archive.setChecked(self.settings.archive)
        export.addAction(archive)

        unit = menu.addMenu("&Units")

        energy = unit.addMenu("energy")
        hartree = QAction("Hartree", self.tool_window.ui_area, checkable=True)
        hartree.setChecked(self.settings.energy == "Hartree")
        kcal = QAction("kcal/mol", self.tool_window.ui_area, checkable=True)
        kcal.setChecked(self.settings.energy == "kcal/mol")
        kjoule = QAction("kJ/mol", self.tool_window.ui_area, checkable=True)
        kjoule.setChecked(self.settings.energy == "kJ/mol")
        energy.addAction(hartree)
        energy.addAction(kcal)
        energy.addAction(kjoule)

        hartree.triggered.connect(
            lambda *args, val="Hartree": setattr(self.settings, "energy", val))
        hartree.triggered.connect(
            lambda *args: self.fill_table(self.file_selector.count() - 1))
        hartree.triggered.connect(
            lambda *args, action=kcal: action.setChecked(False))
        hartree.triggered.connect(
            lambda *args, action=kjoule: action.setChecked(False))

        kcal.triggered.connect(lambda *args, val="kcal/mol": setattr(
            self.settings, "energy", val))
        kcal.triggered.connect(
            lambda *args: self.fill_table(self.file_selector.count() - 1))
        kcal.triggered.connect(
            lambda *args, action=hartree: action.setChecked(False))
        kcal.triggered.connect(
            lambda *args, action=kjoule: action.setChecked(False))

        kjoule.triggered.connect(
            lambda *args, val="kJ/mol": setattr(self.settings, "energy", val))
        kjoule.triggered.connect(
            lambda *args: self.fill_table(self.file_selector.count() - 1))
        kjoule.triggered.connect(
            lambda *args, action=hartree: action.setChecked(False))
        kjoule.triggered.connect(
            lambda *args, action=kcal: action.setChecked(False))

        mass = unit.addMenu("mass")
        kg = QAction("kg", self.tool_window.ui_area, checkable=True)
        kg.setChecked(self.settings.mass == "kg")
        amu = QAction("Da", self.tool_window.ui_area, checkable=True)
        amu.setChecked(self.settings.mass == "Da")
        mass.addAction(kg)
        mass.addAction(amu)

        kg.triggered.connect(
            lambda *args, val="kg": setattr(self.settings, "mass", val))
        kg.triggered.connect(
            lambda *args: self.fill_table(self.file_selector.count() - 1))
        kg.triggered.connect(
            lambda *args, action=amu: action.setChecked(False))

        amu.triggered.connect(
            lambda *args, val="Da": setattr(self.settings, "mass", val))
        amu.triggered.connect(
            lambda *args: self.fill_table(self.file_selector.count() - 1))
        amu.triggered.connect(
            lambda *args, action=kg: action.setChecked(False))

        rot_const = unit.addMenu("rotational constants")
        temperature = QAction("K", self.tool_window.ui_area, checkable=True)
        temperature.setChecked(self.settings.rot_const == "K")
        hertz = QAction("GHz", self.tool_window.ui_area, checkable=True)
        hertz.setChecked(self.settings.rot_const == "GHz")
        rot_const.addAction(temperature)
        rot_const.addAction(hertz)

        temperature.triggered.connect(
            lambda *args, val="K": setattr(self.settings, "rot_const", val))
        temperature.triggered.connect(
            lambda *args: self.fill_table(self.file_selector.count() - 1))
        temperature.triggered.connect(
            lambda *args, action=hertz: action.setChecked(False))

        hertz.triggered.connect(
            lambda *args, val="GHz": setattr(self.settings, "rot_const", val))
        hertz.triggered.connect(
            lambda *args: self.fill_table(self.file_selector.count() - 1))
        hertz.triggered.connect(
            lambda *args, action=temperature: action.setChecked(False))

        menu.setNativeMenuBar(False)
        self._menu = menu
        layout.setMenuBar(menu)
        menu.setVisible(True)

        if len(self.session.filereader_manager.list()) > 0:
            self.fill_table(0)

        self.tool_window.ui_area.setLayout(layout)

        self.tool_window.manage(None)

    def header_check(self, state):
        """user has [un]checked the 'include header' option on the menu"""
        if state:
            self.settings.include_header = True
        else:
            self.settings.include_header = False

    def get_csv(self):
        if self.settings.delimiter == "comma":
            delim = ","
        elif self.settings.delimiter == "space":
            delim = " "
        elif self.settings.delimiter == "tab":
            delim = "\t"
        elif self.settings.delimiter == "semicolon":
            delim = ";"

        if self.tabs.currentIndex() == 0:
            if self.settings.include_header:
                s = delim.join(["Data", "Value"])
                s += "\n"
            else:
                s = ""

            for i in range(0, self.table.rowCount()):
                if self.table.isRowHidden(i):
                    continue
                s += delim.join([
                    item.text().replace("<sub>", "").replace("</sub>", "")
                    for item in [
                        self.table.item(i, j) if self.table.item(i, j)
                        is not None else self.table.cellWidget(i, j)
                        for j in range(0, 2)
                    ]
                ])
                s += "\n"

        elif self.tabs.currentIndex() == 1:
            if self.settings.include_header:
                s = delim.join([
                    "Frequency (cm\u207b\u00b9)", "symmetry", "IR intensity",
                    "force constant"
                ])
                s += "\n"
            else:
                s = ""

            for i in range(0, self.freq_table.rowCount()):
                if self.freq_table.isRowHidden(i):
                    continue
                s += delim.join([
                    item.text().replace("<sub>", "").replace("</sub>", "")
                    for item in [
                        self.freq_table.item(i, j) if self.freq_table.
                        item(i, j) is not None else self.freq_table.
                        cellWidget(i, j) for j in range(0, 4)
                    ]
                ])
                s += "\n"

        else:
            if self.settings.include_header:
                s = delim.join(
                    ["Fundamental (cm\u207b\u00b9)", "Δanh", "IR intensity"])
                s += "\n"
            else:
                s = ""

            for i in range(0, self.fundamental_table.rowCount()):
                if self.fundamental_table.isRowHidden(i):
                    continue
                s += delim.join([
                    item.text().replace("<sub>", "").replace("</sub>", "")
                    for item in [
                        self.fundamental_table.item(i, j) if self.
                        fundamental_table.item(i, j) is not None else self.
                        fundamental_table.cellWidget(i, j)
                        for j in range(0, 3)
                    ]
                ])
                s += "\n"

            if self.settings.include_header:
                s += delim.join([
                    "Fundamental (cm\u207b\u00b9)",
                    "Fundamental (cm\u207b\u00b9)",
                    "Combination (cm\u207b\u00b9)", "IR intensity"
                ])
                s += "\n"
            else:
                s += "\n"

            for i in range(0, self.combo_table.rowCount()):
                if self.combo_table.isRowHidden(i):
                    continue
                s += delim.join([
                    item.text().replace("<sub>", "").replace("</sub>", "")
                    for item in [
                        self.combo_table.item(i, j) if self.combo_table.
                        item(i, j) is not None else self.combo_table.
                        cellWidget(i, j) for j in range(0, 4)
                    ]
                ])
                s += "\n"

        return s

    def copy_csv(self):
        app = QApplication.instance()
        clipboard = app.clipboard()
        csv = self.get_csv()
        clipboard.setText(csv)
        self.session.logger.status("copied to clipboard")

    def save_csv(self):
        """save data on current tab to CSV file"""
        filename, _ = QFileDialog.getSaveFileName(filter="CSV Files (*.csv)")
        if filename:
            s = self.get_csv()

            with open(filename, 'w') as f:
                f.write(s.strip())

            self.session.logger.status("saved to %s" % filename)

    def fill_table(self, ndx):
        self.table.setRowCount(0)
        self.freq_table.setRowCount(0)
        self.fundamental_table.setRowCount(0)
        # self.overtone_table.setRowCount(0)
        self.combo_table.setRowCount(0)

        if ndx < 0:
            self.fundamental_table.setVisible(False)
            self.combo_table.setVisible(False)
            return

        fr = self.file_selector.currentData()
        item = QTableWidgetItem()
        item.setData(Qt.DisplayRole, "name")
        val = QTableWidgetItem()
        val.setData(Qt.DisplayRole, fr.name)
        self.table.insertRow(0)
        self.table.setItem(0, 0, item)
        self.table.setItem(0, 1, val)
        for info in fr.other.keys():
            if info == "archive" and not self.settings.archive:
                continue

            if any(
                    isinstance(fr.other[info], obj)
                    for obj in [str, float, int]):
                row = self.table.rowCount()
                self.table.insertRow(row)

                item = QTableWidgetItem()
                info_name = info.replace("_", " ")
                val = fr.other[info]
                if info == "mass":
                    info_name += " (%s)" % self.settings.mass
                    if self.settings.mass == "Da":
                        val /= UNIT.AMU_TO_KG

                elif info == "temperature":
                    info_name += " (K)"

                elif (any(info == s for s in nrg_infos)
                      or info.lower().endswith("energy")
                      or info.startswith("E(")):
                    if self.settings.energy == "Hartree":
                        info_name += " (E<sub>h</sub>)"
                    else:
                        info_name += " (%s)" % self.settings.energy
                    info_name = info_name.replace("orrelation", "orr.")
                    info_name = info_name.replace("Same-spin", "SS")
                    info_name = info_name.replace("Opposite-spin", "OS")
                    if self.settings.energy == "kcal/mol":
                        val *= UNIT.HART_TO_KCAL
                    elif self.settings.energy == "kJ/mol":
                        val *= 4.184 * UNIT.HART_TO_KCAL

                    val = "%.6f" % val

                elif info.startswith("optical rotation"):
                    info_name += " (°)"

                elif any(info == x for x in pg_infos):
                    info_name = info.replace("_", " ")
                    if re.search("\d", val):
                        val = re.sub(r"(\d+)", r"<sub>\1</sub>", val)
                    # gaussian uses * for infinity
                    val = val.replace("*", "<sub>∞</sub>")
                    # psi4 uses _inf_
                    val = val.replace("_inf_", "<sub>∞</sub>")
                    if any(val.endswith(char) for char in "vhdsiVHDSI"):
                        val = val[:-1] + "<sub>" + val[-1].lower() + "</sub>"

                if "<sub>" in info_name:
                    self.table.setCellWidget(row, 0, QLabel(info_name))
                else:
                    item = QTableWidgetItem()
                    item.setData(Qt.DisplayRole, info_name)
                    self.table.setItem(row, 0, item)

                value = QTableWidgetItem()
                val = str(val)
                if "<sub>" in val:
                    self.table.setCellWidget(row, 1, QLabel(val))
                else:
                    value.setData(Qt.DisplayRole, val)
                    self.table.setItem(row, 1, value)

            elif isinstance(fr.other[info], Theory):
                theory = fr.other[info]
                if theory.method is not None:
                    row = self.table.rowCount()
                    self.table.insertRow(row)

                    item = QTableWidgetItem()
                    item.setData(Qt.DisplayRole, "method")
                    self.table.setItem(row, 0, item)

                    value = QTableWidgetItem()
                    value.setData(Qt.DisplayRole, theory.method.name)
                    self.table.setItem(row, 1, value)

                if theory.basis is not None:
                    if theory.basis.basis:
                        for basis in theory.basis.basis:
                            row = self.table.rowCount()
                            self.table.insertRow(row)

                            item = QTableWidgetItem()
                            if not basis.elements:
                                item.setData(Qt.DisplayRole, "basis set")
                            else:
                                item.setData(
                                    Qt.DisplayRole,
                                    "basis for %s" % ", ".join(basis.elements))
                            self.table.setItem(row, 0, item)

                            value = QTableWidgetItem()
                            value.setData(Qt.DisplayRole, basis.name)
                            self.table.setItem(row, 1, value)

                    if theory.basis.ecp:
                        for ecp in theory.basis.ecp:
                            row = self.table.rowCount()
                            self.table.insertRow(row)

                            item = QTableWidgetItem()
                            if ecp.elements is None:
                                item.setData(Qt.DisplayRole, "ECP")
                            else:
                                item.setData(Qt.DisplayRole,
                                             "ECP %s" % " ".join(ecp.elements))
                            self.table.setItem(row, 0, item)

                            value = QTableWidgetItem()
                            value.setData(Qt.DisplayRole, ecp.name)
                            self.table.setItem(row, 1, value)

            elif (hasattr(fr.other[info], "__iter__")
                  and all(isinstance(x, float) for x in fr.other[info])
                  and len(fr.other[info]) > 1):
                row = self.table.rowCount()
                self.table.insertRow(row)

                item = QTableWidgetItem()
                info_name = info.replace("_", " ")
                vals = fr.other[info]
                if "rotational_temperature" in info:
                    info_name = info_name.replace(
                        "temperature",
                        "constants (%s)" % self.settings.rot_const)
                    if self.settings.rot_const == "GHz":
                        vals = [
                            x * PHYSICAL.KB / (PHYSICAL.PLANCK * (10**9))
                            for x in vals
                        ]

                item.setData(Qt.DisplayRole, info_name)
                self.table.setItem(row, 0, item)

                value = QTableWidgetItem()
                value.setData(Qt.DisplayRole,
                              ", ".join(["%.4f" % x for x in vals]))
                self.table.setItem(row, 1, value)

        if "frequency" in fr.other:
            self.tabs.setTabEnabled(1, True)
            freq_data = fr.other['frequency'].data

            for i, mode in enumerate(freq_data):
                row = self.freq_table.rowCount()
                self.freq_table.insertRow(row)

                freq = FreqTableWidgetItem()
                freq.setData(
                    Qt.DisplayRole, "%.2f%s" %
                    (abs(mode.frequency), "i" if mode.frequency < 0 else ""))
                freq.setData(Qt.UserRole, i)
                self.freq_table.setItem(row, 0, freq)

                if mode.symmetry:
                    text = mode.symmetry
                    if re.search("\d", text):
                        text = re.sub(r"(\d+)", r"<sub>\1</sub>", text)
                    if text.startswith("SG"):
                        text = "Σ" + text[2:]
                    elif text.startswith("PI"):
                        text = "Π" + text[2:]
                    elif text.startswith("DLT"):
                        text = "Δ" + text[3:]
                    if any(text.endswith(char) for char in "vhdugVHDUG"):
                        text = text[:-1] + "<sub>" + text[-1].lower(
                        ) + "</sub>"

                    label = QLabel(text)
                    label.setAlignment(Qt.AlignCenter)
                    self.freq_table.setCellWidget(row, 1, label)

                intensity = QTableWidgetItem()
                if mode.intensity is not None:
                    intensity.setData(Qt.DisplayRole, round(mode.intensity, 2))
                self.freq_table.setItem(row, 2, intensity)

                forcek = QTableWidgetItem()
                if mode.forcek is not None:
                    forcek.setData(Qt.DisplayRole, round(mode.forcek, 2))
                self.freq_table.setItem(row, 3, forcek)

            if fr.other["frequency"].anharm_data:
                self.fundamental_table.setVisible(True)
                self.combo_table.setVisible(True)

                freq = fr.other["frequency"]
                self.tabs.setTabEnabled(2, True)
                anharm_data = sorted(
                    freq.anharm_data,
                    key=lambda x: x.harmonic_frequency,
                )

                for i, mode in enumerate(anharm_data):
                    row = self.fundamental_table.rowCount()
                    self.fundamental_table.insertRow(row)

                    fund = FreqTableWidgetItem()
                    fund.setData(
                        Qt.DisplayRole,
                        "%.2f%s" % (abs(mode.frequency),
                                    "i" if mode.frequency < 0 else ""))
                    fund.setData(Qt.UserRole, i)
                    self.fundamental_table.setItem(row, 0, fund)

                    delta_anh = QTableWidgetItem()
                    delta_anh.setData(Qt.DisplayRole, round(mode.delta_anh, 2))
                    self.fundamental_table.setItem(row, 1, delta_anh)

                    intensity = QTableWidgetItem()
                    if mode.intensity is not None:
                        intensity.setData(Qt.DisplayRole,
                                          round(mode.intensity, 2))
                    self.fundamental_table.setItem(row, 2, intensity)

                    for overtone in mode.overtones:
                        row = self.combo_table.rowCount()
                        self.combo_table.insertRow(row)

                        fund = FreqTableWidgetItem()
                        fund.setData(
                            Qt.DisplayRole,
                            "%.2f%s" % (abs(mode.frequency),
                                        "i" if mode.frequency < 0 else ""))
                        fund.setData(Qt.UserRole, i)
                        self.combo_table.setItem(row, 0, fund)

                        fund = FreqTableWidgetItem()
                        fund.setData(Qt.UserRole, i)
                        self.combo_table.setItem(row, 1, fund)

                        ot = FreqTableWidgetItem()
                        ot.setData(
                            Qt.DisplayRole,
                            "%.2f%s" % (abs(overtone.frequency),
                                        "i" if overtone.frequency < 0 else ""))
                        ot.setData(Qt.UserRole, i)
                        self.combo_table.setItem(row, 2, ot)

                        intensity = QTableWidgetItem()
                        if overtone.intensity is not None:
                            intensity.setData(Qt.DisplayRole,
                                              round(overtone.intensity, 2))
                        self.combo_table.setItem(row, 3, intensity)

                    for key in mode.combinations:
                        for combination in mode.combinations[key]:
                            row = self.combo_table.rowCount()
                            self.combo_table.insertRow(row)

                            fund = FreqTableWidgetItem()
                            fund.setData(
                                Qt.DisplayRole,
                                "%.2f%s" % (abs(mode.frequency),
                                            "i" if mode.frequency < 0 else ""))
                            fund.setData(Qt.UserRole, i)
                            self.combo_table.setItem(row, 0, fund)

                            other_freq = freq.anharm_data[key].frequency
                            fund = FreqTableWidgetItem()
                            fund.setData(
                                Qt.DisplayRole,
                                "%.2f%s" % (abs(other_freq),
                                            "i" if other_freq < 0 else ""))
                            fund.setData(Qt.UserRole,
                                         i + len(freq.anharm_data) * key)
                            self.combo_table.setItem(row, 1, fund)

                            combo = FreqTableWidgetItem()
                            combo.setData(
                                Qt.DisplayRole, "%.2f%s" %
                                (abs(combination.frequency),
                                 "i" if combination.frequency < 0 else ""))
                            combo.setData(Qt.UserRole, i)
                            self.combo_table.setItem(row, 2, combo)

                            intensity = QTableWidgetItem()
                            if combination.intensity is not None:
                                intensity.setData(
                                    Qt.DisplayRole,
                                    round(combination.intensity, 2))
                            self.combo_table.setItem(row, 3, intensity)

            else:
                self.fundamental_table.setVisible(False)
                self.combo_table.setVisible(False)
                self.tabs.setTabEnabled(2, False)

        else:
            self.fundamental_table.setVisible(False)
            self.combo_table.setVisible(False)

            self.tabs.setTabEnabled(1, False)
            self.tabs.setTabEnabled(2, False)

        self.table.resizeColumnToContents(0)
        self.table.resizeColumnToContents(1)

        self.freq_table.resizeColumnToContents(0)
        self.freq_table.resizeColumnToContents(1)
        self.freq_table.resizeColumnToContents(2)

        self.apply_filter()

    def apply_filter(self, text=None):
        if text is None:
            text = self.filter.text()

        if text:
            text = text.replace("(", "\(")
            text = text.replace(")", "\)")
            m = QRegularExpression(text)
            m.setPatternOptions(QRegularExpression.CaseInsensitiveOption)
            if m.isValid():
                m.optimize()
                filter = lambda row_num: m.match(
                    self.table.item(row_num, 0).text()
                    if self.table.item(row_num, 0) is not None else self.table.
                    cellWidget(row_num, 0).text().replace("<sub>", "").replace(
                        "</sub>", "")).hasMatch()
            else:
                return

        else:
            filter = lambda row: True

        for i in range(0, self.table.rowCount()):
            self.table.setRowHidden(i, not filter(i))

    def delete(self):
        self.file_selector.deleteLater()

        return super().delete()

    def close(self):
        self.file_selector.deleteLater()

        return super().close()
コード例 #4
0
class SearchableMenu(Menu, object):
    """
    Extends standard QMenu to make it searchable. First action is a QLineEdit used to recursively search on all actions
    """
    def __init__(self, **kwargs):
        super(SearchableMenu, self).__init__(**kwargs)

        self._search_action = None
        self._search_edit = None

        self.setObjectName(kwargs.get('objectName'))
        self.setTitle(kwargs.get('title'))
        self._init_search_edit()

    def clear(self):
        """
        Extends QMenu clear function
        """

        super(SearchableMenu, self).clear()

        self._init_search_edit()

    def set_search_visible(self, flag):
        """
        Sets the visibility of the search edit
        :param flag: bool
        """

        self._search_action.setVisible(flag)

    def search_visible(self):
        """
        Returns whether or not search edit is visible
        :return: bool
        """

        return self._search_action.isVisible()

    def update_search(self, search_string=None):
        """
        Search all actions for a string tag
        :param str search_string: tag names separated by spaces (for example, "elem1 elem2"
        :return: str
        """
        def _recursive_search(menu, search_str):
            for action in menu.actions():
                sub_menu = action.menu()
                if sub_menu:
                    _recursive_search(sub_menu, search_str)
                    continue
                elif action.isSeparator():
                    continue
                elif isinstance(action, SearchableTaggedAction
                                ) and not action.has_tag(search_str):
                    action.setVisible(False)

            menu_vis = any(action.isVisible() for action in menu.actions())
            menu.menuAction().setVisible(menu_vis)

        def _recursive_search_by_tags(menu, tags):
            for action in menu.actions():
                sub_menu = action.menu()
                if sub_menu:
                    _recursive_search_by_tags(sub_menu, tags)
                    continue
                elif action.isSeparator():
                    continue
                elif isinstance(action, SearchableTaggedAction):
                    action.setVisible(action.has_any_tag(tags))

            menu_vis = any(action.isVisible() for action in menu.actions())
            menu.menuAction().setVisible(menu_vis)

        search_str = search_string or ''
        split = search_str.split()
        if not split:
            qtutils.recursively_set_menu_actions_visibility(menu=self,
                                                            state=True)
            return
        elif len(split) > 1:
            _recursive_search_by_tags(menu=self, tags=split)
            return

        _recursive_search(menu=self, search_str=split[0])

    def _init_search_edit(self):
        """
        Internal function that adds a QLineEdit as the first action in the menu
        """

        self._search_action = QWidgetAction(self)
        self._search_action.setObjectName('SearchAction')
        self._search_edit = QLineEdit(self)
        self._search_edit.setPlaceholderText('Search ...')
        self._search_edit.textChanged.connect(self._on_update_search)
        self._search_action.setDefaultWidget(self._search_edit)
        self.addAction(self._search_action)
        self.addSeparator()

    def _on_update_search(self, search_string):
        """
        Internal callback function that is called when the user interacts with the search line edit
        """

        self.update_search(search_string)
コード例 #5
0
ファイル: libadd.py プロジェクト: QChASM/SEQCROW
class LibAdd(ToolInstance):

    help = "https://github.com/QChASM/SEQCROW/wiki/Add-to-Personal-Library-Tool"
    SESSION_ENDURING = False
    SESSION_SAVE = False

    def __init__(self, session, name):
        super().__init__(session, name)

        from chimerax.ui import MainToolWindow
        self.tool_window = MainToolWindow(self)

        self.key_atomspec = []

        self._build_ui()

    def _build_ui(self):
        layout = QGridLayout()

        library_tabs = QTabWidget()

        #ligand tab
        ligand_tab = QWidget()
        ligand_layout = QFormLayout(ligand_tab)

        self.ligand_name = QLineEdit()
        self.ligand_name.setText("")
        self.ligand_name.setPlaceholderText("leave blank to preview")
        self.ligand_name.setToolTip(
            "name of ligand you are adding to your ligand library\nleave blank to open a new model with just the ligand"
        )
        ligand_layout.addRow("ligand name:", self.ligand_name)

        ligand_key_atoms = QPushButton("set key atoms to current selection")
        ligand_key_atoms.clicked.connect(self.update_key_atoms)
        ligand_key_atoms.setToolTip(
            "the current selection will be the key atoms for the ligand\nleave blank to automatically determine key atoms"
        )
        ligand_layout.addRow(ligand_key_atoms)

        libadd_ligand = QPushButton("add current selection to library")
        libadd_ligand.clicked.connect(self.libadd_ligand)
        ligand_layout.addRow(libadd_ligand)

        #substituent tab
        sub_tab = QWidget()
        sub_layout = QFormLayout(sub_tab)

        self.sub_name = QLineEdit()
        self.sub_name.setText("")
        self.sub_name.setPlaceholderText("leave blank to preview")
        self.sub_name.setToolTip(
            "name of substituent you are adding to your substituent library\nleave blank to open a new model with just the substituent"
        )
        sub_layout.addRow("substituent name:", self.sub_name)

        self.sub_confs = QSpinBox()
        self.sub_confs.setMinimum(1)
        sub_layout.addRow("number of conformers:", self.sub_confs)

        self.sub_angle = QSpinBox()
        self.sub_angle.setRange(0, 180)
        self.sub_angle.setSingleStep(30)
        sub_layout.addRow("angle between conformers:", self.sub_angle)

        libadd_sub = QPushButton("add current selection to library")
        libadd_sub.clicked.connect(self.libadd_substituent)
        sub_layout.addRow(libadd_sub)

        #ring tab
        ring_tab = QWidget()
        ring_layout = QFormLayout(ring_tab)

        self.ring_name = QLineEdit()
        self.ring_name.setText("")
        self.ring_name.setPlaceholderText("leave blank to preview")
        self.ring_name.setToolTip(
            "name of ring you are adding to your ring library\nleave blank to open a new model with just the ring"
        )
        ring_layout.addRow("ring name:", self.ring_name)

        libadd_ring = QPushButton("add ring with selected walk to library")
        libadd_ring.clicked.connect(self.libadd_ring)
        ring_layout.addRow(libadd_ring)

        library_tabs.addTab(sub_tab, "substituent")
        library_tabs.addTab(ring_tab, "ring")
        library_tabs.addTab(ligand_tab, "ligand")
        self.library_tabs = library_tabs

        layout.addWidget(library_tabs)

        whats_this = QLabel()
        whats_this.setText(
            "<a href=\"req\" style=\"text-decoration: none;\">what's this?</a>"
        )
        whats_this.setTextFormat(Qt.RichText)
        whats_this.setTextInteractionFlags(Qt.TextBrowserInteraction)
        whats_this.linkActivated.connect(self.open_link)
        whats_this.setToolTip(
            "click for more information about AaronTools libraries")
        layout.addWidget(whats_this)

        self.tool_window.ui_area.setLayout(layout)

        self.tool_window.manage(None)

    def update_key_atoms(self):
        selection = selected_atoms(self.session)
        if not selection.single_structure:
            raise RuntimeError("selected atoms must be on the same model")

        else:
            self.key_atomspec = selection

        self.tool_window.status("key atoms set to %s" %
                                " ".join(atom.atomspec for atom in selection))

    def libadd_ligand(self):
        """add ligand to library or open it in a new model"""
        selection = selected_atoms(self.session)

        if not selection.single_structure:
            raise RuntimeError("selected atoms must be on the same model")

        rescol = ResidueCollection(selection[0].structure)
        ligand_atoms = [
            atom for atom in rescol.atoms if atom.chix_atom in selection
        ]

        key_chix_atoms = [
            atom for atom in self.key_atomspec if not atom.deleted
        ]
        if len(key_chix_atoms) < 1:
            key_atoms = set([])
            for atom in ligand_atoms:
                for atom2 in atom.connected:
                    if atom2 not in ligand_atoms:
                        key_atoms.add(atom)

        else:
            key_atoms = rescol.find(
                [AtomSpec(atom.atomspec) for atom in key_chix_atoms])

        if len(key_atoms) < 1:
            raise RuntimeError("no key atoms could be determined")

        lig_name = self.ligand_name.text()
        ligand = Component(ligand_atoms, name=lig_name, key_atoms=key_atoms)
        ligand.comment = "K:%s" % ",".join(
            [str(ligand.atoms.index(atom) + 1) for atom in key_atoms])

        if len(lig_name) == 0:
            chimerax_ligand = ResidueCollection(ligand).get_chimera(
                self.session)
            chimerax_ligand.name = "ligand preview"
            self.session.models.add([chimerax_ligand])
            bild_obj = key_atom_highlight(ligand, [0.2, 0.5, 0.8, 0.5],
                                          self.session)
            self.session.models.add(bild_obj, parent=chimerax_ligand)

        else:
            check_aaronlib_dir()
            filename = os.path.join(AARONLIB, "Ligands", lig_name + ".xyz")
            if os.path.exists(filename):
                exists_warning = QMessageBox()
                exists_warning.setIcon(QMessageBox.Warning)
                exists_warning.setText(
                    "%s already exists.\nWould you like to overwrite?" %
                    filename)
                exists_warning.setStandardButtons(QMessageBox.Yes
                                                  | QMessageBox.No)

                rv = exists_warning.exec_()
                if rv == QMessageBox.Yes:
                    ligand.write(outfile=filename)
                    self.tool_window.status("%s added to ligand library" %
                                            lig_name)

                else:
                    self.tool_window.status(
                        "%s has not been added to ligand library" % lig_name)

            else:
                ligand.write(outfile=filename)
                self.tool_window.status("%s added to ligand library" %
                                        lig_name)

    def libadd_ring(self):
        """add ring to library or open it in a new model"""
        selection = self.session.seqcrow_ordered_selection_manager.selection

        if not selection.single_structure:
            raise RuntimeError("selected atoms must be on the same model")

        rescol = ResidueCollection(selection[0].structure)
        walk_atoms = rescol.find(
            [AtomSpec(atom.atomspec) for atom in selection])

        if len(walk_atoms) < 1:
            raise RuntimeError("no walk direction could be determined")

        ring_name = self.ring_name.text()
        ring = Ring(rescol, name=ring_name, end=walk_atoms)
        ring.comment = "E:%s" % ",".join(
            [str(rescol.atoms.index(atom) + 1) for atom in walk_atoms])

        if len(ring_name) == 0:
            chimerax_ring = ResidueCollection(ring).get_chimera(self.session)
            chimerax_ring.name = "ring preview"
            self.session.models.add([chimerax_ring])
            bild_obj = show_walk_highlight(ring, chimerax_ring,
                                           [0.9, 0.4, 0.3, 0.9], self.session)
            self.session.models.add(bild_obj, parent=chimerax_ring)

        else:
            check_aaronlib_dir()
            filename = os.path.join(AARONLIB, "Rings", ring_name + ".xyz")
            if os.path.exists(filename):
                exists_warning = QMessageBox()
                exists_warning.setIcon(QMessageBox.Warning)
                exists_warning.setText(
                    "%s already exists.\nWould you like to overwrite?" %
                    filename)
                exists_warning.setStandardButtons(QMessageBox.Yes
                                                  | QMessageBox.No)

                rv = exists_warning.exec_()
                if rv == QMessageBox.Yes:
                    ring.write(outfile=filename)
                    self.tool_window.status("%s added to ring library" %
                                            ring_name)

                else:
                    self.tool_window.status(
                        "%s has not been added to ring library" % ring_name)

            else:
                ring.write(outfile=filename)
                self.tool_window.status("%s added to ring library" % ring_name)

    def libadd_substituent(self):
        """add ligand to library or open it in a new model"""
        selection = selected_atoms(self.session)

        if not selection.single_structure:
            raise RuntimeError("selected atoms must be on the same model")

        residues = []
        for atom in selection:
            if atom.residue not in residues:
                residues.append(atom.residue)

        rescol = ResidueCollection(selection[0].structure,
                                   convert_residues=residues)

        substituent_atoms = [
            atom for atom in rescol.atoms if atom.chix_atom in selection
        ]

        start = None
        avoid = None
        for atom in substituent_atoms:
            for atom2 in atom.connected:
                if atom2 not in substituent_atoms:
                    if start is None:
                        start = atom
                        avoid = atom2
                    else:
                        raise RuntimeError(
                            "substituent must only have one connection to the molecule"
                        )

        if start is None:
            raise RuntimeError(
                "substituent is not connected to a larger molecule")

        substituent_atoms.remove(start)
        substituent_atoms.insert(0, start)

        sub_name = self.sub_name.text()
        confs = self.sub_confs.value()
        angle = self.sub_angle.value()

        comment = "CF:%i,%i" % (confs, angle)
        if len(sub_name) == 0:
            sub = Substituent(substituent_atoms,
                              name="test",
                              conf_num=confs,
                              conf_angle=angle)
        else:
            sub = Substituent(substituent_atoms,
                              name=sub_name,
                              conf_num=confs,
                              conf_angle=angle)

        sub.comment = comment

        #align substituent bond to x axis
        sub.coord_shift(-avoid.coords)
        x_axis = np.array([1., 0., 0.])
        n = np.linalg.norm(start.coords)
        vb = start.coords / n
        d = np.linalg.norm(vb - x_axis)
        theta = np.arccos((d**2 - 2) / -2)
        vx = np.cross(vb, x_axis)
        sub.rotate(vx, theta)

        add = False
        if len(sub_name) == 0:
            chimerax_sub = ResidueCollection(sub).get_chimera(self.session)
            chimerax_sub.name = "substituent preview"
            self.session.models.add([chimerax_sub])
            bild_obj = ghost_connection_highlight(
                sub, [0.60784, 0.145098, 0.70196, 0.5], self.session)
            self.session.models.add(bild_obj, parent=chimerax_sub)

        else:
            check_aaronlib_dir()
            filename = os.path.join(AARONLIB, "Subs", sub_name + ".xyz")
            if os.path.exists(filename):
                exists_warning = QMessageBox()
                exists_warning.setIcon(QMessageBox.Warning)
                exists_warning.setText(
                    "%s already exists.\nWould you like to overwrite?" %
                    filename)
                exists_warning.setStandardButtons(QMessageBox.Yes
                                                  | QMessageBox.No)

                rv = exists_warning.exec_()
                if rv == QMessageBox.Yes:
                    add = True

                else:
                    self.tool_window.status(
                        "%s has not been added to substituent library" %
                        sub_name)

            else:
                add = True

        if add:
            sub.write(outfile=filename)
            self.tool_window.status("%s added to substituent library" %
                                    sub_name)
            register_selectors(self.session.logger, sub_name)
            if self.session.ui.is_gui:
                if (sub_name not in ELEMENTS and sub_name[0].isalpha()
                        and (len(sub_name) > 1
                             and not any(not (c.isalnum() or c in "+-")
                                         for c in sub_name[1:]))):
                    add_submenu = self.session.ui.main_window.add_select_submenu
                    add_selector = self.session.ui.main_window.add_menu_selector
                    substituent_menu = add_submenu(['Che&mistry'],
                                                   'Substituents')
                    add_selector(substituent_menu, sub_name, sub_name)

    def display_help(self):
        """Show the help for this tool in the help viewer."""
        from chimerax.core.commands import run
        run(self.session,
            'open %s' % self.help if self.help is not None else "")

    def open_link(self, *args):
        if self.library_tabs.currentIndex() == 0:
            link = "https://github.com/QChASM/AaronTools.py/wiki/AaronTools-Libraries#substituents"
        elif self.library_tabs.currentIndex() == 1:
            link = "https://github.com/QChASM/AaronTools.py/wiki/AaronTools-Libraries#rings"
        elif self.library_tabs.currentIndex() == 2:
            link = "https://github.com/QChASM/AaronTools.py/wiki/AaronTools-Libraries#ligands"

        run(self.session, "open %s" % link)
コード例 #6
0
ファイル: structure_editing.py プロジェクト: QChASM/SEQCROW
class EditStructure(ToolInstance):

    help = "https://github.com/QChASM/SEQCROW/wiki/Structure-Modification-Tool"
    SESSION_ENDURING = False
    SESSION_SAVE = False

    def __init__(self, session, name):       
        super().__init__(session, name)
        
        self.settings = _EditStructureSettings(session, "Structure Modification")
        
        self.tool_window = MainToolWindow(self)        

        self.close_previous_bool = self.settings.modify

        self._build_ui()

    def _build_ui(self):
        layout = QGridLayout()
        
        self.alchemy_tabs = QTabWidget()
        
        #substitute
        substitute_tab = QWidget()
        substitute_layout = QGridLayout(substitute_tab) 

        sublabel = QLabel("substituent name:")
        substitute_layout.addWidget(sublabel, 0, 0, Qt.AlignVCenter)
        
        self.subname = QLineEdit()
        # self.subname.setText("Et")
        sub_completer = NameCompleter(Substituent.list(), self.subname)
        self.subname.setCompleter(sub_completer)
        self.subname.setToolTip("name of substituent in the AaronTools library or your personal library\nseparate names with commas and uncheck 'modify selected structure' to create several structures")
        substitute_layout.addWidget(self.subname, 0, 1, Qt.AlignVCenter)
        
        open_sub_lib = QPushButton("from library...")
        open_sub_lib.clicked.connect(self.open_sub_selector)
        substitute_layout.addWidget(open_sub_lib, 0, 2, Qt.AlignTop)        
        
        substitute_layout.addWidget(QLabel("modify selected structure:"), 1, 0, 1, 1, Qt.AlignVCenter)
        
        self.close_previous_sub = QCheckBox()
        self.close_previous_sub.setToolTip("checked: selected structure will be modified\nunchecked: new model will be created for the modified structure")
        self.close_previous_sub.setChecked(self.settings.modify)
        self.close_previous_sub.stateChanged.connect(self.close_previous_change)
        substitute_layout.addWidget(self.close_previous_sub, 1, 1, 1, 2, Qt.AlignTop)    
        
        substitute_layout.addWidget(QLabel("relax substituent:"), 2, 0, 1, 1, Qt.AlignVCenter)
        
        self.minimize = QCheckBox()
        self.minimize.setToolTip("spin the added substituents to try to minimize the LJ potential energy")
        self.minimize.setChecked(self.settings.minimize)
        substitute_layout.addWidget(self.minimize, 2, 1, 1, 1, Qt.AlignTop)
        
        substitute_layout.addWidget(QLabel("guess previous substituent:"), 3, 0, 1, 1, Qt.AlignVCenter)
        
        self.guess_old = QCheckBox()
        self.guess_old.setToolTip("checked: leave the longest connected fragment in the residue\nunchecked: previous substituent must be selected")
        self.guess_old.setChecked(self.settings.guess)
        self.guess_old.stateChanged.connect(lambda state, settings=self.settings: settings.__setattr__("guess", True if state == Qt.Checked else False))
        substitute_layout.addWidget(self.guess_old, 3, 1, 1, 2, Qt.AlignTop)
        
        substitute_layout.addWidget(QLabel("new residue:"), 5, 0, 1, 1, Qt.AlignVCenter)

        self.new_residue = QCheckBox()
        self.new_residue.setToolTip("put the new substituent in its own residue instead\nof adding it to the residue of the old substituent")
        self.new_residue.setChecked(self.settings.new_residue)
        self.new_residue.stateChanged.connect(lambda state, settings=self.settings: settings.__setattr__("new_residue", True if state == Qt.Checked else False))
        substitute_layout.addWidget(self.new_residue, 5, 1, 1, 2, Qt.AlignTop)
        
        substitute_layout.addWidget(QLabel("use distance names:"), 4, 0, 1, 1, Qt.AlignVCenter)
        
        self.use_greek = QCheckBox()
        self.use_greek.setChecked(self.settings.use_greek)
        self.use_greek.setToolTip("indicate distance from point of attachment with atom name")
        substitute_layout.addWidget(self.use_greek, 4, 1, 1, 1, Qt.AlignTop)

        substitute_layout.addWidget(QLabel("change residue name:"), 6, 0, 1, 1, Qt.AlignVCenter)
        
        self.new_sub_name = QLineEdit()
        self.new_sub_name.setToolTip("change name of modified residues")
        self.new_sub_name.setPlaceholderText("leave blank to keep current")
        substitute_layout.addWidget(self.new_sub_name, 6, 1, 1, 2, Qt.AlignTop)

        substitute_button = QPushButton("substitute current selection")
        substitute_button.clicked.connect(self.do_substitute)
        substitute_layout.addWidget(substitute_button, 7, 0, 1, 3, Qt.AlignTop)
        self.substitute_button = substitute_button
        
        substitute_layout.setRowStretch(0, 0)
        substitute_layout.setRowStretch(1, 0)
        substitute_layout.setRowStretch(2, 0)
        substitute_layout.setRowStretch(3, 0)
        substitute_layout.setRowStretch(4, 0)
        substitute_layout.setRowStretch(5, 0)
        substitute_layout.setRowStretch(6, 0)
        substitute_layout.setRowStretch(7, 1)
        
        
        #map ligand
        maplig_tab = QWidget()
        maplig_layout = QGridLayout(maplig_tab)
        
        liglabel = QLabel("ligand name:")
        maplig_layout.addWidget(liglabel, 0, 0, Qt.AlignVCenter)
        
        self.ligname = QLineEdit()
        lig_completer = NameCompleter(Component.list(), self.ligname)
        self.ligname.setCompleter(lig_completer)
        self.ligname.setToolTip("name of ligand in the AaronTools library or your personal library\nseparate names with commas and uncheck 'modify selected structure' to create several structures")
        maplig_layout.addWidget(self.ligname, 0, 1, Qt.AlignVCenter)
        
        open_lig_lib = QPushButton("from library...")
        open_lig_lib.clicked.connect(self.open_lig_selector)
        maplig_layout.addWidget(open_lig_lib, 0, 2, Qt.AlignTop)        
        
        maplig_layout.addWidget(QLabel("modify selected structure:"), 1, 0, 1, 1, Qt.AlignVCenter)
        
        self.close_previous_lig = QCheckBox()
        self.close_previous_lig.setToolTip("checked: selected structure will be modified\nunchecked: new model will be created for the modified structure")
        self.close_previous_lig.setChecked(self.settings.modify)
        self.close_previous_lig.stateChanged.connect(self.close_previous_change)
        maplig_layout.addWidget(self.close_previous_lig, 1, 1, 1, 2, Qt.AlignTop)

        maplig_button = QPushButton("swap ligand with selected coordinating atoms")
        maplig_button.clicked.connect(self.do_maplig)
        maplig_layout.addWidget(maplig_button, 2, 0, 1, 3, Qt.AlignTop)
        self.maplig_button = maplig_button

        start_structure_button = QPushButton("place in:")
        self.lig_model_selector = ModelComboBox(self.session, addNew=True)
        start_structure_button.clicked.connect(self.do_new_lig)
        maplig_layout.addWidget(start_structure_button, 3, 0, 1, 1, Qt.AlignTop)
        maplig_layout.addWidget(self.lig_model_selector, 3, 1, 1, 2, Qt.AlignTop)

        maplig_layout.setRowStretch(0, 0)
        maplig_layout.setRowStretch(1, 0)
        maplig_layout.setRowStretch(2, 0)
        maplig_layout.setRowStretch(3, 1)
        
        
        #close ring
        closering_tab = QWidget()
        closering_layout = QGridLayout(closering_tab)
        
        ringlabel = QLabel("ring name:")
        closering_layout.addWidget(ringlabel, 0, 0, Qt.AlignVCenter)
        
        self.ringname = QLineEdit()
        ring_completer = NameCompleter(Ring.list(), self.ringname)
        self.ringname.setCompleter(ring_completer)
        self.ringname.setToolTip("name of ring in the AaronTools library or your personal library\nseparate names with commas and uncheck 'modify selected structure' to create several structures")
        closering_layout.addWidget(self.ringname, 0, 1, Qt.AlignVCenter)
        
        open_ring_lib = QPushButton("from library...")
        open_ring_lib.clicked.connect(self.open_ring_selector)
        closering_layout.addWidget(open_ring_lib, 0, 2, Qt.AlignTop)        
        
        closering_layout.addWidget(QLabel("modify selected structure:"), 1, 0, 1, 1, Qt.AlignVCenter) 
        
        self.close_previous_ring = QCheckBox()
        self.close_previous_ring.setToolTip("checked: selected structure will be modified\nunchecked: new model will be created for the modified structure")
        self.close_previous_ring.setChecked(self.settings.modify)
        self.close_previous_ring.stateChanged.connect(self.close_previous_change)
        closering_layout.addWidget(self.close_previous_ring, 1, 1, 1, 2, Qt.AlignTop)

        closering_layout.addWidget(QLabel("try multiple:"), 2, 0, 1, 1, Qt.AlignVCenter)

        self.minimize_ring = QCheckBox()
        self.minimize_ring.setToolTip("try to use other versions of this ring in the library to find the one that fits best")
        self.minimize_ring.setChecked(self.settings.minimize_ring)
        closering_layout.addWidget(self.minimize_ring, 2, 1, 1, 2, Qt.AlignTop)

        closering_layout.addWidget(QLabel("new residue name:"), 3, 0, 1, 1, Qt.AlignVCenter)
        
        self.new_ring_name = QLineEdit()
        self.new_ring_name.setToolTip("change name of modified residues")
        self.new_ring_name.setPlaceholderText("leave blank to keep current")
        closering_layout.addWidget(self.new_ring_name, 3, 1, 1, 2, Qt.AlignTop)

        closering_button = QPushButton("put a ring on current selection")
        closering_button.clicked.connect(self.do_fusering)

        closering_layout.addWidget(closering_button, 4, 0, 1, 3, Qt.AlignTop)
        self.closering_button = closering_button

        start_structure_button = QPushButton("place in:")
        self.ring_model_selector = ModelComboBox(self.session, addNew=True)
        start_structure_button.clicked.connect(self.do_new_ring)
        closering_layout.addWidget(start_structure_button, 5, 0, 1, 1, Qt.AlignTop)
        closering_layout.addWidget(self.ring_model_selector, 5, 1, 1, 2, Qt.AlignTop)

        closering_layout.setRowStretch(0, 0)
        closering_layout.setRowStretch(1, 0)
        closering_layout.setRowStretch(2, 0)
        closering_layout.setRowStretch(3, 0)
        closering_layout.setRowStretch(4, 0)
        closering_layout.setRowStretch(5, 1)


        #change element
        changeelement_tab = QWidget()
        changeelement_layout = QFormLayout(changeelement_tab)
        
        self.element = ElementButton("C", single_state=True)
        self.element.clicked.connect(self.open_ptable)
        changeelement_layout.addRow("element:", self.element)
        
        self.vsepr = QComboBox()
        self.vsepr.addItems([
            "do not change",                  # 0
            
            "linear (1 bond)",                # 1
            
            "linear (2 bonds)",               # 2 
            "trigonal planar (2 bonds)",      # 3
            "tetrahedral (2 bonds)",          # 4 
            
            "trigonal planar",                # 5
            "tetrahedral (3 bonds)",          # 6
            "T-shaped",                       # 7
            
            "trigonal pyramidal",             # 8
            "tetrahedral",                    # 9
            "sawhorse",                       #10
            "seesaw",                         #11
            "square planar",                  #12
            
            "trigonal bipyramidal",           #13
            "square pyramidal",               #14
            "pentagonal",                     #15
            
            "octahedral",                     #16
            "hexagonal",                      #17
            "trigonal prismatic",             #18
            "pentagonal pyramidal",           #19
            
            "capped octahedral",              #20
            "capped trigonal prismatic",      #21
            "heptagonal",                     #22
            "hexagonal pyramidal",            #23
            "pentagonal bipyramidal",         #24
            
            "biaugmented trigonal prismatic", #25
            "cubic",                          #26
            "elongated trigonal bipyramidal", #27
            "hexagonal bipyramidal",          #28
            "heptagonal pyramidal",           #29
            "octagonal",                      #30
            "square antiprismatic",           #31
            "trigonal dodecahedral",          #32
            
            "capped cube",                    #33
            "capped square antiprismatic",    #34
            "enneagonal",                     #35
            "heptagonal bipyramidal",         #36
            "hula-hoop",                      #37
            "triangular cupola",              #38
            "tridiminished icosahedral",      #39
            "muffin",                         #40
            "octagonal pyramidal",            #41
            "tricapped trigonal prismatic",   #42
        ])
        
        self.vsepr.setCurrentIndex(9)
        
        self.vsepr.insertSeparator(33)
        self.vsepr.insertSeparator(25)
        self.vsepr.insertSeparator(20)
        self.vsepr.insertSeparator(16)
        self.vsepr.insertSeparator(13)
        self.vsepr.insertSeparator(8)
        self.vsepr.insertSeparator(5)
        self.vsepr.insertSeparator(2)
        self.vsepr.insertSeparator(1)
        self.vsepr.insertSeparator(0)
        changeelement_layout.addRow("geometry:", self.vsepr)
        
        self.change_bonds = QCheckBox()
        self.change_bonds.setChecked(self.settings.change_bonds)
        changeelement_layout.addRow("adjust bond lengths:", self.change_bonds)
        
        change_element_button = QPushButton("change selected elements")
        change_element_button.clicked.connect(self.do_change_element)
        changeelement_layout.addRow(change_element_button)
        self.change_element_button = change_element_button

        start_structure_button = QPushButton("place in:")
        self.model_selector = ModelComboBox(self.session, addNew=True)
        start_structure_button.clicked.connect(self.do_new_atom)
        changeelement_layout.addRow(start_structure_button, self.model_selector)
        
        delete_atoms_button = QPushButton("delete selected atoms")
        delete_atoms_button.clicked.connect(self.delete_atoms)
        changeelement_layout.addRow(delete_atoms_button)

        self.alchemy_tabs.addTab(substitute_tab, "substitute")
        self.alchemy_tabs.addTab(maplig_tab, "swap ligand")
        self.alchemy_tabs.addTab(closering_tab, "fuse ring")
        self.alchemy_tabs.addTab(changeelement_tab, "change element")

        layout.addWidget(self.alchemy_tabs)

        self.tool_window.ui_area.setLayout(layout)

        self.tool_window.manage(None)
    
    def close_previous_change(self, state):
        if state == Qt.Checked:
            self.settings.modify = True
            for checkbox in [self.close_previous_lig, self.close_previous_sub, self.close_previous_ring]:
                checkbox.setChecked(True)
            self.close_previous_bool = True
        else:
            self.settings.modify = False
            for checkbox in [self.close_previous_lig, self.close_previous_sub, self.close_previous_ring]:
                checkbox.setChecked(False)
            self.close_previous_bool = False
    
    def do_substitute(self):
        subnames = self.subname.text()
        
        new_name = self.new_sub_name.text()

        use_attached = not self.guess_old.isChecked()
        
        minimize = self.minimize.isChecked()
        
        new_residue = self.new_residue.isChecked()

        use_greek = self.use_greek.isChecked()
        
        self.settings.minimize = minimize
        self.settings.use_greek = use_greek

        if len(new_name.strip()) > 0:
            run(
                self.session, 
                "substitute sel substituents %s newName %s guessAttachment %s modify %s minimize %s useRemoteness %s newResidue %s" %
                (
                    subnames,
                    new_name,
                    not use_attached,
                    self.close_previous_bool,
                    minimize,
                    use_greek,
                    new_residue,
                )
            )

        else:
            run(
                self.session,
                "substitute sel substituents %s guessAttachment %s modify %s minimize %s useRemoteness %s newResidue %s" %
                (
                    subnames,
                    not use_attached,
                    self.close_previous_bool,
                    minimize,
                    use_greek,
                    new_residue,
                )
            )

    def open_sub_selector(self):
        self.tool_window.create_child_window("select substituents", window_class=SubstituentSelection, textBox=self.subname)

    def do_maplig(self):
        lignames = self.ligname.text()
        selection = selected_atoms(self.session)
        
        if len(selection) < 1:
            raise RuntimeWarning("nothing selected")
        
        models = {}
        for atom in selection:
            if atom.structure not in models:
                models[atom.structure] = [AtomSpec(atom.atomspec)]
            else:
                models[atom.structure].append(AtomSpec(atom.atomspec))        
        
        first_pass = True
        new_structures = []
        for ligname in lignames.split(','):
            ligname = ligname.strip()
            lig = Component(ligname)
            for model in models:
                if self.close_previous_bool and first_pass:
                    rescol = ResidueCollection(model)
                elif self.close_previous_bool and not first_pass:
                    raise RuntimeError("only the first model can be replaced")
                else:
                    model_copy = model.copy()
                    rescol = ResidueCollection(model_copy)
                    for i, atom in enumerate(model.atoms):
                        rescol.atoms[i].atomspec = atom.atomspec
                        rescol.atoms[i].add_tag(atom.atomspec)
                        rescol.atoms[i].chix_atom = atom

                target = rescol.find(models[model])
                if len(target) % len(lig.key_atoms) == 0:
                    k = 0
                    ligands = []
                    while k != len(target):
                        res_lig = ResidueCollection(lig.copy(), comment=lig.comment)
                        res_lig.parse_comment()
                        res_lig = Component(res_lig, key_atoms = ",".join([str(k + 1) for k in res_lig.other["key_atoms"]]))
                        ligands.append(res_lig)
                        k += len(lig.key_atoms)
                else:
                    raise RuntimeError("number of key atoms no not match: %i now, new ligand has %i" % (len(target), len(lig.key_atoms)))
                
                rescol.map_ligand(ligands, target)

                for center_atom in rescol.center:
                    center_atom.connected = set([])
                    for atom in rescol.atoms:
                        if atom not in rescol.center:
                            if center_atom.is_connected(atom):
                                atom.connected.add(center_atom)
                                center_atom.connected.add(atom)
                
                if self.close_previous_bool:    
                    rescol.update_chix(model)
                else:
                    struc = rescol.get_chimera(self.session)
                    new_structures.append(struc)
            
            first_pass = False
        
        if not self.close_previous_bool:
            self.session.models.add(new_structures)

    def open_lig_selector(self):
        self.tool_window.create_child_window("select ligands", window_class=LigandSelection, textBox=self.ligname)
    
    def do_fusering(self):
        ring_names = self.ringname.text()
        
        new_name = self.new_ring_name.text()

        minimize_ring = self.minimize_ring.isChecked()
        self.settings.minimize_ring = minimize_ring

        if len(new_name.strip()) > 0:
            run(
                self.session,
                "fuseRing sel rings %s newName %s modify %s minimize %s" % (
                    ring_names,
                    new_name,
                    self.close_previous_bool,
                    minimize_ring,
                )
            )

        else:
            run(
                self.session,
                "fuseRing sel rings %s modify %s minimize %s" % (
                    ring_names,
                    self.close_previous_bool,
                    minimize_ring,
                )
            )

    def open_ring_selector(self):
        self.tool_window.create_child_window("select rings", window_class=RingSelection, textBox=self.ringname)

    def open_ptable(self):
        self.tool_window.create_child_window("select element", window_class=_PTable, button=self.element)
    
    def display_help(self):
        """Show the help for this tool in the help viewer."""
        from chimerax.core.commands import run
        run(self.session,
            'open %s' % self.help if self.help is not None else "")
    
    def do_change_element(self):
        element = self.element.text()
        adjust_bonds = self.change_bonds.isChecked()
        self.settings.change_bonds = adjust_bonds
        vsepr = self.vsepr.currentText()
        
        if vsepr == "do not change":
            vsepr = False
        elif vsepr == "linear (1 bond)":
            vsepr = "linear 1"
            goal = 1
        elif vsepr == "linear (2 bonds)":
            vsepr = "linear 2"
            goal = 2
        elif vsepr == "trigonal planar (2 bonds)":
            vsepr = "bent 2 planar"
            goal = 2
        elif vsepr == "tetrahedral (2 bonds)":
            vsepr = "bent 2 tetrahedral"
            goal = 2
        elif vsepr == "trigonal planar":
            goal = 3
        elif vsepr == "tetrahedral (3 bonds)":
            vsepr = "bent 3 tetrahedral"
            goal = 3
        else:
            goal = len(Atom.get_shape(vsepr)) - 1
        
        sel = selected_atoms(self.session)
        models, _ = guessAttachmentTargets(sel, self.session, allow_adjacent=False)
        for model in models:
            conv_res = list(models[model].keys())
            for res in models[model]:
                for target in models[model][res]:
                    for neighbor in target.neighbors:
                        if neighbor.residue not in conv_res:
                            conv_res.append(neighbor.residue)
            
                    for pbg in self.session.models.list(type=PseudobondGroup):
                        for pbond in pbg.pseudobonds:
                            if target in pbond.atoms and all(atom.structure is model for atom in pbond.atoms):
                                other_atom = pbond.other_atom(target)
                                if other_atom.residue not in conv_res:
                                    conv_res.append(other_atom.residue)
            
            rescol = ResidueCollection(model, convert_residues=conv_res)
            for res in models[model]:
                residue = [resi for resi in rescol.residues if resi.chix_residue is res][0]
                
                for target in models[model][res]:
                    targ = rescol.find_exact(AtomSpec(target.atomspec))[0]
                    adjust_hydrogens = vsepr
                    if vsepr is not False:
                        cur_bonds = len(targ.connected)
                        change_Hs = goal - cur_bonds
                        adjust_hydrogens = (change_Hs, vsepr)

                    residue.change_element(targ, 
                                           element, 
                                           adjust_bonds=adjust_bonds, 
                                           adjust_hydrogens=adjust_hydrogens,
                    )
                
                residue.update_chix(res)    
    
    def do_new_ring(self):
        rings = self.ringname.text()
        
        for ring in rings.split(","):
            ring = ring.strip()
            
            rescol = ResidueCollection(Ring(ring))
        
            model = self.ring_model_selector.currentData()
            if model is None:
                chix = rescol.get_chimera(self.session)
                self.session.models.add([chix])
                apply_seqcrow_preset(chix, fallback="Ball-Stick-Endcap")
                self.ring_model_selector.setCurrentIndex(self.ring_model_selector.count()-1)
    
            else:
                res = model.new_residue("new", "a", len(model.residues)+1)
                rescol.residues[0].update_chix(res)
                run(self.session, "select add %s" % " ".join([atom.atomspec for atom in res.atoms]))
    
    def do_new_lig(self):
        ligands = self.ligname.text()
        
        for lig in ligands.split(","):
            lig = lig.strip()
            
            rescol = ResidueCollection(Component(lig))
        
            model = self.lig_model_selector.currentData()
            if model is None:
                chix = rescol.get_chimera(self.session)
                self.session.models.add([chix])
                apply_seqcrow_preset(chix, fallback="Ball-Stick-Endcap")
                self.lig_model_selector.setCurrentIndex(self.lig_model_selector.count()-1)
    
            else:
                res = model.new_residue("new", "a", len(model.residues)+1)
                rescol.residues[0].update_chix(res)
                run(self.session, "select add %s" % " ".join([atom.atomspec for atom in res.atoms]))

    def do_new_atom(self):
        element = self.element.text()
        adjust_bonds = self.change_bonds.isChecked()
        self.settings.change_bonds = adjust_bonds
        vsepr = self.vsepr.currentText()
        
        if vsepr == "do not change":
            vsepr = False
        elif vsepr == "linear (1 bond)":
            vsepr = "linear 1"
        elif vsepr == "linear (2 bonds)":
            vsepr = "linear 2"
        elif vsepr == "trigonal planar (2 bonds)":
            vsepr = "bent 2 planar"
        elif vsepr == "tetrahedral (2 bonds)":
            vsepr = "bent 2 tetrahedral"
        elif vsepr == "tetrahedral (3 bonds)":
            vsepr = "bent 3 tetrahedral"
        
        if vsepr:
            atoms = Atom.get_shape(vsepr)
            atoms[0].element = element
            for atom in atoms[1:]:
                atom.element = "H"
            if adjust_bonds:
                # this works b/c all atoms are 1 angstrom from the center
                # just multiply by the distance we want
                expected_dist = RADII[element] + RADII["H"]
                for atom in atoms[1:]:
                    atom.coords *= expected_dist
            for atom in atoms[1:]:
                atoms[0].connected.add(atom)
                atom.connected.add(atoms[0])
        else:
            atoms = [Atom(element=element, coords=np.zeros(3))]

        rescol = ResidueCollection(atoms, name="new", refresh_connected=False)

        model = self.model_selector.currentData()
        if model is None:
            chix = rescol.get_chimera(self.session)
            self.session.models.add([chix])
            apply_seqcrow_preset(chix, fallback="Ball-Stick-Endcap")
            self.model_selector.setCurrentIndex(self.model_selector.count()-1)

        else:
            res = model.new_residue("new", "a", len(model.residues)+1)
            rescol.residues[0].update_chix(res)
            run(self.session, "select add %s" % " ".join([atom.atomspec for atom in res.atoms]))
    
    def delete_atoms(self, *args):
        atoms = selected_atoms(self.session)
        for atom in atoms:
            atom.delete()
    
    def delete(self):
        self.ring_model_selector.deleteLater()
        self.lig_model_selector.deleteLater()
        self.model_selector.deleteLater()

        return super().delete()    
    
    def close(self):
        self.ring_model_selector.deleteLater()
        self.lig_model_selector.deleteLater()
        self.model_selector.deleteLater()

        return super().close()