Skip to content

Commit c038063

Browse files
committed
Most of the match tool (missing the "create region" option)
1 parent 399b862 commit c038063

File tree

6 files changed

+170
-9
lines changed

6 files changed

+170
-9
lines changed

src/bundles/atomic/src/widgets.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -31,15 +31,15 @@ def __init__(self, session, **kw):
3131

3232
class StructureMenuButton(ModelMenuButton):
3333
def __init__(self, session, **kw):
34-
super().__init__(session, class_filter=Structure, **kw)
34+
super().__init__(session, no_value_button_text="No structure chosen", class_filter=Structure, **kw)
3535

3636
class AtomicStructureListWidget(ModelListWidget):
3737
def __init__(self, session, **kw):
3838
super().__init__(session, class_filter=AtomicStructure, **kw)
3939

4040
class AtomicStructureMenuButton(ModelMenuButton):
4141
def __init__(self, session, **kw):
42-
super().__init__(session, class_filter=AtomicStructure, **kw)
42+
super().__init__(session, no_value_button_text="No structure chosen", class_filter=AtomicStructure, **kw)
4343

4444
class ChainListWidget(ItemListWidget):
4545
def __init__(self, session, *, group_identical=False, **kw):
@@ -136,8 +136,8 @@ def _item_text_func(self, item):
136136
return specs
137137

138138
class ChainMenuButton(ItemMenuButton):
139-
def __init__(self, session, **kw):
140-
super().__init__(**_process_chain_kw(session, **kw))
139+
def __init__(self, session, no_value_button_text="No chain chosen", **kw):
140+
super().__init__(no_value_button_text=no_value_button_text, **_process_chain_kw(session, **kw))
141141

142142
def _process_chain_kw(session, list_func=None, trigger_info=None, **kw):
143143
if list_func is None:
@@ -163,8 +163,8 @@ def __init__(self, session, **kw):
163163
super().__init__(**_process_residue_kw(session, **kw))
164164

165165
class ResidueMenuButton(ItemMenuButton):
166-
def __init__(self, session, **kw):
167-
super().__init__(**_process_residue_kw(session, **kw))
166+
def __init__(self, session, no_value_button_text="No residue chosen", **kw):
167+
super().__init__(no_value_button_text=no_value_button_text, **_process_residue_kw(session, **kw))
168168

169169
def _process_residue_kw(session, list_func=None, trigger_info=None, **kw):
170170
if list_func is None:

src/bundles/log/src/tool.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -244,6 +244,7 @@ def sizeHint(self):
244244
from Qt.QtCore import QSize
245245
return QSize(600, 300)
246246
self.error_dialog = BiggerErrorDialog(parent)
247+
self.error_dialog.finished.connect(lambda *args: print("error dialog finished"))
247248
self._add_report_bug_button()
248249
layout = QGridLayout(parent)
249250
layout.setContentsMargins(0,0,0,0)

src/bundles/seq_view/bundle_info.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
<BundleInfo name="ChimeraX-SeqView" version="2.15"
1+
<BundleInfo name="ChimeraX-SeqView" version="2.16"
22
package="chimerax.seq_view"
33
supercedes="ChimeraX-SEQ-VIEW"
44
minSessionVersion="1" maxSessionVersion="1">

src/bundles/seq_view/src/match.py

