Source code for schrodinger.application.msv.gui.menu
from schrodinger import get_maestro
from schrodinger import in_dev_env
from schrodinger.application.msv.gui import gui_models
from schrodinger.application.msv.gui import stylesheets
from schrodinger.application.msv.gui.viewconstants import \
    MULTI_ALIGN_DISABLED_TT
from schrodinger.application.msv.gui.viewconstants import MULTI_ALIGN_ENABLED_TT
from schrodinger.application.msv.gui.viewconstants import PROF_ALIGN_DISABLED_TT
from schrodinger.models import mappers
from schrodinger.Qt import QtCore
from schrodinger.Qt import QtGui
from schrodinger.Qt import QtWidgets
maestro = get_maestro()
TUTORIAL_BASE_PATH = 'https://www.schrodinger.com/sites/default/files/s3/mkt/Tutorials/current/'
MSV_GUIDE = 'https://www.schrodinger.com/system/files/msv_guide_r3.pdf'
url = TUTORIAL_BASE_PATH + "{}.pdf"
BIOLUMINATE_INTRO = url.format('intro_bioluminate')
CHIMERIC_HOMOLGY_MODELING = url.format('chimeric_homology_building')
BATCH_HOMOLOGY_MODELING = url.format('batch_homology')
ANTIBODY_ANNOTATION = url.format('antibody_annotation')
[docs]class MenuAction(QtWidgets.QAction):
[docs]    def __init__(self,
                 label,
                 tooltip=None,
                 shortcut=None,
                 checkable=False,
                 icon_path=None,
                 data=None):
        super().__init__(label)
        if tooltip:
            self.setToolTip(tooltip)
        if shortcut:
            self.setShortcut(shortcut)
        self.setCheckable(checkable)
        if icon_path:
            icon = QtGui.QIcon(icon_path)
            self.setIcon(icon)
        if data:
            self.setData(data)
        # We do this temporarily to indicate that the feature is not yet
        # implemented
        self.setImplemented(False)
[docs]    def setMenu(self, menu):
        """
        Instantiates a menu
        :param menu: menu to instantiate
        :type menu: QtWidgets.QMenu
        """
        super().setMenu(menu)
        self.setImplemented(True)
[docs]    def setImplemented(self, has_been_implemented):
        """
        Sets whether a feature has been implemented yet
        :param has_been_implemented: Whether action has been implemented
        :type has_been_implemented: bool
        """
        font = self.font()
        font.setStrikeOut(not has_been_implemented)
        self.setFont(font)
        self.setEnabled(has_been_implemented)
[docs]class ToggleTextAction(mappers.TargetMixin, MenuAction):
    """
    Menu action that toggles a bool every time it's triggered and updates the
    text based on the value of the bool
    """
[docs]    def __init__(self, true_text, false_text):
        super().__init__("")
        self._texts = [false_text, true_text]
        self.targetSetValue(False)  # Initialize internal state and text
        self.triggered.connect(self._toggleValue, QtCore.Qt.QueuedConnection)
        self.setImplemented(True)  # Override `MenuAction` default
[docs]    def targetSetValue(self, value: bool):
        self._value = value
        self.setText(self._texts[int(self._value)])
    def _toggleValue(self):
        self.targetSetValue(not self.targetGetValue())
        self.targetValueChanged.emit()
[docs]class MenuBarActions(object):
    """
    MenuBarActions provides a namespace for all the actions on the menu bar
    """
