Ejemplo n.º 1
0
class FiberEditor(AppWindow):
    def __init__(self, parent=None):
        super().__init__(parent)
        self.factory = FiberFactory()

        self.setWindowTitle(self.tr("Fiber Editor"))

        actions = {
            'new': (self.tr("&New"), 'document-new', QtGui.QKeySequence.New,
                    self.actionNew),
            'open': (self.tr("&Open"), 'document-open',
                     QtGui.QKeySequence.Open, self.actionOpen),
            'save': (self.tr("&Save"), 'document-save',
                     QtGui.QKeySequence.Save, self.save),
            'saveas': (self.tr("Save &As..."), 'document-save-as',
                       QtGui.QKeySequence.SaveAs, self.actionSaveAs),
            'quit': (
                self.tr("&Quit"),
                None,  # 'application-exit',
                QtGui.QKeySequence.Quit,
                self.close),
            'info': (self.tr("&Fiber properties"), 'document-properties',
                     [QtGui.QKeySequence("Ctrl+I")], self.actionInfo),
            'add': (self.tr("&Add layer"), 'list-add',
                    [QtGui.QKeySequence("Ctrl+Shift++")], self.actionAddLayer),
            'remove': (self.tr("&Remove layer"), 'list-remove',
                       [QtGui.QKeySequence("Ctrl+-")], self.actionRemoveLayer),
        }

        menus = [(self.tr("&File"),
                  ['new', 'open', '-', 'save', 'saveas', '-', 'quit']),
                 (self.tr("Fibe&r"), ['info', '-', 'add', 'remove'])]

        toolbars = [['new', 'open', 'save', 'info'], ['add', 'remove']]

        self.initActions(actions)
        self.initMenubars(self.menuBar(), menus)
        self.initToolbars(toolbars)
        self._initLayout()
        self.setDirty(False)
        self.actionNew()

    def actionNew(self):
        if self._closeDocument():
            self.factory = FiberFactory()
            self.factory.addLayer(name="core", radius=4e-6, index=1.454)
            self.factory.addLayer(name="cladding", index=1.444)
            self.initLayerList()
            self.updateInfo()

    def actionOpen(self, filename=None):
        if not self._closeDocument():
            return

        if not filename:
            openDialog = QtGui.QFileDialog()
            openDialog.setWindowTitle(self.tr("Open fiber..."))
            openDialog.setDirectory(self._dir)
            openDialog.setAcceptMode(QtGui.QFileDialog.AcceptOpen)
            openDialog.setNameFilter(self.tr("Fibers (*.fiber)"))
            if openDialog.exec_() == QtGui.QFileDialog.Accepted:
                filename = openDialog.selectedFiles()[0]
                self._dir = openDialog.directory()

        if filename:
            with open(filename, 'r') as f:
                self.factory.load(f)
            self._dir = os.path.dirname(filename)
            self.initLayerList()
            self.setDirty(False)
            self.setDocumentName(filename)
            self.statusBar().showMessage(self.tr("Fiber opened"), 5000)
            self.updateInfo()

    def save(self):
        if self.documentName() == "":
            self.actionSaveAs()
            return  # actionSaveAs will call save again

        # Use temporary file to prevent overwriting existing file in case
        # of error
        with TemporaryFile(mode="w+") as f:
            self.factory.dump(f)
            f.seek(0)
            with open(self.documentName(), 'w') as fd:
                copyfileobj(f, fd)

        # with open(self.documentName(), 'w') as f:
        #     self.factory.dump(f)
        self.setDirty(False)
        self.statusBar().showMessage(self.tr("Fiber saved"), 5000)
        super().save()

    def actionSaveAs(self):
        saveDialog = QtGui.QFileDialog()
        saveDialog.setWindowTitle(self.tr("Save fibers as..."))
        saveDialog.setDirectory(self._dir)
        saveDialog.setAcceptMode(QtGui.QFileDialog.AcceptSave)
        saveDialog.setNameFilter(self.tr("Fibers (*.fiber)"))
        saveDialog.setDefaultSuffix('fiber')
        if saveDialog.exec_() == QtGui.QFileDialog.Accepted:
            self.setDocumentName(saveDialog.selectedFiles()[0])
            self.save()
            self._dir = saveDialog.directory()

    def actionInfo(self):
        FiberPropertiesWindow(self.factory._fibers, self).exec_()

    def actionAddLayer(self):
        self.addLayer()

    def actionRemoveLayer(self):
        self.removeLayer()

    def setDirty(self, df):
        super().setDirty(df)
        self.actions['save'].setEnabled(df)
        self.fnumInput.setRange(1, len(self.factory))
        self.fnumSlider.setRange(1, self.fnumInput.maximum())
        self.updateInfo()

    def _initLayout(self):
        self.layerName = QtGui.QLineEdit()
        self.layerName.textChanged.connect(self.changeLayerName)
        self.layerName.setEnabled(False)

        self.layerList = QtGui.QListWidget()
        self.layerList.itemSelectionChanged.connect(self.selectLayer)
        self.layerList.itemActivated.connect(self.actLayerName)
        self.initLayerList()

        layout1 = QtGui.QVBoxLayout()
        layout1.addWidget(QtGui.QLabel(self.tr("Fiber layers:")))
        layout1.addWidget(self.layerList)
        frame1 = QtGui.QFrame()
        frame1.setLayout(layout1)

        lnFormLayout = QtGui.QFormLayout()
        lnFormLayout.addRow(QtGui.QLabel(self.tr("Layer name:")),
                            self.layerName)

        l2splitter = QtGui.QSplitter(QtCore.Qt.Vertical)
        l2splitter.addWidget(self._initGeomFrame())
        l2splitter.addWidget(self._initMatFrame())
        layout2 = QtGui.QVBoxLayout()
        layout2.addLayout(lnFormLayout)
        layout2.addWidget(l2splitter)
        frame2 = QtGui.QFrame()
        frame2.setLayout(layout2)

        self.wlInput = QtGui.QDoubleSpinBox()
        self.wlInput.setSuffix(" nm")
        self.wlInput.setRange(500, 3000)
        self.wlInput.setSingleStep(1)
        self.wlInput.setValue(1550)
        self.wlInput.valueChanged.connect(self.wlChanged)
        self.fnumInput = QtGui.QSpinBox()
        self.fnumInput.setValue(1)
        self.fnumInput.setMinimum(1)
        self.fnumInput.setMaximum(len(self.factory))
        self.fnumInput.valueChanged.connect(self.fnumChanged)
        wlForm = QtGui.QFormLayout()
        wlForm.addRow(QtGui.QLabel(self.tr("Wavelength:")), self.wlInput)
        wlForm.addRow(QtGui.QLabel(self.tr("Fiber #")), self.fnumInput)
        self.fnumSlider = QtGui.QSlider(QtCore.Qt.Horizontal)
        self.fnumSlider.setValue(self.fnumInput.value())
        self.fnumSlider.setMinimum(1)
        self.fnumSlider.setMaximum(self.fnumInput.maximum())
        self.fnumSlider.valueChanged.connect(self.fnumChanged)
        self.infoTable = FiberInfoTable()
        self.fiberPlot = FiberPlot()
        infoTab = QtGui.QTabWidget()
        infoTab.addTab(self.infoTable, self.tr("Info"))
        infoTab.addTab(self.fiberPlot, self.tr("Graph"))
        layout3 = QtGui.QVBoxLayout()
        layout3.addLayout(wlForm)
        layout3.addWidget(self.fnumSlider)
        layout3.addWidget(infoTab)
        frame3 = QtGui.QFrame()
        frame3.setLayout(layout3)

        splitter = QtGui.QSplitter(self)
        splitter.addWidget(frame1)
        splitter.addWidget(frame2)
        splitter.addWidget(frame3)

        self.setCentralWidget(splitter)

    def _initGeomFrame(self):
        self.geomType = QtGui.QComboBox()
        self.geomType.addItems(geometry.__all__)
        self.geomType.setEnabled(False)
        self.geomType.setCurrentIndex(-1)
        self.geomType.currentIndexChanged.connect(self.selectGeomType)
        self.geomLayout = QtGui.QFormLayout()
        self.geomLayout.addRow(QtGui.QLabel(self.tr("Geometry type:")),
                               self.geomType)
        geomFrame = QtGui.QGroupBox(self.tr("Geometry"))
        geomFrame.setLayout(self.geomLayout)
        return geomFrame

    def _initMatFrame(self):
        self.matType = QtGui.QComboBox()
        self.matType.addItems(material.__all__)
        self.matType.setEnabled(False)
        self.matType.setCurrentIndex(-1)
        self.matType.currentIndexChanged.connect(self.selectMatType)
        self.matPropBut = QtGui.QPushButton(self.getIcon('info'), "")
        self.matPropBut.clicked.connect(self.aboutFiberMaterial)
        self.matPropBut.setEnabled(False)
        layout = QtGui.QHBoxLayout()
        layout.addWidget(self.matType)
        layout.addWidget(self.matPropBut)
        self.matLayout = QtGui.QFormLayout()
        self.matLayout.addRow(QtGui.QLabel(self.tr("Material type:")), layout)
        matFrame = QtGui.QGroupBox(self.tr("Material"))
        matFrame.setLayout(self.matLayout)
        return matFrame

    def initLayerList(self):
        self.layerList.clear()
        for i, layer in enumerate(self.factory.layers, 1):
            if layer.name == "":
                name = "layer {}".format(i)
            else:
                name = layer.name
            self.layerList.addItem(name)

        self.actions['remove'].setEnabled(False)
        self.layerName.setText("")

    def selectLayer(self):
        index = self.layerList.currentRow()
        self.layerName.setEnabled(True)
        with blockSignals(self.layerName):
            self.layerName.setText(self.factory.layers[index].name)
        self.initGeom()
        self.initMat()
        if index == len(self.factory.layers) - 1:
            self.actions['remove'].setEnabled(False)
        else:
            self.actions['remove'].setEnabled(True)

    def addLayer(self):
        index = self.layerList.currentRow()
        self.factory.addLayer(index + 1)
        self.layerList.insertItem(index + 1, "layer {}".format(index + 2))
        self._adjustLayerNames()
        self.layerList.setCurrentRow(index + 1)
        self.setDirty(True)

    def removeLayer(self):
        index = self.layerList.currentRow()
        self.layerList.takeItem(index)
        self.factory.removeLayer(index)
        self._adjustLayerNames()
        self.layerList.setCurrentRow(index)
        self.setDirty(True)

    def _adjustLayerNames(self):
        for i, layer in enumerate(self.factory.layers):
            if layer.name == "":
                name = "layer {}".format(i + 1)
                if self.layerList.item(i):
                    self.layerList.item(i).setText(name)

    def changeLayerName(self, newText):
        index = self.layerList.currentRow()

        self.factory.layers[index].name = newText
        if newText == "":
            name = "layer {}".format(index + 1)
        else:
            name = newText
        if self.layerList.currentItem():
            self.layerList.currentItem().setText(name)
        self.setDirty(True)

    def actLayerName(self, item):
        self.layerName.setFocus()

    def _updateParam(self, pname, pindex, value):
        layerIndex = self.layerList.currentRow()
        layer = self.factory.layers[layerIndex]
        layer[pname][pindex] = value
        self.setDirty(True)

    def initGeom(self):
        layerIndex = self.layerList.currentRow()
        if layerIndex == len(self.factory.layers) - 1:
            self.geomType.setEnabled(False)
        else:
            self.geomType.setEnabled(True)

        geomtype = self.factory.layers[layerIndex].type
        gtidx = self.geomType.findText(geomtype)
        # We call selectGeomType directly instead of emitting the signal,
        # to prevent it setting dirty state, since we only display the
        # current state, we are not changing the geometry type
        with blockSignals(self.geomType):
            self.geomType.setCurrentIndex(gtidx)
            self.selectGeomType(gtidx, False)

    def selectGeomType(self, index, change=True):
        util.clearLayout(self.geomLayout, 2)
        layerIndex = self.layerList.currentRow()
        self.factory.layers[layerIndex].type = self.geomType.currentText()
        if index == 0:
            self.setStepIndexGeom()
        elif index == 1:
            self.setSuperGaussianGeom()
        if change:
            self.setDirty(True)

    def setStepIndexGeom(self):
        layerIndex = self.layerList.currentRow()
        if layerIndex == len(self.factory.layers) - 1:
            return

        layer = self.factory.layers[layerIndex]
        self.radiusInput = SLRCWidget()
        self.radiusInput.setSuffix(" µm")
        self.radiusInput.setScaleFactor(1e6)
        self.radiusInput.codeParams = ['r', 'fp', 'mp']
        self.radiusInput.setValue(layer.radius)
        self.radiusInput.valueChanged.connect(self.updateRadius)

        self.geomLayout.addRow(QtGui.QLabel(self.tr("Radius:")),
                               self.radiusInput)

    def setSuperGaussianGeom(self):
        layerIndex = self.layerList.currentRow()
        layer = self.factory.layers[layerIndex]
        self.setStepIndexGeom()

        self.muInput = SLRCWidget()
        self.muInput.setSuffix(" µm")
        self.muInput.setScaleFactor(1e6)
        self.muInput.setRange(-100, 100)
        self.muInput.codeParams = ['r', 'fp', 'mp']
        self.muInput.setValue(layer.tparams[1])
        self.muInput.valueChanged.connect(
            lambda v: self._updateParam("tparams", 1, v))

        self.cInput = SLRCWidget()
        self.cInput.setRange(0.001, 100)
        self.cInput.codeParams = ['r', 'fp', 'mp']
        self.cInput.setScaleFactor(1e6)
        self.cInput.setValue(layer.tparams[2])
        self.cInput.valueChanged.connect(
            lambda v: self._updateParam("tparams", 2, v))

        self.mInput = SLRCWidget()
        self.mInput.setRange(1, 100)
        self.mInput.codeParams = ['r', 'fp', 'mp']
        self.mInput.setValue(layer.tparams[3])
        self.mInput.valueChanged.connect(
            lambda v: self._updateParam("tparams", 3, v))

        self.geomLayout.addRow(QtGui.QLabel(self.tr("Center (mu):")),
                               self.muInput)
        self.geomLayout.addRow(QtGui.QLabel(self.tr("Width (c):")),
                               self.cInput)
        self.geomLayout.addRow(QtGui.QLabel(self.tr("m parameter:")),
                               self.mInput)

    def updateRadius(self, value):
        self._updateParam("tparams", 0, value)

    def initMat(self):
        self.matType.setEnabled(True)
        self.matPropBut.setEnabled(True)
        layerIndex = self.layerList.currentRow()
        mattype = self.factory.layers[layerIndex].material
        mtidx = self.matType.findText(mattype)
        state = self.matType.blockSignals(True)
        self.matType.setCurrentIndex(mtidx)
        self.selectMatType(mtidx, False)
        self.matType.blockSignals(state)

    def selectMatType(self, index, change=True):
        layerIndex = self.layerList.currentRow()
        layer = self.factory.layers[layerIndex]
        layer.material = self.matType.currentText()
        util.clearLayout(self.matLayout, 2)
        mat = material.__dict__[layer.material]

        if mat.nparams == 0:
            pass  # No parameters to display

        elif issubclass(mat, material.Fixed):
            if change:
                layer.mparams = [1.444]
            self.setFixedMaterial(layer)

        elif issubclass(mat, material.compmaterial.CompMaterial):
            if change:
                layer.mparams = [0.05]
            self.setConcentrationMaterial(layer)

        if change:
            self.setDirty(True)

    def setFixedMaterial(self, layer):
        self.indexInput = SLRCWidget()
        self.indexInput.setRange(1, 2)
        self.indexInput.setSingleStep(0.01)
        self.indexInput.setValue(layer.mparams[0])
        self.indexInput.valueChanged.connect(self.updateIndex)
        self.indexInput.codeParams = ['r', 'fp', 'mp']

        self.matLayout.addRow(QtGui.QLabel(self.tr("Index:")), self.indexInput)

    def updateIndex(self, value):
        self._updateParam("mparams", 0, value)

    def setConcentrationMaterial(self, layer):
        layerIndex = self.layerList.currentRow()
        layer = self.factory.layers[layerIndex]
        self.molInput = SLRCWidget()
        self.molInput.setSuffix(" %")
        self.molInput.setScaleFactor(100)
        self.molInput.setValue(layer.mparams[0])
        self.molInput.valueChanged.connect(self.updateMol)
        self.molInput.codeParams = ['r', 'fp', 'mp']

        self.matLayout.addRow(QtGui.QLabel(self.tr("Molar concentration:")),
                              self.molInput)

    def updateMol(self, value):
        self._updateParam("mparams", 0, value)

    def updateInfo(self):
        if self.factory.layers:
            self.infoTable.updateInfo(self.factory[self.fnumInput.value() - 1],
                                      self.wlInput.value() * 1e-9)
            self.fiberPlot.updateInfo(self.factory[self.fnumInput.value() - 1],
                                      self.wlInput.value() * 1e-9)

    def fnumChanged(self, value):
        if self.fnumInput.value() != value:
            self.fnumInput.setValue(value)
        if self.fnumSlider.value() != value:
            self.fnumSlider.setValue(value)
        self.updateInfo()

    def wlChanged(self, value):
        self.updateInfo()

    def aboutFiberMaterial(self):
        layerIndex = self.layerList.currentRow()
        layer = self.factory.layers[layerIndex]
        mat = material.__dict__[layer.material]
        msgBox = QtGui.QMessageBox()
        msgBox.setWindowTitle(mat.name)
        text = "<h1>{}</h1>".format(mat.name)
        if mat.info:
            text += "<p>{}</p>".format(mat.info)
        if mat.url:
            text += '<p><a href="{url}">{url}</a></p>'.format(url=mat.url)
        msgBox.setText(text)
        msgBox.exec_()
