def docPartAddedEvent(self, part): if part.crossSectionType() == LatticeType.Honeycomb: self.sliceGraphicsItem = HoneycombSliceGraphicsItem(part, controller=self.win.sliceController, parent=self.win.sliceroot) else: self.sliceGraphicsItem = SquareSliceGraphicsItem(part, controller=self.win.sliceController, parent=self.win.sliceroot) self.pathHelixGroup = PathHelixGroup(part, controller=self.win.pathController, parent=self.win.pathroot) if app().isInMaya(): self.solidHelixGrp = SolidHelixGroup( part, controller=self.win.pathController, htype=part.crossSectionType()) self.win.sliceController.activeSliceLastSignal.connect( self.pathHelixGroup.activeSliceHandle().moveToLastSlice) self.win.sliceController.activeSliceFirstSignal.connect( self.pathHelixGroup.activeSliceHandle().moveToFirstSlice) self.win.pathController.setActivePath(self.pathHelixGroup) self.win.actionFrame.triggered.connect(self.pathHelixGroup.zoomToFit) for vh in part.getVirtualHelices(): xos = vh.get3PrimeXovers(StrandType.Scaffold) for xo in xos: toBase = (xo[1][0], xo[1][2]) self.pathHelixGroup.createXoverItem( xo[0], toBase, StrandType.Scaffold) xos = vh.get3PrimeXovers(StrandType.Staple) for xo in xos: toBase = (xo[1][0], xo[1][2]) self.pathHelixGroup.createXoverItem( xo[0], toBase, StrandType.Staple) # end for self.setActivePart(part)
class DocumentController(): """ The document controller. Hooks high level (read file/write file, add submodel, etc) UI elements to their corresponding actions in the model """ def __init__(self, doc=None, fname=None): app().documentControllers.add(self) if doc != None and doc._undoStack != None: self._undoStack = doc._undoStack else: self._undoStack = QUndoStack() self._undoStack.setClean() self._undoStack.cleanChanged.connect( self.undoStackCleanStatusChangedSlot) self._filename = fname if fname else "untitled.nno" self._activePart = None self.sliceGraphicsItem = None self.pathHelixGroup = None self._hasNoAssociatedFile = fname == None self.win = DocumentWindow(docCtrlr=self) self.win.closeEvent = self.closer self.win.changeEvent = self.changed self.connectWindowEventsToSelf() self.win.show() self._document = None self.setDocument(Document() if not doc else doc) app().undoGroup.addStack(self.undoStack()) self.win.setWindowTitle(self.documentTitle() + '[*]') #self.solidHelixGrp = None if doc != None and doc.parts(): doc.parts()[0].needsFittingToView.emit() def closer(self, event): if self.maybeSave(): if app().testRecordMode: self.win.sliceController.testRecorder.generateTest() event.accept() else: event.ignore() def changed(self, event): if (event.type() == QEvent.ActivationChange or event.type() == QEvent.WindowActivate or event.type() == QEvent.ApplicationActivate): if self.win.isActiveWindow() and app().activeDocument != self: app().activeDocument = self if hasattr(self, 'solidHelixGrp'): if self.solidHelixGrp: self.solidHelixGrp.deleteAllMayaNodes() self.solidHelixGrp.onPersistentDataChanged() def documentTitle(self): fname = os.path.basename(str(self.filename())) if not self.undoStack().isClean(): fname += '[*]' return fname def filename(self): return self._filename def setFilename(self, proposedFName): if self._filename == proposedFName: return True self._filename = proposedFName self._hasNoAssociatedFile = False self.win.setWindowTitle(self.documentTitle()) return True def activePart(self): if self._activePart == None: self._activePart = self._document.selectedPart() return self._activePart def setActivePart(self, part): # should be document.selectedPart self._activePart = part def document(self): return self._document def setDocument(self, doc): self._document = doc doc.setController(self) doc.partAdded.connect(self.docPartAddedEvent) for p in doc.parts(): self.docPartAddedEvent(p) def undoStack(self): return self._undoStack def connectWindowEventsToSelf(self): """Organizational method to collect signal/slot connectors.""" self.win.actionNewHoneycombPart.triggered.connect(self.hcombClicked) self.win.actionNewSquarePart.triggered.connect(self.squareClicked) self.win.actionNew.triggered.connect(app().newDocument) self.win.actionOpen.triggered.connect(self.openClicked) self.win.actionClose.triggered.connect(self.closeClicked) self.win.actionSave.triggered.connect(self.saveClicked) self.win.actionSVG.triggered.connect(self.svgClicked) self.win.actionAutoStaple.triggered.connect(self.autoStapleClicked) self.win.actionCSV.triggered.connect(self.exportCSV) self.win.actionPreferences.triggered.connect(app().prefsClicked) self.win.actionSave_As.triggered.connect(self.saveAsClicked) # self.win.actionQuit.triggered.connect(self.closeClicked) # self.win.actionAdd.triggered.connect(self.addClicked) # self.win.actionDelete.triggered.connect(self.deleteClicked) # self.win.actionCut.triggered.connect(self.cutClicked) # self.win.actionPaste.triggered.connect(self.pasteClicked) # self.win.actionMoveUp.triggered.connect(self.moveUpClicked) # self.win.actionMoveDown.triggered.connect(self.moveDownClicked) # self.win.actionPromote.triggered.connect(self.promoteClicked) # self.win.actionDemote.triggered.connect(self.demoteClicked) # end def def undoStackCleanStatusChangedSlot(self): self.win.setWindowModified(not self.undoStack().isClean()) # The title changes to include [*] on modification self.win.setWindowTitle(self.documentTitle()) def newClicked(self): """Create a new document window""" # Will create a new Document object and will be # be kept alive by the app's document list DocumentController() def openClicked(self): """docstring for openClicked""" # self.filesavedialog = None # self.openFile('/Users/nick/Downloads/nanorobot.v2.json') # return if util.isWindows(): # required for native looking file window fname = QFileDialog.getOpenFileName( None, "Open Document", "/", "CADnano1 / CADnano2 Files (*.nno *.json *.cadnano)") self.filesavedialog = None self.openFile(fname) else: # access through non-blocking callback fdialog = QFileDialog( self.win, "Open Document", "/", "CADnano1 / CADnano2 Files (*.nno *.json *.cadnano)") fdialog.setAcceptMode(QFileDialog.AcceptOpen) fdialog.setWindowFlags(Qt.Sheet) fdialog.setWindowModality(Qt.WindowModal) # fdialog.exec_() # or .show(), or .open() self.filesavedialog = fdialog self.filesavedialog.filesSelected.connect(self.openFile) fdialog.open() # or .show(), or .open() def openFile(self, selected): if isinstance(selected, QStringList) or isinstance(selected, list): fname = selected[0] else: fname = selected if not fname or os.path.isdir(fname): return False fname = str(fname) doc = decode(file(fname).read()) doc.finalizeImport() # updates staple highlighting DocumentController(doc, fname) if self.filesavedialog != None: self.filesavedialog.filesSelected.disconnect(self.openFile) # manual garbage collection to prevent hang (in osx) del self.filesavedialog # end def def exportSequenceCSV(self, fname): """Export all staple sequences to CSV file fnane.""" output = self.activePart().getStapleSequences() f = open(fname, 'w') f.write(output) f.close() # end def def exportCSV(self): fname = self.filename() if fname == None: directory = "." else: directory = QFileInfo(fname).path() if util.isWindows(): # required for native looking file window fname = QFileDialog.getSaveFileName( self.win, "%s - Export As" % QApplication.applicationName(), directory, "(*.csv)") self.filesavedialog = None self.exportFile(fname) else: # access through non-blocking callback fdialog = QFileDialog( self.win, "%s - Export As" % QApplication.applicationName(), directory, "(*.csv)") fdialog.setAcceptMode(QFileDialog.AcceptSave) fdialog.setWindowFlags(Qt.Sheet) fdialog.setWindowModality(Qt.WindowModal) # fdialog.exec_() # or .show(), or .open() self.filesavedialog = fdialog self.filesavedialog.filesSelected.connect(self.exportFile) fdialog.open() # end def def exportFile(self, selected): if isinstance(selected, QStringList) or isinstance(selected, list): fname = selected[0] else: fname = selected if fname.isEmpty() or os.path.isdir(fname): return False fname = str(fname) if not fname.lower().endswith(".csv"): fname += ".csv" # self.setFilename(fname) if self.filesavedialog != None: self.filesavedialog.filesSelected.disconnect(self.exportFile) # manual garbage collection to prevent hang (in osx) del self.filesavedialog return self.exportSequenceCSV(fname) # end def def closeClicked(self): """This will trigger a Window closeEvent""" if util.isWindows(): self.win.close() def maybeSave(self): """ Save on quit, check if document changes have occured. """ if app().dontAskAndJustDiscardUnsavedChanges: return True if not self.undoStack().isClean(): # document dirty? savebox = QMessageBox(QMessageBox.Warning, "Application", "The document has been modified.\nDo you want to save your changes?", QMessageBox.Save | QMessageBox.Discard | QMessageBox.Cancel, self.win, Qt.Dialog | Qt.MSWindowsFixedSizeDialogHint | Qt.Sheet) savebox.setWindowModality(Qt.WindowModal) save = savebox.button(QMessageBox.Save) discard = savebox.button(QMessageBox.Discard) cancel = savebox.button(QMessageBox.Cancel) save.setShortcut("Ctrl+S") discard.setShortcut(QKeySequence("D,Ctrl+D")) cancel.setShortcut(QKeySequence("C,Ctrl+C,.,Ctrl+.")) ret = savebox.exec_() del savebox # manual garbage collection to prevent hang (in osx) if ret == QMessageBox.Save: return self.saveAsClicked() elif ret == QMessageBox.Cancel: return False return True def writeToFile(self, filename=None): if filename == None: assert(not self._hasNoAssociatedFile) filename = self.filename() try: f = open(filename, 'w') encode(self._document, f) f.close() except IOError: flags = Qt.Dialog | Qt.MSWindowsFixedSizeDialogHint | Qt.Sheet errorbox = QMessageBox(QMessageBox.Critical, "CaDNAno", "Could not write to '%s'." % filename, QMessageBox.Ok, self.win, flags) errorbox.setWindowModality(Qt.WindowModal) errorbox.open() return False self.undoStack().setClean() self.setFilename(filename) return True def saveClicked(self): if self._hasNoAssociatedFile or self._document._importedFromJson: self.openSaveFileDialog() return self.writeToFile() def saveAsClicked(self): self.openSaveFileDialog() def openSaveFileDialog(self): fname = self.filename() if fname == None: directory = "." else: directory = QFileInfo(fname).path() if util.isWindows(): # required for native looking file window fname = QFileDialog.getSaveFileName( self.win, "%s - Save As" % QApplication.applicationName(), directory, "%s (*.nno)" % QApplication.applicationName()) self.writeToFile(fname) else: # access through non-blocking callback fdialog = QFileDialog( self.win, "%s - Save As" % QApplication.applicationName(), directory, "%s (*.nno)" % QApplication.applicationName()) fdialog.setAcceptMode(QFileDialog.AcceptSave) fdialog.setWindowFlags(Qt.Sheet) fdialog.setWindowModality(Qt.WindowModal) # fdialog.exec_() # or .show(), or .open() self.filesavedialog = fdialog self.filesavedialog.filesSelected.connect( self.saveFileDialogCallback) fdialog.open() def saveFileDialogCallback(self, selected): if isinstance(selected, QStringList) or isinstance(selected, list): fname = selected[0] else: fname = selected if fname.isEmpty() or os.path.isdir(fname): return False fname = str(fname) if not fname.lower().endswith(".nno"): fname += ".nno" if self.filesavedialog != None: self.filesavedialog.filesSelected.disconnect( self.saveFileDialogCallback) del self.filesavedialog # prevents hang self.writeToFile(fname) # end def def svgClicked(self): if isinstance(selected, QStringList) or isinstance(selected, list): fname = selected[0] else: fname = selected if fname.isEmpty() or os.path.isdir(fname): return False fname = str(fname) if not fname.lower().endswith(".svg"): fname += ".svg" self.setFilename(fname) if self.filesavedialog != None: self.filesavedialog.filesSelected.disconnect(self.saveFile) del self.filesavedialog return self.svgClicked() # end def def hcombClicked(self): """docstring for hcombClicked""" self.addHoneycombHelixGroup() # end def def squareClicked(self): """docstring for squareClicked""" self.addSquareHelixGroup() # end def def autoStapleClicked(self): self.activePart().autoStaple() ############# Spawning / Destroying HoneycombSliceGraphicsItems ########## ##################### and PathHelixGroups for Parts ###################### def docPartAddedEvent(self, part): if part.crossSectionType() == LatticeType.Honeycomb: self.sliceGraphicsItem = HoneycombSliceGraphicsItem(part, controller=self.win.sliceController, parent=self.win.sliceroot) else: self.sliceGraphicsItem = SquareSliceGraphicsItem(part, controller=self.win.sliceController, parent=self.win.sliceroot) self.pathHelixGroup = PathHelixGroup(part, controller=self.win.pathController, parent=self.win.pathroot) if app().isInMaya(): self.solidHelixGrp = SolidHelixGroup( part, controller=self.win.pathController, htype=part.crossSectionType()) self.win.sliceController.activeSliceLastSignal.connect( self.pathHelixGroup.activeSliceHandle().moveToLastSlice) self.win.sliceController.activeSliceFirstSignal.connect( self.pathHelixGroup.activeSliceHandle().moveToFirstSlice) self.win.pathController.setActivePath(self.pathHelixGroup) self.win.actionFrame.triggered.connect(self.pathHelixGroup.zoomToFit) for vh in part.getVirtualHelices(): xos = vh.get3PrimeXovers(StrandType.Scaffold) for xo in xos: toBase = (xo[1][0], xo[1][2]) self.pathHelixGroup.createXoverItem( xo[0], toBase, StrandType.Scaffold) xos = vh.get3PrimeXovers(StrandType.Staple) for xo in xos: toBase = (xo[1][0], xo[1][2]) self.pathHelixGroup.createXoverItem( xo[0], toBase, StrandType.Staple) # end for self.setActivePart(part) # end def def addHoneycombHelixGroup(self): """Adds a honeycomb DNA part to the document. Dimensions are set by the Document addDnaHoneycombPart method.""" dnaPart = self._document.addDnaHoneycombPart() self.setActivePart(dnaPart) if app().testRecordMode: self.win.sliceController.testRecorder.setPart( dnaPart.crossSectionType()) # end def def addSquareHelixGroup(self): """Adds a square DNA part to the document. Dimensions are set by the Document addDnaSquarePart method.""" dnaPart = self._document.addDnaSquarePart() self.setActivePart(dnaPart) if app().testRecordMode: self.win.sliceController.testRecorder.setPart( dnaPart.crossSectionType()) # end def def createAction(self, icon, text, parent, shortcutkey): """ returns a QAction object """ action = QAction(QIcon(icon), text, parent) if not shortcutkey.isEmpty(): action.setShortcut(shortcutkey) return action