Exemplo n.º 1
0
class DocumentController:
    """
    Connects UI buttons to their corresponding actions in the model.
    """

    ### INIT METHODS ###
    def __init__(self, doc=None, fname=None):
        """docstring for __init__"""
        # initialize variables
        self._document = Document() if not doc else doc
        self._filename = fname
        self.win = None
        self._undoStack = None
        self._hasNoAssociatedFile = None
        self._sliceViewInstance = None
        self._pathViewInstance = None
        self._solidView = None
        self._activePart = None
        # call other init methods
        self.initWindow()
        self.initMaya()
        app().documentControllers.add(self)

    def initWindow(self):
        """docstring for initWindow"""
        self.win = DocumentWindow(docCtrlr=self)
        self.connectWindowSignalsToSelf()
        self.win.show()

    def connectWindowSignalsToSelf(self):
        """This method serves to group all the signal & slot connections
        made by DocumentController"""
        self.win.actionNew.triggered.connect(self.actionNewSlot)
        self.win.actionOpen.triggered.connect(self.actionOpenSlot)
        self.win.actionClose.triggered.connect(self.actionCloseSlot)
        self.win.actionSave.triggered.connect(self.actionSaveSlot)
        self.win.actionSave_As.triggered.connect(self.actionSaveAsSlot)
        self.win.actionSVG.triggered.connect(self.actionSVGSlot)
        self.win.actionAutoStaple.triggered.connect(self.actionAutostapleSlot)
        self.win.actionExportStaples.triggered.connect(self.actionExportStaplesSlot)
        self.win.actionPreferences.triggered.connect(self.actionPrefsSlot)
        self.win.actionModify.triggered.connect(self.actionModifySlot)
        self.win.actionNewHoneycombPart.triggered.connect(self.actionAddHoneycombPartSlot)
        self.win.actionNewSquarePart.triggered.connect(self.actionAddSquarePartSlot)
        self.win.closeEvent = self.windowCloseEventHandler

    def initMaya(self):
        """docstring for initMaya"""
        if app().isInMaya():
            # There will only be one document
            if app().activeDocument and app().activeDocument.win and not app().activeDocument.win.close():
                return
            del app().activeDocument
            app().deleteAllMayaNodes()
            app().activeDocument = self

    ### SLOTS ###
    def undoStackCleanChangedSlot(self):
        """The title changes to include [*] on modification."""
        self.win.setWindowModified(not self.undoStack().isClean())
        self.win.setWindowTitle(self.documentTitle())

    def actionNewSlot(self):
        """
        1. If document is has no parts, do nothing.
        2. If document is dirty, call maybeSave and continue if it succeeds.
        3. Create a new document and swap it into the existing ctrlr/window.
        """
        if len(self._document.parts()) == 0:
            # print "no parts!"
            return  # no parts

        if self.maybeSave() == False:
            return  # user canceled in maybe save
        else:  # user did not cancel
            if hasattr(self, "filesavedialog"):  # user did save
                self.filesavedialog.finished.connect(self.newClickedCallback)
            else:  # user did not save
                self.newClickedCallback()  # finalize new

    def actionOpenSlot(self):
        """
        1. If document is untouched, proceed to open dialog.
        2. If document is dirty, call maybesave and continue if it succeeds.
        Downstream, the file is selected in openAfterMaybeSave,
        and the selected file is actually opened in openAfterMaybeSaveCallback.
        """
        if self.maybeSave() == False:
            return  # user canceled in maybe save
        else:  # user did not cancel
            if hasattr(self, "filesavedialog"):  # user did save
                if self.filesavedialog != None:
                    self.filesavedialog.finished.connect(self.openAfterMaybeSave)
                else:
                    self.openAfterMaybeSave()  # windows
            else:  # user did not save
                self.openAfterMaybeSave()  # finalize new

    def actionCloseSlot(self):
        """This will trigger a Window closeEvent."""
        if util.isWindows():
            self.win.close()

    def actionSaveSlot(self):
        """SaveAs if necessary, otherwise overwrite existing file."""
        if self._hasNoAssociatedFile:
            self.saveFileDialog()
            return
        # if importedFromJson:
        #     self.saveFileDialog()
        #     return
        self.writeDocumentToFile()

    def actionSaveAsSlot(self):
        """Open a save file dialog so user can choose a name."""
        self.saveFileDialog()

    def actionSVGSlot(self):
        """docstring for actionSVGSlot"""
        pass

    def actionExportStaplesSlot(self):
        """
        Triggered by clicking Export Staples button. Opens a file dialog to
        determine where the staples should be saved. The callback is
        exportCSVCallback which collects the staple sequences and exports
        the file.
        """
        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.saveCSVdialog = 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.saveCSVdialog = fdialog
            self.saveCSVdialog.filesSelected.connect(self.exportCSVCallback)
            fdialog.open()

    # end def

    def actionPrefsSlot(self):
        """docstring for actionPrefsSlot"""
        app().prefsClicked

    def actionAutostapleSlot(self):
        """docstring for actionAutostapleSlot"""
        self.activePart().autoStaple()

    def actionModifySlot(self):
        """docstring for actionModifySlot"""
        pass

    def actionAddHoneycombPartSlot(self):
        """docstring for actionAddHoneycombPartSlot"""
        part = self._document.addDnaHoneycombPart()
        self.setActivePart(part)

    def actionAddSquarePartSlot(self):
        """docstring for actionAddSquarePartSlot"""
        part = self._document.addDnaSquarePart()
        self.setActivePart(part)

    ### METHODS ###
    def undoStack(self):
        return self._document.undoStack()

    def newDocument(self, doc=None, fname=None):
        """Creates a new Document, reusing the DocumentController."""
        if app().isInMaya():
            app().deleteAllMayaNodes()
        self._document.removeAllParts()  # clear out old parts
        self._undoStack.clear()  # reset undostack
        del self.sliceGraphicsItem
        del self.pathHelixGroup
        self.sliceGraphicsItem = None
        self.pathHelixGroup = None
        self.solidHelixGroup = None
        self._filename = fname if fname else "untitled.nno"
        self._hasNoAssociatedFile = fname == None
        self._activePart = None
        self.win.setWindowTitle(self.documentTitle() + "[*]")
        if doc != None and doc.parts():
            part = doc.parts()[0]
            self._activePart = part
            self.setDocument(doc)
            part.needsFittingToView.emit()  # must come after setDocument
        else:
            self.setDocument(Document())

    def windowCloseEventHandler(self, event):
        """Intercept close events when user attempts to close the window."""
        if self.maybeSave():
            event.accept()
            if app().isInMaya():
                self.windock.setVisible(False)
                del self.windock
            app().documentControllers.remove(self)
        else:
            event.ignore()

    ### slot callbacks ###
    def actionNewSlotCallback(self):
        """
        Gets called on completion of filesavedialog after newClicked's 
        maybeSave. Removes the dialog if necessary, but it was probably
        already removed by saveFileDialogCallback.
        """
        if hasattr(self, "filesavedialog"):  # user did save
            self.filesavedialog.finished.disconnect(self.actionNewSlotCallback)
            del self.filesavedialog  # prevents hang (?)
        self.newDocument()

    def saveFileDialogCallback(self):
        """docstring for saveFileDialogCallback"""
        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.writeDocumentToFile(fname)

    def openAfterMaybeSaveCallback(self):
        """docstring for openAfterMaybeSaveCallback"""
        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())
        self.newDocument(doc, fname)
        doc.finalizeImport()  # updates staple highlighting
        if self.fileopendialog != None:
            self.fileopendialog.filesSelected.disconnect(self.openAfterMaybeSaveCallback)
            # manual garbage collection to prevent hang (in osx)
            del self.fileopendialog

    ### file input ##
    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):
        if self._filename == proposedFName:
            return True
        self._filename = proposedFName
        self._hasNoAssociatedFile = False
        self.win.setWindowTitle(self.documentTitle())
        return True

    def openAfterMaybeSave(self):
        """docstring for openAfterMaybeSave"""
        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.openAfterMaybeSaveCallback(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.fileopendialog = fdialog
            self.fileopendialog.filesSelected.connect(self.openAfterMaybeSaveCallback)
            fdialog.open()  # or .show(), or .open()

    def openAfterMaybeSaveCallback(self):
        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())
        # DocumentController(doc, fname)
        self.newDocument(doc, fname)
        doc.finalizeImport()  # updates staple highlighting
        if self.fileopendialog != None:
            self.fileopendialog.filesSelected.disconnect(self.openAfterMaybeSaveCallback)
            # manual garbage collection to prevent hang (in osx)
            del self.fileopendialog

    ### file output ###
    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 writeDocumentToFile(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 saveFileDialog(self):
        """Spawn a QFileDialog to allow user to choose a filename and path."""
        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.writeDocumentToFile(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 the user chose to save, write to that file."""
        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.writeDocumentToFile(fname)

    ### document related ###
    def document(self):
        """docstring for document"""
        return self._document

    def setDocument(self, doc):
        """docstring for setDocument"""
        print "in setDocument", doc
        self._document = doc

    ### part related ##
    def activePart(self):
        if self._activePart == None:
            self._activePart = self._document.selectedPart()
        return self._activePart

    def setActivePart(self, part):
        """docstring for setActivePart"""
        self._activePart = part