diff --git a/MDANSE/Src/MDANSE/Framework/AtomSelector/group_selection.py b/MDANSE/Src/MDANSE/Framework/AtomSelector/group_selection.py index 6b1ecf42f..ad94a9994 100644 --- a/MDANSE/Src/MDANSE/Framework/AtomSelector/group_selection.py +++ b/MDANSE/Src/MDANSE/Framework/AtomSelector/group_selection.py @@ -41,7 +41,7 @@ def select_labels(trajectory: Trajectory, **function_parameters: Dict[str, Any]) atom_labels = function_parameters.get("atom_labels", None) for label in atom_labels: if label in system._labels: - selection = selection.union(reduce(list.__add__, system._labels[label])) + selection = selection.union(system._labels[label]) return selection diff --git a/MDANSE_GUI/Src/MDANSE_GUI/InputWidgets/AtomSelectionWidget.py b/MDANSE_GUI/Src/MDANSE_GUI/InputWidgets/AtomSelectionWidget.py index 858ff7be9..b8473290a 100644 --- a/MDANSE_GUI/Src/MDANSE_GUI/InputWidgets/AtomSelectionWidget.py +++ b/MDANSE_GUI/Src/MDANSE_GUI/InputWidgets/AtomSelectionWidget.py @@ -30,6 +30,8 @@ QPlainTextEdit, QWidget, QListView, + QAbstractItemView, + QScrollArea, ) from MDANSE_GUI.InputWidgets.WidgetBase import WidgetBase from MDANSE_GUI.Tabs.Visualisers.View3D import View3D @@ -40,6 +42,9 @@ AllAtomSelection, AtomSelection, IndexSelection, + MoleculeSelection, + PatternSelection, + LabelSelection, ) @@ -110,29 +115,9 @@ class SelectionHelper(QDialog): ---------- _helper_title : str The title of the helper dialog window. - _cbox_text : dict - The dictionary that maps the selector settings to text used in - the helper dialog. """ _helper_title = "Atom selection helper" - _cbox_text = { - "all": "All atoms (excl. dummy atoms):", - "dummy": "All dummy atoms:", - "hs_on_heteroatom": "Hs on heteroatoms:", - "primary_amine": "Primary amine groups:", - "hydroxy": "Hydroxy groups:", - "methyl": "Methyl groups:", - "phosphate": "Phosphate groups:", - "sulphate": "Sulphate groups:", - "thiol": "Thiol groups:", - "water": "Water molecules:", - "hs_on_element": "Hs on elements:", - "element": "Elements:", - "name": "Atom name:", - "fullname": "Atom fullname:", - "index": "Indexes:", - } def __init__( self, @@ -176,16 +161,19 @@ def __init__( for button in self.create_buttons(): bottom.addWidget(button) - layouts[-1].addLayout(bottom) - helper_layout = QHBoxLayout() - for layout in layouts: - helper_layout.addLayout(layout) + sub_layout = QVBoxLayout() + helper_layout.addLayout(layouts[0]) + helper_layout.addLayout(sub_layout) + for layout in layouts[1:]: + sub_layout.addLayout(layout) + sub_layout.addLayout(bottom) self.setLayout(helper_layout) self.all_selection = True self.selected = set([]) + self.reset() def closeEvent(self, a0): """Hide the window instead of closing. Some issues occur in the @@ -224,7 +212,7 @@ def create_layouts(self) -> list[QVBoxLayout]: for widget in self.left_widgets(): left.addWidget(widget) - right = QVBoxLayout() + right = QHBoxLayout() for widget in self.right_widgets(): right.addWidget(widget) @@ -238,7 +226,7 @@ def right_widgets(self) -> list[QWidget]: List of QWidgets to add to the right layout from create_layouts. """ - return [self.selection_textbox] + return [self.selection_operations_view, self.selection_textbox] def left_widgets(self) -> list[QWidget]: """ @@ -251,11 +239,16 @@ def left_widgets(self) -> list[QWidget]: select = QGroupBox("selection") select_layout = QVBoxLayout() + scroll_area = QScrollArea() + scroll_area.setLayout(select_layout) self.selection_widgets = [ AllAtomSelection(self), AtomSelection(self, self.trajectory), IndexSelection(self), + MoleculeSelection(self, self.trajectory), + PatternSelection(self), + LabelSelection(self, self.trajectory), ] for widget in self.selection_widgets: @@ -274,15 +267,15 @@ def left_widgets(self) -> list[QWidget]: select.setLayout(select_layout) - preview = QGroupBox("Selection operations") - preview_layout = QVBoxLayout() - preview.setLayout(preview_layout) - self.selection_operations_view = QListView(preview) + self.selection_operations_view = QListView(self) self.selection_operations_view.setDragEnabled(True) + self.selection_operations_view.setAcceptDrops(True) + self.selection_operations_view.setDragDropMode( + QAbstractItemView.DragDropMode.InternalMove + ) self.selection_operations_view.setModel(self.selection_model) self.selection_model.selection_changed.connect(self.recalculate_selection) - preview_layout.addWidget(self.selection_operations_view) - return [select, preview] + return [select] @Slot() def recalculate_selection(self): diff --git a/MDANSE_GUI/Src/MDANSE_GUI/InputWidgets/CheckableComboBox.py b/MDANSE_GUI/Src/MDANSE_GUI/InputWidgets/CheckableComboBox.py index bc21c7f22..9a621d33a 100644 --- a/MDANSE_GUI/Src/MDANSE_GUI/InputWidgets/CheckableComboBox.py +++ b/MDANSE_GUI/Src/MDANSE_GUI/InputWidgets/CheckableComboBox.py @@ -44,6 +44,10 @@ def __init__(self, *args, **kwargs): def clear(self): result = super().clear() + self.items = [] + self.checked = [] + self.text = [] + self.select_all_item = None self.addItem("select all", underline=True) self.lineEdit().setText("") return result diff --git a/MDANSE_GUI/Src/MDANSE_GUI/Widgets/SelectionWidgets.py b/MDANSE_GUI/Src/MDANSE_GUI/Widgets/SelectionWidgets.py index 9d7e734b5..91bd9446d 100644 --- a/MDANSE_GUI/Src/MDANSE_GUI/Widgets/SelectionWidgets.py +++ b/MDANSE_GUI/Src/MDANSE_GUI/Widgets/SelectionWidgets.py @@ -30,11 +30,6 @@ from MDANSE_GUI.InputWidgets.CheckableComboBox import CheckableComboBox from MDANSE.MolecularDynamics.Trajectory import Trajectory -from MDANSE.Framework.AtomSelector.general_selection import ( - select_all, - select_none, - invert_selection, -) class BasicSelectionWidget(QGroupBox): @@ -143,7 +138,7 @@ def parameter_dictionary(self): class IndexSelection(BasicSelectionWidget): - def __init__(self, parent=None, widget_label="Select atoms"): + def __init__(self, parent=None, widget_label="Index selection"): super().__init__(parent, widget_label) self.selection_keyword = "index_list" @@ -170,18 +165,112 @@ def switch_mode(self, new_mode: str): if new_mode == "list": self.selection_field.setPlaceholderText("0,1,2") self.selection_keyword = "index_list" - self.selection_separator = ',' + self.selection_separator = "," if new_mode == "range": self.selection_field.setPlaceholderText("0-20") self.selection_keyword = "index_range" - self.selection_separator = '-' + self.selection_separator = "-" if new_mode == "slice": self.selection_field.setPlaceholderText("first:last:step") self.selection_keyword = "index_slice" - self.selection_separator = ':' + self.selection_separator = ":" def parameter_dictionary(self): function_parameters = {"function_name": "select_atoms"} selection = self.selection_field.text() - function_parameters[self.selection_keyword] = [int(x) for x in selection.split(self.selection_separator)] + function_parameters[self.selection_keyword] = [ + int(x) for x in selection.split(self.selection_separator) + ] + return function_parameters + + +class MoleculeSelection(BasicSelectionWidget): + def __init__( + self, + parent=None, + trajectory: Trajectory = None, + widget_label="Select molecules", + ): + self.molecule_names = [] + if trajectory: + self.molecule_names = list(trajectory.chemical_system._clusters.keys()) + super().__init__(parent, widget_label) + + def add_specific_widgets(self): + layout = self.layout() + layout.addWidget(QLabel("Select molecules named: ")) + self.selection_field = CheckableComboBox(self) + layout.addWidget(self.selection_field) + self.selection_field.addItems(self.molecule_names) + + def parameter_dictionary(self): + function_parameters = {"function_name": "select_molecules"} + selection = self.selection_field.checked_values() + function_parameters["molecule_names"] = selection + return function_parameters + + +class LabelSelection(BasicSelectionWidget): + def __init__( + self, + parent=None, + trajectory: Trajectory = None, + widget_label="Select by label", + ): + self.labels = [] + if trajectory: + self.labels = list(trajectory.chemical_system._labels.keys()) + super().__init__(parent, widget_label) + + def add_specific_widgets(self): + layout = self.layout() + layout.addWidget(QLabel("Select atoms with label: ")) + self.selection_field = CheckableComboBox(self) + layout.addWidget(self.selection_field) + self.selection_field.addItems(self.labels) + + def parameter_dictionary(self): + function_parameters = {"function_name": "select_labels"} + selection = self.selection_field.checked_values() + function_parameters["atom_labels"] = selection + return function_parameters + + +class PatternSelection(BasicSelectionWidget): + def __init__( + self, + parent=None, + widget_label="SMARTS pattern matching", + ): + self.pattern_dictionary = { + "primary amine": "[#7X3;H2;!$([#7][#6X3][!#6]);!$([#7][#6X2][!#6])](~[H])~[H]", + "hydroxy": "[#8;H1,H2]~[H]", + "methyl": "[#6;H3](~[H])(~[H])~[H]", + "phosphate": "[#15X4](~[#8])(~[#8])(~[#8])~[#8]", + "sulphate": "[#16X4](~[#8])(~[#8])(~[#8])~[#8]", + "thiol": "[#16X2;H1]~[H]", + } + super().__init__(parent, widget_label) + + def add_specific_widgets(self): + layout = self.layout() + layout.addWidget(QLabel("Pick a group")) + self.selection_field = QComboBox(self) + layout.addWidget(self.selection_field) + self.selection_field.addItems(self.pattern_dictionary.keys()) + layout.addWidget(QLabel("pattern:")) + self.input_field = QLineEdit("", self) + self.input_field.setPlaceholderText("can be edited") + layout.addWidget(self.input_field) + self.selection_field.currentTextChanged.connect(self.update_string) + + @Slot(str) + def update_string(self, key_string: str): + if key_string in self.pattern_dictionary: + self.input_field.setText(self.pattern_dictionary[key_string]) + + def parameter_dictionary(self): + function_parameters = {"function_name": "select_pattern"} + selection = self.input_field.text() + function_parameters["rdkit_pattern"] = selection return function_parameters