Lines changed: 147 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,147 @@
1+
# vim: set expandtab ts=4 sw=4:
2+
3+
# === UCSF ChimeraX Copyright ===
4+
# Copyright 2022 Regents of the University of California. All rights reserved.
5+
# The ChimeraX application is provided pursuant to the ChimeraX license
6+
# agreement, which covers academic and commercial uses. For more details, see
7+
# <https://www.rbvi.ucsf.edu/chimerax/docs/licensing.html>
8+
#
9+
# This particular file is part of the ChimeraX library. You can also
10+
# redistribute and/or modify it under the terms of the GNU Lesser General
11+
# Public License version 2.1 as published by the Free Software Foundation.
12+
# For more details, see
13+
# <https://www.gnu.org/licenses/old-licenses/lgpl-2.1.html>
14+
#
15+
# THIS SOFTWARE IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER
16+
# EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
17+
# OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. ADDITIONAL LIABILITY
18+
# LIMITATIONS ARE DESCRIBED IN THE GNU LESSER GENERAL PUBLIC LICENSE
19+
# VERSION 2.1
20+
#
21+
# This notice must be embedded in or attached to all copies, including partial
22+
# copies, of the software or any revisions or derivations thereof.
23+
# === UCSF ChimeraX Copyright ===
24+
25+
class MatchDialog:
26+
def __init__(self, sv, tool_window):
27+
self.sv = sv
28+
self.tool_window = tool_window
29+
tool_window.help = "help:user/tools/sequenceviewer.html#match"
30+
31+
from Qt.QtWidgets import QHBoxLayout, QVBoxLayout, QLabel, QGridLayout, QLineEdit, QCheckBox
32+
from Qt.QtGui import QDoubleValidator
33+
from Qt.QtCore import Qt
34+
layout = QVBoxLayout()
35+
#layout.setContentsMargins(0,0,0,0)
36+
layout.setSpacing(0)
37+
38+
from chimerax.atomic.widgets import ChainMenuButton, ChainListWidget
39+
chains_layout = QGridLayout()
40+
chains_layout.setSpacing(3)
41+
chains_layout.addWidget(QLabel("Reference chain"), 0, 0, alignment=Qt.AlignCenter)
42+
chains_layout.addWidget(QLabel("Match chain(s)"), 0, 1, alignment=Qt.AlignCenter)
43+
self.ref_chain_menu = ChainMenuButton(sv.session, list_func=sv.alignment.associations.keys)
44+
chains_layout.addWidget(self.ref_chain_menu, 1, 0, alignment=Qt.AlignCenter)
45+
self.match_chain_list = ChainListWidget(sv.session, autoselect=ChainListWidget.AUTOSELECT_FIRST,
46+
list_func=sv.alignment.associations.keys, filter_func=lambda c, menu=self.ref_chain_menu:
47+
c.structure is not getattr(menu.value, 'structure', None))
48+
chains_layout.addWidget(self.match_chain_list, 1, 1)
49+
self.ref_chain_menu.value_changed.connect(self.match_chain_list.refresh)
50+
chains_layout.setRowStretch(1, 1)
51+
layout.addLayout(chains_layout)
52+
53+
cv_layout = QHBoxLayout()
54+
self.conserved_check_box = QCheckBox("Only match residues in columns with at least ")
55+
cv_layout.addWidget(self.conserved_check_box)
56+
self.conservation_value = cv = QLineEdit("80")
57+
cv.setAlignment(Qt.AlignCenter)
58+
cv.setMaximumWidth(4 * cv.fontMetrics().averageCharWidth())
59+
cv.setValidator(QDoubleValidator(0.0, 100.0, -1))
60+
cv_layout.addWidget(cv)
61+
cv_layout.addWidget(QLabel("% identity"), alignment=Qt.AlignLeft, stretch=1)
62+
layout.addLayout(cv_layout)
63+
64+
it_layout = QHBoxLayout()
65+
self.iterate_check_box = QCheckBox("Iterate by pruning long atom pairs until no pair exceeds ")
66+
it_layout.addWidget(self.iterate_check_box)
67+
self.iteration_value = iv = QLineEdit("2.0")
68+
iv.setAlignment(Qt.AlignCenter)
69+
iv.setMaximumWidth(4 * iv.fontMetrics().averageCharWidth())
70+
dv = QDoubleValidator()
71+
dv.setBottom(0.0)
72+
iv.setValidator(dv)
73+
it_layout.addWidget(iv)
74+
it_layout.addWidget(QLabel(" angstroms"), alignment=Qt.AlignLeft, stretch=1)
75+
layout.addLayout(it_layout)
76+
77+
ur_layout = QHBoxLayout()
78+
self.use_region_check_box = QCheckBox("Match active region only")
79+
ur_layout.addWidget(self.use_region_check_box, alignment=Qt.AlignLeft, stretch=1)
80+
layout.addLayout(ur_layout)
81+
82+
#cr_layout = QHBoxLayout()
83+
#self.create_region_check_box = QCheckBox("Create region showing matched residues")
84+
#cr_layout.addWidget(self.create_region_check_box, alignment=Qt.AlignLeft, stretch=1)
85+
#layout.addLayout(cr_layout)
86+
87+
from Qt.QtWidgets import QDialogButtonBox as qbbox
88+
bbox = qbbox(qbbox.Ok | qbbox.Apply | qbbox.Close | qbbox.Help)
89+
bbox.accepted.connect(self.match)
90+
bbox.button(qbbox.Apply).clicked.connect(lambda f = self.match: f(apply=True))
91+
hide_self = lambda *args, tw=tool_window: setattr(tool_window, 'shown', False)
92+
bbox.rejected.connect(hide_self)
93+
from chimerax.core.commands import run
94+
bbox.helpRequested.connect(lambda *, run=run, ses=self.sv.session, help=tool_window.help:
95+
run(ses, "help " + help))
96+
layout.addWidget(bbox)
97+
98+
tool_window.ui_area.setLayout(layout)
99+
100+
def match(self, *, apply=False):
101+
from chimerax.core.errors import UserError
102+
ref = self.ref_chain_menu.value
103+
if ref is None:
104+
raise UserError("No reference chain chosen")
105+
used_chains = { ref.structure: ref }
106+
matches = self.match_chain_list.value
107+
if not matches:
108+
raise UserError("No match chains chosen")
109+
for match in matches:
110+
if match.structure in used_chains:
111+
self.tool_window.shown = True
112+
raise UserError("Cannot match multiple chains from the same structure (%s and %s)"
113+
% (used_chains[match.structure], match))
114+
used_chains[match.structure] = match
115+
116+
args = ""
117+
if self.conserved_check_box.isChecked():
118+
if not self.conservation_value.hasAcceptableInput():
119+
raise UserError("Percent identity value must be between 0 and 100")
120+
args += " conservation " + self.conservation_value.text()
121+
if self.iterate_check_box.isChecked():
122+
if not self.iteration_value.hasAcceptableInput():
123+
raise UserError("Iteration cutoff value must be 0 or more")
124+
args += " iterate " + self.iteration_value.text()
125+
else:
126+
args += " iterate none"
127+
if self.use_region_check_box.isChecked():
128+
region = self.sv.active_region
129+
if region is None:
130+
raise UserError("No active region")
131+
cols = []
132+
for block in region.blocks:
133+
line1, line2, pos1, pos2 = block
134+
cols.extend(list(range(pos1+1,pos2+2)))
135+
if not cols:
136+
raise UserError("Active region is empty")
137+
args += " columns %s" % ','.join(["%d" % col for col in cols])
138+
139+
if not apply:
140+
self.tool_window.shown = False
141+
from chimerax.core.commands import run, StringArg
142+
results = run(self.sv.session, "seq match %s %s to %s%s"
143+
% (StringArg.unparse(self.sv.alignment.ident), ref.atomspec,
144+
','.join([c.atomspec for c in matches]), args))
145+
146+
if self.create_region_check_box.isChecked():
147+
pass #TODO

