class DnaStrand_PropertyManager( DnaOrCnt_PropertyManager): """ The DnaStrand_PropertyManager class provides a Property Manager for the DnaStrand_EditCommand. @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 = "DnaStrand Properties" pmName = title iconPath = "ui/actions/Properties Manager/Strand.png" def __init__( self, win, editCommand ): """ Constructor for the Build DNA property manager. """ #For model changed signal self.previousSelectionParams = None #see self.connect_or_disconnect_signals for comment about this flag self.isAlreadyConnected = False self.isAlreadyDisconnected = False self.sequenceEditor = None self._numberOfBases = 0 self._conformation = 'B-DNA' self.duplexRise = 3.18 self.basesPerTurn = 10 self.dnaModel = 'PAM3' _superclass.__init__( self, win, editCommand) self.showTopRowButtons( PM_DONE_BUTTON | \ PM_WHATS_THIS_BUTTON) self._loadSequenceEditor() msg = "Use resize handles to resize the strand. Use sequence editor"\ "to assign a new sequence or the current one to a file." self.updateMessage(msg) def _addGroupBoxes( self ): """ Add the DNA Property Manager group boxes. """ self._pmGroupBox1 = PM_GroupBox( self, title = "Parameters" ) self._loadGroupBox1( self._pmGroupBox1 ) self._displayOptionsGroupBox = PM_GroupBox( self, title = "Display Options" ) self._loadDisplayOptionsGroupBox( self._displayOptionsGroupBox ) def _loadGroupBox1(self, pmGroupBox): """ Load widgets in group box 4. """ self.nameLineEdit = PM_LineEdit( pmGroupBox, label = "Strand name:", text = "", setAsDefault = False) self.numberOfBasesSpinBox = \ PM_SpinBox( pmGroupBox, label = "Number of bases:", value = self._numberOfBases, setAsDefault = False, minimum = 2, maximum = 10000 ) self.basesPerTurnDoubleSpinBox = \ PM_DoubleSpinBox( pmGroupBox, label = "Bases per turn:", value = self.basesPerTurn, setAsDefault = True, minimum = 8.0, maximum = 20.0, decimals = 2, singleStep = 0.1 ) self.duplexRiseDoubleSpinBox = \ PM_DoubleSpinBox( pmGroupBox, label = "Rise:", value = self.duplexRise, setAsDefault = True, minimum = 2.0, maximum = 4.0, decimals = 3, singleStep = 0.01 ) self.disableStructHighlightingCheckbox = \ PM_CheckBox( pmGroupBox, text = "Don't highlight while editing DNA", widgetColumn = 0, state = Qt.Unchecked, setAsDefault = True, spanWidth = True ) #As of 2008-03-31, the properties such as number of bases will be #editable only by using the resize handles. post FNANO we will support #the self.numberOfBasesSpinBox.setEnabled(False) self.basesPerTurnDoubleSpinBox.setEnabled(False) self.duplexRiseDoubleSpinBox.setEnabled(False) def _loadSequenceEditor(self): """ Temporary code that shows the Sequence editor ..a doc widget docked at the bottom of the mainwindow. The implementation is going to change before 'rattleSnake' product release. As of 2007-11-20: This feature (sequence editor) is waiting for the ongoing dna model work to complete. """ self.sequenceEditor = self.win.createDnaSequenceEditorIfNeeded() self.sequenceEditor.hide() def _loadDisplayOptionsGroupBox(self, pmGroupBox): """ Overrides superclass method. Also loads the color chooser widget. """ self._loadColorChooser(pmGroupBox) _superclass._loadDisplayOptionsGroupBox(self, pmGroupBox) def _connect_showCursorTextCheckBox(self): """ Connect the show cursor text checkbox with user prefs_key. Overrides DnaOrCnt_PropertyManager._connect_showCursorTextCheckBox """ connect_checkbox_with_boolean_pref( self.showCursorTextCheckBox , dnaStrandEditCommand_showCursorTextCheckBox_prefs_key) def _params_for_creating_cursorTextCheckBoxes(self): """ Returns params needed to create various cursor text checkboxes connected to prefs_keys that allow custom cursor texts. @return: A list containing tuples in the following format: ('checkBoxTextString' , preference_key). PM_PrefsCheckBoxes uses this data to create checkboxes with the the given names and connects them to the provided preference keys. (Note that PM_PrefsCheckBoxes puts thes within a GroupBox) @rtype: list @see: PM_PrefsCheckBoxes @see: self._loadDisplayOptionsGroupBox where this list is used. @see: Superclass method which is overridden here -- DnaOrCnt_PropertyManager._params_for_creating_cursorTextCheckBoxes() """ params = \ [ #Format: (" checkbox text", prefs_key) ("Number of bases", dnaStrandEditCommand_cursorTextCheckBox_numberOfBases_prefs_key), ("Number of bases to be changed", dnaStrandEditCommand_cursorTextCheckBox_changedBases_prefs_key) ] return params def getParameters(self): numberOfBases = self.numberOfBasesSpinBox.value() dnaForm = self._conformation dnaModel = self.dnaModel basesPerTurn = self.basesPerTurn duplexRise = self.duplexRise color = self._colorChooser.getColor() return (numberOfBases, dnaForm, dnaModel, basesPerTurn, duplexRise, color ) def setParameters(self, params): """ This is usually called when you are editing an existing structure. Some property manager ui elements then display the information obtained from the object being edited. TODO: - Make this a EditCommand_PM API method? - See also the routines GraphicsMode.setParams or object.setProps ..better to name them all in one style? """ #Set the duplex rise and bases per turn spinbox values. numberOfBases, \ dnaForm, \ dnaModel, \ basesPerTurn, \ duplexRise, \ color = params if numberOfBases is not None: self.numberOfBasesSpinBox.setValue(numberOfBases) if dnaForm is not None: self._conformation = dnaForm if dnaModel is not None: self.dnaModel = dnaModel if duplexRise is not None: self.duplexRiseDoubleSpinBox.setValue(duplexRise) if basesPerTurn is not None: self.basesPerTurnDoubleSpinBox.setValue(basesPerTurn) if color is not None: self._colorChooser.setColor(color) def connect_or_disconnect_signals(self, isConnect): """ Connect or disconnect widget signals sent to their slot methods. This can be overridden in subclasses. By default it does nothing. @param isConnect: If True the widget will send the signals to the slot method. @type isConnect: boolean """ #TODO: This is a temporary fix for a bug. When you invoke a temporary # mode Entering such a temporary mode keeps the signals of #PM from the previous mode connected ( #but while exiting that temporary mode and reentering the #previous mode, it atucally reconnects the signal! This gives rise to #lots of bugs. This needs more general fix in Temporary mode API. # -- Ninad 2008-01-09 (similar comment exists in MovePropertyManager.py if isConnect and self.isAlreadyConnected: if debug_flags.atom_debug: print_compact_stack("warning: attempt to connect widgets"\ "in this PM that are already connected." ) return if not isConnect and self.isAlreadyDisconnected: if debug_flags.atom_debug: print_compact_stack("warning: attempt to disconnect widgets"\ "in this PM that are already disconnected.") return self.isAlreadyConnected = isConnect self.isAlreadyDisconnected = not isConnect if isConnect: change_connect = self.win.connect else: change_connect = self.win.disconnect if self.sequenceEditor: self.sequenceEditor.connect_or_disconnect_signals(isConnect) _superclass.connect_or_disconnect_signals(self, isConnect) change_connect(self.disableStructHighlightingCheckbox, SIGNAL('stateChanged(int)'), self.change_struct_highlightPolicy) change_connect(self.showCursorTextCheckBox, SIGNAL('stateChanged(int)'), self._update_state_of_cursorTextGroupBox) def model_changed(self): """ @see: DnaStrand_EditCommand.model_changed() @see: DnaStrand_EditCommand.hasResizableStructure() """ isStructResizable, why_not = self.editCommand.hasResizableStructure() if not isStructResizable: #disable all widgets if self._pmGroupBox1.isEnabled(): self._pmGroupBox1.setEnabled(False) msg1 = ("Viewing properties of %s <br>") %(self.editCommand.struct.name) msg2 = redmsg("DnaStrand is not resizable. Reason: %s"%(why_not)) self.updateMessage(msg1 + msg2) else: if not self._pmGroupBox1.isEnabled(): self._pmGroupBox1.setEnabled(True) msg1 = ("Viewing properties of %s <br>") %(self.editCommand.struct.name) msg2 = "Use resize handles to resize the strand. Use sequence editor"\ "to assign a new sequence or the current one to a file." self.updateMessage(msg1 + msg2) def show(self): """ Show this PM As of 2007-11-20, it also shows the Sequence Editor widget and hides the history widget. This implementation may change in the near future This method also retrives the name information from the editCommand's structure for its name line edit field. @see: DnaStrand_EditCommand.getStructureName() @see: self.close() """ _superclass.show(self) self._showSequenceEditor() if self.editCommand is not None: name = self.editCommand.getStructureName() if name is not None: self.nameLineEdit.setText(name) def close(self): """ Close this property manager. Also sets the name of the self.editCommand's structure to the one displayed in the line edit field. @see self.show() @see: DnaSegment_EditCommand.setStructureName """ if self.editCommand is not None: name = str(self.nameLineEdit.text()) self.editCommand.setStructureName(name) if self.sequenceEditor: self.sequenceEditor.close() _superclass.close(self) def _showSequenceEditor(self): if self.sequenceEditor: if not self.sequenceEditor.isVisible(): #Show the sequence editor #ATTENTION: the sequence editor also closes (temporarily) the #reports dockwidget (if visible) Its state is later restored when #the sequuence Editor is closed. self.sequenceEditor.show() self.updateSequence() def updateSequence(self): """ Update the sequence string in the sequence editor @see: DnaSequenceEditor.setSequence() @see DnaSequenceEditor._determine_complementSequence() @see: DnaSequenceEditor.setComplementSequence() @see: DnaStrand.getStrandSequenceAndItsComplement() """ #Read in the strand sequence of the selected strand and #show it in the text edit in the sequence editor. ##strand = self.strandListWidget.getPickedItem() if not self.editCommand.hasValidStructure(): return strand = self.editCommand.struct titleString = 'Sequence Editor for ' + strand.name self.sequenceEditor.setWindowTitle(titleString) sequenceString, complementSequenceString = strand.getStrandSequenceAndItsComplement() if sequenceString: sequenceString = QString(sequenceString) sequenceString = sequenceString.toUpper() #Set the initial sequence (read in from the file) self.sequenceEditor.setSequence(sequenceString) #Set the initial complement sequence for DnaSequence editor. #do this independently because 'complementSequenceString' may have #some characters (such as * ) that denote a missing base on the #complementary strand. this information is used by the sequence #editor. See DnaSequenceEditor._determine_complementSequence() #for more details. See also bug 2787 self.sequenceEditor.setComplementSequence(complementSequenceString) def change_struct_highlightPolicy(self,checkedState = False): """ Change the 'highlight policy' of the structure being edited (i.e. self.editCommand.struct) . @param checkedState: The checked state of the checkbox that says 'Don't highlight while editing DNA'. So, it its True, the structure being edited won't get highlighted. @see: DnaStrand.setHighlightPolicy for more comments """ if self.editCommand and self.editCommand.hasValidStructure(): highlight = not checkedState self.editCommand.struct.setHighlightPolicy(highlight = highlight) def _addWhatsThisText(self): """ Add what's this text. Abstract method. """ pass
class InsertNanotube_PropertyManager(DnaOrCnt_PropertyManager): """ The InsertNanotube_PropertyManager class provides a Property Manager for the B{Build > Nanotube > CNT} command. @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 = "Insert Nanotube" pmName = title iconPath = "ui/actions/Tools/Build Structures/InsertNanotube.png" def __init__(self, win, editCommand): """ Constructor for the Nanotube property manager. """ self.endPoint1 = None self.endPoint2 = None self.nanotube = Nanotube() # A 5x5 CNT. _superclass.__init__(self, win, editCommand) self.showTopRowButtons( PM_DONE_BUTTON | \ PM_CANCEL_BUTTON | \ PM_WHATS_THIS_BUTTON) def connect_or_disconnect_signals(self, isConnect): """ Connect or disconnect widget signals sent to their slot methods. This can be overridden in subclasses. By default it does nothing. @param isConnect: If True the widget will send the signals to the slot method. @type isConnect: boolean """ if isConnect: change_connect = self.win.connect else: change_connect = self.win.disconnect change_connect(self.ntTypeComboBox, SIGNAL("currentIndexChanged(const QString&)"), self._ntTypeComboBoxChanged) change_connect(self.chiralityNSpinBox, SIGNAL("valueChanged(int)"), self._chiralityFixup) change_connect(self.chiralityMSpinBox, SIGNAL("valueChanged(int)"), self._chiralityFixup) change_connect(self.endingsComboBox, SIGNAL("currentIndexChanged(const QString&)"), self._endingsComboBoxChanged) # This spin box is currently hidden. change_connect(self.bondLengthDoubleSpinBox, SIGNAL("valueChanged(double)"), self._bondLengthChanged) change_connect(self.showCursorTextCheckBox, SIGNAL('stateChanged(int)'), self._update_state_of_cursorTextGroupBox) def ok_btn_clicked(self): """ Slot for the OK button """ if self.editCommand: self.editCommand.preview_or_finalize_structure(previewing=False) ##env.history.message(self.editCommand.logMessage) self.win.toolsDone() def cancel_btn_clicked(self): """ Slot for the Cancel button. """ if self.editCommand: self.editCommand.cancelStructure() self.win.toolsCancel() def _update_widgets_in_PM_before_show(self): """ Update various widgets in this Property manager. Overrides MotorPropertyManager._update_widgets_in_PM_before_show. The various widgets , (e.g. spinboxes) will get values from the structure for which this propMgr is constructed for (self.editcCntroller.struct) @see: MotorPropertyManager._update_widgets_in_PM_before_show @see: self.show where it is called. """ pass def getFlyoutActionList(self): """ Returns custom actionlist that will be used in a specific mode or editing a feature etc Example: while in movie mode, the _createFlyoutToolBar method calls this. """ #'allActionsList' returns all actions in the flyout toolbar #including the subcontrolArea actions allActionsList = [] #Action List for subcontrol Area buttons. #In this mode there is really no subcontrol area. #We will treat subcontrol area same as 'command area' #(subcontrol area buttons will have an empty list as their command area #list). We will set the Comamnd Area palette background color to the #subcontrol area. subControlAreaActionList = [] self.exitEditCommandAction.setChecked(True) subControlAreaActionList.append(self.exitEditCommandAction) separator = QAction(self.w) separator.setSeparator(True) subControlAreaActionList.append(separator) allActionsList.extend(subControlAreaActionList) #Empty actionlist for the 'Command Area' commandActionLists = [] #Append empty 'lists' in 'commandActionLists equal to the #number of actions in subControlArea for i in range(len(subControlAreaActionList)): lst = [] commandActionLists.append(lst) params = (subControlAreaActionList, commandActionLists, allActionsList) return params def _addGroupBoxes(self): """ Add the Insert Nanotube Property Manager group boxes. """ self._pmGroupBox1 = PM_GroupBox(self, title="Endpoints") self._loadGroupBox1(self._pmGroupBox1) self._pmGroupBox1.hide() self._pmGroupBox2 = PM_GroupBox(self, title="Parameters") self._loadGroupBox2(self._pmGroupBox2) self._displayOptionsGroupBox = PM_GroupBox(self, title="Display Options") self._loadDisplayOptionsGroupBox(self._displayOptionsGroupBox) self._pmGroupBox3 = PM_GroupBox(self, title="Nanotube Distortion") self._loadGroupBox3(self._pmGroupBox3) self._pmGroupBox3.hide() #@ Temporary. self._pmGroupBox4 = PM_GroupBox(self, title="Multi-Walled CNTs") self._loadGroupBox4(self._pmGroupBox4) self._pmGroupBox4.hide() #@ Temporary. self._pmGroupBox5 = PM_GroupBox(self, title="Advanced Options") self._loadGroupBox5(self._pmGroupBox5) self._pmGroupBox5.hide() #@ Temporary. def _loadGroupBox1(self, pmGroupBox): """ Load widgets in group box 1. """ #Following toolbutton facilitates entering a temporary NanotubeLineMode #to create a CNT using endpoints of the specified line. self.specifyCntLineButton = PM_ToolButton( pmGroupBox, text="Specify Endpoints", iconPath="ui/actions/Properties Manager/Pencil.png", spanWidth=True) self.specifyCntLineButton.setCheckable(True) self.specifyCntLineButton.setAutoRaise(True) self.specifyCntLineButton.setToolButtonStyle( Qt.ToolButtonTextBesideIcon) #EndPoint1 and endPoint2 coordinates. These widgets are hidden # as of 2007- 12 - 05 self._endPoint1SpinBoxes = PM_CoordinateSpinBoxes(pmGroupBox, label="End Point 1") self.x1SpinBox = self._endPoint1SpinBoxes.xSpinBox self.y1SpinBox = self._endPoint1SpinBoxes.ySpinBox self.z1SpinBox = self._endPoint1SpinBoxes.zSpinBox self._endPoint2SpinBoxes = PM_CoordinateSpinBoxes(pmGroupBox, label="End Point 2") self.x2SpinBox = self._endPoint2SpinBoxes.xSpinBox self.y2SpinBox = self._endPoint2SpinBoxes.ySpinBox self.z2SpinBox = self._endPoint2SpinBoxes.zSpinBox self._endPoint1SpinBoxes.hide() self._endPoint2SpinBoxes.hide() def _loadGroupBox2(self, pmGroupBox): """ Load widgets in group box 2. """ _ntTypeChoices = ['Carbon', 'Boron Nitride'] self.ntTypeComboBox = \ PM_ComboBox( pmGroupBox, label = "Type:", choices = _ntTypeChoices, setAsDefault = True) self.ntRiseDoubleSpinBox = \ PM_DoubleSpinBox( pmGroupBox, label = "Rise:", value = self.nanotube.getRise(), setAsDefault = True, minimum = 2.0, maximum = 4.0, decimals = 3, singleStep = 0.01 ) self.ntRiseDoubleSpinBox.hide() # Nanotube Length self.ntLengthLineEdit = \ PM_LineEdit( pmGroupBox, label = "Nanotube Length: ", text = "0.0 Angstroms", setAsDefault = False) self.ntLengthLineEdit.setDisabled(True) self.ntLengthLineEdit.hide() # Nanotube diameter self.ntDiameterLineEdit = \ PM_LineEdit( pmGroupBox, label = "Diameter: ", setAsDefault = False) self.ntDiameterLineEdit.setDisabled(True) self.updateNanotubeDiameter() self.chiralityNSpinBox = \ PM_SpinBox( pmGroupBox, label = "Chirality (n):", value = self.nanotube.getChiralityN(), minimum = 2, maximum = 100, setAsDefault = True ) self.chiralityMSpinBox = \ PM_SpinBox( pmGroupBox, label = "Chirality (m):", value = self.nanotube.getChiralityM(), minimum = 0, maximum = 100, setAsDefault = True ) # How about having user prefs for CNT and BNNT bond lengths? # I'm guessing that if the user wants to set these values, they will # do it once and would like those bond length values persist forever. # Need to discuss with others to determine if this spinbox comes out. # --Mark 2008-03-29 self.bondLengthDoubleSpinBox = \ PM_DoubleSpinBox( pmGroupBox, label = "Bond length:", value = self.nanotube.getBondLength(), setAsDefault = True, minimum = 1.0, maximum = 3.0, singleStep = 0.1, decimals = 3, suffix = " Angstroms" ) #self.bondLengthDoubleSpinBox.hide() endingChoices = ["Hydrogen", "None"] # Removed:, "Nitrogen"] self.endingsComboBox= \ PM_ComboBox( pmGroupBox, label = "Endings:", choices = endingChoices, index = 0, setAsDefault = True, spanWidth = False ) def _loadGroupBox3(self, inPmGroupBox): """ Load widgets in group box 3. """ self.zDistortionDoubleSpinBox = \ PM_DoubleSpinBox( inPmGroupBox, label = "Z-distortion:", value = 0.0, setAsDefault = True, minimum = 0.0, maximum = 10.0, singleStep = 0.1, decimals = 3, suffix = " Angstroms" ) self.xyDistortionDoubleSpinBox = \ PM_DoubleSpinBox( inPmGroupBox, label = "XY-distortion:", value = 0.0, setAsDefault = True, minimum = 0.0, maximum = 2.0, singleStep = 0.1, decimals = 3, suffix = " Angstroms" ) self.twistSpinBox = \ PM_SpinBox( inPmGroupBox, label = "Twist:", value = 0, setAsDefault = True, minimum = 0, maximum = 100, # What should maximum be? suffix = " deg/A" ) self.bendSpinBox = \ PM_SpinBox( inPmGroupBox, label = "Bend:", value = 0, setAsDefault = True, minimum = 0, maximum = 360, suffix = " deg" ) def _loadGroupBox4(self, inPmGroupBox): """ Load widgets in group box 4. """ # "Number of Nanotubes" SpinBox self.mwntCountSpinBox = \ PM_SpinBox( inPmGroupBox, label = "Number:", value = 1, setAsDefault = True, minimum = 1, maximum = 10, suffix = " nanotubes" ) self.mwntCountSpinBox.setSpecialValueText("SWNT") # "Spacing" lineedit. self.mwntSpacingDoubleSpinBox = \ PM_DoubleSpinBox( inPmGroupBox, label = "Spacing:", value = 2.46, setAsDefault = True, minimum = 1.0, maximum = 10.0, singleStep = 0.1, decimals = 3, suffix = " Angstroms" ) def _loadGroupBox5(self, pmGroupBox): """ Load widgets in group box 5. """ self._rubberbandLineGroupBox = PM_GroupBox(pmGroupBox, title='Rubber band Line:') ntLineChoices = ['Ladder'] self.ntRubberBandLineDisplayComboBox = \ PM_ComboBox( self._rubberbandLineGroupBox , label = " Display as:", choices = ntLineChoices, setAsDefault = True) self.lineSnapCheckBox = \ PM_CheckBox(self._rubberbandLineGroupBox , text = 'Enable line snap' , widgetColumn = 1, state = Qt.Checked ) def _connect_showCursorTextCheckBox(self): """ Connect the show cursor text checkbox with user prefs_key. Overrides DnaOrCnt_PropertyManager._connect_showCursorTextCheckBox """ connect_checkbox_with_boolean_pref( self.showCursorTextCheckBox, insertNanotubeEditCommand_showCursorTextCheckBox_prefs_key) def _params_for_creating_cursorTextCheckBoxes(self): """ Returns params needed to create various cursor text checkboxes connected to prefs_keys that allow custom cursor texts. @return: A list containing tuples in the following format: ('checkBoxTextString' , preference_key). PM_PrefsCheckBoxes uses this data to create checkboxes with the the given names and connects them to the provided preference keys. (Note that PM_PrefsCheckBoxes puts thes within a GroupBox) @rtype: list @see: PM_PrefsCheckBoxes @see: self._loadDisplayOptionsGroupBox where this list is used. @see: Superclass method which is overridden here -- DnaOrCnt_PropertyManager._params_for_creating_cursorTextCheckBoxes() """ params = \ [ #Format: (" checkbox text", prefs_key) ("Nanotube length", insertNanotubeEditCommand_cursorTextCheckBox_length_prefs_key ), ("Angle", insertNanotubeEditCommand_cursorTextCheckBox_angle_prefs_key ) ] return params def _addToolTipText(self): """ Tool Tip text for widgets in the Insert Nanotube Property Manager. """ pass def _setEndPoints(self): """ Set the two endpoints of the nanotube using the values from the X, Y, Z coordinate spinboxes in the property manager. @note: The group box containing the 2 sets of XYZ spin boxes are currently hidden. """ # First endpoint (origin) of nanotube x1 = self.x1SpinBox.value() y1 = self.y1SpinBox.value() z1 = self.z1SpinBox.value() # Second endpoint (direction vector/axis) of nanotube. x2 = self.x2SpinBox.value() y2 = self.y2SpinBox.value() z2 = self.z2SpinBox.value() if not self.endPoint1: self.endPoint1 = V(x1, y1, z1) if not self.endPoint2: self.endPoint2 = V(x2, y2, z2) self.nanotube.setEndPoints(self.endPoint1, self.endPoint2) # Need arg "recompute=True", which will recompute the second # endpoint (endPoint2) using the nanotube rise. def getParameters(self): """ Return the parameters from this property manager to be used to create the nanotube. @return: A nanotube instance with its attrs set to the current parameters in the property manager. @rtype: L{Nanotube} @see: L{InsertNanotube_EditCommand._gatherParameters} where this is used """ self._setEndPoints() return (self.nanotube) def _ntTypeComboBoxChanged(self, type): """ Slot for the Type combobox. It is called whenever the Type choice is changed. @param inIndex: The new index. @type inIndex: int """ self.nanotube.setType(str(type)) print "Bond Length =", self.nanotube.getBondLength() self.bondLengthDoubleSpinBox.setValue(self.nanotube.getBondLength()) #self.bondLengthDoubleSpinBox.setValue(ntBondLengths[inIndex]) def _bondLengthChanged(self, bondLength): """ Slot for the B{Bond Length} spinbox. """ self.nanotube.setBondLength(bondLength) self.updateNanotubeDiameter() return def _chiralityFixup(self, spinBoxValueJunk=None): """ Slot for several validators for different parameters. This gets called whenever the user changes the n, m chirality values. @param spinBoxValueJunk: This is the Spinbox value from the valueChanged signal. It is not used. We just want to know that the spinbox value has changed. @type spinBoxValueJunk: double or None """ _n, _m = self.nanotube.setChirality(self.chiralityNSpinBox.value(), self.chiralityMSpinBox.value()) #self.n, self.m = self.nanotube.getChirality() self.connect_or_disconnect_signals(isConnect=False) self.chiralityNSpinBox.setValue(_n) self.chiralityMSpinBox.setValue(_m) self.connect_or_disconnect_signals(isConnect=True) self.updateNanotubeDiameter() def updateNanotubeDiameter(self): """ Update the nanotube Diameter lineEdit widget. """ diameterText = "%-7.4f Angstroms" % (self.nanotube.getDiameter()) self.ntDiameterLineEdit.setText(diameterText) # ntRiseDoubleSpinBox is currently hidden. self.ntRiseDoubleSpinBox.setValue(self.nanotube.getRise()) def _endingsComboBoxChanged(self, endings): """ Slot for the B{Ending} combobox. @param endings: The option's text. @type endings: string """ self.nanotube.setEndings(str(endings)) return def _addWhatsThisText(self): """ What's This text for widgets in this Property Manager. """ whatsThis_InsertNanotube_PropertyManager(self) return
class DnaStrand_PropertyManager( DnaOrCnt_PropertyManager): """ The DnaStrand_PropertyManager class provides a Property Manager for the DnaStrand_EditCommand. @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 = "DnaStrand Properties" iconPath = "ui/actions/Properties Manager/Strand.png" def __init__( self, command ): """ Constructor for the Build DNA property manager. """ self.sequenceEditor = None self._numberOfBases = 0 self._conformation = 'B-DNA' self.dnaModel = 'PAM3' _superclass.__init__( self, command) self.showTopRowButtons( PM_DONE_BUTTON | \ PM_WHATS_THIS_BUTTON) return def _addGroupBoxes( self ): """ Add group boxes to this PM. """ self._pmGroupBox1 = PM_GroupBox( self, title = "Parameters" ) self._loadGroupBox1( self._pmGroupBox1 ) self._displayOptionsGroupBox = PM_GroupBox( self, title = "Display Options" ) self._loadDisplayOptionsGroupBox( self._displayOptionsGroupBox ) #Sequence Editor. This is NOT a groupbox, needs cleanup. Doing it here #so that the sequence editor gets connected! Perhaps #superclass should define _loadAdditionalWidgets. -- Ninad2008-10-03 self._loadSequenceEditor() return def _loadGroupBox1(self, pmGroupBox): """ Load widgets in group box 1. """ self.nameLineEdit = PM_LineEdit( pmGroupBox, label = "Name:", text = "", setAsDefault = False) self.numberOfBasesSpinBox = \ PM_SpinBox( pmGroupBox, label = "Number of bases:", value = self._numberOfBases, setAsDefault = False, minimum = 2, maximum = 10000 ) self.disableStructHighlightingCheckbox = \ PM_CheckBox( pmGroupBox, text = "Don't highlight while editing DNA", widgetColumn = 0, state = Qt.Unchecked, setAsDefault = True, spanWidth = True ) #As of 2008-03-31, the properties such as number of bases will be #editable only by using the resize handles. self.numberOfBasesSpinBox.setEnabled(False) return def _loadSequenceEditor(self): """ Temporary code that shows the Sequence editor ..a doc widget docked at the bottom of the mainwindow. The implementation is going to change before 'rattleSnake' product release. As of 2007-11-20: This feature (sequence editor) is waiting for the ongoing dna model work to complete. """ self.sequenceEditor = self.win.createDnaSequenceEditorIfNeeded() self.sequenceEditor.hide() return def _loadDisplayOptionsGroupBox(self, pmGroupBox): """ Overrides superclass method. Also loads the color chooser widget. """ self._loadColorChooser(pmGroupBox) _superclass._loadDisplayOptionsGroupBox(self, pmGroupBox) return def _connect_showCursorTextCheckBox(self): """ Connect the show cursor text checkbox with user prefs_key. Overrides DnaOrCnt_PropertyManager._connect_showCursorTextCheckBox """ connect_checkbox_with_boolean_pref( self.showCursorTextCheckBox , dnaStrandEditCommand_showCursorTextCheckBox_prefs_key) return def _params_for_creating_cursorTextCheckBoxes(self): """ Returns params needed to create various cursor text checkboxes connected to prefs_keys that allow custom cursor texts. @return: A list containing tuples in the following format: ('checkBoxTextString' , preference_key). PM_PrefsCheckBoxes uses this data to create checkboxes with the the given names and connects them to the provided preference keys. (Note that PM_PrefsCheckBoxes puts thes within a GroupBox) @rtype: list @see: PM_PrefsCheckBoxes @see: self._loadDisplayOptionsGroupBox where this list is used. @see: Superclass method which is overridden here -- DnaOrCnt_PropertyManager._params_for_creating_cursorTextCheckBoxes() """ params = \ [ #Format: (" checkbox text", prefs_key) ("Number of bases", dnaStrandEditCommand_cursorTextCheckBox_numberOfBases_prefs_key), ("Number of bases to be changed", dnaStrandEditCommand_cursorTextCheckBox_changedBases_prefs_key) ] return params def getParameters(self): name = self.nameLineEdit.text() numberOfBases = self.numberOfBasesSpinBox.value() dnaForm = self._conformation dnaModel = self.dnaModel color = self._colorChooser.getColor() return (numberOfBases, dnaForm, dnaModel, color, name ) def setParameters(self, params): """ This is usually called when you are editing an existing structure. It also gets called when selecting a new strand (within this command). Some property manager ui elements then display the information obtained from the object being edited. TODO: - Make this a EditCommand_PM API method? - See also the routines GraphicsMode.setParams or object.setProps ..better to name them all in one style? """ numberOfBases, \ dnaForm, \ dnaModel, \ color, \ name = params if numberOfBases is not None: self.numberOfBasesSpinBox.setValue(numberOfBases) if dnaForm is not None: self._conformation = dnaForm if dnaModel is not None: self.dnaModel = dnaModel if color is not None: self._colorChooser.setColor(color) if name: # Minimal test. Should add a validator. --Mark 2008-12-16 self.nameLineEdit.setText(name) # This gets called when we enter the command *and* when selecting a new # strand. In either case, we must update the sequence in the sequenece # editor. Fixes bug 2951. --Mark 2008-12-16 if self.command and self.command.hasValidStructure(): #print "setParameters(): loading sequence in sequence editor for ", name self.updateSequence(strand = self.command.struct) return def connect_or_disconnect_signals(self, isConnect): """ Connect or disconnect widget signals sent to their slot methods. This can be overridden in subclasses. By default it does nothing. @param isConnect: If True the widget will send the signals to the slot method. @type isConnect: boolean """ #TODO: This is a temporary fix for a bug. When you invoke a temporary # mode Entering such a temporary mode keeps the signals of #PM from the previous mode connected ( #but while exiting that temporary mode and reentering the #previous mode, it atucally reconnects the signal! This gives rise to #lots of bugs. This needs more general fix in Temporary mode API. # -- Ninad 2008-01-09 (similar comment exists in MovePropertyManager.py if isConnect and self.isAlreadyConnected: if debug_flags.atom_debug: print_compact_stack("warning: attempt to connect widgets"\ "in this PM that are already connected." ) return if not isConnect and self.isAlreadyDisconnected: if debug_flags.atom_debug: print_compact_stack("warning: attempt to disconnect widgets"\ "in this PM that are already disconnected.") return self.isAlreadyConnected = isConnect self.isAlreadyDisconnected = not isConnect if isConnect: change_connect = self.win.connect else: change_connect = self.win.disconnect if self.sequenceEditor: self.sequenceEditor.connect_or_disconnect_signals(isConnect) _superclass.connect_or_disconnect_signals(self, isConnect) change_connect(self.disableStructHighlightingCheckbox, SIGNAL('stateChanged(int)'), self.change_struct_highlightPolicy) change_connect(self.showCursorTextCheckBox, SIGNAL('stateChanged(int)'), self._update_state_of_cursorTextGroupBox) change_connect(self.nameLineEdit, SIGNAL("editingFinished()"), self._nameChanged) return def _update_UI_do_updates(self): """ @see: Command_PropertyManager. _update_UI_do_updates() @see: DnaStrand_EditCommand.command_update_UI() @see: DnaStrand_EditCommand.hasResizableStructure() """ if not self.command.hasValidStructure(): print "DnaStrand not a valid structure." self._pmGroupBox1.setEnabled(False) self._displayOptionsGroupBox.setEnabled(False) self.sequenceEditor.updateSequence(strand = " ") self.sequenceEditor.setEnabled(False) self.nameLineEdit.setText("") self.numberOfBasesSpinBox.setValue(0) return else: self._pmGroupBox1.setEnabled(True) self._displayOptionsGroupBox.setEnabled(True) self.sequenceEditor.setEnabled(True) isStructResizable, why_not = self.command.hasResizableStructure() if not isStructResizable: #disable all widgets if self._pmGroupBox1.isEnabled(): self._pmGroupBox1.setEnabled(False) msg1 = ("Attention: ") % (self.command.struct.name) msg2 = redmsg("DnaStrand <b>%s</b> is not resizable. Reason: %s" % \ (self.command.struct.name, why_not)) self.updateMessage(msg1 + msg2) else: if not self._pmGroupBox1.isEnabled(): self._pmGroupBox1.setEnabled(True) msg1 = ("Editing <b>%s</b>. ") % (self.command.struct.name) msg2 = "Use resize handles to resize the strand. "\ "Use the <i>Sequence Editor</i> to edit the sequence." self.updateMessage(msg1 + msg2) return def close(self): """ Close this property manager. Also sets the name of the self.command's structure to the one displayed in the line edit field. @see self.show() @see: DnaSegment_EditCommand.setStructureName """ if self.command is not None: name = str(self.nameLineEdit.text()) self.command.setStructureName(name) if self.sequenceEditor: self.sequenceEditor.close() _superclass.close(self) return def updateSequence(self, strand = None): """ Public method provided for convenience. If any callers outside of this command need to update the sequence in the sequence editor, they can simply do DnaStrand_ProprtyManager.updateSequence() rather than DnaStrand_ProprtyManager.sequenceEditor.updateSequence() @see: Ui_DnaSequenceEditor.updateSequence() """ if self.sequenceEditor: self.sequenceEditor.updateSequence(strand = strand) return def change_struct_highlightPolicy(self,checkedState = False): """ Change the 'highlight policy' of the structure being edited (i.e. self.command.struct) . @param checkedState: The checked state of the checkbox that says 'Don't highlight while editing DNA'. So, it its True, the structure being edited won't get highlighted. @see: DnaStrand.setHighlightPolicy for more comments """ if self.command and self.command.hasValidStructure(): highlight = not checkedState self.command.struct.setHighlightPolicy(highlight = highlight) return def _addWhatsThisText(self): """ Add what's this text. Abstract method. """ pass def _nameChanged(self): # Added by Mark. 2008-12-16 """ Slot for "Name" field. Changes the name of the strand if the user types in a new name. @warning: this lacks a validator. User can type in a name with invalid characters. """ if not self.command.hasValidStructure(): return name = str(self.nameLineEdit.text()) if not name: # Minimal test. Should add a validator. Ask Bruce for example validator code somewhere. --Mark 2008-12-16 if self.command.hasValidStructure(): self.nameLineEdit.setText(self.command.getStructureName()) return self.command.setStructureName(name) self._update_UI_do_updates() # Updates the message box. return
class DnaGeneratorPropertyManager(PM_Dialog, DebugMenuMixin): """ The DnaGeneratorPropertyManager class provides a Property Manager for the "Build > Atoms" command. @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 @ivar validSymbols: Miscellaneous symbols that may appear in the sequence (but are ignored). The hyphen '-' is a special case that must be dealt with individually; it is not included because it can confuse regular expressions. @type validSymbols: QString """ title = "DNA" pmName = title iconPath = "ui/actions/Tools/Build Structures/DNA.png" validSymbols = QString(' <>~!@#%&_+`=$*()[]{}|^\'"\\.;:,/?') # The following class variables guarantee the UI's menu items # are synchronized with their action code. The arrays should # not be changed, unless an item is removed or inserted. # Changes should be made via only the _action... variables. # e.g., Change _action_Complement from "Complement" # to "Complement Sequences". The menu item will # change and its related code will need no update. _action_Complement = "Complement" _action_Reverse = "Reverse" _action_RemoveUnrecognized = 'Remove unrecognized letters' _action_ConvertUnrecognized = 'Convert unrecognized letters to "N"' _actionChoices = [ "Action", "---", _action_Complement, _action_Reverse, _action_RemoveUnrecognized, _action_ConvertUnrecognized ] _modeltype_PAM3 = "PAM3" _modeltype_PAM5 = "PAM5" _modeltype_Atomistic = "Atomistic" # deprecated _modelChoices = [_modeltype_PAM3, _modeltype_PAM5] def __init__(self): """ Constructor for the DNA Generator property manager. """ PM_Dialog.__init__(self, self.pmName, self.iconPath, self.title) DebugMenuMixin._init1(self) msg = "Edit the DNA parameters and select <b>Preview</b> to \ preview the structure. Click <b>Done</b> to insert it into \ the model." # This causes the "Message" box to be displayed as well. # setAsDefault=True causes this message to be reset whenever # this PM is (re)displayed via show(). Mark 2007-06-01. self.MessageGroupBox.insertHtmlMessage(msg, setAsDefault=True) def _addGroupBoxes(self): """ Add the DNA Property Manager group boxes. """ self._pmGroupBox1 = PM_GroupBox(self, title="Strand Sequence") self._loadGroupBox1(self._pmGroupBox1) self._pmGroupBox2 = PM_GroupBox(self, title="DNA Parameters") self._loadGroupBox2(self._pmGroupBox2) self._pmGroupBox3 = PM_GroupBox(self, title="Endpoints") self._loadGroupBox3(self._pmGroupBox3) def _loadGroupBox1(self, pmGroupBox): """ Load widgets in group box 1. """ # Duplex Length self.duplexLengthSpinBox = \ PM_DoubleSpinBox( pmGroupBox, label = "Duplex Length: ", value = 0, setAsDefault = False, minimum = 0, maximum = 34000, singleStep = self.getDuplexRise("B-DNA"), decimals = 3, suffix = ' Angstroms') self.connect(self.duplexLengthSpinBox, SIGNAL("valueChanged(double)"), self.duplexLengthChanged) # Strand Length self.strandLengthSpinBox = \ PM_SpinBox( pmGroupBox, label = "Strand Length :", value = 0, setAsDefault = False, minimum = 0, maximum = 10000, suffix = ' bases' ) self.connect(self.strandLengthSpinBox, SIGNAL("valueChanged(int)"), self.strandLengthChanged) # New Base choices newBaseChoices = [] for theBase in basesDict.keys(): newBaseChoices = newBaseChoices \ + [ theBase + ' (' \ + basesDict[theBase]['Name'] + ')' ] try: defaultBaseChoice = basesDict.keys().index('N') except: defaultBaseChoice = 0 # Strand Sequence self.sequenceTextEdit = \ PM_TextEdit( pmGroupBox, label = "", spanWidth = True ) self.sequenceTextEdit.setCursorWidth(2) self.sequenceTextEdit.setWordWrapMode(QTextOption.WrapAnywhere) self.connect(self.sequenceTextEdit, SIGNAL("textChanged()"), self.sequenceChanged) self.connect(self.sequenceTextEdit, SIGNAL("cursorPositionChanged()"), self.cursorPosChanged) # Actions self.actionsComboBox = \ PM_ComboBox( pmGroupBox, label = '', choices = self._actionChoices, index = 0, setAsDefault = True, spanWidth = True ) # If SIGNAL("activate(const QString&)") is used, we get a TypeError. # This is a bug that needs Bruce. Using currentIndexChanged(int) as # a workaround, but there is still a bug when the "Reverse" action # is selected. Mark 2007-08-15 self.connect(self.actionsComboBox, SIGNAL("currentIndexChanged(int)"), self.actionsComboBoxChanged) def _loadGroupBox2(self, pmGroupBox): """ Load widgets in group box 2. """ self.modelComboBox = \ PM_ComboBox( pmGroupBox, label = "Model :", choices = self._modelChoices, setAsDefault = True) self.conformationComboBox = \ PM_ComboBox( pmGroupBox, label = "Conformation :", choices = ["B-DNA"], setAsDefault = True) self.connect(self.conformationComboBox, SIGNAL("currentIndexChanged(int)"), self.conformationComboBoxChanged) self.basesPerTurnComboBox= \ PM_ComboBox( pmGroupBox, label = "Bases Per Turn :", choices = ["10.0", "10.5", "10.67"], setAsDefault = True) # I may decide to reintroduce "base-pair chunks" at a later time. # Please talk to me if you have a strong feeling about including # this. Mark 2007-08-19. createChoices = ["Strand chunks", \ "Single chunk" ] #@ "Base-pair chunks"] self.createComboBox = \ PM_ComboBox( pmGroupBox, label = "Create :", choices = createChoices, index = 0, setAsDefault = True, spanWidth = False ) def _loadGroupBox3(self, pmGroupBox): """ Load widgets in group box 3. """ self._endPoint1GroupBox = PM_GroupBox(pmGroupBox, title="Endpoint1") self._endPoint2GroupBox = PM_GroupBox(pmGroupBox, title="Endpoint2") # Point 1 self.x1SpinBox = \ PM_DoubleSpinBox( self._endPoint1GroupBox, label = \ "ui/actions/Properties Manager/X_Coordinate.png", value = 0, setAsDefault = True, minimum = -100.0, maximum = 100.0, decimals = 3, suffix = ' Angstroms') self.y1SpinBox = \ PM_DoubleSpinBox( self._endPoint1GroupBox, label = \ "ui/actions/Properties Manager/Y_Coordinate.png", value = 0, setAsDefault = True, minimum = -100.0, maximum = 100.0, decimals = 3, suffix = ' Angstroms') self.z1SpinBox = \ PM_DoubleSpinBox( self._endPoint1GroupBox, label = \ "ui/actions/Properties Manager/Z_Coordinate.png", value = 0, setAsDefault = True, minimum = -100.0, maximum = 100.0, decimals = 3, suffix = ' Angstroms') # Point 2 self.x2SpinBox = \ PM_DoubleSpinBox( self._endPoint2GroupBox, label = \ "ui/actions/Properties Manager/X_Coordinate.png", value = 10.0, setAsDefault = True, minimum = -100.0, maximum = 100.0, decimals = 3, suffix = ' Angstroms') self.y2SpinBox = \ PM_DoubleSpinBox( self._endPoint2GroupBox, label = \ "ui/actions/Properties Manager/Y_Coordinate.png", value = 0, setAsDefault = True, minimum = -100.0, maximum = 100.0, decimals = 3, suffix = ' Angstroms') self.z2SpinBox = \ PM_DoubleSpinBox( self._endPoint2GroupBox, label = \ "ui/actions/Properties Manager/Z_Coordinate.png", value = 0, setAsDefault = True, minimum = -100.0, maximum = 100.0, decimals = 3, suffix = ' Angstroms') def _addWhatsThisText(self): """ What's This text for some of the widgets in the DNA Property Manager. @note: Many PM widgets are still missing their "What's This" text. """ self.conformationComboBox.setWhatsThis("""<b>Conformation</b> <p>There are three DNA geometries, A-DNA, B-DNA, and Z-DNA. Only B-DNA and Z-DNA are currently supported.</p>""") self.sequenceTextEdit.setWhatsThis("""<b>Strand Sequence</b> <p>Type in the strand sequence you want to generate here (5' => 3')<br> <br> Recognized base letters:<br> <br> A = Adenosine<br> C = Cytosine<br> G = Guanosine<br> T = Thymidine<br> N = aNy base<br> X = Unassigned<br> <br> Other base letters currently recognized:<br> <br> B = C,G, or T<br> D = A,G, or T<br> H = A,C, or T<br> V = A,C, or G<br> R = A or G (puRine)<br> Y = C or T (pYrimidine)<br> K = G or T (Keto)<br> M = A or C (aMino)<br> S = G or C (Strong -3H bonds)<br> W = A or T (Weak - 2H bonds)<br> </p>""") self.actionsComboBox.setWhatsThis("""<b>Action</b> <p>Select an action to perform on the sequence.</p>""") def conformationComboBoxChanged(self, inIndex): """ Slot for the Conformation combobox. It is called whenever the Conformation choice is changed. @param inIndex: The new index. @type inIndex: int """ self.basesPerTurnComboBox.clear() conformation = self.conformationComboBox.currentText() if conformation == "B-DNA": self.basesPerTurnComboBox.insertItem(0, "10.0") self.basesPerTurnComboBox.insertItem(1, "10.5") self.basesPerTurnComboBox.insertItem(2, "10.67") #10.5 is the default value for Bases per turn. #So set the current index to 1 self.basesPerTurnComboBox.setCurrentIndex(1) elif conformation == "Z-DNA": self.basesPerTurnComboBox.insertItem(0, "12.0") elif inIndex == -1: # Caused by clear(). This is tolerable for now. Mark 2007-05-24. conformation = "B-DNA" # Workaround for "Restore Defaults". pass else: msg = redmsg("conformationComboBoxChanged(): \ Error - unknown DNA conformation. Index = " + inIndex) env.history.message(msg) self.duplexLengthSpinBox.setSingleStep( self.getDuplexRise(conformation)) def modelComboBoxChanged(self, inIndex): """ Slot for the Model combobox. It is called whenever the Model choice is changed. @param inIndex: The new index. @type inIndex: int """ conformation = self._modelChoices[inIndex] self.disconnect(self.conformationComboBox, SIGNAL("currentIndexChanged(int)"), self.conformationComboBoxChanged) self.conformationComboBox.clear() # Generates signal! if conformation == self._modeltype_PAM3: self.conformationComboBox.addItem("B-DNA") elif conformation == self._modeltype_PAM5: self.conformationComboBox.addItem("B-DNA") elif conformation == self._modeltype_Atomistic: self.conformationComboBox.addItem("B-DNA") self.conformationComboBox.addItem("Z-DNA") elif inIndex == -1: # Caused by clear(). This is tolerable for now. Mark 2007-05-24. pass else: msg = "Error - unknown model representation. Index = " + inIndex env.history.message(redmsg(msg)) self.connect(self.conformationComboBox, SIGNAL("currentIndexChanged(int)"), self.conformationComboBoxChanged) # GroupBox3 slots (and other methods) supporting the Strand Sequence groupbox. def getDuplexRise(self, inConformation): """ Return the 'rise' between base pairs of the specified DNA type (conformation). @param inConformation: The current conformation. @type inConformation: int """ return dnaDict[str(inConformation)]['DuplexRise'] def synchronizeLengths(self): """ Guarantees the values of the duplex length and strand length spinboxes agree with the strand sequence (textedit). """ self.updateStrandLength() self.updateDuplexLength() return # Added :jbirac 20070613: def duplexLengthChanged(self, inDuplexLength): """ Slot for the duplex length spinbox, called whenever the value of the Duplex Length spinbox changes. @param inDuplexLength: The duplex length. @type inDuplexLength: float """ conformation = self.conformationComboBox.currentText() duplexRise = self.getDuplexRise(conformation) newStrandLength = inDuplexLength / duplexRise + 0.5 newStrandLength = int(newStrandLength) self.strandLengthChanged(newStrandLength) def updateDuplexLength(self): # Added :jbirac 20070615: """ Update the Duplex Length spinbox; always the length of the strand sequence multiplied by the 'rise' of the duplex. This method is called by slots of other controls (i.e., this itself is not a slot.) """ conformation = self.conformationComboBox.currentText() newDuplexLength = self.getDuplexRise( conformation ) \ * self.getSequenceLength() self.disconnect(self.duplexLengthSpinBox, SIGNAL("valueChanged(double)"), self.duplexLengthChanged) self.duplexLengthSpinBox.setValue(newDuplexLength) self.connect(self.duplexLengthSpinBox, SIGNAL("valueChanged(double)"), self.duplexLengthChanged) # Renamed from length_changed :jbirac 20070613: def strandLengthChanged(self, inStrandLength): """ Slot for the Strand Length spin box, called whenever the value of the Strand Length spin box changes. @param inStrandLength: The number of bases in the strand sequence. @type inStrandLength: int """ theSequence = self.getPlainSequence() sequenceLen = len(theSequence) lengthChange = inStrandLength - self.getSequenceLength() # Preserve the cursor's position/selection cursor = self.sequenceTextEdit.textCursor() #cursorPosition = cursor.position() selectionStart = cursor.selectionStart() selectionEnd = cursor.selectionEnd() if inStrandLength < 0: return # Should never happen. if lengthChange < 0: # If length is less than the previous length, # simply truncate the current sequence. theSequence.chop(-lengthChange) elif lengthChange > 0: # If length has increased, add the correct number of base # letters to the current strand sequence. numNewBases = lengthChange # Get current base selected in combobox. chosenBase = 'X' # Unassigned. basesToAdd = chosenBase * numNewBases theSequence.append(basesToAdd) else: env.history.message( orangemsg("strandLengthChanged(): Length has not changed.")) self.setSequence(theSequence) return # Renamed from updateLength :jbirac 20070613: def updateStrandLength(self): """ Update the Strand Length spinbox; always the length of the strand sequence. """ self.disconnect(self.strandLengthSpinBox, SIGNAL("valueChanged(int)"), self.strandLengthChanged) self.strandLengthSpinBox.setValue(self.getSequenceLength()) self.connect(self.strandLengthSpinBox, SIGNAL("valueChanged(int)"), self.strandLengthChanged) return def sequenceChanged(self): """ Slot for the Strand Sequence textedit widget. Assumes the sequence changed directly by user's keystroke in the textedit. Other methods... """ cursorPosition = self.getCursorPosition() theSequence = self.getPlainSequence() # Disconnect while we edit the sequence. self.disconnect(self.sequenceTextEdit, SIGNAL("textChanged()"), self.sequenceChanged) # How has the text changed? if theSequence.length() == 0: # There is no sequence. self.updateStrandLength() self.updateDuplexLength() else: # Insert the sequence; it will be "stylized" by setSequence(). self.setSequence(theSequence) # Reconnect to respond when the sequence is changed. self.connect(self.sequenceTextEdit, SIGNAL("textChanged()"), self.sequenceChanged) self.synchronizeLengths() return def getPlainSequence(self, inOmitSymbols=False): """ Returns a plain text QString (without HTML stylization) of the current sequence. All characters are preserved (unless specified explicitly), including valid base letters, punctuation symbols, whitespace and invalid letters. @param inOmitSymbols: Omits characters listed in self.validSymbols. @type inOmitSymbols: bool @return: The current DNA sequence in the PM. @rtype: QString """ outSequence = self.sequenceTextEdit.toPlainText() if inOmitSymbols: # This may look like a sloppy piece of code, but Qt's QRegExp # class makes it pretty tricky to remove all punctuation. theString = '[<>' \ + str( QRegExp.escape(self.validSymbols) ) \ + ']|-' outSequence.remove(QRegExp(theString)) return outSequence def stylizeSequence(self, inSequence): """ Converts a plain text string of a sequence (including optional symbols) to an HTML rich text string. @param inSequence: A DNA sequence. @type inSequence: QString @return: The sequence. @rtype: QString """ outSequence = str(inSequence) # Verify that all characters (bases) in the sequence are "valid". invalidSequence = False basePosition = 0 sequencePosition = 0 invalidStartTag = "<b><font color=black>" invalidEndTag = "</b>" previousChar = chr(1) # Null character; may be revised. # Some characters must be substituted to preserve # whitespace and tags in HTML code. substituteDict = {' ': ' ', '<': '<', '>': '>'} while basePosition < len(outSequence): theSeqChar = outSequence[basePosition] if (theSeqChar in basesDict or theSeqChar in self.validSymbols): # Close any preceding invalid sequence segment. if invalidSequence == True: outSequence = outSequence[:basePosition] \ + invalidEndTag \ + outSequence[basePosition:] basePosition += len(invalidEndTag) invalidSequence = False # Color the valid characters. if theSeqChar != previousChar: # We only need to insert 'color' tags in places where # the adjacent characters are different. if theSeqChar in basesDict: theTag = '<font color=' \ + basesDict[ theSeqChar ]['Color'] \ + '>' elif not previousChar in self.validSymbols: # The character is a 'valid' symbol to be greyed # out. Only one 'color' tag is needed for a # group of adjacent symbols. theTag = '<font color=dimgrey>' else: theTag = '' outSequence = outSequence[:basePosition] \ + theTag + outSequence[basePosition:] basePosition += len(theTag) # Any <space> character must be substituted with an # ASCII code tag because the HTML engine will collapse # whitespace to a single <space> character; whitespace # is truncated from the end of HTML by default. # Also, many symbol characters must be substituted # because they confuse the HTML syntax. #if str( outSequence[basePosition] ) in substituteDict: if outSequence[basePosition] in substituteDict: #theTag = substituteDict[theSeqChar] theTag = substituteDict[outSequence[basePosition]] outSequence = outSequence[:basePosition] \ + theTag \ + outSequence[basePosition + 1:] basePosition += len(theTag) - 1 else: # The sequence character is invalid (but permissible). # Tags (e.g., <b> and </b>) must be inserted at both the # beginning and end of a segment of invalid characters. if invalidSequence == False: outSequence = outSequence[:basePosition] \ + invalidStartTag \ + outSequence[basePosition:] basePosition += len(invalidStartTag) invalidSequence = True basePosition += 1 previousChar = theSeqChar #basePosition += 1 # Specify that theSequence is definitely HTML format, because # Qt can get confused between HTML and Plain Text. outSequence = "<html>" + outSequence outSequence += "</html>" return outSequence def setSequence(self, inSequence, inStylize=True, inRestoreCursor=True): """ Replace the current strand sequence with the new sequence text. @param inSequence: The new sequence. @type inSequence: QString @param inStylize: If True, inSequence will be converted from a plain text string (including optional symbols) to an HTML rich text string. @type inStylize: bool @param inRestoreCursor: Not implemented yet. @type inRestoreCursor: bool @attention: Signals/slots must be managed before calling this method. The textChanged() signal will be sent to any connected widgets. """ cursor = self.sequenceTextEdit.textCursor() selectionStart = cursor.selectionStart() selectionEnd = cursor.selectionEnd() if inStylize: inSequence = self.stylizeSequence(inSequence) self.sequenceTextEdit.insertHtml(inSequence) if inRestoreCursor: cursor.setPosition(min(selectionStart, self.getSequenceLength()), QTextCursor.MoveAnchor) cursor.setPosition(min(selectionEnd, self.getSequenceLength()), QTextCursor.KeepAnchor) self.sequenceTextEdit.setTextCursor(cursor) return def getSequenceLength(self): """ Returns the number of characters in the strand sequence textedit widget. """ theSequence = self.getPlainSequence(inOmitSymbols=True) outLength = theSequence.length() return outLength def getCursorPosition(self): """ Returns the cursor position in the strand sequence textedit widget. """ cursor = self.sequenceTextEdit.textCursor() return cursor.position() def cursorPosChanged(self): """ Slot called when the cursor position changes. """ cursor = self.sequenceTextEdit.textCursor() if 0: env.history.message( greenmsg("cursorPosChanged: Selection (" + str(cursor.selectionStart()) + " thru " + str(cursor.selectionEnd()) + ')')) return def actionsComboBoxChanged(self, inIndex): """ Slot for the Actions combobox. It is called whenever the Action choice is changed. @param inIndex: The index of the selected action choice. @type inIndex: int """ if inIndex == 0: # Very important. return actionName = str(self.actionsComboBox.currentText()) self.actionsComboBox.setCurrentIndex(0) # Generates signal! self.invokeAction(actionName) return def invokeAction(self, inActionName): """ Applies an action on the current sequence displayed in the PM. @param inActionName: The action name. @type inActionName: str @return: The sequence after the action has been applied. @rtype: str """ sequence, allKnown = self._getSequence() outResult = "" if inActionName == self._action_Complement: outResult = getComplementSequence(sequence) elif inActionName == self._action_Reverse: outResult = getReverseSequence(sequence) elif inActionName == self._action_ConvertUnrecognized: outResult = replaceUnrecognized(sequence, replaceBase='N') self.setSequence(outResult) elif inActionName == self._action_RemoveUnrecognized: outResult = replaceUnrecognized(sequence, replaceBase='') self.setSequence(outResult) return
class NanotubeGeneratorPropertyManager(PM_Dialog): """ The NanotubeGeneratorPropertyManager class provides a Property Manager for the "Build > Nanotube" command. """ # The title that appears in the property manager header. title = "Nanotube" # The name of this property manager. This will be set to # the name of the PropMgr (this) object via setObjectName(). pmName = title # The relative path to PNG file that appears in the header. iconPath = "ui/actions/Tools/Build Structures/Nanotube.png" def __init__(self): """Construct the Graphene Property Manager. """ PM_Dialog.__init__(self, self.pmName, self.iconPath, self.title) #@self.addGroupBoxes() #@self.add_whats_this_text() self.updateMessageGroupBox() def updateMessageGroupBox(self): msg = "" # A (4, 4) tube is stable, but a (3, 3) has not been seen in # isolation. Circumference of a (4, 4) tube is about 6.93. xOffset = self.n + self.m * math.cos(math.pi/3.0) yOffset = self.m * math.sin(math.pi/3.0) circumference = math.sqrt(xOffset * xOffset + yOffset * yOffset) if (circumference < 6.5): msg = "Warning: Small diameter nanotubes may be unstable, \ and may give unexpected results when minimized.<p>" msg = msg + "Edit the Nanotube parameters and select <b>Preview</b> to \ preview the structure. Click <b>Done</b> to insert it into the model." # This causes the "Message" box to be displayed as well. # setAsDefault=True causes this message to be reset whenever # this PropMgr is (re)displayed via show(). Mark 2007-06-01. self.MessageGroupBox.insertHtmlMessage(msg, setAsDefault=True) def _addGroupBoxes(self): """ Add the 3 group boxes to the Nanotube Property Manager dialog. """ self.pmGroupBox1 = \ PM_GroupBox( self, title = "Nanotube Parameters" ) self.pmGroupBox2 = \ PM_GroupBox( self, title = "Nanotube Distortion" ) self.pmGroupBox3 = \ PM_GroupBox( self, title = "Multi-Walled Nanotubes" ) # Add group box widgets. self._loadGroupBox1(self.pmGroupBox1) self._loadGroupBox2(self.pmGroupBox2) self._loadGroupBox3(self.pmGroupBox3) def _loadGroupBox1(self, inPmGroupBox): """ Load widgets in group box 1. """ memberChoices = ["Carbon", "Boron Nitride"] self.typeComboBox= \ PM_ComboBox( inPmGroupBox, label = "Type :", choices = memberChoices, index = 0, setAsDefault = True, spanWidth = False ) self.connect( self.typeComboBox, SIGNAL("currentIndexChanged(int)"), self.nt_type_changed) self.lengthField = \ PM_DoubleSpinBox( inPmGroupBox, label = "Length :", value = 20.0, setAsDefault = True, minimum = 1.0, maximum = 1000.0, singleStep = 1.0, decimals = 3, suffix = " Angstroms" ) self.n = 5 self.chiralityNSpinBox = \ PM_SpinBox( inPmGroupBox, label = "Chirality (n) :", value = self.n, setAsDefault = True ) self.connect(self.chiralityNSpinBox, SIGNAL("valueChanged(int)"), self.chirality_fixup) self.m = 5 self.chiralityMSpinBox = \ PM_SpinBox( inPmGroupBox, label = "Chirality (m) :", value = self.m, setAsDefault = True ) self.connect(self.chiralityMSpinBox, SIGNAL("valueChanged(int)"), self.chirality_fixup) self.bondLengthField = \ PM_DoubleSpinBox( inPmGroupBox, label = "Bond Length :", value = CC_GRAPHITIC_BONDLENGTH, setAsDefault = True, minimum = 1.0, maximum = 3.0, singleStep = 0.1, decimals = 3, suffix = " Angstroms" ) endingChoices = ["None", "Hydrogen", "Nitrogen"] self.endingsComboBox= \ PM_ComboBox( inPmGroupBox, label = "Endings :", choices = endingChoices, index = 0, setAsDefault = True, spanWidth = False ) def _loadGroupBox2(self, inPmGroupBox): """ Load widgets in group box 2. """ self.zDistortionField = \ PM_DoubleSpinBox( inPmGroupBox, label = "Z-distortion :", value = 0.0, setAsDefault = True, minimum = 0.0, maximum = 10.0, singleStep = 0.1, decimals = 3, suffix = " Angstroms" ) self.xyDistortionField = \ PM_DoubleSpinBox( inPmGroupBox, label = "XY-distortion :", value = 0.0, setAsDefault = True, minimum = 0.0, maximum = 2.0, singleStep = 0.1, decimals = 3, suffix = " Angstroms" ) self.twistSpinBox = \ PM_SpinBox( inPmGroupBox, label = "Twist :", value = 0, setAsDefault = True, minimum = 0, maximum = 100, # What should maximum be? suffix = " deg/A" ) self.bendSpinBox = \ PM_SpinBox( inPmGroupBox, label = "Bend :", value = 0, setAsDefault = True, minimum = 0, maximum = 360, suffix = " deg" ) def _loadGroupBox3(self, inPmGroupBox): """ Load widgets in group box 3. """ # "Number of Nanotubes" SpinBox self.mwntCountSpinBox = \ PM_SpinBox( inPmGroupBox, label = "Number :", value = 1, setAsDefault = True, minimum = 1, maximum = 10, suffix = " nanotubes" ) self.mwntCountSpinBox.setSpecialValueText("SWNT") # "Spacing" lineedit. self.mwntSpacingField = \ PM_DoubleSpinBox( inPmGroupBox, label = "Spacing :", value = 2.46, setAsDefault = True, minimum = 1.0, maximum = 10.0, singleStep = 0.1, decimals = 3, suffix = " Angstroms" ) def _addWhatsThisText(self): """ What's This text for widgets in this Property Manager. """ from ne1_ui.WhatsThisText_for_PropertyManagers import whatsThis_NanotubeGeneratorPropertyManager whatsThis_NanotubeGeneratorPropertyManager(self) def _addToolTipText(self): """ Tool Tip text for widgets in this Property Manager. """ from ne1_ui.ToolTipText_for_PropertyManagers import ToolTip_NanotubeGeneratorPropertyManager ToolTip_NanotubeGeneratorPropertyManager(self) def chirality_fixup(self, spinBoxValueJunk = None): """ Slot for several validators for different parameters. This gets called each time a user types anything into a widget or changes a spinbox. @param spinBoxValueJunk: This is the Spinbox value from the valueChanged signal. It is not used. We just want to know that the spinbox value has changed. @type spinBoxValueJunk: double or None """ if not hasattr(self, 'n'): print_compact_traceback("Bug: no attr 'n' ") # mark 2007-05-24 return n_previous = int(self.n) m_previous = int(self.m) n = self.chiralityNSpinBox.value() m = self.chiralityMSpinBox.value() # Two restrictions to maintain # n >= 2 # 0 <= m <= n if n < 2: n = 2 if m != self.m: # The user changed m. If m became larger than n, make n bigger. if m > n: n = m elif n != self.n: # The user changed n. If n became smaller than m, make m smaller. if m > n: m = n self.chiralityNSpinBox.setValue(n) self.chiralityMSpinBox.setValue(m) self.m, self.n = m, n self.updateMessageGroupBox() def nt_type_changed(self, idx): """ Slot for Nanotube Type combobox. Update the bond length field when the type changes. """ self.bondLengthField.setValue(ntBondLengths[idx])
class EditNanotube_PropertyManager( DnaOrCnt_PropertyManager ): """ The NanotubeSegmenta_PropertyManager class provides a Property Manager for the EditNanotube_EditCommand. @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 = "Nanotube Properties" pmName = title iconPath = "ui/actions/Command Toolbar/BuildNanotube/EditNanotube.png" def __init__( self, command ): """ Constructor for the Cnt Segment Properties property manager. """ #For model changed signal self.previousSelectionParams = None #see self.connect_or_disconnect_signals for comment about this flag self.isAlreadyConnected = False self.isAlreadyDisconnected = False # Initialized here. Their values will be set in # _update_widgets_in_PM_before_show() self.endPoint1 = V(0, 0, 0) self.endPoint2 = V(0, 0, 0) _superclass.__init__( self, command) self.showTopRowButtons( PM_DONE_BUTTON | \ PM_CANCEL_BUTTON | \ PM_WHATS_THIS_BUTTON) def connect_or_disconnect_signals(self, isConnect): """ Connect or disconnect widget signals sent to their slot methods. This can be overridden in subclasses. By default it does nothing. @param isConnect: If True the widget will send the signals to the slot method. @type isConnect: boolean """ if isConnect: change_connect = self.win.connect else: change_connect = self.win.disconnect change_connect(self.nameLineEdit, SIGNAL("editingFinished()"), self._nameChanged) change_connect(self.showCursorTextCheckBox, SIGNAL('stateChanged(int)'), self._update_state_of_cursorTextGroupBox) return def show(self): """ Show this property manager. Overrides EditCommand_PM.show() This method also retrives the name information from the command's structure for its name line edit field. @see: EditNanotube_EditCommand.getStructureName() @see: self.close() """ _superclass.show(self) #if self.command is not None: #name = self.command.getStructureName() #if name is not None: #self.nameLineEdit.setText(name) def close(self): """ Close this property manager. Also sets the name of the self.command's structure to the one displayed in the line edit field. @see self.show() @see: EditNanotube_EditCommand.setStructureName """ if self.command is not None: name = str(self.nameLineEdit.text()) self.command.setStructureName(name) _superclass.close(self) def _connect_showCursorTextCheckBox(self): """ Connect the show cursor text checkbox with user prefs_key. Overrides DnaOrCnt_PropertyManager._connect_showCursorTextCheckBox """ connect_checkbox_with_boolean_pref( self.showCursorTextCheckBox , editNanotubeEditCommand_showCursorTextCheckBox_prefs_key) def _params_for_creating_cursorTextCheckBoxes(self): """ Returns params needed to create various cursor text checkboxes connected to prefs_keys that allow custom cursor texts. @return: A list containing tuples in the following format: ('checkBoxTextString' , preference_key). PM_PrefsCheckBoxes uses this data to create checkboxes with the the given names and connects them to the provided preference keys. (Note that PM_PrefsCheckBoxes puts thes within a GroupBox) @rtype: list @see: PM_PrefsCheckBoxes @see: self._loadDisplayOptionsGroupBox where this list is used. @see: Superclass method which is overridden here -- DnaOrCnt_PropertyManager._params_for_creating_cursorTextCheckBoxes() """ params = \ [ #Format: (" checkbox text", prefs_key) ("Nanotube length", editNanotubeEditCommand_cursorTextCheckBox_length_prefs_key) ] return params def setParameters(self, params): """ This is called when entering "Nanotube Segment Properties (i.e. "Edit properties...") to retrieve and set parameters of the nanotube segment that might be modified during this command and are needed to regenerate the nanotube segment. @param params: The parameters of the nanotube segment. These parameters are retreived via L{NanotubeSegment.getProps()}, called from L{EditNanotube_EditCommand.editStructure()}. Parameters: - n, m (chirality) - type (i.e. carbon or boron nitride) - endings (none, hydrogen, nitrogen) - endpoints (endPoint1, endPoint2) @type params: list (n, m), type, endings, (endPoint1, endPoint2) @note: Any widgets in the property manager that display these parameters should be updated here. @see: L{NanotubeSegment.getProps()} TODO: - Make this a EditCommand_PM API method? - See also the routines GraphicsMode.setParams or object.setProps ..better to name them all in one style? """ (self.n, self.m), self.type, self.endings,\ (self.endPoint1, self.endPoint2) = params # This is needed to update the endpoints since the Nanotube segment # may have been moved (i.e. translated or rotated). In that case, # the endpoints are not updated, so we recompute them here. nanotubeChunk = self.command.struct.members[0] self.endPoint1, self.endPoint2, radius = \ self.command.struct.nanotube.computeEndPointsFromChunk(nanotubeChunk) if 0: print "\n--------------" print "setParameters():" print "Struct=", self.command.struct print "N, M:", self.n, self.m print "type:", self.type print "endings:", self.endings print "pt1, pt2:", self.endPoint1, self.endPoint2 def getParameters(self): """ Get the parameters that the edit command will use to determine if any have changed. If any have, then the nanotube will be modified. """ if 0: print "\n--------------" print "getParameters():" print "Struct=", self.command.struct print "N, M:", self.n, self.m print "type:", self.type print "endings:", self.endings print "pt1, pt2:", self.endPoint1, self.endPoint2 return (self.n, self.m, self.type, self.endings, self.endPoint1, self.endPoint2) def _update_widgets_in_PM_before_show(self): """ This is called only when user is editing an existing structure. Its different than self.update_widgets_in_pm_before_show. (that method is called just before showing the property manager) @see: EditNanotube_EditCommand.editStructure() """ if self.command and self.command.hasValidStructure(): self.nanotube = self.command.struct.nanotube self.n, self.m = self.nanotube.getChirality() self.type = self.nanotube.getType() self.endings = self.nanotube.getEndings() self.endPoint1, self.endPoint2 = self.nanotube.getEndPoints() pass # Note that _update_widgets_in_PM_before_show() is called in # self.show, before you connect the signals. So, for the # 'first show' we will need to manually set the value of any # widgets that need updated. But later, when a different # NanotubeSegment is clicked, (while still in # EditNanotube_EditCommand, the propMgr will already be connected # so any calls in that case is redundant. self.updateNameField() self.updateLength() self.updateNanotubeDiameter() self.updateChirality() return def _update_UI_do_updates(self): """ Overrides superclass method. @see: Command_PropertyManager._update_UI_do_updates() """ self._update_widgets_in_PM_before_show() if self.command.struct: msg = "Editing structure <b>%s</b>." % \ self.command.getStructureName() else: msg = "Select a nanotube to edit." self.updateMessage(msg) return def _addGroupBoxes( self ): """ Add the Property Manager group boxes. """ self._pmGroupBox1 = PM_GroupBox( self, title = "Parameters" ) self._loadGroupBox1( self._pmGroupBox1 ) self._displayOptionsGroupBox = PM_GroupBox( self, title = "Display Options" ) self._loadDisplayOptionsGroupBox( self._displayOptionsGroupBox ) def _loadGroupBox1(self, pmGroupBox): """ Load widgets in group box 4. """ self.nameLineEdit = PM_LineEdit( pmGroupBox, label = "Name:", text = "", setAsDefault = False) # Nanotube Length self.ntLengthLineEdit = \ PM_LineEdit( pmGroupBox, label = "Length: ", text = "0.0 Angstroms", setAsDefault = False) self.ntLengthLineEdit.setDisabled(True) # Nanotube Radius self.ntDiameterLineEdit = \ PM_LineEdit( pmGroupBox, label = "Nanotube Diameter: ", setAsDefault = False) self.ntDiameterLineEdit.setDisabled(True) # Nanotube chirality. These are disabled (read-only) for now. --Mark self.chiralityNSpinBox = \ PM_SpinBox( pmGroupBox, label = "Chirality (n) :", minimum = 2, maximum = 100, setAsDefault = True ) self.chiralityNSpinBox.setDisabled(True) self.chiralityMSpinBox = \ PM_SpinBox( pmGroupBox, label = "Chirality (m) :", minimum = 0, maximum = 100, setAsDefault = True ) self.chiralityMSpinBox.setDisabled(True) def _addWhatsThisText(self): """ Add what's this text. """ pass def _addToolTipText(self): """ Add Tooltip text """ pass def _nameChanged(self): """ Slot for "Name" field. @TODO: Include a validator for the name field. """ _name = str(self.nameLineEdit.text()) if not _name: # Minimal test. Need to implement a validator. self.updateNameField() return self.command.setStructureName(_name) msg = "Editing structure <b>%s</b>." % _name self.updateMessage(msg) return def updateNameField(self): """ Update the name field showing the name of the currently selected protein. clear the combobox list. """ if self.command.hasValidStructure(): self.nameLineEdit.setEnabled(True) self.nameLineEdit.setText(self.command.getStructureName()) else: self.nameLineEdit.setDisabled(True) self.nameLineEdit.setText("") return def updateLength( self ): """ Update the nanotube Length lineEdit widget. """ if self.command.hasValidStructure(): _nanotubeLength = vlen(self.endPoint1 - self.endPoint2) _lengthText = "%-7.4f Angstroms" % (_nanotubeLength) else: _lengthText = "" self.ntLengthLineEdit.setText(_lengthText) return def updateNanotubeDiameter(self): """ Update the nanotube Diameter lineEdit widget. """ if self.command.hasValidStructure(): _diameterText = "%-7.4f Angstroms" % (self.nanotube.getDiameter()) else: _diameterText = "" self.ntDiameterLineEdit.setText(_diameterText) return def updateChirality( self ): """ Update the nanotube chirality spinboxes (read-only). """ if self.command.hasValidStructure(): n, m = self.nanotube.getChirality() else: n = 0 m = 0 self.chiralityNSpinBox.setValue(n) self.chiralityMSpinBox.setValue(m) return pass # End of EditNanotube_PropertyManager class
class DnaGeneratorPropertyManager( PM_Dialog, DebugMenuMixin ): """ The DnaGeneratorPropertyManager class provides a Property Manager for the "Build > Atoms" command. @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 @ivar validSymbols: Miscellaneous symbols that may appear in the sequence (but are ignored). The hyphen '-' is a special case that must be dealt with individually; it is not included because it can confuse regular expressions. @type validSymbols: QString """ title = "DNA" pmName = title iconPath = "ui/actions/Tools/Build Structures/DNA.png" validSymbols = QString(' <>~!@#%&_+`=$*()[]{}|^\'"\\.;:,/?') # The following class variables guarantee the UI's menu items # are synchronized with their action code. The arrays should # not be changed, unless an item is removed or inserted. # Changes should be made via only the _action... variables. # e.g., Change _action_Complement from "Complement" # to "Complement Sequences". The menu item will # change and its related code will need no update. _action_Complement = "Complement" _action_Reverse = "Reverse" _action_RemoveUnrecognized = 'Remove unrecognized letters' _action_ConvertUnrecognized = 'Convert unrecognized letters to "N"' _actionChoices = [ "Action", "---", _action_Complement, _action_Reverse, _action_RemoveUnrecognized, _action_ConvertUnrecognized ] _modeltype_PAM3 = "PAM3" _modeltype_PAM5 = "PAM5" _modeltype_Atomistic = "Atomistic" # deprecated _modelChoices = [ _modeltype_PAM3, _modeltype_PAM5 ] def __init__( self ): """ Constructor for the DNA Generator property manager. """ PM_Dialog.__init__( self, self.pmName, self.iconPath, self.title ) DebugMenuMixin._init1( self ) msg = "Edit the DNA parameters and select <b>Preview</b> to \ preview the structure. Click <b>Done</b> to insert it into \ the model." # This causes the "Message" box to be displayed as well. # setAsDefault=True causes this message to be reset whenever # this PM is (re)displayed via show(). Mark 2007-06-01. self.MessageGroupBox.insertHtmlMessage( msg, setAsDefault = True ) def _addGroupBoxes( self ): """ Add the DNA Property Manager group boxes. """ self._pmGroupBox1 = PM_GroupBox( self, title = "Strand Sequence" ) self._loadGroupBox1( self._pmGroupBox1 ) self._pmGroupBox2 = PM_GroupBox( self, title = "DNA Parameters" ) self._loadGroupBox2( self._pmGroupBox2 ) self._pmGroupBox3 = PM_GroupBox( self, title = "Endpoints" ) self._loadGroupBox3( self._pmGroupBox3 ) def _loadGroupBox1(self, pmGroupBox): """ Load widgets in group box 1. """ # Duplex Length self.duplexLengthSpinBox = \ PM_DoubleSpinBox( pmGroupBox, label = "Duplex Length: ", value = 0, setAsDefault = False, minimum = 0, maximum = 34000, singleStep = self.getDuplexRise("B-DNA"), decimals = 3, suffix = ' Angstroms') self.connect( self.duplexLengthSpinBox, SIGNAL("valueChanged(double)"), self.duplexLengthChanged ) # Strand Length self.strandLengthSpinBox = \ PM_SpinBox( pmGroupBox, label = "Strand Length :", value = 0, setAsDefault = False, minimum = 0, maximum = 10000, suffix = ' bases' ) self.connect( self.strandLengthSpinBox, SIGNAL("valueChanged(int)"), self.strandLengthChanged ) # New Base choices newBaseChoices = [] for theBase in basesDict.keys(): newBaseChoices = newBaseChoices \ + [ theBase + ' (' \ + basesDict[theBase]['Name'] + ')' ] try: defaultBaseChoice = basesDict.keys().index('N') except: defaultBaseChoice = 0 # Strand Sequence self.sequenceTextEdit = \ PM_TextEdit( pmGroupBox, label = "", spanWidth = True ) self.sequenceTextEdit.setCursorWidth(2) self.sequenceTextEdit.setWordWrapMode( QTextOption.WrapAnywhere ) self.connect( self.sequenceTextEdit, SIGNAL("textChanged()"), self.sequenceChanged ) self.connect( self.sequenceTextEdit, SIGNAL("cursorPositionChanged()"), self.cursorPosChanged ) # Actions self.actionsComboBox = \ PM_ComboBox( pmGroupBox, label = '', choices = self._actionChoices, index = 0, setAsDefault = True, spanWidth = True ) # If SIGNAL("activate(const QString&)") is used, we get a TypeError. # This is a bug that needs Bruce. Using currentIndexChanged(int) as # a workaround, but there is still a bug when the "Reverse" action # is selected. Mark 2007-08-15 self.connect( self.actionsComboBox, SIGNAL("currentIndexChanged(int)"), self.actionsComboBoxChanged ) def _loadGroupBox2( self, pmGroupBox ): """ Load widgets in group box 2. """ self.modelComboBox = \ PM_ComboBox( pmGroupBox, label = "Model :", choices = self._modelChoices, setAsDefault = True) self.conformationComboBox = \ PM_ComboBox( pmGroupBox, label = "Conformation :", choices = ["B-DNA"], setAsDefault = True) self.connect( self.conformationComboBox, SIGNAL("currentIndexChanged(int)"), self.conformationComboBoxChanged ) self.basesPerTurnComboBox= \ PM_ComboBox( pmGroupBox, label = "Bases Per Turn :", choices = ["10.0", "10.5", "10.67"], setAsDefault = True) # I may decide to reintroduce "base-pair chunks" at a later time. # Please talk to me if you have a strong feeling about including # this. Mark 2007-08-19. createChoices = ["Strand chunks", \ "Single chunk" ] #@ "Base-pair chunks"] self.createComboBox = \ PM_ComboBox( pmGroupBox, label = "Create :", choices = createChoices, index = 0, setAsDefault = True, spanWidth = False ) def _loadGroupBox3(self, pmGroupBox): """ Load widgets in group box 3. """ self._endPoint1GroupBox = PM_GroupBox( pmGroupBox, title = "Endpoint1" ) self._endPoint2GroupBox = PM_GroupBox( pmGroupBox, title = "Endpoint2" ) # Point 1 self.x1SpinBox = \ PM_DoubleSpinBox( self._endPoint1GroupBox, label = \ "ui/actions/Properties Manager/X_Coordinate.png", value = 0, setAsDefault = True, minimum = -100.0, maximum = 100.0, decimals = 3, suffix = ' Angstroms') self.y1SpinBox = \ PM_DoubleSpinBox( self._endPoint1GroupBox, label = \ "ui/actions/Properties Manager/Y_Coordinate.png", value = 0, setAsDefault = True, minimum = -100.0, maximum = 100.0, decimals = 3, suffix = ' Angstroms') self.z1SpinBox = \ PM_DoubleSpinBox( self._endPoint1GroupBox, label = \ "ui/actions/Properties Manager/Z_Coordinate.png", value = 0, setAsDefault = True, minimum = -100.0, maximum = 100.0, decimals = 3, suffix = ' Angstroms') # Point 2 self.x2SpinBox = \ PM_DoubleSpinBox( self._endPoint2GroupBox, label = \ "ui/actions/Properties Manager/X_Coordinate.png", value = 10.0, setAsDefault = True, minimum = -100.0, maximum = 100.0, decimals = 3, suffix = ' Angstroms') self.y2SpinBox = \ PM_DoubleSpinBox( self._endPoint2GroupBox, label = \ "ui/actions/Properties Manager/Y_Coordinate.png", value = 0, setAsDefault = True, minimum = -100.0, maximum = 100.0, decimals = 3, suffix = ' Angstroms') self.z2SpinBox = \ PM_DoubleSpinBox( self._endPoint2GroupBox, label = \ "ui/actions/Properties Manager/Z_Coordinate.png", value = 0, setAsDefault = True, minimum = -100.0, maximum = 100.0, decimals = 3, suffix = ' Angstroms') def _addWhatsThisText( self ): """ What's This text for some of the widgets in the DNA Property Manager. @note: Many PM widgets are still missing their "What's This" text. """ self.conformationComboBox.setWhatsThis("""<b>Conformation</b> <p>There are three DNA geometries, A-DNA, B-DNA, and Z-DNA. Only B-DNA and Z-DNA are currently supported.</p>""") self.sequenceTextEdit.setWhatsThis("""<b>Strand Sequence</b> <p>Type in the strand sequence you want to generate here (5' => 3')<br> <br> Recognized base letters:<br> <br> A = Adenosine<br> C = Cytosine<br> G = Guanosine<br> T = Thymidine<br> N = aNy base<br> X = Unassigned<br> <br> Other base letters currently recognized:<br> <br> B = C,G, or T<br> D = A,G, or T<br> H = A,C, or T<br> V = A,C, or G<br> R = A or G (puRine)<br> Y = C or T (pYrimidine)<br> K = G or T (Keto)<br> M = A or C (aMino)<br> S = G or C (Strong -3H bonds)<br> W = A or T (Weak - 2H bonds)<br> </p>""") self.actionsComboBox.setWhatsThis("""<b>Action</b> <p>Select an action to perform on the sequence.</p>""") def conformationComboBoxChanged( self, inIndex ): """ Slot for the Conformation combobox. It is called whenever the Conformation choice is changed. @param inIndex: The new index. @type inIndex: int """ self.basesPerTurnComboBox.clear() conformation = self.conformationComboBox.currentText() if conformation == "B-DNA": self.basesPerTurnComboBox.insertItem(0, "10.0") self.basesPerTurnComboBox.insertItem(1, "10.5") self.basesPerTurnComboBox.insertItem(2, "10.67") #10.5 is the default value for Bases per turn. #So set the current index to 1 self.basesPerTurnComboBox.setCurrentIndex(1) elif conformation == "Z-DNA": self.basesPerTurnComboBox.insertItem(0, "12.0") elif inIndex == -1: # Caused by clear(). This is tolerable for now. Mark 2007-05-24. conformation = "B-DNA" # Workaround for "Restore Defaults". pass else: msg = redmsg("conformationComboBoxChanged(): \ Error - unknown DNA conformation. Index = "+ inIndex) env.history.message(msg) self.duplexLengthSpinBox.setSingleStep( self.getDuplexRise(conformation) ) def modelComboBoxChanged( self, inIndex ): """ Slot for the Model combobox. It is called whenever the Model choice is changed. @param inIndex: The new index. @type inIndex: int """ conformation = self._modelChoices[ inIndex ] self.disconnect( self.conformationComboBox, SIGNAL("currentIndexChanged(int)"), self.conformationComboBoxChanged ) self.conformationComboBox.clear() # Generates signal! if conformation == self._modeltype_PAM3: self.conformationComboBox.addItem("B-DNA") elif conformation == self._modeltype_PAM5: self.conformationComboBox.addItem("B-DNA") elif conformation == self._modeltype_Atomistic: self.conformationComboBox.addItem("B-DNA") self.conformationComboBox.addItem("Z-DNA") elif inIndex == -1: # Caused by clear(). This is tolerable for now. Mark 2007-05-24. pass else: msg = "Error - unknown model representation. Index = " + inIndex env.history.message(redmsg(msg)) self.connect( self.conformationComboBox, SIGNAL("currentIndexChanged(int)"), self.conformationComboBoxChanged) # GroupBox3 slots (and other methods) supporting the Strand Sequence groupbox. def getDuplexRise( self, inConformation ): """ Return the 'rise' between base pairs of the specified DNA type (conformation). @param inConformation: The current conformation. @type inConformation: int """ return dnaDict[str(inConformation)]['DuplexRise'] def synchronizeLengths( self ): """ Guarantees the values of the duplex length and strand length spinboxes agree with the strand sequence (textedit). """ self.updateStrandLength() self.updateDuplexLength() return # Added :jbirac 20070613: def duplexLengthChanged( self, inDuplexLength ): """ Slot for the duplex length spinbox, called whenever the value of the Duplex Length spinbox changes. @param inDuplexLength: The duplex length. @type inDuplexLength: float """ conformation = self.conformationComboBox.currentText() duplexRise = self.getDuplexRise( conformation ) newStrandLength = inDuplexLength / duplexRise + 0.5 newStrandLength = int( newStrandLength ) self.strandLengthChanged( newStrandLength ) def updateDuplexLength( self ): # Added :jbirac 20070615: """ Update the Duplex Length spinbox; always the length of the strand sequence multiplied by the 'rise' of the duplex. This method is called by slots of other controls (i.e., this itself is not a slot.) """ conformation = self.conformationComboBox.currentText() newDuplexLength = self.getDuplexRise( conformation ) \ * self.getSequenceLength() self.disconnect( self.duplexLengthSpinBox, SIGNAL("valueChanged(double)"), self.duplexLengthChanged) self.duplexLengthSpinBox.setValue( newDuplexLength ) self.connect( self.duplexLengthSpinBox, SIGNAL("valueChanged(double)"), self.duplexLengthChanged) # Renamed from length_changed :jbirac 20070613: def strandLengthChanged( self, inStrandLength ): """ Slot for the Strand Length spin box, called whenever the value of the Strand Length spin box changes. @param inStrandLength: The number of bases in the strand sequence. @type inStrandLength: int """ theSequence = self.getPlainSequence() sequenceLen = len( theSequence ) lengthChange = inStrandLength - self.getSequenceLength() # Preserve the cursor's position/selection cursor = self.sequenceTextEdit.textCursor() #cursorPosition = cursor.position() selectionStart = cursor.selectionStart() selectionEnd = cursor.selectionEnd() if inStrandLength < 0: return # Should never happen. if lengthChange < 0: # If length is less than the previous length, # simply truncate the current sequence. theSequence.chop( -lengthChange ) elif lengthChange > 0: # If length has increased, add the correct number of base # letters to the current strand sequence. numNewBases = lengthChange # Get current base selected in combobox. chosenBase = 'X' # Unassigned. basesToAdd = chosenBase * numNewBases theSequence.append( basesToAdd ) else: env.history.message( orangemsg( "strandLengthChanged(): Length has not changed." )) self.setSequence( theSequence ) return # Renamed from updateLength :jbirac 20070613: def updateStrandLength( self ): """ Update the Strand Length spinbox; always the length of the strand sequence. """ self.disconnect( self.strandLengthSpinBox, SIGNAL("valueChanged(int)"), self.strandLengthChanged ) self.strandLengthSpinBox.setValue( self.getSequenceLength() ) self.connect( self.strandLengthSpinBox, SIGNAL("valueChanged(int)"), self.strandLengthChanged ) return def sequenceChanged( self ): """ Slot for the Strand Sequence textedit widget. Assumes the sequence changed directly by user's keystroke in the textedit. Other methods... """ cursorPosition = self.getCursorPosition() theSequence = self.getPlainSequence() # Disconnect while we edit the sequence. self.disconnect( self.sequenceTextEdit, SIGNAL("textChanged()"), self.sequenceChanged ) # How has the text changed? if theSequence.length() == 0: # There is no sequence. self.updateStrandLength() self.updateDuplexLength() else: # Insert the sequence; it will be "stylized" by setSequence(). self.setSequence( theSequence ) # Reconnect to respond when the sequence is changed. self.connect( self.sequenceTextEdit, SIGNAL("textChanged()"), self.sequenceChanged ) self.synchronizeLengths() return def getPlainSequence( self, inOmitSymbols = False ): """ Returns a plain text QString (without HTML stylization) of the current sequence. All characters are preserved (unless specified explicitly), including valid base letters, punctuation symbols, whitespace and invalid letters. @param inOmitSymbols: Omits characters listed in self.validSymbols. @type inOmitSymbols: bool @return: The current DNA sequence in the PM. @rtype: QString """ outSequence = self.sequenceTextEdit.toPlainText() if inOmitSymbols: # This may look like a sloppy piece of code, but Qt's QRegExp # class makes it pretty tricky to remove all punctuation. theString = '[<>' \ + str( QRegExp.escape(self.validSymbols) ) \ + ']|-' outSequence.remove(QRegExp( theString )) return outSequence def stylizeSequence( self, inSequence ): """ Converts a plain text string of a sequence (including optional symbols) to an HTML rich text string. @param inSequence: A DNA sequence. @type inSequence: QString @return: The sequence. @rtype: QString """ outSequence = str(inSequence) # Verify that all characters (bases) in the sequence are "valid". invalidSequence = False basePosition = 0 sequencePosition = 0 invalidStartTag = "<b><font color=black>" invalidEndTag = "</b>" previousChar = chr(1) # Null character; may be revised. # Some characters must be substituted to preserve # whitespace and tags in HTML code. substituteDict = { ' ':' ', '<':'<', '>':'>' } while basePosition < len(outSequence): theSeqChar = outSequence[basePosition] if ( theSeqChar in basesDict or theSeqChar in self.validSymbols ): # Close any preceding invalid sequence segment. if invalidSequence == True: outSequence = outSequence[:basePosition] \ + invalidEndTag \ + outSequence[basePosition:] basePosition += len(invalidEndTag) invalidSequence = False # Color the valid characters. if theSeqChar != previousChar: # We only need to insert 'color' tags in places where # the adjacent characters are different. if theSeqChar in basesDict: theTag = '<font color=' \ + basesDict[ theSeqChar ]['Color'] \ + '>' elif not previousChar in self.validSymbols: # The character is a 'valid' symbol to be greyed # out. Only one 'color' tag is needed for a # group of adjacent symbols. theTag = '<font color=dimgrey>' else: theTag = '' outSequence = outSequence[:basePosition] \ + theTag + outSequence[basePosition:] basePosition += len(theTag) # Any <space> character must be substituted with an # ASCII code tag because the HTML engine will collapse # whitespace to a single <space> character; whitespace # is truncated from the end of HTML by default. # Also, many symbol characters must be substituted # because they confuse the HTML syntax. #if str( outSequence[basePosition] ) in substituteDict: if outSequence[basePosition] in substituteDict: #theTag = substituteDict[theSeqChar] theTag = substituteDict[ outSequence[basePosition] ] outSequence = outSequence[:basePosition] \ + theTag \ + outSequence[basePosition + 1:] basePosition += len(theTag) - 1 else: # The sequence character is invalid (but permissible). # Tags (e.g., <b> and </b>) must be inserted at both the # beginning and end of a segment of invalid characters. if invalidSequence == False: outSequence = outSequence[:basePosition] \ + invalidStartTag \ + outSequence[basePosition:] basePosition += len(invalidStartTag) invalidSequence = True basePosition += 1 previousChar = theSeqChar #basePosition += 1 # Specify that theSequence is definitely HTML format, because # Qt can get confused between HTML and Plain Text. outSequence = "<html>" + outSequence outSequence += "</html>" return outSequence def setSequence( self, inSequence, inStylize = True, inRestoreCursor = True ): """ Replace the current strand sequence with the new sequence text. @param inSequence: The new sequence. @type inSequence: QString @param inStylize: If True, inSequence will be converted from a plain text string (including optional symbols) to an HTML rich text string. @type inStylize: bool @param inRestoreCursor: Not implemented yet. @type inRestoreCursor: bool @attention: Signals/slots must be managed before calling this method. The textChanged() signal will be sent to any connected widgets. """ cursor = self.sequenceTextEdit.textCursor() selectionStart = cursor.selectionStart() selectionEnd = cursor.selectionEnd() if inStylize: inSequence = self.stylizeSequence( inSequence ) self.sequenceTextEdit.insertHtml( inSequence ) if inRestoreCursor: cursor.setPosition( min(selectionStart, self.getSequenceLength()), QTextCursor.MoveAnchor ) cursor.setPosition( min(selectionEnd, self.getSequenceLength()), QTextCursor.KeepAnchor ) self.sequenceTextEdit.setTextCursor( cursor ) return def getSequenceLength( self ): """ Returns the number of characters in the strand sequence textedit widget. """ theSequence = self.getPlainSequence( inOmitSymbols = True ) outLength = theSequence.length() return outLength def getCursorPosition( self ): """ Returns the cursor position in the strand sequence textedit widget. """ cursor = self.sequenceTextEdit.textCursor() return cursor.position() def cursorPosChanged( self ): """ Slot called when the cursor position changes. """ cursor = self.sequenceTextEdit.textCursor() if 0: env.history.message( greenmsg( "cursorPosChanged: Selection (" + str(cursor.selectionStart()) + " thru " + str(cursor.selectionEnd())+')' ) ) return def actionsComboBoxChanged( self, inIndex ): """ Slot for the Actions combobox. It is called whenever the Action choice is changed. @param inIndex: The index of the selected action choice. @type inIndex: int """ if inIndex == 0: # Very important. return actionName = str(self.actionsComboBox.currentText()) self.actionsComboBox.setCurrentIndex( 0 ) # Generates signal! self.invokeAction( actionName ) return def invokeAction( self, inActionName ): """ Applies an action on the current sequence displayed in the PM. @param inActionName: The action name. @type inActionName: str @return: The sequence after the action has been applied. @rtype: str """ sequence, allKnown = self._getSequence() outResult = "" if inActionName == self._action_Complement: outResult = getComplementSequence(sequence) elif inActionName == self._action_Reverse: outResult = getReverseSequence(sequence) elif inActionName == self._action_ConvertUnrecognized: outResult = replaceUnrecognized(sequence, replaceBase = 'N') self.setSequence( outResult ) elif inActionName == self._action_RemoveUnrecognized: outResult = replaceUnrecognized(sequence, replaceBase = '') self.setSequence( outResult ) return
class DnaStrand_PropertyManager(DnaOrCnt_PropertyManager): """ The DnaStrand_PropertyManager class provides a Property Manager for the DnaStrand_EditCommand. @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 = "DnaStrand Properties" iconPath = "ui/actions/Properties Manager/Strand.png" def __init__(self, command): """ Constructor for the Build DNA property manager. """ self.sequenceEditor = None self._numberOfBases = 0 self._conformation = 'B-DNA' self.dnaModel = 'PAM3' _superclass.__init__(self, command) self.showTopRowButtons( PM_DONE_BUTTON | \ PM_WHATS_THIS_BUTTON) return def _addGroupBoxes(self): """ Add group boxes to this PM. """ self._pmGroupBox1 = PM_GroupBox(self, title="Parameters") self._loadGroupBox1(self._pmGroupBox1) self._displayOptionsGroupBox = PM_GroupBox(self, title="Display Options") self._loadDisplayOptionsGroupBox(self._displayOptionsGroupBox) #Sequence Editor. This is NOT a groupbox, needs cleanup. Doing it here #so that the sequence editor gets connected! Perhaps #superclass should define _loadAdditionalWidgets. -- Ninad2008-10-03 self._loadSequenceEditor() return def _loadGroupBox1(self, pmGroupBox): """ Load widgets in group box 1. """ self.nameLineEdit = PM_LineEdit(pmGroupBox, label="Name:", text="", setAsDefault=False) self.numberOfBasesSpinBox = \ PM_SpinBox( pmGroupBox, label = "Number of bases:", value = self._numberOfBases, setAsDefault = False, minimum = 2, maximum = 10000 ) self.disableStructHighlightingCheckbox = \ PM_CheckBox( pmGroupBox, text = "Don't highlight while editing DNA", widgetColumn = 0, state = Qt.Unchecked, setAsDefault = True, spanWidth = True ) #As of 2008-03-31, the properties such as number of bases will be #editable only by using the resize handles. self.numberOfBasesSpinBox.setEnabled(False) return def _loadSequenceEditor(self): """ Temporary code that shows the Sequence editor ..a doc widget docked at the bottom of the mainwindow. The implementation is going to change before 'rattleSnake' product release. As of 2007-11-20: This feature (sequence editor) is waiting for the ongoing dna model work to complete. """ self.sequenceEditor = self.win.createDnaSequenceEditorIfNeeded() self.sequenceEditor.hide() return def _loadDisplayOptionsGroupBox(self, pmGroupBox): """ Overrides superclass method. Also loads the color chooser widget. """ self._loadColorChooser(pmGroupBox) _superclass._loadDisplayOptionsGroupBox(self, pmGroupBox) return def _connect_showCursorTextCheckBox(self): """ Connect the show cursor text checkbox with user prefs_key. Overrides DnaOrCnt_PropertyManager._connect_showCursorTextCheckBox """ connect_checkbox_with_boolean_pref( self.showCursorTextCheckBox, dnaStrandEditCommand_showCursorTextCheckBox_prefs_key) return def _params_for_creating_cursorTextCheckBoxes(self): """ Returns params needed to create various cursor text checkboxes connected to prefs_keys that allow custom cursor texts. @return: A list containing tuples in the following format: ('checkBoxTextString' , preference_key). PM_PrefsCheckBoxes uses this data to create checkboxes with the the given names and connects them to the provided preference keys. (Note that PM_PrefsCheckBoxes puts thes within a GroupBox) @rtype: list @see: PM_PrefsCheckBoxes @see: self._loadDisplayOptionsGroupBox where this list is used. @see: Superclass method which is overridden here -- DnaOrCnt_PropertyManager._params_for_creating_cursorTextCheckBoxes() """ params = \ [ #Format: (" checkbox text", prefs_key) ("Number of bases", dnaStrandEditCommand_cursorTextCheckBox_numberOfBases_prefs_key), ("Number of bases to be changed", dnaStrandEditCommand_cursorTextCheckBox_changedBases_prefs_key) ] return params def getParameters(self): name = self.nameLineEdit.text() numberOfBases = self.numberOfBasesSpinBox.value() dnaForm = self._conformation dnaModel = self.dnaModel color = self._colorChooser.getColor() return (numberOfBases, dnaForm, dnaModel, color, name) def setParameters(self, params): """ This is usually called when you are editing an existing structure. It also gets called when selecting a new strand (within this command). Some property manager ui elements then display the information obtained from the object being edited. TODO: - Make this a EditCommand_PM API method? - See also the routines GraphicsMode.setParams or object.setProps ..better to name them all in one style? """ numberOfBases, \ dnaForm, \ dnaModel, \ color, \ name = params if numberOfBases is not None: self.numberOfBasesSpinBox.setValue(numberOfBases) if dnaForm is not None: self._conformation = dnaForm if dnaModel is not None: self.dnaModel = dnaModel if color is not None: self._colorChooser.setColor(color) if name: # Minimal test. Should add a validator. --Mark 2008-12-16 self.nameLineEdit.setText(name) # This gets called when we enter the command *and* when selecting a new # strand. In either case, we must update the sequence in the sequenece # editor. Fixes bug 2951. --Mark 2008-12-16 if self.command and self.command.hasValidStructure(): #print "setParameters(): loading sequence in sequence editor for ", name self.updateSequence(strand=self.command.struct) return def connect_or_disconnect_signals(self, isConnect): """ Connect or disconnect widget signals sent to their slot methods. This can be overridden in subclasses. By default it does nothing. @param isConnect: If True the widget will send the signals to the slot method. @type isConnect: boolean """ #TODO: This is a temporary fix for a bug. When you invoke a temporary # mode Entering such a temporary mode keeps the signals of #PM from the previous mode connected ( #but while exiting that temporary mode and reentering the #previous mode, it atucally reconnects the signal! This gives rise to #lots of bugs. This needs more general fix in Temporary mode API. # -- Ninad 2008-01-09 (similar comment exists in MovePropertyManager.py if isConnect and self.isAlreadyConnected: if debug_flags.atom_debug: print_compact_stack("warning: attempt to connect widgets"\ "in this PM that are already connected." ) return if not isConnect and self.isAlreadyDisconnected: if debug_flags.atom_debug: print_compact_stack("warning: attempt to disconnect widgets"\ "in this PM that are already disconnected.") return self.isAlreadyConnected = isConnect self.isAlreadyDisconnected = not isConnect if isConnect: change_connect = self.win.connect else: change_connect = self.win.disconnect if self.sequenceEditor: self.sequenceEditor.connect_or_disconnect_signals(isConnect) _superclass.connect_or_disconnect_signals(self, isConnect) change_connect(self.disableStructHighlightingCheckbox, SIGNAL('stateChanged(int)'), self.change_struct_highlightPolicy) change_connect(self.showCursorTextCheckBox, SIGNAL('stateChanged(int)'), self._update_state_of_cursorTextGroupBox) change_connect(self.nameLineEdit, SIGNAL("editingFinished()"), self._nameChanged) return def _update_UI_do_updates(self): """ @see: Command_PropertyManager. _update_UI_do_updates() @see: DnaStrand_EditCommand.command_update_UI() @see: DnaStrand_EditCommand.hasResizableStructure() """ if not self.command.hasValidStructure(): print "DnaStrand not a valid structure." self._pmGroupBox1.setEnabled(False) self._displayOptionsGroupBox.setEnabled(False) self.sequenceEditor.updateSequence(strand=" ") self.sequenceEditor.setEnabled(False) self.nameLineEdit.setText("") self.numberOfBasesSpinBox.setValue(0) return else: self._pmGroupBox1.setEnabled(True) self._displayOptionsGroupBox.setEnabled(True) self.sequenceEditor.setEnabled(True) isStructResizable, why_not = self.command.hasResizableStructure() if not isStructResizable: #disable all widgets if self._pmGroupBox1.isEnabled(): self._pmGroupBox1.setEnabled(False) msg1 = ("Attention: ") % (self.command.struct.name) msg2 = redmsg("DnaStrand <b>%s</b> is not resizable. Reason: %s" % \ (self.command.struct.name, why_not)) self.updateMessage(msg1 + msg2) else: if not self._pmGroupBox1.isEnabled(): self._pmGroupBox1.setEnabled(True) msg1 = ("Editing <b>%s</b>. ") % (self.command.struct.name) msg2 = "Use resize handles to resize the strand. "\ "Use the <i>Sequence Editor</i> to edit the sequence." self.updateMessage(msg1 + msg2) return def close(self): """ Close this property manager. Also sets the name of the self.command's structure to the one displayed in the line edit field. @see self.show() @see: DnaSegment_EditCommand.setStructureName """ if self.command is not None: name = str(self.nameLineEdit.text()) self.command.setStructureName(name) if self.sequenceEditor: self.sequenceEditor.close() _superclass.close(self) return def updateSequence(self, strand=None): """ Public method provided for convenience. If any callers outside of this command need to update the sequence in the sequence editor, they can simply do DnaStrand_ProprtyManager.updateSequence() rather than DnaStrand_ProprtyManager.sequenceEditor.updateSequence() @see: Ui_DnaSequenceEditor.updateSequence() """ if self.sequenceEditor: self.sequenceEditor.updateSequence(strand=strand) return def change_struct_highlightPolicy(self, checkedState=False): """ Change the 'highlight policy' of the structure being edited (i.e. self.command.struct) . @param checkedState: The checked state of the checkbox that says 'Don't highlight while editing DNA'. So, it its True, the structure being edited won't get highlighted. @see: DnaStrand.setHighlightPolicy for more comments """ if self.command and self.command.hasValidStructure(): highlight = not checkedState self.command.struct.setHighlightPolicy(highlight=highlight) return def _addWhatsThisText(self): """ Add what's this text. Abstract method. """ pass def _nameChanged(self): # Added by Mark. 2008-12-16 """ Slot for "Name" field. Changes the name of the strand if the user types in a new name. @warning: this lacks a validator. User can type in a name with invalid characters. """ if not self.command.hasValidStructure(): return name = str(self.nameLineEdit.text()) if not name: # Minimal test. Should add a validator. Ask Bruce for example validator code somewhere. --Mark 2008-12-16 if self.command.hasValidStructure(): self.nameLineEdit.setText(self.command.getStructureName()) return self.command.setStructureName(name) self._update_UI_do_updates() # Updates the message box. return
class InsertNanotube_PropertyManager( DnaOrCnt_PropertyManager): """ The InsertNanotube_PropertyManager class provides a Property Manager for the B{Build > Nanotube > CNT} command. @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 = "Insert Nanotube" pmName = title iconPath = "ui/actions/Tools/Build Structures/InsertNanotube.png" def __init__( self, win, editCommand ): """ Constructor for the Nanotube property manager. """ self.endPoint1 = None self.endPoint2 = None self.nanotube = Nanotube() # A 5x5 CNT. _superclass.__init__( self, win, editCommand) self.showTopRowButtons( PM_DONE_BUTTON | \ PM_CANCEL_BUTTON | \ PM_WHATS_THIS_BUTTON) def connect_or_disconnect_signals(self, isConnect): """ Connect or disconnect widget signals sent to their slot methods. This can be overridden in subclasses. By default it does nothing. @param isConnect: If True the widget will send the signals to the slot method. @type isConnect: boolean """ if isConnect: change_connect = self.win.connect else: change_connect = self.win.disconnect change_connect( self.ntTypeComboBox, SIGNAL("currentIndexChanged(const QString&)"), self._ntTypeComboBoxChanged ) change_connect(self.chiralityNSpinBox, SIGNAL("valueChanged(int)"), self._chiralityFixup) change_connect(self.chiralityMSpinBox, SIGNAL("valueChanged(int)"), self._chiralityFixup) change_connect(self.endingsComboBox, SIGNAL("currentIndexChanged(const QString&)"), self._endingsComboBoxChanged ) # This spin box is currently hidden. change_connect(self.bondLengthDoubleSpinBox, SIGNAL("valueChanged(double)"), self._bondLengthChanged) change_connect(self.showCursorTextCheckBox, SIGNAL('stateChanged(int)'), self._update_state_of_cursorTextGroupBox) def ok_btn_clicked(self): """ Slot for the OK button """ if self.editCommand: self.editCommand.preview_or_finalize_structure(previewing = False) ##env.history.message(self.editCommand.logMessage) self.win.toolsDone() def cancel_btn_clicked(self): """ Slot for the Cancel button. """ if self.editCommand: self.editCommand.cancelStructure() self.win.toolsCancel() def _update_widgets_in_PM_before_show(self): """ Update various widgets in this Property manager. Overrides MotorPropertyManager._update_widgets_in_PM_before_show. The various widgets , (e.g. spinboxes) will get values from the structure for which this propMgr is constructed for (self.editcCntroller.struct) @see: MotorPropertyManager._update_widgets_in_PM_before_show @see: self.show where it is called. """ pass def getFlyoutActionList(self): """ Returns custom actionlist that will be used in a specific mode or editing a feature etc Example: while in movie mode, the _createFlyoutToolBar method calls this. """ #'allActionsList' returns all actions in the flyout toolbar #including the subcontrolArea actions allActionsList = [] #Action List for subcontrol Area buttons. #In this mode there is really no subcontrol area. #We will treat subcontrol area same as 'command area' #(subcontrol area buttons will have an empty list as their command area #list). We will set the Comamnd Area palette background color to the #subcontrol area. subControlAreaActionList =[] self.exitEditCommandAction.setChecked(True) subControlAreaActionList.append(self.exitEditCommandAction) separator = QAction(self.w) separator.setSeparator(True) subControlAreaActionList.append(separator) allActionsList.extend(subControlAreaActionList) #Empty actionlist for the 'Command Area' commandActionLists = [] #Append empty 'lists' in 'commandActionLists equal to the #number of actions in subControlArea for i in range(len(subControlAreaActionList)): lst = [] commandActionLists.append(lst) params = (subControlAreaActionList, commandActionLists, allActionsList) return params def _addGroupBoxes( self ): """ Add the Insert Nanotube Property Manager group boxes. """ self._pmGroupBox1 = PM_GroupBox( self, title = "Endpoints" ) self._loadGroupBox1( self._pmGroupBox1 ) self._pmGroupBox1.hide() self._pmGroupBox2 = PM_GroupBox( self, title = "Parameters" ) self._loadGroupBox2( self._pmGroupBox2 ) self._displayOptionsGroupBox = PM_GroupBox( self, title = "Display Options" ) self._loadDisplayOptionsGroupBox( self._displayOptionsGroupBox ) self._pmGroupBox3 = PM_GroupBox( self, title = "Nanotube Distortion" ) self._loadGroupBox3( self._pmGroupBox3 ) self._pmGroupBox3.hide() #@ Temporary. self._pmGroupBox4 = PM_GroupBox( self, title = "Multi-Walled CNTs" ) self._loadGroupBox4( self._pmGroupBox4 ) self._pmGroupBox4.hide() #@ Temporary. self._pmGroupBox5 = PM_GroupBox( self, title = "Advanced Options" ) self._loadGroupBox5( self._pmGroupBox5 ) self._pmGroupBox5.hide() #@ Temporary. def _loadGroupBox1(self, pmGroupBox): """ Load widgets in group box 1. """ #Following toolbutton facilitates entering a temporary NanotubeLineMode #to create a CNT using endpoints of the specified line. self.specifyCntLineButton = PM_ToolButton( pmGroupBox, text = "Specify Endpoints", iconPath = "ui/actions/Properties Manager/Pencil.png", spanWidth = True ) self.specifyCntLineButton.setCheckable(True) self.specifyCntLineButton.setAutoRaise(True) self.specifyCntLineButton.setToolButtonStyle( Qt.ToolButtonTextBesideIcon) #EndPoint1 and endPoint2 coordinates. These widgets are hidden # as of 2007- 12 - 05 self._endPoint1SpinBoxes = PM_CoordinateSpinBoxes(pmGroupBox, label = "End Point 1") self.x1SpinBox = self._endPoint1SpinBoxes.xSpinBox self.y1SpinBox = self._endPoint1SpinBoxes.ySpinBox self.z1SpinBox = self._endPoint1SpinBoxes.zSpinBox self._endPoint2SpinBoxes = PM_CoordinateSpinBoxes(pmGroupBox, label = "End Point 2") self.x2SpinBox = self._endPoint2SpinBoxes.xSpinBox self.y2SpinBox = self._endPoint2SpinBoxes.ySpinBox self.z2SpinBox = self._endPoint2SpinBoxes.zSpinBox self._endPoint1SpinBoxes.hide() self._endPoint2SpinBoxes.hide() def _loadGroupBox2(self, pmGroupBox): """ Load widgets in group box 2. """ _ntTypeChoices = ['Carbon', 'Boron Nitride'] self.ntTypeComboBox = \ PM_ComboBox( pmGroupBox, label = "Type:", choices = _ntTypeChoices, setAsDefault = True) self.ntRiseDoubleSpinBox = \ PM_DoubleSpinBox( pmGroupBox, label = "Rise:", value = self.nanotube.getRise(), setAsDefault = True, minimum = 2.0, maximum = 4.0, decimals = 3, singleStep = 0.01 ) self.ntRiseDoubleSpinBox.hide() # Nanotube Length self.ntLengthLineEdit = \ PM_LineEdit( pmGroupBox, label = "Nanotube Length: ", text = "0.0 Angstroms", setAsDefault = False) self.ntLengthLineEdit.setDisabled(True) self.ntLengthLineEdit.hide() # Nanotube diameter self.ntDiameterLineEdit = \ PM_LineEdit( pmGroupBox, label = "Diameter: ", setAsDefault = False) self.ntDiameterLineEdit.setDisabled(True) self.updateNanotubeDiameter() self.chiralityNSpinBox = \ PM_SpinBox( pmGroupBox, label = "Chirality (n):", value = self.nanotube.getChiralityN(), minimum = 2, maximum = 100, setAsDefault = True ) self.chiralityMSpinBox = \ PM_SpinBox( pmGroupBox, label = "Chirality (m):", value = self.nanotube.getChiralityM(), minimum = 0, maximum = 100, setAsDefault = True ) # How about having user prefs for CNT and BNNT bond lengths? # I'm guessing that if the user wants to set these values, they will # do it once and would like those bond length values persist forever. # Need to discuss with others to determine if this spinbox comes out. # --Mark 2008-03-29 self.bondLengthDoubleSpinBox = \ PM_DoubleSpinBox( pmGroupBox, label = "Bond length:", value = self.nanotube.getBondLength(), setAsDefault = True, minimum = 1.0, maximum = 3.0, singleStep = 0.1, decimals = 3, suffix = " Angstroms" ) #self.bondLengthDoubleSpinBox.hide() endingChoices = ["Hydrogen", "None"] # Removed:, "Nitrogen"] self.endingsComboBox= \ PM_ComboBox( pmGroupBox, label = "Endings:", choices = endingChoices, index = 0, setAsDefault = True, spanWidth = False ) def _loadGroupBox3(self, inPmGroupBox): """ Load widgets in group box 3. """ self.zDistortionDoubleSpinBox = \ PM_DoubleSpinBox( inPmGroupBox, label = "Z-distortion:", value = 0.0, setAsDefault = True, minimum = 0.0, maximum = 10.0, singleStep = 0.1, decimals = 3, suffix = " Angstroms" ) self.xyDistortionDoubleSpinBox = \ PM_DoubleSpinBox( inPmGroupBox, label = "XY-distortion:", value = 0.0, setAsDefault = True, minimum = 0.0, maximum = 2.0, singleStep = 0.1, decimals = 3, suffix = " Angstroms" ) self.twistSpinBox = \ PM_SpinBox( inPmGroupBox, label = "Twist:", value = 0, setAsDefault = True, minimum = 0, maximum = 100, # What should maximum be? suffix = " deg/A" ) self.bendSpinBox = \ PM_SpinBox( inPmGroupBox, label = "Bend:", value = 0, setAsDefault = True, minimum = 0, maximum = 360, suffix = " deg" ) def _loadGroupBox4(self, inPmGroupBox): """ Load widgets in group box 4. """ # "Number of Nanotubes" SpinBox self.mwntCountSpinBox = \ PM_SpinBox( inPmGroupBox, label = "Number:", value = 1, setAsDefault = True, minimum = 1, maximum = 10, suffix = " nanotubes" ) self.mwntCountSpinBox.setSpecialValueText("SWNT") # "Spacing" lineedit. self.mwntSpacingDoubleSpinBox = \ PM_DoubleSpinBox( inPmGroupBox, label = "Spacing:", value = 2.46, setAsDefault = True, minimum = 1.0, maximum = 10.0, singleStep = 0.1, decimals = 3, suffix = " Angstroms" ) def _loadGroupBox5(self, pmGroupBox): """ Load widgets in group box 5. """ self._rubberbandLineGroupBox = PM_GroupBox( pmGroupBox, title = 'Rubber band Line:') ntLineChoices = ['Ladder'] self.ntRubberBandLineDisplayComboBox = \ PM_ComboBox( self._rubberbandLineGroupBox , label = " Display as:", choices = ntLineChoices, setAsDefault = True) self.lineSnapCheckBox = \ PM_CheckBox(self._rubberbandLineGroupBox , text = 'Enable line snap' , widgetColumn = 1, state = Qt.Checked ) def _connect_showCursorTextCheckBox(self): """ Connect the show cursor text checkbox with user prefs_key. Overrides DnaOrCnt_PropertyManager._connect_showCursorTextCheckBox """ connect_checkbox_with_boolean_pref( self.showCursorTextCheckBox , insertNanotubeEditCommand_showCursorTextCheckBox_prefs_key ) def _params_for_creating_cursorTextCheckBoxes(self): """ Returns params needed to create various cursor text checkboxes connected to prefs_keys that allow custom cursor texts. @return: A list containing tuples in the following format: ('checkBoxTextString' , preference_key). PM_PrefsCheckBoxes uses this data to create checkboxes with the the given names and connects them to the provided preference keys. (Note that PM_PrefsCheckBoxes puts thes within a GroupBox) @rtype: list @see: PM_PrefsCheckBoxes @see: self._loadDisplayOptionsGroupBox where this list is used. @see: Superclass method which is overridden here -- DnaOrCnt_PropertyManager._params_for_creating_cursorTextCheckBoxes() """ params = \ [ #Format: (" checkbox text", prefs_key) ("Nanotube length", insertNanotubeEditCommand_cursorTextCheckBox_length_prefs_key ), ("Angle", insertNanotubeEditCommand_cursorTextCheckBox_angle_prefs_key ) ] return params def _addToolTipText(self): """ Tool Tip text for widgets in the Insert Nanotube Property Manager. """ pass def _setEndPoints(self): """ Set the two endpoints of the nanotube using the values from the X, Y, Z coordinate spinboxes in the property manager. @note: The group box containing the 2 sets of XYZ spin boxes are currently hidden. """ # First endpoint (origin) of nanotube x1 = self.x1SpinBox.value() y1 = self.y1SpinBox.value() z1 = self.z1SpinBox.value() # Second endpoint (direction vector/axis) of nanotube. x2 = self.x2SpinBox.value() y2 = self.y2SpinBox.value() z2 = self.z2SpinBox.value() if not self.endPoint1: self.endPoint1 = V(x1, y1, z1) if not self.endPoint2: self.endPoint2 = V(x2, y2, z2) self.nanotube.setEndPoints(self.endPoint1, self.endPoint2) # Need arg "recompute=True", which will recompute the second # endpoint (endPoint2) using the nanotube rise. def getParameters(self): """ Return the parameters from this property manager to be used to create the nanotube. @return: A nanotube instance with its attrs set to the current parameters in the property manager. @rtype: L{Nanotube} @see: L{InsertNanotube_EditCommand._gatherParameters} where this is used """ self._setEndPoints() return (self.nanotube) def _ntTypeComboBoxChanged( self, type ): """ Slot for the Type combobox. It is called whenever the Type choice is changed. @param inIndex: The new index. @type inIndex: int """ self.nanotube.setType(str(type)) print "Bond Length =", self.nanotube.getBondLength() self.bondLengthDoubleSpinBox.setValue(self.nanotube.getBondLength()) #self.bondLengthDoubleSpinBox.setValue(ntBondLengths[inIndex]) def _bondLengthChanged(self, bondLength): """ Slot for the B{Bond Length} spinbox. """ self.nanotube.setBondLength(bondLength) self.updateNanotubeDiameter() return def _chiralityFixup(self, spinBoxValueJunk = None): """ Slot for several validators for different parameters. This gets called whenever the user changes the n, m chirality values. @param spinBoxValueJunk: This is the Spinbox value from the valueChanged signal. It is not used. We just want to know that the spinbox value has changed. @type spinBoxValueJunk: double or None """ _n, _m = self.nanotube.setChirality(self.chiralityNSpinBox.value(), self.chiralityMSpinBox.value()) #self.n, self.m = self.nanotube.getChirality() self.connect_or_disconnect_signals(isConnect = False) self.chiralityNSpinBox.setValue(_n) self.chiralityMSpinBox.setValue(_m) self.connect_or_disconnect_signals(isConnect = True) self.updateNanotubeDiameter() def updateNanotubeDiameter(self): """ Update the nanotube Diameter lineEdit widget. """ diameterText = "%-7.4f Angstroms" % (self.nanotube.getDiameter()) self.ntDiameterLineEdit.setText(diameterText) # ntRiseDoubleSpinBox is currently hidden. self.ntRiseDoubleSpinBox.setValue(self.nanotube.getRise()) def _endingsComboBoxChanged(self, endings): """ Slot for the B{Ending} combobox. @param endings: The option's text. @type endings: string """ self.nanotube.setEndings(str(endings)) return def _addWhatsThisText(self): """ What's This text for widgets in this Property Manager. """ whatsThis_InsertNanotube_PropertyManager(self) return
class DnaStrand_PropertyManager(DnaOrCnt_PropertyManager): """ The DnaStrand_PropertyManager class provides a Property Manager for the DnaStrand_EditCommand. @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 = "DnaStrand Properties" pmName = title iconPath = "ui/actions/Properties Manager/Strand.png" def __init__(self, win, editCommand): """ Constructor for the Build DNA property manager. """ #For model changed signal self.previousSelectionParams = None #see self.connect_or_disconnect_signals for comment about this flag self.isAlreadyConnected = False self.isAlreadyDisconnected = False self.sequenceEditor = None self._numberOfBases = 0 self._conformation = 'B-DNA' self.duplexRise = 3.18 self.basesPerTurn = 10 self.dnaModel = 'PAM3' _superclass.__init__(self, win, editCommand) self.showTopRowButtons( PM_DONE_BUTTON | \ PM_WHATS_THIS_BUTTON) self._loadSequenceEditor() msg = "Use resize handles to resize the strand. Use sequence editor"\ "to assign a new sequence or the current one to a file." self.updateMessage(msg) def _addGroupBoxes(self): """ Add the DNA Property Manager group boxes. """ self._pmGroupBox1 = PM_GroupBox(self, title="Parameters") self._loadGroupBox1(self._pmGroupBox1) self._displayOptionsGroupBox = PM_GroupBox(self, title="Display Options") self._loadDisplayOptionsGroupBox(self._displayOptionsGroupBox) def _loadGroupBox1(self, pmGroupBox): """ Load widgets in group box 4. """ self.nameLineEdit = PM_LineEdit(pmGroupBox, label="Strand name:", text="", setAsDefault=False) self.numberOfBasesSpinBox = \ PM_SpinBox( pmGroupBox, label = "Number of bases:", value = self._numberOfBases, setAsDefault = False, minimum = 2, maximum = 10000 ) self.basesPerTurnDoubleSpinBox = \ PM_DoubleSpinBox( pmGroupBox, label = "Bases per turn:", value = self.basesPerTurn, setAsDefault = True, minimum = 8.0, maximum = 20.0, decimals = 2, singleStep = 0.1 ) self.duplexRiseDoubleSpinBox = \ PM_DoubleSpinBox( pmGroupBox, label = "Rise:", value = self.duplexRise, setAsDefault = True, minimum = 2.0, maximum = 4.0, decimals = 3, singleStep = 0.01 ) self.disableStructHighlightingCheckbox = \ PM_CheckBox( pmGroupBox, text = "Don't highlight while editing DNA", widgetColumn = 0, state = Qt.Unchecked, setAsDefault = True, spanWidth = True ) #As of 2008-03-31, the properties such as number of bases will be #editable only by using the resize handles. post FNANO we will support #the self.numberOfBasesSpinBox.setEnabled(False) self.basesPerTurnDoubleSpinBox.setEnabled(False) self.duplexRiseDoubleSpinBox.setEnabled(False) def _loadSequenceEditor(self): """ Temporary code that shows the Sequence editor ..a doc widget docked at the bottom of the mainwindow. The implementation is going to change before 'rattleSnake' product release. As of 2007-11-20: This feature (sequence editor) is waiting for the ongoing dna model work to complete. """ self.sequenceEditor = self.win.createDnaSequenceEditorIfNeeded() self.sequenceEditor.hide() def _loadDisplayOptionsGroupBox(self, pmGroupBox): """ Overrides superclass method. Also loads the color chooser widget. """ self._loadColorChooser(pmGroupBox) _superclass._loadDisplayOptionsGroupBox(self, pmGroupBox) def _connect_showCursorTextCheckBox(self): """ Connect the show cursor text checkbox with user prefs_key. Overrides DnaOrCnt_PropertyManager._connect_showCursorTextCheckBox """ connect_checkbox_with_boolean_pref( self.showCursorTextCheckBox, dnaStrandEditCommand_showCursorTextCheckBox_prefs_key) def _params_for_creating_cursorTextCheckBoxes(self): """ Returns params needed to create various cursor text checkboxes connected to prefs_keys that allow custom cursor texts. @return: A list containing tuples in the following format: ('checkBoxTextString' , preference_key). PM_PrefsCheckBoxes uses this data to create checkboxes with the the given names and connects them to the provided preference keys. (Note that PM_PrefsCheckBoxes puts thes within a GroupBox) @rtype: list @see: PM_PrefsCheckBoxes @see: self._loadDisplayOptionsGroupBox where this list is used. @see: Superclass method which is overridden here -- DnaOrCnt_PropertyManager._params_for_creating_cursorTextCheckBoxes() """ params = \ [ #Format: (" checkbox text", prefs_key) ("Number of bases", dnaStrandEditCommand_cursorTextCheckBox_numberOfBases_prefs_key), ("Number of bases to be changed", dnaStrandEditCommand_cursorTextCheckBox_changedBases_prefs_key) ] return params def getParameters(self): numberOfBases = self.numberOfBasesSpinBox.value() dnaForm = self._conformation dnaModel = self.dnaModel basesPerTurn = self.basesPerTurn duplexRise = self.duplexRise color = self._colorChooser.getColor() return (numberOfBases, dnaForm, dnaModel, basesPerTurn, duplexRise, color) def setParameters(self, params): """ This is usually called when you are editing an existing structure. Some property manager ui elements then display the information obtained from the object being edited. TODO: - Make this a EditCommand_PM API method? - See also the routines GraphicsMode.setParams or object.setProps ..better to name them all in one style? """ #Set the duplex rise and bases per turn spinbox values. numberOfBases, \ dnaForm, \ dnaModel, \ basesPerTurn, \ duplexRise, \ color = params if numberOfBases is not None: self.numberOfBasesSpinBox.setValue(numberOfBases) if dnaForm is not None: self._conformation = dnaForm if dnaModel is not None: self.dnaModel = dnaModel if duplexRise is not None: self.duplexRiseDoubleSpinBox.setValue(duplexRise) if basesPerTurn is not None: self.basesPerTurnDoubleSpinBox.setValue(basesPerTurn) if color is not None: self._colorChooser.setColor(color) def connect_or_disconnect_signals(self, isConnect): """ Connect or disconnect widget signals sent to their slot methods. This can be overridden in subclasses. By default it does nothing. @param isConnect: If True the widget will send the signals to the slot method. @type isConnect: boolean """ #TODO: This is a temporary fix for a bug. When you invoke a temporary # mode Entering such a temporary mode keeps the signals of #PM from the previous mode connected ( #but while exiting that temporary mode and reentering the #previous mode, it atucally reconnects the signal! This gives rise to #lots of bugs. This needs more general fix in Temporary mode API. # -- Ninad 2008-01-09 (similar comment exists in MovePropertyManager.py if isConnect and self.isAlreadyConnected: if debug_flags.atom_debug: print_compact_stack("warning: attempt to connect widgets"\ "in this PM that are already connected." ) return if not isConnect and self.isAlreadyDisconnected: if debug_flags.atom_debug: print_compact_stack("warning: attempt to disconnect widgets"\ "in this PM that are already disconnected.") return self.isAlreadyConnected = isConnect self.isAlreadyDisconnected = not isConnect if isConnect: change_connect = self.win.connect else: change_connect = self.win.disconnect if self.sequenceEditor: self.sequenceEditor.connect_or_disconnect_signals(isConnect) _superclass.connect_or_disconnect_signals(self, isConnect) change_connect(self.disableStructHighlightingCheckbox, SIGNAL('stateChanged(int)'), self.change_struct_highlightPolicy) change_connect(self.showCursorTextCheckBox, SIGNAL('stateChanged(int)'), self._update_state_of_cursorTextGroupBox) def model_changed(self): """ @see: DnaStrand_EditCommand.model_changed() @see: DnaStrand_EditCommand.hasResizableStructure() """ isStructResizable, why_not = self.editCommand.hasResizableStructure() if not isStructResizable: #disable all widgets if self._pmGroupBox1.isEnabled(): self._pmGroupBox1.setEnabled(False) msg1 = ("Viewing properties of %s <br>") % ( self.editCommand.struct.name) msg2 = redmsg("DnaStrand is not resizable. Reason: %s" % (why_not)) self.updateMessage(msg1 + msg2) else: if not self._pmGroupBox1.isEnabled(): self._pmGroupBox1.setEnabled(True) msg1 = ("Viewing properties of %s <br>") % ( self.editCommand.struct.name) msg2 = "Use resize handles to resize the strand. Use sequence editor"\ "to assign a new sequence or the current one to a file." self.updateMessage(msg1 + msg2) def show(self): """ Show this PM As of 2007-11-20, it also shows the Sequence Editor widget and hides the history widget. This implementation may change in the near future This method also retrives the name information from the editCommand's structure for its name line edit field. @see: DnaStrand_EditCommand.getStructureName() @see: self.close() """ _superclass.show(self) self._showSequenceEditor() if self.editCommand is not None: name = self.editCommand.getStructureName() if name is not None: self.nameLineEdit.setText(name) def close(self): """ Close this property manager. Also sets the name of the self.editCommand's structure to the one displayed in the line edit field. @see self.show() @see: DnaSegment_EditCommand.setStructureName """ if self.editCommand is not None: name = str(self.nameLineEdit.text()) self.editCommand.setStructureName(name) if self.sequenceEditor: self.sequenceEditor.close() _superclass.close(self) def _showSequenceEditor(self): if self.sequenceEditor: if not self.sequenceEditor.isVisible(): #Show the sequence editor #ATTENTION: the sequence editor also closes (temporarily) the #reports dockwidget (if visible) Its state is later restored when #the sequuence Editor is closed. self.sequenceEditor.show() self.updateSequence() def updateSequence(self): """ Update the sequence string in the sequence editor @see: DnaSequenceEditor.setSequence() @see DnaSequenceEditor._determine_complementSequence() @see: DnaSequenceEditor.setComplementSequence() @see: DnaStrand.getStrandSequenceAndItsComplement() """ #Read in the strand sequence of the selected strand and #show it in the text edit in the sequence editor. ##strand = self.strandListWidget.getPickedItem() if not self.editCommand.hasValidStructure(): return strand = self.editCommand.struct titleString = 'Sequence Editor for ' + strand.name self.sequenceEditor.setWindowTitle(titleString) sequenceString, complementSequenceString = strand.getStrandSequenceAndItsComplement( ) if sequenceString: sequenceString = QString(sequenceString) sequenceString = sequenceString.toUpper() #Set the initial sequence (read in from the file) self.sequenceEditor.setSequence(sequenceString) #Set the initial complement sequence for DnaSequence editor. #do this independently because 'complementSequenceString' may have #some characters (such as * ) that denote a missing base on the #complementary strand. this information is used by the sequence #editor. See DnaSequenceEditor._determine_complementSequence() #for more details. See also bug 2787 self.sequenceEditor.setComplementSequence(complementSequenceString) def change_struct_highlightPolicy(self, checkedState=False): """ Change the 'highlight policy' of the structure being edited (i.e. self.editCommand.struct) . @param checkedState: The checked state of the checkbox that says 'Don't highlight while editing DNA'. So, it its True, the structure being edited won't get highlighted. @see: DnaStrand.setHighlightPolicy for more comments """ if self.editCommand and self.editCommand.hasValidStructure(): highlight = not checkedState self.editCommand.struct.setHighlightPolicy(highlight=highlight) def _addWhatsThisText(self): """ Add what's this text. Abstract method. """ pass
class EditNanotube_PropertyManager(DnaOrCnt_PropertyManager): """ The NanotubeSegmenta_PropertyManager class provides a Property Manager for the EditNanotube_EditCommand. @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 = "Nanotube Properties" pmName = title iconPath = "ui/actions/Command Toolbar/BuildNanotube/EditNanotube.png" def __init__(self, command): """ Constructor for the Cnt Segment Properties property manager. """ #For model changed signal self.previousSelectionParams = None #see self.connect_or_disconnect_signals for comment about this flag self.isAlreadyConnected = False self.isAlreadyDisconnected = False # Initialized here. Their values will be set in # _update_widgets_in_PM_before_show() self.endPoint1 = V(0, 0, 0) self.endPoint2 = V(0, 0, 0) _superclass.__init__(self, command) self.showTopRowButtons( PM_DONE_BUTTON | \ PM_CANCEL_BUTTON | \ PM_WHATS_THIS_BUTTON) def connect_or_disconnect_signals(self, isConnect): """ Connect or disconnect widget signals sent to their slot methods. This can be overridden in subclasses. By default it does nothing. @param isConnect: If True the widget will send the signals to the slot method. @type isConnect: boolean """ if isConnect: change_connect = self.win.connect else: change_connect = self.win.disconnect change_connect(self.nameLineEdit, SIGNAL("editingFinished()"), self._nameChanged) change_connect(self.showCursorTextCheckBox, SIGNAL('stateChanged(int)'), self._update_state_of_cursorTextGroupBox) return def show(self): """ Show this property manager. Overrides EditCommand_PM.show() This method also retrives the name information from the command's structure for its name line edit field. @see: EditNanotube_EditCommand.getStructureName() @see: self.close() """ _superclass.show(self) #if self.command is not None: #name = self.command.getStructureName() #if name is not None: #self.nameLineEdit.setText(name) def close(self): """ Close this property manager. Also sets the name of the self.command's structure to the one displayed in the line edit field. @see self.show() @see: EditNanotube_EditCommand.setStructureName """ if self.command is not None: name = str(self.nameLineEdit.text()) self.command.setStructureName(name) _superclass.close(self) def _connect_showCursorTextCheckBox(self): """ Connect the show cursor text checkbox with user prefs_key. Overrides DnaOrCnt_PropertyManager._connect_showCursorTextCheckBox """ connect_checkbox_with_boolean_pref( self.showCursorTextCheckBox, editNanotubeEditCommand_showCursorTextCheckBox_prefs_key) def _params_for_creating_cursorTextCheckBoxes(self): """ Returns params needed to create various cursor text checkboxes connected to prefs_keys that allow custom cursor texts. @return: A list containing tuples in the following format: ('checkBoxTextString' , preference_key). PM_PrefsCheckBoxes uses this data to create checkboxes with the the given names and connects them to the provided preference keys. (Note that PM_PrefsCheckBoxes puts thes within a GroupBox) @rtype: list @see: PM_PrefsCheckBoxes @see: self._loadDisplayOptionsGroupBox where this list is used. @see: Superclass method which is overridden here -- DnaOrCnt_PropertyManager._params_for_creating_cursorTextCheckBoxes() """ params = \ [ #Format: (" checkbox text", prefs_key) ("Nanotube length", editNanotubeEditCommand_cursorTextCheckBox_length_prefs_key) ] return params def setParameters(self, params): """ This is called when entering "Nanotube Segment Properties (i.e. "Edit properties...") to retrieve and set parameters of the nanotube segment that might be modified during this command and are needed to regenerate the nanotube segment. @param params: The parameters of the nanotube segment. These parameters are retreived via L{NanotubeSegment.getProps()}, called from L{EditNanotube_EditCommand.editStructure()}. Parameters: - n, m (chirality) - type (i.e. carbon or boron nitride) - endings (none, hydrogen, nitrogen) - endpoints (endPoint1, endPoint2) @type params: list (n, m), type, endings, (endPoint1, endPoint2) @note: Any widgets in the property manager that display these parameters should be updated here. @see: L{NanotubeSegment.getProps()} TODO: - Make this a EditCommand_PM API method? - See also the routines GraphicsMode.setParams or object.setProps ..better to name them all in one style? """ (self.n, self.m), self.type, self.endings,\ (self.endPoint1, self.endPoint2) = params # This is needed to update the endpoints since the Nanotube segment # may have been moved (i.e. translated or rotated). In that case, # the endpoints are not updated, so we recompute them here. nanotubeChunk = self.command.struct.members[0] self.endPoint1, self.endPoint2, radius = \ self.command.struct.nanotube.computeEndPointsFromChunk(nanotubeChunk) if 0: print "\n--------------" print "setParameters():" print "Struct=", self.command.struct print "N, M:", self.n, self.m print "type:", self.type print "endings:", self.endings print "pt1, pt2:", self.endPoint1, self.endPoint2 def getParameters(self): """ Get the parameters that the edit command will use to determine if any have changed. If any have, then the nanotube will be modified. """ if 0: print "\n--------------" print "getParameters():" print "Struct=", self.command.struct print "N, M:", self.n, self.m print "type:", self.type print "endings:", self.endings print "pt1, pt2:", self.endPoint1, self.endPoint2 return (self.n, self.m, self.type, self.endings, self.endPoint1, self.endPoint2) def _update_widgets_in_PM_before_show(self): """ This is called only when user is editing an existing structure. Its different than self.update_widgets_in_pm_before_show. (that method is called just before showing the property manager) @see: EditNanotube_EditCommand.editStructure() """ if self.command and self.command.hasValidStructure(): self.nanotube = self.command.struct.nanotube self.n, self.m = self.nanotube.getChirality() self.type = self.nanotube.getType() self.endings = self.nanotube.getEndings() self.endPoint1, self.endPoint2 = self.nanotube.getEndPoints() pass # Note that _update_widgets_in_PM_before_show() is called in # self.show, before you connect the signals. So, for the # 'first show' we will need to manually set the value of any # widgets that need updated. But later, when a different # NanotubeSegment is clicked, (while still in # EditNanotube_EditCommand, the propMgr will already be connected # so any calls in that case is redundant. self.updateNameField() self.updateLength() self.updateNanotubeDiameter() self.updateChirality() return def _update_UI_do_updates(self): """ Overrides superclass method. @see: Command_PropertyManager._update_UI_do_updates() """ self._update_widgets_in_PM_before_show() if self.command.struct: msg = "Editing structure <b>%s</b>." % \ self.command.getStructureName() else: msg = "Select a nanotube to edit." self.updateMessage(msg) return def _addGroupBoxes(self): """ Add the Property Manager group boxes. """ self._pmGroupBox1 = PM_GroupBox(self, title="Parameters") self._loadGroupBox1(self._pmGroupBox1) self._displayOptionsGroupBox = PM_GroupBox(self, title="Display Options") self._loadDisplayOptionsGroupBox(self._displayOptionsGroupBox) def _loadGroupBox1(self, pmGroupBox): """ Load widgets in group box 4. """ self.nameLineEdit = PM_LineEdit(pmGroupBox, label="Name:", text="", setAsDefault=False) # Nanotube Length self.ntLengthLineEdit = \ PM_LineEdit( pmGroupBox, label = "Length: ", text = "0.0 Angstroms", setAsDefault = False) self.ntLengthLineEdit.setDisabled(True) # Nanotube Radius self.ntDiameterLineEdit = \ PM_LineEdit( pmGroupBox, label = "Nanotube Diameter: ", setAsDefault = False) self.ntDiameterLineEdit.setDisabled(True) # Nanotube chirality. These are disabled (read-only) for now. --Mark self.chiralityNSpinBox = \ PM_SpinBox( pmGroupBox, label = "Chirality (n) :", minimum = 2, maximum = 100, setAsDefault = True ) self.chiralityNSpinBox.setDisabled(True) self.chiralityMSpinBox = \ PM_SpinBox( pmGroupBox, label = "Chirality (m) :", minimum = 0, maximum = 100, setAsDefault = True ) self.chiralityMSpinBox.setDisabled(True) def _addWhatsThisText(self): """ Add what's this text. """ pass def _addToolTipText(self): """ Add Tooltip text """ pass def _nameChanged(self): """ Slot for "Name" field. @TODO: Include a validator for the name field. """ _name = str(self.nameLineEdit.text()) if not _name: # Minimal test. Need to implement a validator. self.updateNameField() return self.command.setStructureName(_name) msg = "Editing structure <b>%s</b>." % _name self.updateMessage(msg) return def updateNameField(self): """ Update the name field showing the name of the currently selected protein. clear the combobox list. """ if self.command.hasValidStructure(): self.nameLineEdit.setEnabled(True) self.nameLineEdit.setText(self.command.getStructureName()) else: self.nameLineEdit.setDisabled(True) self.nameLineEdit.setText("") return def updateLength(self): """ Update the nanotube Length lineEdit widget. """ if self.command.hasValidStructure(): _nanotubeLength = vlen(self.endPoint1 - self.endPoint2) _lengthText = "%-7.4f Angstroms" % (_nanotubeLength) else: _lengthText = "" self.ntLengthLineEdit.setText(_lengthText) return def updateNanotubeDiameter(self): """ Update the nanotube Diameter lineEdit widget. """ if self.command.hasValidStructure(): _diameterText = "%-7.4f Angstroms" % (self.nanotube.getDiameter()) else: _diameterText = "" self.ntDiameterLineEdit.setText(_diameterText) return def updateChirality(self): """ Update the nanotube chirality spinboxes (read-only). """ if self.command.hasValidStructure(): n, m = self.nanotube.getChirality() else: n = 0 m = 0 self.chiralityNSpinBox.setValue(n) self.chiralityMSpinBox.setValue(m) return pass # End of EditNanotube_PropertyManager class