# Copyright 2008 Nanorex, Inc. See LICENSE file for details. """ EditProtein_PropertyManager.py The EditProtein_PropertyManager class provides a Property Manager for the B{Edit Protein} command on the flyout toolbar in the Build > Protein mode. @author: Piotr, Mark @version: $Id$ @copyright: 2008 Nanorex, Inc. See LICENSE file for details. TODO: - Better messages, especially when selecting different peptides. - Need to implement a validator for the Name line edit field. - Collapse all rotamers when leaving command. NICETOHAVE: - Some way of showing the current rotamer when all rotamers are displayed. Ideas include: - Dim all rotamers in the current protein except the atoms in the current rotamer. - Highlight atoms in current rotamer for 1 second (when switching rotamers). - Include "Show entire model" checkbox in PM (checked by default). - Show residue label in GA of current residue, including AA and # (i.e. SER[14]). - "Show adjacent rotamers" checkbox. REFACTORING: Things to discuss with Bruce include an asterisk: - Should current_protein be renamed to command.struct everywhere? * - Move some methods to EditProtein_Command or EditCommand class. * - add setStructureName(name) in EditProtein_Command or in superclass EditCommand? - other methods that edit the current protein. """ import os, time, fnmatch, string import foundation.env as env from widgets.prefs_widgets import connect_checkbox_with_boolean_pref from utilities.prefs_constants import getDefaultWorkingDirectory from utilities.prefs_constants import workingDirectory_prefs_key from utilities.Log import greenmsg from utilities.constants import yellow, orange, red, magenta from utilities.constants import cyan, blue, white, black, gray from utilities.constants import diPROTEIN from PyQt4.Qt import SIGNAL from PyQt4.Qt import Qt from PyQt4 import QtGui from PyQt4.Qt import QFileDialog, QString, QMessageBox, QSlider from PM.PM_PushButton import PM_PushButton from PM.PM_GroupBox import PM_GroupBox from PM.PM_ComboBox import PM_ComboBox from PM.PM_LineEdit import PM_LineEdit from PM.PM_StackedWidget import PM_StackedWidget from PM.PM_CheckBox import PM_CheckBox from PM.PM_Dial import PM_Dial from PM.PM_ToolButtonRow import PM_ToolButtonRow from PM.PM_Slider import PM_Slider from PM.PM_Constants import PM_DONE_BUTTON from PM.PM_Constants import PM_WHATS_THIS_BUTTON from PM.PM_ColorComboBox import PM_ColorComboBox from PyQt4.Qt import QTextCursor from command_support.Command_PropertyManager import Command_PropertyManager _superclass = Command_PropertyManager class EditProtein_PropertyManager(Command_PropertyManager): """ The ProteinDisplayStyle_PropertyManager class provides a Property Manager for the B{Edit Protein} command on the Build Protein command toolbar. The selected peptide/protein is displayed in the protein reduced display style. The user can select individual rotamers and edit their chi angles. This is useful for certain types of protein design protocols using a 3rd party program like Rosetta. @ivar title: The title that appears in the property manager header. @type title: str @ivar pmName: The name of this property manager. This is used to set the name of the PM_Dialog object via setObjectName(). @type name: str @ivar iconPath: The relative path to the PNG file that contains a 22 x 22 icon image that appears in the PM header. @type iconPath: str """ title = "Protein Properties" pmName = title iconPath = "ui/actions/Command Toolbar/BuildProtein/EditProtein.png" current_protein = None # The currently selected peptide. previous_protein = None # The last peptide selected. current_aa = None # The current amino acid. def __init__( self, command ): """ Constructor for the property manager. """ self.currentWorkingDirectory = env.prefs[workingDirectory_prefs_key] _superclass.__init__(self, command) self.sequenceEditor = self.win.createProteinSequenceEditorIfNeeded() self.showTopRowButtons( PM_DONE_BUTTON | \ PM_WHATS_THIS_BUTTON) return def connect_or_disconnect_signals(self, isConnect = True): if isConnect: change_connect = self.win.connect else: change_connect = self.win.disconnect change_connect(self.nameLineEdit, SIGNAL("editingFinished()"), self._nameChanged) change_connect(self.currentResidueComboBox, SIGNAL("activated(int)"), self.setCurrentAminoAcid) change_connect(self.prevButton, SIGNAL("clicked()"), self._expandPreviousRotamer) change_connect(self.nextButton, SIGNAL("clicked()"), self._expandNextRotamer) change_connect(self.recenterViewCheckBox, SIGNAL("toggled(bool)"), self._centerViewToggled) change_connect(self.showAllResiduesCheckBox, SIGNAL("toggled(bool)"), self._showAllResidues) # Rotamer control widgets. change_connect(self.chi1Dial, SIGNAL("valueChanged(int)"), self._rotateChi1) change_connect(self.chi2Dial, SIGNAL("valueChanged(int)"), self._rotateChi2) change_connect(self.chi3Dial, SIGNAL("valueChanged(int)"), self._rotateChi3) # Chi4 dial is hidden. change_connect(self.chi4Dial, SIGNAL("valueChanged(double)"), self._rotateChi4) return #== def _nameChanged(self): """ Slot for "Name" field. @TODO: Include a validator for the name field. """ if not self.current_protein: return _name = str(self.nameLineEdit.text()) if not _name: # Minimal test. Need to implement a validator. if self.current_protein: self.nameLineEdit.setText(self.current_protein.name) return self.current_protein.name = _name msg = "Editing structure %s." % _name self.updateMessage(msg) return def update_name_field(self): """ Update the name field showing the name of the currently selected protein. clear the combobox list. """ if not self.current_protein: self.nameLineEdit.setText("") else: self.nameLineEdit.setText(self.current_protein.name) return def update_length_field(self): """ Update the name field showing the name of the currently selected protein. clear the combobox list. """ if not self.current_protein: self.lengthLineEdit.setText("") else: length_str = "%d residues" % self.current_protein.protein.count_amino_acids() self.lengthLineEdit.setText(length_str) return def update_residue_combobox(self): """ Update the residue combobox with the amino acid sequence of the currently selected protein. If there is no currently selected protein, clear the combobox list. """ self.currentResidueComboBox.clear() if not self.current_protein: return aa_list = self.current_protein.protein.get_amino_acid_id_list() for j in range(len(aa_list)): aa_id, residue_id = aa_list[j].strip().split(":") self.currentResidueComboBox.addItem(residue_id) pass self.setCurrentAminoAcid() return def close(self): """ Closes the Property Manager. Overrides EditCommand_PM.close() """ self.sequenceEditor.hide() env.history.statusbar_msg("") if self.current_protein: self.current_protein.setDisplayStyle(self.previous_protein_display_style) self.previous_protein = None # Update name in case the it was changed by the user. self.current_protein.name = str(self.nameLineEdit.text()) _superclass.close(self) return def show(self): """ Show the PM. Extends superclass method. @note: _update_UI_do_updates() gets called immediately after this and updates PM widgets with their correct values/settings. """ _superclass.show(self) env.history.statusbar_msg("") return def _addGroupBoxes( self ): """ Add the Property Manager group boxes. """ self._pmGroupBox1 = PM_GroupBox( self, title = "Parameters") self._loadGroupBox1( self._pmGroupBox1 ) self._pmGroupBox2 = PM_GroupBox( self, title = "Rotamer Controls") self._loadGroupBox2( self._pmGroupBox2 ) return def _loadGroupBox1(self, pmGroupBox): """ Load widgets in group box. """ self.nameLineEdit = PM_LineEdit( pmGroupBox, label = "Name:") self.lengthLineEdit = PM_LineEdit( pmGroupBox, label = "Length:") self.lengthLineEdit.setEnabled(False) self.currentResidueComboBox = PM_ComboBox( pmGroupBox, label = "Current residue:", setAsDefault = False) BUTTON_LIST = [ ("QToolButton", 1, "Previous residue", "ui/actions/Properties Manager/Previous.png", "", "Previous residue", 0 ), ( "QToolButton", 2, "Next residue", "ui/actions/Properties Manager/Next.png", "", "Next residue", 1 ) ] self.prevNextButtonRow = \ PM_ToolButtonRow( pmGroupBox, title = "", buttonList = BUTTON_LIST, label = 'Previous / Next:', isAutoRaise = True, isCheckable = False ) self.prevButton = self.prevNextButtonRow.getButtonById(1) self.nextButton = self.prevNextButtonRow.getButtonById(2) self.recenterViewCheckBox = \ PM_CheckBox( pmGroupBox, text = "Center view on current residue", setAsDefault = True, state = Qt.Unchecked, widgetColumn = 0, spanWidth = True) self.lockEditedCheckBox = \ PM_CheckBox( pmGroupBox, text = "Lock edited rotamers", setAsDefault = True, state = Qt.Checked, widgetColumn = 0, spanWidth = True) self.showAllResiduesCheckBox = \ PM_CheckBox( pmGroupBox, text = "Show all residues", setAsDefault = False, state = Qt.Unchecked, widgetColumn = 0, spanWidth = True) return def _loadGroupBox2(self, pmGroupBox): """ Load widgets in group box. """ self.discreteStepsCheckBox = \ PM_CheckBox( pmGroupBox, text = "Use discrete steps", setAsDefault = True, state = Qt.Unchecked) self.chi1Dial = \ PM_Dial( pmGroupBox, label = "Chi1:", value = 0.000, setAsDefault = True, minimum = -180.0, maximum = 180.0, wrapping = True, suffix = "deg", spanWidth = False) self.chi1Dial.setEnabled(False) self.chi2Dial = \ PM_Dial( pmGroupBox, label = "Chi2:", value = 0.000, setAsDefault = True, minimum = -180.0, maximum = 180.0, suffix = "deg", spanWidth = False) self.chi2Dial.setEnabled(False) self.chi3Dial = \ PM_Dial( pmGroupBox, label = "Chi3:", value = 0.000, setAsDefault = True, minimum = -180.0, maximum = 180.0, suffix = "deg", spanWidth = False) self.chi3Dial.setEnabled(False) self.chi4Dial = \ PM_Dial( pmGroupBox, label = "Chi4:", value = 0.000, setAsDefault = True, minimum = -180.0, maximum = 180.0, suffix = "deg", spanWidth = False) self.chi4Dial.setEnabled(False) self.chi4Dial.hide() return def _addWhatsThisText( self ): pass def _addToolTipText(self): pass def _expandNextRotamer(self): """ Displays the next rotamer in the chain. @attention: this only works when the GDS is a reduced display style. """ if not self.current_protein: return self.current_protein.protein.traverse_forward() self.setCurrentAminoAcid() return def _expandPreviousRotamer(self): """ Displays the previous rotamer in the chain. @attention: this only works when the GDS is a reduced display style. """ if not self.current_protein: return self.current_protein.protein.traverse_backward() self.setCurrentAminoAcid() return def _centerViewToggled(self, checked): """ Slot for "Center view on current residue" checkbox. """ if checked: self.display_and_recenter() return def _showAllResidues(self, show): """ Slot for "Show all residues" checkbox. """ if not self.current_protein: return print "Show =",show if show: self._expandAllRotamers() else: self._collapseAllRotamers() return def _collapseAllRotamers(self): """ Hides all the rotamers (except the current rotamer). """ self.display_and_recenter() return def _expandAllRotamers(self): """ Displays all the rotamers. """ if not self.current_protein: return self.current_protein.protein.expand_all_rotamers() self.win.glpane.gl_update() return def display_and_recenter(self): """ Recenter the view on the current amino acid selected in the residue combobox (or the sequence editor). All rotamers except the current rotamer are collapsed (hidden). """ if not self.current_protein: return # Uncheck the "Show all residues" checkbox since they are being collapsed. # Disconnect signals so that showAllResiduesCheckBox won't general a signal. self.connect_or_disconnect_signals(isConnect = False) self.showAllResiduesCheckBox.setChecked(False) self.connect_or_disconnect_signals(isConnect = True) self.current_protein.protein.collapse_all_rotamers() # Display the current amino acid and center it in the view if the # "Center view on current residue" is checked. if self.current_aa: self.current_protein.protein.expand_rotamer(self.current_aa) self._update_chi_angles(self.current_aa) if self.recenterViewCheckBox.isChecked(): ca_atom = self.current_aa.get_c_alpha_atom() if ca_atom: self.win.glpane.pov = -ca_atom.posn() self.win.glpane.gl_update() return def _update_chi_angles(self, aa): """ """ angle = aa.get_chi_angle(0) if angle: self.chi1Dial.setEnabled(True) self.chi1Dial.setValue(angle) else: self.chi1Dial.setEnabled(False) self.chi1Dial.setValue(0.0) angle = aa.get_chi_angle(1) if angle: self.chi2Dial.setEnabled(True) self.chi2Dial.setValue(angle) else: self.chi2Dial.setEnabled(False) self.chi2Dial.setValue(0.0) angle = aa.get_chi_angle(2) if angle: self.chi3Dial.setEnabled(True) self.chi3Dial.setValue(angle) else: self.chi3Dial.setEnabled(False) self.chi3Dial.setValue(0.0) angle = aa.get_chi_angle(3) if angle: self.chi4Dial.setEnabled(True) self.chi4Dial.setValue(angle) else: self.chi4Dial.setEnabled(False) self.chi4Dial.setValue(0.0) return def setCurrentAminoAcid(self, aa_index = -1): """ Set the current amino acid to I{aa_index} and update the "Current residue" combobox and the sequence editor. @param aa_index: the amino acid index. If negative, update the PM and sequence editor based on the current aa_index. @type aa_index: int @note: This is the slot for the "Current residue" combobox. """ if not self.current_protein: return if aa_index < 0: aa_index = self.current_protein.protein.get_current_amino_acid_index() if 0: # Debugging statement print"setCurrentAminoAcid(): aa_index=", aa_index self.currentResidueComboBox.setCurrentIndex(aa_index) self.current_protein.protein.set_current_amino_acid_index(aa_index) self.current_aa = self.current_protein.protein.get_current_amino_acid() self.display_and_recenter() self.sequenceEditor.setCursorPosition(aa_index) return def _rotateChiAngle(self, chi, angle): """ Rotate around chi1 angle. """ if not self.current_protein: return if self.current_aa: self.current_protein.protein.expand_rotamer(self.current_aa) self.current_aa.set_chi_angle(chi, angle) self.win.glpane.gl_update() return def _rotateChi1(self, angle): """ Slot for Chi1 dial. """ self._rotateChiAngle(0, angle) self.chi1Dial.updateValueLabel() return def _rotateChi2(self, angle): """ Slot for Chi2 dial. """ self._rotateChiAngle(1, angle) self.chi2Dial.updateValueLabel() return def _rotateChi3(self, angle): """ Slot for Chi3 dial. """ self._rotateChiAngle(2, angle) self.chi3Dial.updateValueLabel() return def _rotateChi4(self, angle): """ Slot for Chi4 dial. @note: this dial is currently hidden and unused. """ self._rotateChiAngle(3, angle) return def _update_UI_do_updates(self): """ Overrides superclass method. @see: Command_PropertyManager._update_UI_do_updates() """ self.current_protein = self.win.assy.getSelectedProteinChunk() if self.current_protein is self.previous_protein: if 0: print "Edit Protein: _update_UI_do_updates() - DO NOTHING." return # It is common that the user will unselect the current protein. # If so, set current_protein to previous_protein so that it # (the previously selected protein) remains the current protein # in the PM and sequence editor. if not self.current_protein: self.current_protein = self.previous_protein return # Update all PM widgets that need to be since something has changed. if 0: print "Edit Protein: _update_UI_do_updates() - UPDATING THE PMGR." self.update_name_field() self.update_length_field() self.sequenceEditor.updateSequence(proteinChunk = self.current_protein) self.update_residue_combobox() # NOTE: Changing the display style of the protein chunks can take some # time. We should put up the wait (hourglass) cursor and restore # before returning. if self.previous_protein: self.previous_protein.setDisplayStyle(self.previous_protein_display_style) self.previous_protein = self.current_protein if self.current_protein: self.previous_protein_display_style = self.current_protein.getDisplayStyle() self.current_protein.setDisplayStyle(diPROTEIN) if self.current_protein: msg = "Editing structure %s." % self.current_protein.name else: msg = "Select a single structure to edit." self.updateMessage(msg) return