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 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 NanotubeSegment(Group):
    """
    Model object which represents a Nanotube Segment inside a Nanotube Group.

    Internally, this is just a specialized Group containing a single chunk,
    itself containing all the atoms of a nanotube.
    """

    # This should be a tuple of classifications that appear in
    # files_mmp._GROUP_CLASSIFICATIONS, most general first.
    # See comment in class Group for more info. [bruce 080115]
    _mmp_group_classifications = ('NanotubeSegment', )

    nanotube = None

    _endPoint1 = None
    _endPoint2 = None
    # TODO: undo or copy code for those attrs,
    # and updating them when the underlying structure changes.
    # But maybe that won't be needed, if they are replaced
    # by computing them from the atom geometry as needed.
    # [bruce 080227 comment]

    autodelete_when_empty = True
    # (but only if current command permits that for this class --
    #  see comment near Group.autodelete_when_empty for more info,
    #  and implems of Command.keep_empty_group)

    iconPath = "ui/modeltree/NanotubeSegment.png"
    hide_iconPath = "ui/modeltree/NanotubeSegment-hide.png"

    # This partially fixes bug 2914. Copying now works, but the following
    # "warning" is printed to stdout:
    # ****************** needs _copyOfObject: <cnt.model.Nanotube.Nanotube instance at 0x164FC030>
    # I'm guessing this means that we need to override abstract method
    # _copyOfObject() of DataMixin, but I'd like to discuss this with Bruce first.
    # I also have confirmed that there is still a bug when editing the
    # copied nanotube (it will automatically move from the clipboard
    # to the part after it is resized).
    # Mark 2008-07-09.
    copyable_attrs = Group.copyable_attrs + ('nanotube', )

    def __init__(self, name, assy, dad, members=(), editCommand=None):

        Group.__init__(self,
                       name,
                       assy,
                       dad,
                       members=members,
                       editCommand=editCommand)
        ###BUG: not all callers pass an editCommand. It would be better
        # to figure out on demand which editCommand would be appropriate.
        # [bruce 080227 comment]
        return

    def writemmp_other_info_opengroup(self,
                                      mapping):  #bruce 080507 refactoring
        """
        """
        #bruce 080507 refactoring (split this out of Group.writemmp)
        # (I think the following condition is always true, but I didn't
        #  prove this just now, so I left in the test for now.)
        encoded_classifications = self._encoded_classifications()
        if encoded_classifications == "NanotubeSegment":
            # This is a nanotube segment, so write the parameters into an info
            # record so we can read and restore them in the next session.
            # --Mark 2008-04-12
            assert self.nanotube
            mapping.write("info opengroup nanotube-parameters = %d, %d, %s, %s\n" \
                          % (self.nanotube.getChiralityN(),
                             self.nanotube.getChiralityM(),
                             self.nanotube.getType(),
                             self.nanotube.getEndings()))
            pass
        return

    def readmmp_info_opengroup_setitem(self, key, val, interp):
        """
        [extends superclass method]
        """
        #bruce 080507 refactoring (split this out of the superclass method)
        if key == ['nanotube-parameters']:
            # val includes all the parameters, separated by commas.
            n, m, type, endings = val.split(",")
            self.n = int(n)
            self.m = int(m)
            self.type = type.lstrip()
            self.endings = endings.lstrip()
            # Create the nanotube.
            from cnt.model.Nanotube import Nanotube
            self.nanotube = Nanotube()  # Returns a 5x5 CNT.
            self.nanotube.setChirality(self.n, self.m)
            self.nanotube.setType(self.type)
            self.nanotube.setEndings(self.endings)
            # The endpoints are recomputed every time it is edited.
        else:
            Group.readmmp_info_opengroup_setitem(self, key, val, interp)
        return

    def edit(self):
        """
        Edit this NanotubeSegment. 
        @see: NanotubeSegment_EditCommand
        """

        commandSequencer = self.assy.w.commandSequencer

        if commandSequencer.currentCommand.commandName != "NANOTUBE_SEGMENT":
            commandSequencer.userEnterTemporaryCommand('NANOTUBE_SEGMENT')

        assert commandSequencer.currentCommand.commandName == 'NANOTUBE_SEGMENT'
        commandSequencer.currentCommand.editStructure(self)

    #Following methods are likely to be revised in a fully functional dna data
    # model. These methods are mainly created to get working many core UI
    # operations for Rattlesnake.  -- Ninad 2008-02-01

    def get_all_content_chunks(self):
        """
        Return all the chunks contained within this NanotubeSegment.
        
        @note: there is only one chunk inside this group.
        """
        all_content_chunk_list = []

        for member in self.members:
            if isinstance(member, Chunk):
                all_content_chunk_list.append(member)

        return all_content_chunk_list

    def getAxisVector(self, atomAtVectorOrigin=None):
        """
        Returns the unit axis vector of the segment (vector between two axis 
        end points)
        """
        endPoint1, endPoint2 = self.nanotube.getEndPoints()

        if endPoint1 is None or endPoint2 is None:
            return V(0, 0, 0)

        #@see: RotateAboutAPoint command. The following code is disabled
        #as it has bugs (not debugged but could be in
        #self.nanotube.getEndPoints). So, rotate about a point won't work for
        #rotating a nanotube. -- Ninad 2008-05-13

        ##if atomAtVectorOrigin is not None:
        ###If atomAtVectorOrigin is specified, we will return a vector that
        ###starts at this atom and ends at endPoint1 or endPoint2 .
        ###Which endPoint to choose will be dicided by the distance between
        ###atomAtVectorOrigin and the respective endPoints. (will choose the
        ###frthest endPoint
        ##origin = atomAtVectorOrigin.posn()
        ##if vlen(endPoint2 - origin ) > vlen(endPoint1 - origin):
        ##return norm(endPoint2 - endPoint1)
        ##else:
        ##return norm(endPoint1 - endPoint2)

        return norm(endPoint2 - endPoint1)

    def setProps(self, props):
        """
        Sets some properties. These will be used while editing the structure. 
        (but if the structure is read from an mmp file, this won't work. As a 
        fall back, it returns some constant values) 
        @see: InsertNanotube_EditCommand.createStructure which calls this method. 
        @see: self.getProps, NanotubeSegment_EditCommand.editStructure        
        """
        (_n, _m), _type, _endings, (_endPoint1, _endPoint2) = props

        from cnt.model.Nanotube import Nanotube
        self.nanotube = Nanotube()
        self.nanotube.setChirality(_n, _m)
        self.nanotube.setType(_type)
        self.nanotube.setEndings(_endings)
        self.nanotube.setEndPoints(_endPoint1, _endPoint2)

    def getProps(self):
        """
        Returns nanotube parameters necessary for editing.
        
        @see: NanotubeSegment_EditCommand.editStructure where it is used. 
        @see: NanotubeSegment_PropertyManager.getParameters
        @see: NanotubeSegmentEditCommand._createStructure        
        """
        # Recompute the endpoints in case this nanotube was read from
        # MMP file (which means this nanotube doesn't have endpoint
        # parameters yet).
        self.nanotube.computeEndPointsFromChunk(self.members[0])

        return self.nanotube.getParameters()

    def getNanotubeGroup(self):
        """
        Return the NanotubeGroup we are contained in, or None if we're not
        inside one.
        """
        return self.parent_node_of_class(self.assy.NanotubeGroup)

    def isAncestorOf(self, obj):
        """
        Checks whether the object <obj> is contained within the NanotubeSegment
        
        Example: If the object is an Atom, it checks whether the 
        atom's chunk is a member of this NanotubeSegment (chunk.dad is self)
        
        It also considers all the logical contents of the NanotubeSegment to determine
        whether self is an ancestor. (returns True even for logical contents)
        
        @see: self.get_all_content_chunks()
        @see: NanotubeSegment_GraphicsMode.leftDrag
        """
        # TODO: this needs cleanup (it looks like it's made of two alternative
        # implems, one after the other), and speedup. [bruce 080507 comment]

        c = None
        if isinstance(obj, Atom):
            c = obj.molecule
        elif isinstance(obj, Bond):
            chunk1 = obj.atom1.molecule
            chunk2 = obj.atom1.molecule
            if chunk1 is chunk2:
                c = chunk1
        elif isinstance(obj, Chunk):
            c = obj

        if c is not None:
            if c in self.get_all_content_chunks():
                return True

        #NOTE: Need to check if the isinstance checks are acceptable (apparently
        #don't add any import cycle) Also this method needs to be revised
        #after we completely switch to dna data model.
        if isinstance(obj, Atom):
            chunk = obj.molecule
            if chunk.dad is self:
                return True
            else:
                return False
        elif isinstance(obj, Bond):
            chunk1 = obj.atom1.molecule
            chunk2 = obj.atom1.molecule
            if (chunk1.dad is self) or (chunk2.dad is self):
                return True
        elif isinstance(obj, Chunk):
            if obj.dad is self:
                return True
        return False

    def node_icon(self, display_prefs):
        del display_prefs  # unused

        if self.all_content_is_hidden():
            return imagename_to_pixmap(self.hide_iconPath)
        else:
            return imagename_to_pixmap(self.iconPath)

    # =======================================================================
    # These methods were copied from DnaStrandOrSegment and edited for
    # this class.

    def permit_addnode_inside(self):  #bruce 080626
        """
        [overrides Group method]
        """
        return False

    def permits_ungrouping(self):
        """
        Should the user interface permit users to dissolve this Group
        using self.ungroup?
        [overridden from Group]
        """
        return self._show_all_kids_for_debug()  # normally False
        # note: modelTree should modify menu text for Ungroup to say "(unsupported)",
        # but this is broken as of before 080318 since it uses a self.is_block() test.

    def _show_all_kids_for_debug(self):
        #bruce 080207 in deprecated class Block 080318
        classname_short = self.__class__.__name__.split('.')[-1]
        debug_pref_name = "Model Tree: show content of %s?" % classname_short
        # typical examples (for text searches to find them here):
        # Model Tree: show content of DnaStrand?
        # Model Tree: show content of DnaSegment?
        return debug_pref(debug_pref_name, Choice_boolean_False)

    def permit_as_member(self, node, pre_updaters=True, **opts):
        """
        [friend method for enforce_permitted_members_in_groups and subroutines]

        Does self permit node as a direct member,
        when called from enforce_permitted_members_in_groups with
        the same options as we are passed?

        @rtype: boolean

        [overrides Group method]
        """
        #bruce 080319
        # someday, reject if superclass would reject -- so far, it never does
        del opts
        assy = self.assy
        res = isinstance(node, assy.Chunk)  #@ NEEDS SOMETHING MORE.
        return res

    def _f_wants_to_be_killed(self,
                              pre_updaters=True,
                              **opts):  # in DnaStrandOrSegment
        """
        [friend method for enforce_permitted_members_in_groups and subroutines]
        
        Does self want to be killed due to members that got ejected
        by _f_move_nonpermitted_members (or due to completely invalid structure
        from before then, and no value in keeping self even temporarily)?

        @rtype: boolean

        [overrides Group method]   
        """
        #bruce 080319
        del opts, pre_updaters
        return not self.members

    def MT_DND_can_drop_inside(self):  #bruce 080317, revised 080318
        """
        Are ModelTree Drag and Drop operations permitted to drop nodes
        inside self?

        [overrides Node/Group method]
        """
        return self._show_all_kids_for_debug()  # normally False

    def openable(self):  # overrides Node.openable()
        """
        whether tree widgets should permit the user to open/close their view of this node
        """
        # if we decide this depends on the tree widget or on somet for thing about it,
        # we'll have to pass in some args... don't do that unless/until we need to.

        #If there are no MT_kids (subnodes visible in MT under this group) then
        #don't make this node 'openable'. This makes sure that expand/ collapse
        #pixmap next to the node is not shown for this type of Group with 0
        #MT_kids
        #Examples of such groups include empty groups, DnaStrand Groups,
        #DnaSegments etc -- Ninad 2008-03-15
        return len(self.MT_kids()) != 0

    def _raw_MT_kids(self, display_prefs={}):
        """
        DnaStrand or DnaSegment groups (subclasses of this class) should not 
        show any MT kids.
        @see: Group._raw__MT_kids()
        @see: Group.MT_kids()
        """
        if self._show_all_kids_for_debug():  # normally False
            # bruce 080318
            return self.members
        return ()

    pass  # end of class NanotubeSegment