src/bundles/seq_view/src/tool.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -585,6 +585,11 @@ def fill_context_menu(self, menu, x, y):
585585
action.triggered.connect(lambda act, *args, func=self.select_by_column_identity, val=value:
586586
func(val))
587587
cons_sel_menu.addAction(action)
588+
match_action = QAction("Match...", structure_menu)
589+
match_action.triggered.connect(self.show_match_dialog)
590+
match_action.setEnabled(
591+
len(set([chain.structure for chain in self.alignment.associations.keys()])) > 1)
592+
structure_menu.addAction(match_action)
588593
xfer_action = QAction("Update Chain Sequence...", structure_menu)
589594
xfer_action.triggered.connect(self.show_transfer_seq_dialog)
590595
xfer_action.setEnabled(bool(self.alignment.associations))
@@ -808,6 +813,14 @@ def show_feature_browser(self, seq, *, state=None):
808813
self._feature_browsers[seq].tool_window.manage(None)
809814
self._feature_browsers[seq].tool_window.shown = True
810815

816+
def show_match_dialog(self):
817+
if not hasattr(self, "match_dialog"):
818+
from .match import MatchDialog
819+
self.match_dialog = MatchDialog(self,
820+
self.tool_window.create_child_window("Match", close_destroys=False))
821+
self.match_dialog.tool_window.manage(None)
822+
self.match_dialog.tool_window.shown = True
823+
811824
def show_percent_identity_dialog(self):
812825
if not hasattr(self, "percent_identity_dialog"):
813826
from .identity import PercentIdentityDialog

src/bundles/seqalign/src/cmd.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -498,7 +498,7 @@ def seqalign_align(session, seq_source, *, program=CLUSTAL_OMEGA, replace=False)
498498
def register_seqalign_command(logger):
499499
# REMINDER: update manager._builtin_subcommands as additional subcommands are added
500500
from chimerax.core.commands import CmdDesc, register, create_alias, Or, EmptyArg, RestOfLine, ListOf, \
501-
EnumOf, BoolArg, NoneArg, PositiveIntArg, PercentFloatArg
501+
EnumOf, BoolArg, NoneArg, PositiveIntArg, PercentFloatArg, NonNegativeFloatArg
502502
from chimerax.atomic import UniqueChainsArg, SequencesArg, ChainArg
503503

504504
apns = list(alignment_program_name_args.keys())
@@ -552,7 +552,7 @@ def register_seqalign_command(logger):
552552
desc = CmdDesc(
553553
required = [('alignment', Or(AlignmentArg, EmptyArg)), ('match_chains', UniqueChainsArg)],
554554
required_arguments = ['to'],
555-
keyword = [('to', ChainArg), ('iterate', Or(NoneArg, PositiveIntArg)),
555+
keyword = [('to', ChainArg), ('iterate', Or(NoneArg, NonNegativeFloatArg)),
556556
('conservation', PercentFloatArg),
557557
('columns', ListOf(PositiveIntArg))],
558558
synopsis = "superimpose chains associated with sequence alignment"

0 commit comments

Comments
 (0)