# Copyright 2008 Nanorex, Inc. See LICENSE file for details.
"""
OrderDna_PropertyManager.py
The OrderDna_PropertyManager class provides a Property Manager
for the B{Order Dna} command on the flyout toolbar in the
Build > Dna mode.
@author: Mark
@version: $Id$
@copyright: 2008 Nanorex, Inc. See LICENSE file for details.
"""
import os, time
from widgets.prefs_widgets import connect_checkbox_with_boolean_pref
from PyQt4.Qt import Qt
from PyQt4.Qt import SIGNAL
from PM.PM_GroupBox import PM_GroupBox
from PM.PM_ComboBox import PM_ComboBox
from PM.PM_LineEdit import PM_LineEdit
from PM.PM_PushButton import PM_PushButton
from PM.PM_Constants import PM_DONE_BUTTON
from PM.PM_Constants import PM_WHATS_THIS_BUTTON
from utilities.prefs_constants import assignColorToBrokenDnaStrands_prefs_key
from platform_dependent.PlatformDependent import find_or_make_Nanorex_subdir
from platform_dependent.PlatformDependent import open_file_in_editor
from dna.model.DnaStrand import DnaStrand
from command_support.Command_PropertyManager import Command_PropertyManager
from ne1_ui.WhatsThisText_for_PropertyManagers import whatsThis_OrderDna_PropertyManager
def writeDnaOrderFile(fileName,
assy,
numberOfBases,
numberOfUnassignedBases,
dnaSequence):
"""
Writes a DNA Order file in comma-separated value (CSV) format.
@param fileName: The full path of the DNA order file.
@param assy: The assembly.
@param numberOfBase: The number of bases.
@param numberOfUnassignedBases: The number of unassigned (i.e. X) bases.
@param dnaSequence: The dnaSequence string to be written to the file.
@see: self.orderDna
"""
#Create Header
date_header = "#NanoEngineer-1 DNA Order Form created on %s\n" \
% time.strftime("%Y-%m-%d at %H:%M:%S")
if assy.filename:
mmpFileName = "[" + os.path.normpath(assy.filename) + "]"
else:
mmpFileName = "[" + assy.name + "]" + \
" ( The mmp file was probably not saved when the "\
" sequence was written)"
fileNameInfo_header = "#This sequence is created for file '%s'\n" \
% mmpFileName
numberOfBases_header = "#Total number of bases: %d\n" % numberOfBases
unassignedBases_header = ""
if numberOfUnassignedBases:
unassignedBases_header = \
"#WARNING: This order includes %d unassigned (i.e. \"X\") bases.\n"\
% numberOfUnassignedBases
info_header = "#This file is written in comma-separated value (CSV) format. "\
"Open with Excel or any other program that supports CSV format.\n"
column_header = "Name,Length,Sequence,Notes\n"
file_header = date_header \
+ fileNameInfo_header \
+ numberOfBases_header \
+ unassignedBases_header \
+ info_header \
+ "\n" \
+ column_header
# Write file
f = open(fileName,'w')
f.write(file_header)
f.write(dnaSequence)
f.close()
return
_superclass = Command_PropertyManager
class OrderDna_PropertyManager(Command_PropertyManager):
"""
The OrderDna_PropertyManager class provides a Property Manager
for the B{Order Dna} command on the flyout toolbar in the
Build > Dna mode.
@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 = "Order DNA"
pmName = title
iconPath = "ui/actions/Command Toolbar/BuildDna/OrderDna.png"
def __init__( self, command ):
"""
Constructor for the property manager.
"""
_superclass.__init__(self, command)
self.assy = self.win.assy
self.showTopRowButtons( PM_DONE_BUTTON | \
PM_WHATS_THIS_BUTTON)
self.update_includeStrands() # Updates the message box.
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
"""
if isConnect:
change_connect = self.win.connect
else:
change_connect = self.win.disconnect
change_connect( self.viewDnaOrderFileButton,
SIGNAL("clicked()"),
self.viewDnaOrderFile)
change_connect( self.includeStrandsComboBox,
SIGNAL("activated(int)"),
self.update_includeStrands )
return
def _addGroupBoxes( self ):
"""
Add the Property Manager group boxes.
"""
self._pmGroupBox1 = PM_GroupBox( self, title = "Options" )
self._loadGroupBox1( self._pmGroupBox1 )
def _loadGroupBox1(self, pmGroupBox):
"""
Load widgets in group box.
"""
includeStrandsChoices = ["All strands in model",
"Selected strands only"]
self.includeStrandsComboBox = \
PM_ComboBox( pmGroupBox,
label = "Include strands:",
choices = includeStrandsChoices,
setAsDefault = True)
self.numberOfBasesLineEdit = \
PM_LineEdit( pmGroupBox,
label = "Total nucleotides:",
text = str(self.getNumberOfBases()))
self.numberOfBasesLineEdit.setEnabled(False)
self.numberOfXBasesLineEdit = \
PM_LineEdit( pmGroupBox,
label = "Unassigned:",
text = str(self.getNumberOfBases(unassignedOnly = True)))
self.numberOfXBasesLineEdit.setEnabled(False)
self.viewDnaOrderFileButton = \
PM_PushButton( pmGroupBox,
label = "",
text = "View DNA Order File...",
spanWidth = True)
return
def _addWhatsThisText(self):
"""
What's This text for widgets in this Property Manager.
"""
whatsThis_OrderDna_PropertyManager(self)
return
def _addToolTipText(self):
"""
Tool Tip text for widgets in the DNA Property Manager.
"""
pass
# Ask Bruce where this should live (i.e. class Part?) --Mark
def getAllDnaStrands(self, selectedOnly = False):
"""
Returns a list of all the DNA strands in the current part, or only
the selected strands if I{selectedOnly} is True.
@param selectedOnly: If True, return only the selected DNA strands.
@type selectedOnly: bool
"""
dnaStrandList = []
def func(node):
if isinstance(node, DnaStrand):
if selectedOnly:
if node.picked:
dnaStrandList.append(node)
else:
dnaStrandList.append(node)
self.win.assy.part.topnode.apply2all(func)
return dnaStrandList
def getNumberOfBases(self, selectedOnly = False, unassignedOnly = False):
"""
Returns the number of bases count for all the DNA strands in the
current part, or only the selected strand if I{selectedOnly} is True.
@param selectedOnly: If True, return only the number of bases in the
selected DNA strands.
@type selectedOnly: bool
@param unassignedOnly: If True, return only the number of unassigned
bases (i.e. base letters = X).
@type unassignedOnly: bool
"""
dnaSequenceString = ''
selectedOnly = self.includeStrandsComboBox.currentIndex()
strandList = self.getAllDnaStrands(selectedOnly)
for strand in strandList:
strandSequenceString = str(strand.getStrandSequence())
dnaSequenceString += strandSequenceString
if unassignedOnly:
return dnaSequenceString.count("X")
return len(dnaSequenceString)
def _update_UI_do_updates(self):
"""
Overrides superclass method.
"""
self.update_includeStrands()
return
def getDnaSequence(self, format = 'CSV'):
"""
Return the complete Dna sequence information string (i.e. all strand
sequences) in the specified format.
@return: The Dna sequence string
@rtype: string
"""
if format == 'CSV': #comma separated values.
separator = ','
dnaSequenceString = ''
selectedOnly = self.includeStrandsComboBox.currentIndex()
strandList = self.getAllDnaStrands(selectedOnly)
for strand in strandList:
dnaSequenceString = dnaSequenceString + strand.name + separator
strandSequenceString = str(strand.getStrandSequence())
if strandSequenceString:
strandSequenceString = strandSequenceString.upper()
strandLength = str(len(strandSequenceString)) + separator
dnaSequenceString = dnaSequenceString + strandLength + strandSequenceString
dnaSequenceString = dnaSequenceString + "\n"
return dnaSequenceString
def viewDnaOrderFile(self, openFileInEditor = True):
"""
Writes a DNA Order file in comma-separated values (CSV) format
and opens it in a text editor.
The user must save the file to a permanent location using the
text editor.
@see: Ui_DnaFlyout.orderDnaCommand
@see: writeDnaOrderFile()
@TODO: assy.getAllDnaObjects().
"""
dnaSequence = self.getDnaSequence(format = 'CSV')
if dnaSequence:
tmpdir = find_or_make_Nanorex_subdir('temp')
fileBaseName = 'DnaOrder'
temporaryFile = os.path.join(tmpdir, "%s.csv" % fileBaseName)
writeDnaOrderFile(temporaryFile,
self.assy,
self.getNumberOfBases(),
self.getNumberOfBases(unassignedOnly = True),
dnaSequence)
if openFileInEditor:
open_file_in_editor(temporaryFile)
return
def update_includeStrands(self, ignoreVal = 0):
"""
Slot method for "Include (strands)" combobox.
"""
idx = self.includeStrandsComboBox.currentIndex()
includeType = ["model", "selection"]
_numberOfBases = self.getNumberOfBases()
self.numberOfBasesLineEdit.setText(str(_numberOfBases) + " bases")
_numberOfXBases = self.getNumberOfBases(unassignedOnly = True)
self.numberOfXBasesLineEdit.setText(str(_numberOfXBases) + " bases")
# Make the background color red if there are any unassigned bases.
if _numberOfXBases:
self.numberOfXBasesLineEdit.setStyleSheet(\
"QLineEdit {"\
"background-color: rgb(255, 0, 0)"\
"}")
else:
self.numberOfXBasesLineEdit.setStyleSheet(\
"QLineEdit {"\
"background-color: rgb(255, 255, 255)"\
"}")
if _numberOfBases > 0:
self.viewDnaOrderFileButton.setEnabled(True)
msg = "Click on View DNA Order File... to preview a " \
"DNA order for all DNA strands in the current %s." \
% includeType[idx]
else:
self.viewDnaOrderFileButton.setEnabled(False)
msg = "" \
"There are no DNA strands in the current %s." \
% includeType[idx]
self.updateMessage(msg)
return