[docs]    def __init__(self):
        MA = MenuAction
        # File
        # New Submenu - Currently has no contents
        self.close_project = MA("Close MSV Project")
        self.new_project = MA("New MSV Project...")
        self.open_project = MA("Open MSV Project...",
                               tooltip="Load project from file",
                               shortcut="Ctrl+Alt+O")
        self.import_project = MA("Import MSV Project...",
                                 tooltip="Load project and add its contents to "
                                 "the current project.")
        self.save_project = MA("Save",
                               tooltip="Save current project",
                               icon_path=":/msv/icons/save_file.png")
        self.save_as = MA("Save As...",
                          tooltip="Save current project to a specified file",
                          icon_path=":/msv/icons/save_file.png")
        self.get_pdb = MA("Get PDB...")
        self.get_sequences = MA("Fetch Sequences...")
        self.import_sequences = MA("Import Sequences from File...",
                                   tooltip="Import sequences from a file",
                                   shortcut="Ctrl+O",
                                   icon_path=":/msv/icons/import_sequences.png")
        self.paste_sequences = MA("Paste in New Sequence Text...")
        self.import_from_maestro = MA("Load from Maestro")
        self.import_from_maestro_workspace = MA("Workspace...")
        self.import_from_maestro_selected = MA("Selected Entries...")
        self.export_sequences = MA("&Export Sequences...",
                                   tooltip="Export the sequences to file",
                                   shortcut="Ctrl+S")
        self.save_image = MA("&Save Image...",
                             tooltip="Save Image...",
                             shortcut="Ctrl+I")
        self.close = MA("E&xit", tooltip="Close Panel", shortcut="Ctrl+W")
        # Edit
        self.undo = MA("&Undo",
                       shortcut="Ctrl+Z",
                       tooltip="Undo last editing operation",
                       icon_path=":/msv/icons/undo.png")
        self.redo = MA("&Redo",
                       tooltip="Redo last editing operation",
                       shortcut="Shift+Ctrl+Z",
                       icon_path=":/mvs/icons/redo.png")
        self.edit_sequence = MA("Edit Sequence In Place",
                                tooltip="Edit a sequence in place",
                                checkable=True)
        self.edit_sequence.setImplemented(True)
        self.copy = MA("Copy Selected Residues", shortcut="Ctrl+C")
        self.delete_sub = MA("Delete")
        self.delete_sel_residues = MA("Selected Residues", shortcut="Shift+Del")
        self.delete_sel_gaps = MA("Selected Gaps")
        self.delete_gap_cols = MA("All Gap-Only Columns")
        self.delete_sel_seqs = MA("Selected Sequences")
        self.delete_redundant_seqs = MA("Redundant Sequences...")
        self.delete_all_predictions = MA("All Predictions")
        self.delete_this_tab = MA("This Tab")
        self.delete_all_view_tabs = MA("All View Tabs")
        self.move_seq = MA("Move Sequence")
        self.move_to_top = MA("To Top")
        self.move_up = MA("Up")
        self.move_down = MA("Down")
        self.move_to_bottom = MA("To Bottom")
        self.move_set_as_ref = MA("Set as Reference")
        self.duplicate_seq = MA('Duplicate Sequence')
        self.duplicate_in_place = MA("In Place", shortcut="Ctrl+D")
        self.duplicate_at_bottom = MA("At Bottom")
        self.duplicate_at_top = MA("At Top")
        self.duplicate_as_ref = MA("As Reference Sequence")
        self.duplicate_into_new_tab = MA("Into New Tab")
        self.duplicate_into_existing_tab = MA("Into Existing Tab")
        self.set_as_ref_seq = MA("Set as Reference Sequence")
        self.rename_seq = MA("Rename Sequence")
        self.color_seq_name = MA("Color Sequence Name...")
        self.sort_seq = MA("Sort Sequences by")
        # Ascending Sort Submenu
        self.sort_ascending = MA("Sort Sequences &Ascending")
        self.sort_ascending_by_name = MA("Name")
        self.sort_ascending_by_chain_id = MA("Chain ID")
        self.sort_ascending_by_gaps = MA("Number of Gaps")
        self.sort_ascending_by_length = MA("Length")
        self.sort_ascending_by_seq_identity = MA("Identity %")
        self.sort_ascending_by_seq_similarity = MA("Similarity %")
        self.sort_ascending_by_seq_homology = MA("Conservation %")
        self.sort_ascending_by_seq_score = MA("Overall Score")
        # Descending Sort Submenu
        self.sort_descending = MA("Sort Sequences &Descending")
        self.sort_descending_by_name = MA("Name")
        self.sort_descending_by_chain_id = MA("Chain ID")
        self.sort_descending_by_gaps = MA("Number of Gaps")
        self.sort_descending_by_length = MA("Length")
        self.sort_descending_by_seq_identity = MA("Identity %")
        self.sort_descending_by_seq_similarity = MA("Similarity %")
        self.sort_descending_by_seq_homology = MA("Conservation %")
        self.sort_descending_by_seq_score = MA("Overall Score")
        self.sort_by_tree_order = MA("Tree Order", checkable=True)
        self.reverse_last_sort = MA("Reverse Last Sort")
        self.get_pdb_sts = MA("Get PDB Structures")
        self.translate_seq = MA("Translate DNA / RNA Sequence")
        self.replace_sel_with_gaps = MA("Replace Selection with Gaps")
        self.renumber_residues = MA("Renumber Residues...")
        self.edit_as_text = MA("Edit Sequence as Plain Text...")
        self.settings = MA("Settings and Defaults")
        self.job_settings = MA("Job Settings...")
        self.local_server = MA("Use Local Server Only for")
        self.sequence_local = MA("Fetching Sequences", checkable=True)
        self.sequence_local.setImplemented(True)
        self.blast_local = MA("BLAST Searches/Access", checkable=True)
        self.blast_local.setImplemented(True)
        self.pdb_local = MA("Getting PDB Files", checkable=True)
        self.pdb_local.setImplemented(True)
        self.reset_remote_server_ask = MA("Reset Remote Server Confirmations")
        self.view_job_log = MA("View Job Log...")
        self.reset_job_settings = MA("Reset All Job Settings")
        self.font_size = MA("Set Default Font Size...")
        self.auto_save = MA("Autosave Only After Edits", checkable=True)
        self.auto_save.setImplemented(True)
        self.light_mode = MA("Use Light Theme")
        self.light_mode.setCheckable(True)
        self.light_mode.setImplemented(True)
        # View
        self.hide_selected_seqs = MA("Hide Selected Sequences")
        self.show_workspace_seqs = MA("Show Workspace Sequences Only")
        self.show_all_seqs = MA("Show All Sequences")
        self.find_seqs_in_list = MA("Find Sequence in List...")
        self.expand = MA("Expand")
        self.collapse = MA("Collapse")
        # Expand/Collapse Menu
        self.expand_selected = MA("Selected")
        self.expand_unselected = MA("Unselected")
        self.expand_all = MA("All")
        self.collapse_selected = MA("Selected")
        self.collapse_unselected = MA("Unselected")
        self.collapse_all = MA("All")
        self.hide_sel_columns = MA("Hide Selected Columns")
        self.show_all_cols_within_sel = MA("Show All Columns within Selection")
        self.show_all_hidden_cols = MA("Show All Hidden Columns")
        self.hide_annotations = ToggleTextAction("Hide Annotations",
                                                 "Show Annotations")
        self.hide_colors = ToggleTextAction("Hide Colors", "Show Colors")
        self.configure = MA("Configure")
        #  Configure Menu
        self.configure_annotations = MA("Annotations...")
        self.configure_colors = MA("Colors...")
        self.configure_view = MA("View...")
        self.reset_to_defaults = MA("Reset to Defaults")
        # Select
        self.select_all_sequences = MA("All Sequences")
        self.select_no_sequences = MA("No Sequences")
        self.select_sequences_by_feature = MA("Sequences by Feature")
        self.select_sequence_with_structure = MA("With Structure")
        self.select_sequence_by_identity = MA("By Identity...")
        self.select_sequence_antibody_heavy = MA("Antibody Heavy Chain")
        self.select_sequence_antibody_light = MA("Antibody Light Chain")
        self.select_invert_seq_selection = MA("Invert Sequence Selection")
        self.select_all_residues = MA("All Residues", shortcut="Ctrl+A")
        self.select_no_residues = MA("No Residues", shortcut="Ctrl+U")
        self.select_residues_with_structure = MA("Residues with Structure")
        self.select_pattern_matching_residues = MA("Pattern-Matching Residues")
        self.select_deselect_gaps = MA("Deselect All Gaps")
        self.select_invert_res_selection = MA("Invert Residue Selection")
        self.select_identities = MA("Identities")
        self.select_aligned_residues = MA("Aligned Residues")
        self.select_binding_sites = MA("Binding Site Residues")
        self.select_protein_interface = MA("Protein Interface")
        self.select_antibody_cdr = MA("Antibody CDRs")
        self.select_columns_with_structure = MA("Columns with Structure")
        self.select_expand_along_cols = MA("Expand Along Columns")
        self.select_expand_ref_sel_only = MA("Expand Reference Selection Only")
        self.select_update_workspace_selection = MA(
            "Update Workspace Selection")
        self.select_link_seq_to_entries = MA("Link Sequences to Entries...")
        # Alignment
        self.auto_align = MA("Auto-Align New Sequences", checkable=True)
        self.auto_align.setImplemented(True)
        self.align_sequences = MA("Align Sequences")
        self.multiple_alignment = MA("Multiple Alignment")
        self.pairwise_alignment = MA(
            "Pairwise Alignment",
            tooltip=("Align selected sequence(s) with the reference sequence "
                     "using pairwise alignment"))
        self.pairwise_ss_alignment = MA(
            "Pairwise with Secondary Structure Prediction")
        self.align_from_superposition = MA(
            "Align Based on Structure Superposition")
        self.profile_alignment = MA("Profile Alignment")
        self.align_structures = MA("Align Structures")
        self.prot_struct_align = MA("Protein Structure Alignment")
        self.align_binding_sites = MA("Align Binding Sites")
        self.align_based_sequence = MA("Align Based on Sequence Alignment")
        self.dendrogram = MA("View Dendrogram", tooltip="Display dendrogram")
        self.set_constraints = MA("Set Constraints", checkable=True)
        self.set_constraints.setImplemented(True)
        self.clear_constraints = MA("Clear Constraints",
                                    tooltip="Clear alignment constraints")
        self.reset_align = MA("Reset Alignment Settings")
        self.anchor_selection = MA("Anchor Selection")
        self.clear_anchoring = MA("Clear Anchoring")
        # Help menu
        self.msv_help = MA("Multiple Sequence Viewer/Editor...",
                           data="MSV_MAIN_WINDOW")
        self.homology_modeling_help = MA("Homology Modeling...",
                                         data="MSV_BUILD_HOMOLOGY_MODEL")
        self.alignment_pane_help = MA("Alignment Pane...",
                                      data="MSV_ALIGN_PANE")
        self.getting_started_help = MA("Getting Started Guide...",
                                       data=MSV_GUIDE)
        self.bioluminate_intro = MA(
            "Structures, Sequences, && Homology Modeling...",
            data=BIOLUMINATE_INTRO)
        self.chimeric_hm_help = MA("Chimeric Homology Modeling...",
                                   data=CHIMERIC_HOMOLGY_MODELING)
        self.batch_hm_help = MA("Batch Homology Modeling...",
                                data=BATCH_HOMOLOGY_MODELING)
        self.antibody_anno_help = MA("Coloring && Annotation of Antibodies...",
                                     data=ANTIBODY_ANNOTATION)
        self.tutorials = MA("Tutorials")
        # Development tools
        self.start_ipython_session = MA("Start IPython Terminal Session")
        self.show_debug_gui = MA("Show Debug Gui")
        self.show_color_scheme_editor = MA("Show Color Scheme Editor")
        self.time_scroll = MA("Time Scrolling")
        self.time_scroll_by_page = MA("Time Scrolling By Page")
        self.profile_scroll = MA("Profile Scrolling")
        self.profile_scroll_by_page = MA("Profile Scrolling By Page")
        self.view_history = MA("History", checkable=True)