Esempio n. 4
0
class NanotubeSegment(Group):
    """
    Model object which represents a Nanotube Segment inside a Nanotube Group.

    Internally, this is just a specialized Group containing a single chunk,
    itself containing all the atoms of a nanotube.
    """

    # This should be a tuple of classifications that appear in
    # files_mmp._GROUP_CLASSIFICATIONS, most general first.
    # See comment in class Group for more info. [bruce 080115]
    _mmp_group_classifications = ('NanotubeSegment',)
    
    nanotube = None
    
    _endPoint1 = None
    _endPoint2 = None
        # TODO: undo or copy code for those attrs,
        # and updating them when the underlying structure changes.
        # But maybe that won't be needed, if they are replaced
        # by computing them from the atom geometry as needed.
        # [bruce 080227 comment]
        
    autodelete_when_empty = True
        # (but only if current command permits that for this class --
        #  see comment near Group.autodelete_when_empty for more info,
        #  and implems of Command.keep_empty_group)
        
    iconPath = "ui/modeltree/NanotubeSegment.png"
    hide_iconPath = "ui/modeltree/NanotubeSegment-hide.png"
    
    # This partially fixes bug 2914. Copying now works, but the following
    # "warning" is printed to stdout:
    # ****************** needs _copyOfObject: <cnt.model.Nanotube.Nanotube instance at 0x164FC030>
    # I'm guessing this means that we need to override abstract method 
    # _copyOfObject() of DataMixin, but I'd like to discuss this with Bruce first.
    # I also have confirmed that there is still a bug when editing the 
    # copied nanotube (it will automatically move from the clipboard
    # to the part after it is resized).
    # Mark 2008-07-09.
    copyable_attrs = Group.copyable_attrs + ('nanotube',)
    
    def __init__(self, name, assy, dad, members = (), editCommand = None):
            
        Group.__init__(self, 
                       name, 
                       assy, 
                       dad, 
                       members = members, 
                       editCommand = editCommand)
        ###BUG: not all callers pass an editCommand. It would be better
        # to figure out on demand which editCommand would be appropriate.
        # [bruce 080227 comment]
        return

    def writemmp_other_info_opengroup(self, mapping): #bruce 080507 refactoring
        """
        """
        #bruce 080507 refactoring (split this out of Group.writemmp)
        # (I think the following condition is always true, but I didn't
        #  prove this just now, so I left in the test for now.)
        encoded_classifications = self._encoded_classifications()
        if encoded_classifications == "NanotubeSegment":
            # This is a nanotube segment, so write the parameters into an info
            # record so we can read and restore them in the next session. 
            # --Mark 2008-04-12
            assert self.nanotube
            mapping.write("info opengroup nanotube-parameters = %d, %d, %s, %s\n" \
                          % (self.nanotube.getChiralityN(),
                             self.nanotube.getChiralityM(),
                             self.nanotube.getType(),
                             self.nanotube.getEndings()))
            pass
        return

    def readmmp_info_opengroup_setitem( self, key, val, interp ):
        """
        [extends superclass method]
        """
        #bruce 080507 refactoring (split this out of the superclass method)
        if key == ['nanotube-parameters']:
            # val includes all the parameters, separated by commas.
            n, m, type, endings = val.split(",")
            self.n = int(n)
            self.m = int(m)
            self.type = type.lstrip()
            self.endings = endings.lstrip()
            # Create the nanotube.
            from cnt.model.Nanotube import Nanotube
            self.nanotube = Nanotube() # Returns a 5x5 CNT.
            self.nanotube.setChirality(self.n, self.m)
            self.nanotube.setType(self.type)
            self.nanotube.setEndings(self.endings)
            # The endpoints are recomputed every time it is edited.
        else:
            Group.readmmp_info_opengroup_setitem( self, key, val, interp)
        return
    
    def edit(self):
        """
        Edit this NanotubeSegment. 
        @see: NanotubeSegment_EditCommand
        """
        
        commandSequencer = self.assy.w.commandSequencer       
        
        if commandSequencer.currentCommand.commandName != "NANOTUBE_SEGMENT":
            commandSequencer.userEnterTemporaryCommand('NANOTUBE_SEGMENT')
            
        assert commandSequencer.currentCommand.commandName == 'NANOTUBE_SEGMENT'
        commandSequencer.currentCommand.editStructure(self)
    

    #Following methods are likely to be revised in a fully functional dna data 
    # model. These methods are mainly created to get working many core UI 
    # operations for Rattlesnake.  -- Ninad 2008-02-01
    
    def get_all_content_chunks(self):
        """
        Return all the chunks contained within this NanotubeSegment.
        
        @note: there is only one chunk inside this group.
        """
        all_content_chunk_list = []
        
        for member in self.members:
            if isinstance(member, Chunk):
                all_content_chunk_list.append(member)
        
        return all_content_chunk_list 
    
    def getAxisVector(self, atomAtVectorOrigin = None):
        """
        Returns the unit axis vector of the segment (vector between two axis 
        end points)
        """
        endPoint1, endPoint2 = self.nanotube.getEndPoints()
                
        if endPoint1 is None or endPoint2 is None:
            return V(0, 0, 0)
        
        #@see: RotateAboutAPoint command. The following code is disabled 
        #as it has bugs (not debugged but could be in 
        #self.nanotube.getEndPoints). So, rotate about a point won't work for 
        #rotating a nanotube. -- Ninad 2008-05-13
      
        ##if atomAtVectorOrigin is not None:
            ###If atomAtVectorOrigin is specified, we will return a vector that
            ###starts at this atom and ends at endPoint1 or endPoint2 . 
            ###Which endPoint to choose will be dicided by the distance between
            ###atomAtVectorOrigin and the respective endPoints. (will choose the 
            ###frthest endPoint
            ##origin = atomAtVectorOrigin.posn()
            ##if vlen(endPoint2 - origin ) > vlen(endPoint1 - origin):
                ##return norm(endPoint2 - endPoint1)
            ##else:
                ##return norm(endPoint1 - endPoint2)
        
        return norm(endPoint2 - endPoint1)
    
    def setProps(self, props):
        """
        Sets some properties. These will be used while editing the structure. 
        (but if the structure is read from an mmp file, this won't work. As a 
        fall back, it returns some constant values) 
        @see: InsertNanotube_EditCommand.createStructure which calls this method. 
        @see: self.getProps, NanotubeSegment_EditCommand.editStructure        
        """
        (_n, _m), _type, _endings, (_endPoint1, _endPoint2) = props
        
        from cnt.model.Nanotube import Nanotube
        self.nanotube = Nanotube()
        self.nanotube.setChirality(_n, _m)
        self.nanotube.setType(_type)
        self.nanotube.setEndings(_endings)
        self.nanotube.setEndPoints(_endPoint1, _endPoint2)
        
    def getProps(self):
        """
        Returns nanotube parameters necessary for editing.
        
        @see: NanotubeSegment_EditCommand.editStructure where it is used. 
        @see: NanotubeSegment_PropertyManager.getParameters
        @see: NanotubeSegmentEditCommand._createStructure        
        """
        # Recompute the endpoints in case this nanotube was read from
        # MMP file (which means this nanotube doesn't have endpoint 
        # parameters yet). 
        self.nanotube.computeEndPointsFromChunk(self.members[0])
        
        return self.nanotube.getParameters()
        
    def getNanotubeGroup(self):
        """
        Return the NanotubeGroup we are contained in, or None if we're not
        inside one.
        """
        return self.parent_node_of_class( self.assy.NanotubeGroup)
        
    def isAncestorOf(self, obj):
        """
        Checks whether the object <obj> is contained within the NanotubeSegment
        
        Example: If the object is an Atom, it checks whether the 
        atom's chunk is a member of this NanotubeSegment (chunk.dad is self)
        
        It also considers all the logical contents of the NanotubeSegment to determine
        whether self is an ancestor. (returns True even for logical contents)
        
        @see: self.get_all_content_chunks()
        @see: NanotubeSegment_GraphicsMode.leftDrag
        """
        # TODO: this needs cleanup (it looks like it's made of two alternative
        # implems, one after the other), and speedup. [bruce 080507 comment]
        
        c = None
        if isinstance(obj, Atom):       
            c = obj.molecule                 
        elif isinstance(obj, Bond):
            chunk1 = obj.atom1.molecule
            chunk2 = obj.atom1.molecule            
            if chunk1 is chunk2:
                c = chunk1
        elif isinstance(obj, Chunk):
            c = obj
        
        if c is not None:
            if c in self.get_all_content_chunks():
                return True        
        
        #NOTE: Need to check if the isinstance checks are acceptable (apparently
        #don't add any import cycle) Also this method needs to be revised 
        #after we completely switch to dna data model. 
        if isinstance(obj, Atom):       
            chunk = obj.molecule                
            if chunk.dad is self:
                return True
            else:
                return False
        elif isinstance(obj, Bond):
            chunk1 = obj.atom1.molecule
            chunk2 = obj.atom1.molecule            
            if (chunk1.dad is self) or (chunk2.dad is self):
                return True               
        elif isinstance(obj, Chunk):
            if obj.dad is self:
                return True
        return False
    
    def node_icon(self, display_prefs):        
        del display_prefs # unused
        
        if self.all_content_is_hidden():    
            return imagename_to_pixmap( self.hide_iconPath)
        else:
            return imagename_to_pixmap( self.iconPath)
        
    # =======================================================================
    # These methods were copied from DnaStrandOrSegment and edited for 
    # this class.

    def permit_addnode_inside(self): #bruce 080626
        """
        [overrides Group method]
        """
        return False
    
    def permits_ungrouping(self): 
        """
        Should the user interface permit users to dissolve this Group
        using self.ungroup?
        [overridden from Group]
        """
        return self._show_all_kids_for_debug() # normally False
            # note: modelTree should modify menu text for Ungroup to say "(unsupported)",
            # but this is broken as of before 080318 since it uses a self.is_block() test.

    def _show_all_kids_for_debug(self):
         #bruce 080207 in deprecated class Block 080318
        classname_short = self.__class__.__name__.split('.')[-1]
        debug_pref_name = "Model Tree: show content of %s?" % classname_short
            # typical examples (for text searches to find them here):
            # Model Tree: show content of DnaStrand?
            # Model Tree: show content of DnaSegment?
        return debug_pref( debug_pref_name, Choice_boolean_False )

    def permit_as_member(self, node, pre_updaters = True, **opts):
        """
        [friend method for enforce_permitted_members_in_groups and subroutines]

        Does self permit node as a direct member,
        when called from enforce_permitted_members_in_groups with
        the same options as we are passed?

        @rtype: boolean

        [overrides Group method]
        """
        #bruce 080319
        # someday, reject if superclass would reject -- so far, it never does
        del opts
        assy = self.assy
        res = isinstance( node, assy.Chunk) #@ NEEDS SOMETHING MORE.
        return res
    
    def _f_wants_to_be_killed(self, pre_updaters = True, **opts): # in DnaStrandOrSegment
        """
        [friend method for enforce_permitted_members_in_groups and subroutines]
        
        Does self want to be killed due to members that got ejected
        by _f_move_nonpermitted_members (or due to completely invalid structure
        from before then, and no value in keeping self even temporarily)?

        @rtype: boolean

        [overrides Group method]   
        """
        #bruce 080319
        del opts, pre_updaters
        return not self.members

    def MT_DND_can_drop_inside(self): #bruce 080317, revised 080318
        """
        Are ModelTree Drag and Drop operations permitted to drop nodes
        inside self?

        [overrides Node/Group method]
        """
        return self._show_all_kids_for_debug() # normally False
    
    def openable(self): # overrides Node.openable()
        """
        whether tree widgets should permit the user to open/close their view of this node
        """
        # if we decide this depends on the tree widget or on somet for thing about it,
        # we'll have to pass in some args... don't do that unless/until we need to.

        #If there are no MT_kids (subnodes visible in MT under this group) then
        #don't make this node 'openable'. This makes sure that expand/ collapse
        #pixmap next to the node is not shown for this type of Group with 0 
        #MT_kids
        #Examples of such groups include empty groups, DnaStrand Groups,
        #DnaSegments etc -- Ninad 2008-03-15
        return len(self.MT_kids()) != 0
    
    def _raw_MT_kids(self, display_prefs = {}):
        """
        DnaStrand or DnaSegment groups (subclasses of this class) should not 
        show any MT kids.
        @see: Group._raw__MT_kids()
        @see: Group.MT_kids()
        """
        if self._show_all_kids_for_debug(): # normally False
            # bruce 080318
            return self.members
        return ()
    
    pass # end of class NanotubeSegment