Ejemplo n.º 2
0
class ModeSolver(AppWindow):

    PARAMETERS = (("cutoff (V)", "cutoff (wavelength)"),
                  ("neff", "b", "vp", "beta0"),
                  ("ng", "vg", "beta1"),
                  ("D", "beta2"),
                  ("S", "beta3"))

    def __init__(self, parent=None):
        super().__init__(parent)

        self.doc = SolverDocument(self)

        self.setWindowTitle(self.tr("Mode Solver"))
        self._initLayout()

        actions = {
            'open': (
                self.tr("&Open"),
                'document-open',
                QtGui.QKeySequence.Open,
                self.actionOpen
            ),
            'save': (
                self.tr("&Save"),
                'document-save',
                QtGui.QKeySequence.Save,
                self.save),
            'exportcur': (
                self.tr("&Export current table"),
                'export',
                [QtGui.QKeySequence("Ctrl+E")],
                self.export_current_table
            ),
            'quit': (
                self.tr("&Quit"),
                None,  # 'application-exit',
                QtGui.QKeySequence.Quit,
                self.close
            ),
            'new': (
                self.tr("&New fiber file"),
                'document-new',
                QtGui.QKeySequence.New,
                self.fiberSelector.editFiber
            ),
            'load': (
                self.tr("&Load fiber file"),
                'document-open',
                QtGui.QKeySequence.Open,
                self.fiberSelector.chooseFiber
            ),
            'edit': (
                self.tr("&Edit fiber"),
                'pen',
                [],
                self.fiberSelector.editFiber
            ),
            'info': (
                self.tr("&Fiber properties"),
                'document-properties',
                [QtGui.QKeySequence("Ctrl+I")],
                self.fiberSelector.fiberProperties
            ),
            'start': (
                self.tr("&Run simulation"),
                'media-playback-start',
                [QtGui.QKeySequence("F5")],
                self.run_simulation
            ),
            'stop': (
                self.tr("&Stop simulation"),
                'media-playback-stop',
                [QtGui.QKeySequence("Ctrl+.")],
                self.stop_simulation
            ),
            'mcalc': (
                self.tr("&Material calculator"),
                'index-calculator',
                [QtGui.QKeySequence("F8")],
                self.toggle_mcalc
            ),
            'wlcalc': (
                self.tr("&Wavelength calculator"),
                'lambda-calculator',
                [QtGui.QKeySequence("F7")],
                self.toggle_wlcalc
            ),
            'plotchareq': (
                self.tr("&Plot characteristic equation"),
                None,
                [],
                self.plot_chareq
            ),
            'clearcaches': (
                self.tr("&Clear all caches"),
                None,
                [],
                self.doc.clear_all_caches
            ),
            'simparams': (
                self.tr("Advanced &Parameters"),
                'document-properties',
                [],
                self.simParams
            ),
            'paramwin': (
                self.tr("&Parameters"),
                'function',
                [QtGui.QKeySequence("F2")],
                self.togglePanes
            ),
            'tablewin': (
                self.tr("&Result table"),
                'table',
                [QtGui.QKeySequence("F3")],
                self.togglePanes
            ),
            'graphwin': (
                self.tr("&Graph"),
                'statistics',
                [QtGui.QKeySequence("F4")],
                self.togglePanes
            ),
            'fullscreen': (
                self.tr("&Fullscreen"),
                'view-fullscreen',
                [QtGui.QKeySequence("F11")],
                self.toggleFullscreen
            ),
            'fields': (
                self.tr("Show &fields"),
                'report',
                [QtGui.QKeySequence("F6")],
                self.plotFields
            )
        }

        menus = [
            (
                self.tr("&File"), [
                    'open',
                    'save',
                    'exportcur',
                    '-',
                    'quit'
                ]
            ),
            (
                self.tr("Fibe&r"), [
                    'new',
                    'load',
                    '-',
                    'edit',
                    'info',
                ]
            ),
            (
                self.tr("&Simulation"), [
                    'start',
                    'stop',
                    '-',
                    'simparams',
                ]
            ),
            (
                self.tr("&Tools"), [
                    'fields',
                    '-',
                    'wlcalc',
                    'mcalc'
                ]
            ),
            (
                self.tr("&Debug"), [
                    'plotchareq',
                    self.logmenu,
                    'clearcaches',
                ]
            ),
            (
                self.tr("&Window"), [
                    'paramwin',
                    'tablewin',
                    'graphwin',
                    '-',
                    'fullscreen',
                ]
            ),
        ]

        toolbars = [
            ['open', 'save', 'exportcur'],
            ['start', 'stop'],
            ['fields', '-', 'wlcalc', 'mcalc'],
            ['paramwin', 'tablewin', 'graphwin'],
        ]

        self.initActions(actions)
        self.initMenubars(self.menuBar(), menus)
        self.initToolbars(toolbars)

        self.actions['exportcur'].setEnabled(False)
        self.actions['edit'].setEnabled(False)
        self.actions['info'].setEnabled(False)
        self.actions['start'].setCheckable(True)
        self.actions['stop'].setCheckable(True)
        self.actions['stop'].setChecked(True)
        self.actions['stop'].setEnabled(False)
        self.actions['mcalc'].setCheckable(True)
        self.actions['wlcalc'].setCheckable(True)
        self.actions['paramwin'].setCheckable(True)
        self.actions['paramwin'].setChecked(True)
        self.actions['tablewin'].setCheckable(True)
        self.actions['tablewin'].setChecked(True)
        self.actions['graphwin'].setCheckable(True)
        self.actions['graphwin'].setChecked(True)
        self.actions['fullscreen'].setCheckable(True)
        self.actions['plotchareq'].setEnabled(False)
        self.actions['fields'].setEnabled(False)
        self.wavelengthInput.setValue(1550e-9)
        self.setDirty(False)
        self.closed.connect(self.doc.stop_thread)

        self.mcalc = None
        self.wlcalc = None

    def _initLayout(self):
        self.progressBar = QtGui.QProgressBar()
        self.statusBar().addWidget(self.progressBar, 1)

        self.timeLabel = QtGui.QLabel()
        self.timeLabel.setFrameStyle(QtGui.QFrame.Panel | QtGui.QFrame.Sunken)
        self.statusBar().addPermanentWidget(self.timeLabel)
        self.time = QtCore.QTime()
        self.timer = QtCore.QTimer(self)
        self.timer.timeout.connect(self.updateTime)

        self.countLabel = QtGui.QLabel(self.tr("No fiber"))
        self.countLabel.setFrameStyle(QtGui.QFrame.Panel | QtGui.QFrame.Sunken)
        self.statusBar().addPermanentWidget(self.countLabel)

        self.splitter = QtGui.QSplitter(self)
        self.splitter.addWidget(self._parametersFrame())
        self.splitter.addWidget(self._modesFrame())
        self.plotFrame = PlotFrame(self)
        self.plotFrame.modified.connect(self.setDirty)
        self.modeTableView.selChanged.connect(self.plotFrame.updateModeSel)
        self.modeTableModel.dataChanged.connect(self.plotFrame.updatePlot)
        self.splitter.addWidget(self.plotFrame)
        self.setCentralWidget(self.splitter)

        self.doc.computeStarted.connect(self.initProgressBar)
        self.doc.valueAvailable.connect(self.updateProgressBar)
        self.doc.computeFinished.connect(self.stopProgressBar)

    def _parametersFrame(self):
        self.fiberSelector = FiberSelector(self.doc, self)
        self.fiberSelector.fileLoaded.connect(self.updateFiber)
        self.fiberSelector.fiberEdited.connect(self.updateFiber)

        self.wavelengthInput = SLRCWidget()
        self.wavelengthInput.setSuffix(" nm")
        self.wavelengthInput.setScaleFactor(1e9)
        self.wavelengthInput.setRange(200, 10000)
        self.wavelengthInput.setSingleStep(1)
        self.wavelengthInput.valueChanged.connect(self.updateWavelengths)

        self.modeSelector = QtGui.QComboBox()
        self.modeSelector.addItems(['vector', 'scalar', 'both'])
        self.modeSelector.currentIndexChanged.connect(self.setMode)

        formLayout = QtGui.QFormLayout()
        formLayout.addRow(
            QtGui.QLabel(self.tr("Wavelength")),
            self.wavelengthInput)
        formLayout.addRow(
            QtGui.QLabel(self.tr("Modes")),
            self.modeSelector)

        self.nuMaxInput = QtGui.QSpinBox()
        self.nuMaxInput.valueChanged.connect(self.setMaxNu)
        self.nuMaxInput.setRange(-1, 99)
        self.nuMaxInput.setSpecialValueText(" ")
        self.mMaxInput = QtGui.QSpinBox()
        self.mMaxInput.setRange(0, 100)
        self.mMaxInput.valueChanged.connect(self.setMaxM)
        self.mMaxInput.setSpecialValueText(" ")

        simParamFormLayout = QtGui.QHBoxLayout()
        simParamFormLayout.addWidget(
            QtGui.QLabel(self.tr("l max")), 0, QtCore.Qt.AlignRight)
        simParamFormLayout.addWidget(self.nuMaxInput)
        simParamFormLayout.addWidget(
            QtGui.QLabel(self.tr("m max")), 0, QtCore.Qt.AlignRight)
        simParamFormLayout.addWidget(self.mMaxInput)

        simParamLayout = QtGui.QVBoxLayout()
        simParamLayout.addLayout(simParamFormLayout)

        self.simParamBoxes = {}
        for row in self.PARAMETERS:
            hlayout = QtGui.QHBoxLayout()
            for param in row:
                box = QtGui.QCheckBox(param)
                box.toggled.connect(self.updateParams)
                hlayout.addWidget(box)
                self.simParamBoxes[param] = box
            hlayout.addStretch(1)
            simParamLayout.addLayout(hlayout)

        simParamsGroup = QtGui.QGroupBox(self.tr("Simulation Parameters"))
        simParamsGroup.setLayout(simParamLayout)

        topLayout = QtGui.QVBoxLayout()
        topLayout.addWidget(QtGui.QLabel(self.tr("Fiber")))
        topLayout.addWidget(self.fiberSelector)
        topLayout.addLayout(formLayout)
        topLayout.addWidget(simParamsGroup)
        topLayout.addStretch(1)

        topFrame = QtGui.QFrame()
        topFrame.setLayout(topLayout)

        splitter = QtGui.QSplitter(self)
        splitter.addWidget(topFrame)

        return splitter

    def _modesFrame(self):
        self.modeTableModel = ModeTableModel(self.doc, self)
        self.modeTableProxy = QtGui.QSortFilterProxyModel(self)
        self.modeTableProxy.setSourceModel(self.modeTableModel)
        self.modeTableProxy.setSortRole(QtCore.Qt.UserRole)
        self.modeTableProxy.setDynamicSortFilter(True)
        self.modeTableView = ModeTableView(self.modeTableProxy)
        self.modeTableView.selChanged.connect(self.updateUIsel)
        self.modeTableModel.dataChanged.connect(
            self.modeTableView.resizeColumnsToContents)
        self.modeTableModel.modelReset.connect(self.updateUImodes)

        self.fiberSlider = FiberSlider()
        self.fiberSlider.valueChanged.connect(self.setFiber)

        self.wavelengthSlider = WavelengthSlider()
        self.wavelengthSlider.valueChanged.connect(self.setWavelength)

        self.showhidesel = ShowHideMode()
        self.showhidesel.showModes.connect(self.on_show_modes)
        self.showhidesel.hideModes.connect(self.on_hide_modes)

        layout1 = QtGui.QHBoxLayout()
        layout1.addWidget(self.fiberSlider)
        layout1.addWidget(self.wavelengthSlider)

        layout2 = QtGui.QVBoxLayout()
        layout2.addLayout(layout1)
        layout2.addWidget(self.showhidesel)
        layout2.addWidget(self.modeTableView, stretch=1)

        frame = QtGui.QFrame()
        frame.setLayout(layout2)

        # Default values
        self.nuMaxInput.setValue(-1)
        self.mMaxInput.setValue(0)

        return frame

    def documentName(self):
        if self.doc.factory:
            fn = self.doc.filename
            p, f = os.path.split(fn)
            b, e = os.path.splitext(f)
            return os.path.join(p, b+".solver")
        return ""

    def actionOpen(self, filename=None):
        if not self._closeDocument():
            return

        if not filename:
            openDialog = QtGui.QFileDialog()
            openDialog.setWindowTitle(self.tr("Open solver..."))
            openDialog.setDirectory(self._dir)
            openDialog.setAcceptMode(QtGui.QFileDialog.AcceptOpen)
            openDialog.setNameFilter(self.tr("Solver (*.solver)"))
            if openDialog.exec_() == QtGui.QFileDialog.Accepted:
                filename = openDialog.selectedFiles()[0]
                self._dir = openDialog.directory()

        if filename:
            if self.load(filename):
                self._dir = os.path.dirname(filename)
                self.setDirty(False)
                self.statusBar().showMessage(self.tr("Solver loaded"), 5000)

    def load(self, filename):
        # Test for fiber file
        p, f = os.path.split(filename)
        b, e = os.path.splitext(f)
        fiberfile = os.path.join(p, b+".fiber")
        if os.path.exists(fiberfile):
            self.doc.filename = fiberfile
            self.fiberSelector.fileLoaded.emit()
        else:
            # Trouver fiberfile...
            print(fiberfile, "not found")
            return False

        self.stop_simulation()
        with open(filename, 'r') as f:
            s = json.load(f)
            self.wavelengthInput.setValue(s['wl'])
            self.modeSelector.setCurrentIndex(int(s['modes']))
            self.doc.params = s['params']
            for p, box in self.simParamBoxes.items():
                with blockSignals(box):
                    box.setChecked(p in self.doc.params)
            self.nuMaxInput.setValue(int(s['numax']))
            self.mMaxInput.setValue(int(s['mmax']))
            self.fiberSlider.fiberInput.setValue(int(s['fnum']))
            self.wavelengthSlider.wavelengthInput.setValue(int(s['wlnum']))
            self.actions['paramwin'].setChecked(s['panes']['params'])
            self.actions['tablewin'].setChecked(s['panes']['modes'])
            self.actions['graphwin'].setChecked(s['panes']['graph'])
            self.togglePanes()
            self.plotFrame.load(s['graph'])
        return True

    def save(self):
        s = {
            'wl': self.wavelengthInput._value,
            'modes': self.modeSelector.currentIndex(),
            'params': self.doc.params,
            'numax': self.doc.numax if self.doc.numax is not None else -1,
            'mmax': self.doc.mmax if self.doc.mmax is not None else -1,
            'fnum': self.fiberSlider.fiberInput.value(),
            'wlnum': self.wavelengthSlider.wavelengthInput.value(),
            'panes': {
                'params': int(self.actions['paramwin'].isChecked()),
                'modes': int(self.actions['tablewin'].isChecked()),
                'graph': int(self.actions['graphwin'].isChecked())},
            'graph': self.plotFrame.save()
        }

        # Use temporary file to prevent overwriting existing file in case
        # of error
        with TemporaryFile(mode="w+") as f:
            json.dump(s, f)
            f.seek(0)
            with open(self.documentName(), 'w') as fd:
                copyfileobj(f, fd)

        self.setDirty(False)
        self.statusBar().showMessage(self.tr("Fiber saved"), 5000)
        super().save()

    def setDirty(self, df=True):
        if 'save' in self.actions:
            if not self.doc.factory:
                self.actions['save'].setEnabled(False)
            else:
                self.actions['save'].setEnabled(df)
        if not self.doc.factory:
            df = False
        super().setDirty(df)

    def _updateFiberCount(self):
        if self.doc.factory:
            nf = len(self.doc.fibers)
            nw = len(self.doc.wavelengths)
            self.countLabel.setText(
                self.tr("{} F x {} W = {}").format(
                    nf, nw, nf*nw))
            self.countLabel.setToolTip(
                self.tr("{} fibers x {} wavelengths = {} combinations".format(
                    nf, nw, nf*nw)))

    def updateFiber(self):
        """New fiber loaded, or fiber modified."""
        self.actions['new'].setEnabled(False)
        self.actions['edit'].setEnabled(True)
        self.actions['info'].setEnabled(True)
        self.actions['plotchareq'].setEnabled(True)
        self.fiberSelector.updateFiberName()
        self.fiberSlider.setNum(len(self.doc.fibers))
        self.setDirty(True)
        self._updateFiberCount()
        self.statusBar().showMessage(self.tr("Fiber factory loaded"), 5000)

    def updateWavelengths(self):
        self.doc.wavelengths = self.wavelengthInput
        self.wavelengthSlider.setNum(len(self.doc.wavelengths))
        self.setWavelength(self.wavelengthSlider.value())
        self._updateFiberCount()
        self.setDirty(True)

    def setMode(self, index):
        self.doc.modeKind = self.modeSelector.currentText()
        self.setDirty(True)

    def setMaxNu(self, value):
        self.doc.numax = value
        self.setDirty(True)

    def setMaxM(self, value):
        self.doc.mmax = value
        self.setDirty(True)

    def setFiber(self, value):
        self.modeTableModel.setFiber(value)
        self.plotFrame.setFiber(value)
        self.showVNumber()
        self.setDirty(True)

    def setWavelength(self, value):
        if 0 <= value - 1 < len(self.doc.wavelengths):
            wl = self.doc.wavelengths[value-1]
            self.wavelengthSlider.wlLabel.setText(
                "{:.5f} nm".format(wl * 1e9))
            self.modeTableModel.setWavelength(value)
            self.plotFrame.setWavelength(value)
            self.showVNumber()
            self.setDirty(True)

    def showVNumber(self):
        try:
            wlnum = self.modeTableModel._wl
            fnum = self.modeTableModel._fnum
            wl = self.doc.wavelengths[wlnum]
            fiber = self.doc.fibers[fnum]
            self.wavelengthSlider.vLabel.setText(
                "| V = {:.5f}".format(fiber.V0(wl)))
        except ValueError:
            pass

    def updateParams(self, checked):
        params = []
        for row in self.PARAMETERS:
            for p in row:
                if self.simParamBoxes[p].isChecked():
                    params.append(p)
        self.doc.params = params
        self.plotFrame.updatePMButs()
        self.setDirty(True)

    def initProgressBar(self):
        self.progressBar.setFormat("%v / %m (%p%)")
        self.progressBar.setRange(0, 0)
        self.progressBar.setValue(0)
        self.time.start()
        self.estimation = 0
        self.timer.start(0)
        self.actions['exportcur'].setEnabled(False)

    def updateProgressBar(self):
        tot = self.progressBar.maximum()
        if tot == 0:
            tot = self.doc.toCompute + 1
            self.progressBar.setMaximum(tot)
        self.progressBar.setValue(self.progressBar.value() + 1)
        elapsed = self.time.elapsed()
        self.estimation = elapsed
        if self.doc.toCompute > 0:
            done = self.progressBar.value()
            if done:
                self.estimation = tot * (elapsed / done)

    def stopProgressBar(self):
        tot = self.progressBar.maximum()
        self.progressBar.setValue(tot)
        self.timer.stop()
        self.updateTime()
        self.plotFrame.updatePlot()
        self.actions['exportcur'].setEnabled(True)

    def updateTime(self):
        elapsed = self.time.elapsed()
        remaining = int(self.estimation - elapsed)
        if remaining > 0:
            self.timeLabel.setText(self.tr("E: {} (R: {})").format(
                msToStr(elapsed), msToStr(remaining, False)))
        else:
            self.timeLabel.setText(self.tr("E: {}").format(
                msToStr(elapsed)))

    def run_simulation(self):
        self.doc.ready = True
        self.doc.start()
        self.actions['start'].setEnabled(False)
        self.actions['stop'].setEnabled(True)
        self.actions['stop'].setChecked(False)

    def stop_simulation(self):
        self.timer.stop()
        self.progressBar.setFormat("")
        self.progressBar.setValue(0)
        self.doc.stop_thread()
        self.doc.ready = False
        self.actions['stop'].setEnabled(False)
        self.actions['start'].setEnabled(True)
        self.actions['start'].setChecked(False)

    def toggle_mcalc(self):
        if self.mcalc is None:
            self.mcalc = MaterialCalculator(self)
            self.mcalc.hidden.connect(self.actions['mcalc'].toggle)

        if self.actions['mcalc'].isChecked():
            self.mcalc.show()
        else:
            with blockSignals(self.mcalc):
                self.mcalc.hide()

    def toggle_wlcalc(self):
        if self.wlcalc is None:
            self.wlcalc = WavelengthCalculator(self)
            self.wlcalc.hidden.connect(self.actions['wlcalc'].toggle)

        if self.actions['wlcalc'].isChecked():
            self.wlcalc.show()
        else:
            with blockSignals(self.wlcalc):
                self.wlcalc.hide()

    def plot_chareq(self):
        sel = self.modeTableView.selectedIndexes()
        try:
            idx = self.modeTableProxy.mapToSource(sel[0])
            mode = self.modeTableModel.modes[idx.row()]
        except IndexError:
            mode = None
        dlg = CharEqDialog(mode, self)
        dlg.show()

    def simParams(self):
        dlg = SimParamsDialog(self.doc)
        dlg.exec_()
        self.doc.numProcs = dlg.numProcs.value()
        self.doc.simulator.delta = float(dlg.delta.text())

    def on_show_modes(self, what, option):
        self._show_hide_modes(True, what, option)

    def on_hide_modes(self, what, option):
        self._show_hide_modes(False, what, option)

    def _show_hide_modes(self, show, what, option):
        nm = self.modeTableModel.rowCount()
        for i in range(nm):
            mode = self.modeTableModel.headerData(i,
                                                  QtCore.Qt.Vertical,
                                                  QtCore.Qt.UserRole)
            if (what == 0 or
                    (what == 1 and mode.family is ModeFamily(option+1)) or
                    (what == 2 and mode.nu == option) or
                    (what == 3 and mode.m == option+1)):
                index = self.modeTableModel.index(i, 0)
                self.modeTableModel.setData(
                    index,
                    QtCore.Qt.Checked if show else QtCore.Qt.Unchecked,
                    QtCore.Qt.CheckStateRole)

    def togglePanes(self):
        states = [
            self.actions['paramwin'].isChecked(),
            self.actions['tablewin'].isChecked(),
            self.actions['graphwin'].isChecked()]
        self.splitter.setSizes(states)
        if sum(states) == 1:
            self.actions['paramwin'].setEnabled(not states[0])
            self.actions['tablewin'].setEnabled(not states[1])
            self.actions['graphwin'].setEnabled(not states[2])
        else:
            self.actions['paramwin'].setEnabled(True)
            self.actions['tablewin'].setEnabled(True)
            self.actions['graphwin'].setEnabled(True)
        self.setDirty(True)

    def toggleFullscreen(self):
        if self.isFullScreen():
            self.showNormal()
        else:
            self.showFullScreen()

    def export_current_table(self):
        wlnum = self.modeTableModel._wl
        fnum = self.modeTableModel._fnum

        exportDialog = QtGui.QFileDialog()
        exportDialog.setWindowTitle(self.tr("Export results"))
        exportDialog.setDirectory(self._dir)
        exportDialog.setAcceptMode(QtGui.QFileDialog.AcceptSave)
        exportDialog.setNameFilter(self.tr("Comma Separated Values (*.csv)"))
        exportDialog.setDefaultSuffix('csv')
        if exportDialog.exec_() == QtGui.QFileDialog.Accepted:
            filename = exportDialog.selectedFiles()[0]
            self._dir = exportDialog.directory()
            self.doc.export(filename, wlnum, fnum)

    def updateUIsel(self, modes):
        pass
        # ms = len(modes) > 0
        # self.actions['plotchareq'].setEnabled(ms)

    def updateUImodes(self):
        nm = self.modeTableModel.rowCount()
        self.actions['fields'].setEnabled(nm > 0)
        self.showhidesel.modes = self.modeTableModel.modes

    def plotFields(self):
        fv = FieldVisualizer(self)
        sm = self.modeTableView.selectedModes()
        if sm:
            fv.setModes([[m, 0, 0, 1] for m in sm])
        fv.show()