[docs]class EnabledTargetSpec(mappers.TargetSpec):
    """
    A TargetSpec for mapping a BoolParam to the enabled state of a widget
    """
[docs]    def __init__(self, widget):
        super().__init__(widget,
                         getter=None,
                         setter=widget.setEnabled,
                         signal=None)
[docs]class MsvMenuBar(mappers.MapperMixin, QtWidgets.QMenuBar):
    model_class = gui_models.MenuEnabledModel
[docs]    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.setNativeMenuBar(False)
        self.setStyleSheet(stylesheets.MENU)
        self._setMenuActions()
        self._makeMenus()
        self._setupMapperMixin()
[docs]    def defineMappings(self):
        ma = self.menu_actions
        M = self.model_class
        actions = [
            (ma.edit_as_text, M.can_edit_as_text),
            (ma.copy, M.can_copy_residues),
            (ma.delete_sel_seqs, M.can_delete_sequences),
            (ma.delete_sel_residues, M.can_delete_residues),
            (ma.delete_sel_gaps, M.can_delete_gaps),
            (ma.delete_all_predictions, M.can_delete_predictions),
            (ma.delete_this_tab, M.can_delete_tab),
            (ma.move_seq, M.can_move_sequence),
            (ma.set_as_ref_seq, M.can_set_as_ref),
            (ma.rename_seq, M.can_rename_seq),
            (ma.move_set_as_ref, M.can_set_as_ref),
            (ma.duplicate_seq, M.can_duplicate_sequence),
            (ma.duplicate_as_ref, M.can_duplicate_as_ref),
            (ma.duplicate_in_place, M.can_duplicate_seq_same_tab),
            (ma.duplicate_at_bottom, M.can_duplicate_seq_same_tab),
            (ma.duplicate_at_top, M.can_duplicate_seq_same_tab),
            (ma.replace_sel_with_gaps, M.can_replace_res_with_gaps),
            (ma.renumber_residues, M.can_renumber_residues),
            (ma.select_all_sequences, M.can_select),
            (ma.select_no_sequences, M.can_select),
            (ma.select_sequence_with_structure, M.can_select),
            (ma.select_sequence_by_identity,M.can_select),
            (ma.select_invert_seq_selection, M.can_select),
            (ma.select_all_residues, M.can_select),
            (ma.select_no_residues, M.can_select),
            (ma.select_residues_with_structure, M.can_select),
            (ma.select_invert_res_selection, M.can_select),
            (ma.select_protein_interface, M.can_select_protein_interface),
            (ma.expand, M.can_expand),
            (ma.collapse, M.can_expand),
            (ma.anchor_selection, M.can_anchor_res),
            (ma.clear_anchoring, M.can_unanchor_res),
            (ma.hide_sel_columns, M.can_hide_columns),
            (ma.set_constraints, M.can_set_constraints),
            (ma.clear_constraints, M.can_clear_constraints),
            (ma.sort_ascending_by_chain_id, M.can_sort_by_chain),
            (ma.sort_descending_by_chain_id, M.can_sort_by_chain),
            (ma.select_sequence_antibody_heavy, M.can_select_antibody_chain),
            (ma.select_sequence_antibody_light, M.can_select_antibody_chain),
            (ma.get_pdb_sts, M.can_get_pdb_sts),
            (ma.select_link_seq_to_entries, M.can_link_or_unlink_sequences),
        ]  # yapf: disable
        EnabledTS = EnabledTargetSpec
        mappings = [(EnabledTS(action), param) for (action, param) in actions]
        update_align_tgt = mappers.TargetSpec(slot=self._updateCanAlign)
        for param in (M.can_only_multiple_align, M.can_only_profile_align,
                      M.can_aln_set_align):
            mappings.append((update_align_tgt, param))
        return mappings
[docs]    @QtCore.pyqtSlot(bool)
    def canRedoChanged(self, can_redo):
        """
        Update the menu state depending on whether redo is available
        """
        redo_action = self.menu_actions.redo
        if redo_action.isEnabled() != can_redo:
            redo_action.setEnabled(can_redo)
[docs]    @QtCore.pyqtSlot(bool)
    def canUndoChanged(self, can_undo):
        """
        Update the menu state depending on whether undo is available
        """
        undo_action = self.menu_actions.undo
        if undo_action.isEnabled() != can_undo:
            undo_action.setEnabled(can_undo)
[docs]    @QtCore.pyqtSlot(str)
    def onRedoTextChanged(self, redo_text):
        """
        Update the Red menu action text to reflect the current redo action.
        :param redo_text: Text of the current redo action
        :type redo_text: str
        """
        redo_action = self.menu_actions.redo
        if not redo_text:
            redo_action.setText('&Redo')
        else:
            redo_action.setText('&Redo - {0}'.format(redo_text))
[docs]    @QtCore.pyqtSlot(str)
    def onUndoTextChanged(self, undo_text):
        """
        Update the Undo menu action text to reflect the current undo action.
        :param undo_text: Text of the current undo action
        :type undo_text: str
        """
        undo_action = self.menu_actions.undo
        if not undo_text:
            undo_action.setText('&Undo')
        else:
            undo_action.setText("&Undo - {0}".format(undo_text))
    def _updateCanAlign(self):
        ma = self.menu_actions
        align_actions = {
            ma.multiple_alignment, ma.pairwise_alignment,
            ma.pairwise_ss_alignment, ma.align_from_superposition,
            ma.profile_alignment, ma.prot_struct_align, ma.align_binding_sites,
            ma.align_based_sequence
        }
        if not self.model.can_aln_set_align:
            allowed_actions = set()
        elif self.model.can_only_profile_align:
            allowed_actions = {ma.profile_alignment}
        elif self.model.can_only_multiple_align:
            allowed_actions = {ma.multiple_alignment}
        else:
            # Profile alignment is only allowed when it's the only allowed align
            allowed_actions = align_actions - {ma.profile_alignment}
        if not maestro:
            allowed_actions.discard(ma.align_based_sequence)
        for action in align_actions:
            enabled = action in allowed_actions
            action.setEnabled(enabled)
        prof_align_tooltip = ("" if ma.profile_alignment.isEnabled() else
                              PROF_ALIGN_DISABLED_TT)
        multi_align_tooltop = (MULTI_ALIGN_ENABLED_TT
                               if ma.multiple_alignment.isEnabled() else
                               MULTI_ALIGN_DISABLED_TT)
        ma.profile_alignment.setToolTip(prof_align_tooltip)
        ma.multiple_alignment.setToolTip(multi_align_tooltop)
    def _setMenuActions(self):
        self.menu_actions = MenuBarActions()
    def _makeMenus(self):
        self._addFileMenu()
        self._addEditMenu()
        self._addSelectionMenu()
        self._addViewMenu()
        self._addAlignmentMenu()
        self._addHelpMenu()
        self._maybeAddDevelopmentMenu()
    def _addActionsToMenu(self, menu, actions):
        for action in actions:
            self._addActionToMenu(menu, action)
        menu.addSeparator()
    def _addActionToMenu(self, menu, action):
        action.setParent(menu)
        menu.addAction(action)
        menu.setToolTipsVisible(True)
    def _addFileMenu(self):
        file_menu = QtWidgets.QMenu("&File", self)
        ma = self.menu_actions
        add_project_group = [
            ma.open_project, ma.import_project, ma.close_project
        ]
        save_or_close_proj_group = [ma.save_project, ma.save_as]
        import_group = [
            ma.get_pdb, ma.get_sequences, ma.import_sequences,
            ma.paste_sequences, ma.import_from_maestro
        ]
        import_from_maestro = [
            ma.import_from_maestro_workspace, ma.import_from_maestro_selected
        ]
        export_group = [ma.export_sequences, ma.save_image]
        close_group = [ma.close]
        for group in [
                add_project_group, save_or_close_proj_group, import_group,
                export_group, close_group
        ]:
            self._addActionsToMenu(file_menu, group)
        maestro_menu = QtWidgets.QMenu(self)
        self._addActionsToMenu(maestro_menu, import_from_maestro)
        ma.import_from_maestro.setMenu(maestro_menu)
        self.addMenu(file_menu)
    def _addEditMenu(self):
        edit_menu = QtWidgets.QMenu("&Edit", self)
        ma = self.menu_actions
        edit_group = [ma.undo, ma.redo]
        copy_del_move_dup_group = [
            ma.copy, ma.delete_sub, ma.move_seq, ma.duplicate_seq
        ]
        name_group = [
            ma.set_as_ref_seq,
            ma.rename_seq,
            # MSV-3357: Hiding unimplemented features for beta
            #ma.color_seq_name
        ]
        sort_group = [ma.sort_seq, ma.get_pdb_sts, ma.translate_seq]
        replace_res_group = [
            ma.replace_sel_with_gaps, ma.renumber_residues, ma.edit_as_text
        ]
        for group in [
                edit_group,
            [ma.edit_sequence],
                copy_del_move_dup_group,
                name_group,
                sort_group,
                replace_res_group,
            [ma.settings],
        ]:
            self._addActionsToMenu(edit_menu, group)
        delete_res_group = [
            ma.delete_sel_residues, ma.delete_sel_gaps, ma.delete_gap_cols
        ]
        delete_seq_group = [
            ma.delete_sel_seqs, ma.delete_redundant_seqs,
            ma.delete_all_predictions
        ]
        delete_tab_group = [ma.delete_this_tab, ma.delete_all_view_tabs]
        delete_menu = QtWidgets.QMenu(self)
        self._addActionsToMenu(delete_menu, delete_res_group)
        self._addActionsToMenu(delete_menu, delete_seq_group)
        self._addActionsToMenu(delete_menu, delete_tab_group)
        ma.delete_sub.setMenu(delete_menu)
        move_group = [
            ma.move_to_top, ma.move_up, ma.move_down, ma.move_to_bottom
        ]
        move_ref_group = [ma.move_set_as_ref]
        move_menu = QtWidgets.QMenu(self)
        self._addActionsToMenu(move_menu, move_group)
        self._addActionsToMenu(move_menu, move_ref_group)
        ma.move_seq.setMenu(move_menu)
        duplicate_menu = QtWidgets.QMenu(self)
        duplicate_group = [
            ma.duplicate_in_place, ma.duplicate_at_bottom, ma.duplicate_at_top,
            ma.duplicate_as_ref
        ]
        duplicate_new_tab_group = [
            ma.duplicate_into_new_tab, ma.duplicate_into_existing_tab
        ]
        self._addActionsToMenu(duplicate_menu, duplicate_group)
        self._addActionsToMenu(duplicate_menu, duplicate_new_tab_group)
        ma.duplicate_seq.setMenu(duplicate_menu)
        sort_seq = [
            # MSV-3357: Hiding unimplemented features for beta
            #[ma.sort_by_tree_order],
            [ma.sort_ascending, ma.sort_descending],
            [ma.reverse_last_sort]
        ]
        sort_ascending = [
            ma.sort_ascending_by_name, ma.sort_ascending_by_chain_id,
            ma.sort_ascending_by_gaps, ma.sort_ascending_by_length,
            ma.sort_ascending_by_seq_identity,
            ma.sort_ascending_by_seq_similarity,
            ma.sort_ascending_by_seq_homology, ma.sort_ascending_by_seq_score
        ]
        sort_descending = [
            ma.sort_descending_by_name, ma.sort_descending_by_chain_id,
            ma.sort_descending_by_gaps, ma.sort_descending_by_length,
            ma.sort_descending_by_seq_identity,
            ma.sort_descending_by_seq_similarity,
            ma.sort_descending_by_seq_homology, ma.sort_descending_by_seq_score
        ]
        sort_menu = QtWidgets.QMenu(self)
        for group in sort_seq:
            self._addActionsToMenu(sort_menu, group)
        ma.sort_seq.setMenu(sort_menu)
        ascending_menu = QtWidgets.QMenu(self)
        self._addActionsToMenu(ascending_menu, sort_ascending)
        ma.sort_ascending.setMenu(ascending_menu)
        descending_menu = QtWidgets.QMenu(self)
        self._addActionsToMenu(descending_menu, sort_descending)
        ma.sort_descending.setMenu(descending_menu)
        local_menu = QtWidgets.QMenu(self)
        local_group = [ma.sequence_local, ma.blast_local, ma.pdb_local]
        self._addActionsToMenu(local_menu, local_group)
        ma.local_server.setMenu(local_menu)
        settings_menu = QtWidgets.QMenu(self)
        #job_group = [ma.job_settings, ma.view_job_log]
        server_group = [
            ma.local_server,
            ma.reset_remote_server_ask,
            # MSV-3357: Hiding unimplemented features for beta
            #ma.reset_job_settings
        ]
        #defaults_group = [ma.font_size]
        for group in (
                # MSV-3357: Hiding unimplemented features for beta
                #job_group,
                server_group,
                #defaults_group,
            [ma.auto_save, ma.light_mode]):
            self._addActionsToMenu(settings_menu, group)
        ma.settings.setMenu(settings_menu)
        self.addMenu(edit_menu)
    def _addSelectionMenu(self):
        selection_menu = QtWidgets.QMenu("&Select", self)
        ma = self.menu_actions
        select_antibody_group = [
            ma.select_sequence_antibody_heavy, ma.select_sequence_antibody_light
        ]
        select_by_feature_group = [
            ma.select_sequence_with_structure, ma.select_sequence_by_identity
        ]
        select_feature_menu = QtWidgets.QMenu(self)
        for group in (select_by_feature_group, select_antibody_group):
            self._addActionsToMenu(select_feature_menu, group)
        ma.select_sequences_by_feature.setMenu(select_feature_menu)
        seq_res_group = [
            ma.select_all_sequences, ma.select_no_sequences,
            ma.select_sequences_by_feature, ma.select_invert_seq_selection
        ]
        residue_group = [
            ma.select_all_residues, ma.select_no_residues,
            ma.select_residues_with_structure,
            ma.select_pattern_matching_residues, ma.select_deselect_gaps,
            ma.select_invert_res_selection
        ]
        misc_group = [
            ma.select_identities, ma.select_aligned_residues,
            ma.select_binding_sites, ma.select_protein_interface,
            ma.select_antibody_cdr, ma.select_columns_with_structure
        ]
        expand_group = [
            ma.select_expand_along_cols, ma.select_expand_ref_sel_only
        ]
        for group in [
                seq_res_group, residue_group, misc_group, expand_group,
            [
                ma.select_update_workspace_selection,
                ma.select_link_seq_to_entries
            ]
        ]:
            self._addActionsToMenu(selection_menu, group)
        self.addMenu(selection_menu)
    def _addViewMenu(self):
        view_menu = QtWidgets.QMenu("&View", self)
        ma = self.menu_actions
        seq_group = [
            ma.hide_selected_seqs, ma.show_workspace_seqs, ma.show_all_seqs,
            ma.find_seqs_in_list
        ]
        expand_collapse_group = [ma.expand, ma.collapse]
        # MSV-3357: Hiding unimplemented features for beta
        #cols_group = [
        #ma.hide_sel_columns, ma.show_all_cols_within_sel,
        #ma.show_all_hidden_cols
        #]
        annotations_colors_group = [
            ma.hide_annotations, ma.hide_colors, ma.configure,
            ma.reset_to_defaults
        ]
        expand_menu = QtWidgets.QMenu(self)
        collapse_menu = QtWidgets.QMenu(self)
        configure_menu = QtWidgets.QMenu(self)
        expand_sel_group = [
            ma.expand_selected, ma.expand_unselected, ma.expand_all
        ]
        collapse_sel_group = [
            ma.collapse_selected, ma.collapse_unselected, ma.collapse_all
        ]
        config_group = [
            ma.configure_annotations, ma.configure_colors, ma.configure_view
        ]
        for group in [expand_sel_group]:
            self._addActionsToMenu(expand_menu, group)
        for group in [collapse_sel_group]:
            self._addActionsToMenu(collapse_menu, group)
        for group in [config_group]:
            self._addActionsToMenu(configure_menu, group)
        for group in [
                seq_group,
                expand_collapse_group,
                # MSV-3357: Hiding unimplemented features for beta
                #cols_group,
                annotations_colors_group
        ]:
            self._addActionsToMenu(view_menu, group)
        ma.expand.setMenu(expand_menu)
        ma.collapse.setMenu(collapse_menu)
        ma.configure.setMenu(configure_menu)
        self.addMenu(view_menu)
    def _addAlignmentMenu(self):
        align_menu = QtWidgets.QMenu("&Align", self)
        ma = self.menu_actions
        align_seq_group = [
            ma.multiple_alignment,
            ma.pairwise_alignment,
            ma.pairwise_ss_alignment,
            ma.profile_alignment,
        ]
        align_based_superposition = [ma.align_from_superposition]
        align_seq_menu = QtWidgets.QMenu(self)
        for group in [align_seq_group, align_based_superposition]:
            self._addActionsToMenu(align_seq_menu, group)
        align_struct_menu = QtWidgets.QMenu(self)
        stucture_alignment = [ma.prot_struct_align, ma.align_binding_sites]
        align_based_seq = [ma.align_based_sequence]
        for group in [stucture_alignment, align_based_seq]:
            self._addActionsToMenu(align_struct_menu, group)
        seq_align_group = [
            ma.align_sequences, ma.align_structures, ma.dendrogram
        ]
        ma.align_sequences.setMenu(align_seq_menu)
        ma.align_structures.setMenu(align_struct_menu)
        constrain_group = [
            ma.set_constraints,
            ma.clear_constraints,
        ]
        reset_group = [ma.reset_align]
        anchor_group = [ma.anchor_selection, ma.clear_anchoring]
        for group in [
            [ma.auto_align],
                seq_align_group,
                constrain_group,
                reset_group,
                anchor_group,
        ]:
            self._addActionsToMenu(align_menu, group)
        self.addMenu(align_menu)
    def _maybeAddDevelopmentMenu(self):
        """
        Adds the development menu if SCHRODINGER_SRC is set in the user's
        environment
        """
        if not in_dev_env():
            return
        dev_menu = QtWidgets.QMenu("&Development", self)
        self._addActionsToMenu(dev_menu, [
            self.menu_actions.start_ipython_session,
            self.menu_actions.show_debug_gui,
            self.menu_actions.show_color_scheme_editor,
            self.menu_actions.time_scroll,
            self.menu_actions.time_scroll_by_page,
            self.menu_actions.profile_scroll,
            self.menu_actions.profile_scroll_by_page,
            self.menu_actions.view_history
        ])
        self.addMenu(dev_menu)
    def _addHelpMenu(self):
        help_menu = QtWidgets.QMenu("&Help", self)
        ma = self.menu_actions
        self._addActionsToMenu(help_menu, [
            ma.msv_help,
            ma.homology_modeling_help,
            ma.alignment_pane_help,
        ])
        self._addActionToMenu(help_menu, ma.getting_started_help)
        self._addActionToMenu(help_menu, ma.tutorials)
        self.addMenu(help_menu)
        tutorials_menu = QtWidgets.QMenu(self)
        for act in (ma.bioluminate_intro, ma.chimeric_hm_help, ma.batch_hm_help,
                    ma.antibody_anno_help):
            self._addActionToMenu(tutorials_menu, act)
        ma.tutorials.setMenu(tutorials_menu)