class __Tab(QWidget):
        """ This code is for showing device specific items. This is just a scrollable display area. """
        def __init__(self):
            self.logger = logging.getLogger(__name__)
            self.logger.debug("Initializing")
            super().__init__()
            self.setLayout(QVBoxLayout())
            self.__scroll_area = QScrollArea(self)
            size_policy = QSizePolicy(QSizePolicy.Minimum, QSizePolicy.Minimum)
            size_policy.setHorizontalStretch(0)
            size_policy.setVerticalStretch(0)
            size_policy.setHeightForWidth(
                self.__scroll_area.hasHeightForWidth())
            self.__scroll_area.setVerticalScrollBarPolicy(Qt.ScrollBarAsNeeded)
            self.__scroll_area.setHorizontalScrollBarPolicy(
                Qt.ScrollBarAsNeeded)
            self.__scroll_area.setSizeAdjustPolicy(
                QAbstractScrollArea.AdjustToContents)
            self.__scroll_area.setSizePolicy(size_policy)
            self.__scroll_area.setWidgetResizable(True)
            self.layout().addWidget(self.__scroll_area)
            self.logger.debug("Initialized")

        def add_contents(self, contents):
            self.logger.debug("running")
            self.__scroll_area.setWidget(contents)
            self.logger.debug("done")
    def __init__(self, path: pathlib.Path, *args: Any, **kwargs: Any):
        super().__init__(*args, **kwargs)

        self._sketch_class: Optional[Type[vsketch.Vsketch]] = None
        self._path = path
        self._vsk: Optional[vsketch.Vsketch] = None
        self._vsk_master: Optional[vsketch.Vsketch] = None
        self._param_set: Dict[str, Any] = {}
        self._seed: Optional[int] = None
        self._thread: Optional[QThread] = None

        self._task = asyncio.get_event_loop().create_task(self.watch())

        # noinspection PyUnresolvedReferences
        self.sketchFileChanged.connect(
            self.reload_sketch_class)  # type: ignore

        self._sidebar = SideBarWidget(get_config_path(self._path))
        self._sidebar.params_widget.paramUpdated.connect(
            self.redraw_sketch)  # type: ignore
        self._sidebar.seed_widget.seed_spin.valueChanged.connect(self.set_seed)
        self._seed = self._sidebar.seed_widget.seed_spin.value()
        self._sidebar.config_widget.saveConfig.connect(
            self.save_config)  # type: ignore
        self._sidebar.config_widget.loadConfig.connect(
            self.load_config)  # type: ignore
        self._sidebar.like_btn.clicked.connect(self.on_like)

        scroller = QScrollArea()
        scroller.setWidget(self._sidebar)
        scroller.setWidgetResizable(True)
        sp = scroller.sizePolicy()
        sp.setHorizontalPolicy(QSizePolicy.Minimum)
        scroller.setSizePolicy(sp)
        self.add_side_widget(scroller)

        self._trigger_fit_to_viewport = True
        self.reload_sketch_class()
class MainWindow(QMainWindow):
    def __init__(self):
        super(MainWindow, self).__init__()

        openmc.capi.init(['-c'])

        self.setWindowTitle('OpenMC Plot Explorer')

        self.restored = False
        self.pixmap = None
        self.zoom = 100

        self.model = PlotModel()
        self.updateRelativeBases()
        self.restoreModelSettings()

        self.cellsModel = DomainTableModel(self.model.activeView.cells)
        self.materialsModel = DomainTableModel(self.model.activeView.materials)

        # Create viewing area
        self.frame = QScrollArea(self)
        self.frame.setAlignment(QtCore.Qt.AlignCenter)
        self.frame.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
        self.setCentralWidget(self.frame)

        # Create plot image
        self.plotIm = PlotImage(self.model, self.frame, self)
        self.frame.setWidget(self.plotIm)

        # Dock
        self.dock = OptionsDock(self.model, FM, self)
        self.dock.setObjectName("OptionsDock")
        self.addDockWidget(QtCore.Qt.RightDockWidgetArea, self.dock)

        # Color Dialog
        self.colorDialog = ColorDialog(self.model, FM, self)
        self.colorDialog.hide()

        # Restore Window Settings
        self.restoreWindowSettings()

        # Create menubar
        self.createMenuBar()

        # Status Bar
        self.coord_label = QLabel()
        self.statusBar().addPermanentWidget(self.coord_label)
        self.coord_label.hide()

        # Load Plot
        self.statusBar().showMessage('Generating Plot...')
        self.dock.updateDock()
        self.colorDialog.updateDialogValues()

        if self.restored:
            self.showCurrentView()
        else:
            # Timer allows GUI to render before plot finishes loading
            QtCore.QTimer.singleShot(0, self.model.generatePlot)
            QtCore.QTimer.singleShot(0, self.showCurrentView)

    # Create and update menus:
    def createMenuBar(self):
        self.mainMenu = self.menuBar()

        # File Menu
        self.saveImageAction = QAction("&Save Image As...", self)
        self.saveImageAction.setShortcut("Ctrl+Shift+S")
        self.saveImageAction.setToolTip('Save plot image')
        self.saveImageAction.setStatusTip('Save plot image')
        self.saveImageAction.triggered.connect(self.saveImage)

        self.saveViewAction = QAction("Save &View Settings...", self)
        self.saveViewAction.setShortcut(QtGui.QKeySequence.Save)
        self.saveViewAction.setStatusTip('Save current view settings')
        self.saveViewAction.triggered.connect(self.saveView)

        self.openAction = QAction("&Open View Settings...", self)
        self.openAction.setShortcut(QtGui.QKeySequence.Open)
        self.openAction.setToolTip('Open saved view settings')
        self.openAction.setStatusTip('Open saved view settings')
        self.openAction.triggered.connect(self.openView)

        self.quitAction = QAction("&Quit", self)
        self.quitAction.setShortcut(QtGui.QKeySequence.Quit)
        self.quitAction.setToolTip('Quit OpenMC Plot Explorer')
        self.quitAction.setStatusTip('Quit OpenMC Plot Explorer')
        self.quitAction.triggered.connect(self.close)

        self.fileMenu = self.mainMenu.addMenu('&File')
        self.fileMenu.addAction(self.saveImageAction)
        self.fileMenu.addSeparator()
        self.fileMenu.addAction(self.saveViewAction)
        self.fileMenu.addAction(self.openAction)
        self.fileMenu.addSeparator()
        self.fileMenu.addAction(self.quitAction)

        # Edit Menu
        self.applyAction = QAction("&Apply Changes", self)
        self.applyAction.setShortcut("Shift+Return")
        self.applyAction.setToolTip('Generate new view with changes applied')
        self.applyAction.setStatusTip('Generate new view with changes applied')
        self.applyAction.triggered.connect(self.applyChanges)

        self.undoAction = QAction('&Undo', self)
        self.undoAction.setShortcut(QtGui.QKeySequence.Undo)
        self.undoAction.setToolTip('Undo')
        self.undoAction.setStatusTip('Undo last plot view change')
        self.undoAction.setDisabled(True)
        self.undoAction.triggered.connect(self.undo)

        self.redoAction = QAction('&Redo', self)
        self.redoAction.setDisabled(True)
        self.redoAction.setToolTip('Redo')
        self.redoAction.setStatusTip('Redo last plot view change')
        self.redoAction.setShortcut(QtGui.QKeySequence.Redo)
        self.redoAction.triggered.connect(self.redo)

        self.restoreAction = QAction("&Restore Default Plot", self)
        self.restoreAction.setShortcut("Ctrl+R")
        self.restoreAction.setToolTip('Restore to default plot view')
        self.restoreAction.setStatusTip('Restore to default plot view')
        self.restoreAction.triggered.connect(self.restoreDefault)

        self.editMenu = self.mainMenu.addMenu('&Edit')
        self.editMenu.addAction(self.applyAction)
        self.editMenu.addSeparator()
        self.editMenu.addAction(self.undoAction)
        self.editMenu.addAction(self.redoAction)
        self.editMenu.addSeparator()
        self.editMenu.addAction(self.restoreAction)
        self.editMenu.addSeparator()
        self.editMenu.aboutToShow.connect(self.updateEditMenu)

        # Edit -> Basis Menu
        self.xyAction = QAction('&xy  ', self)
        self.xyAction.setCheckable(True)
        self.xyAction.setShortcut('Alt+X')
        self.xyAction.setToolTip('Change to xy basis')
        self.xyAction.setStatusTip('Change to xy basis')
        self.xyAction.triggered.connect(
            lambda: self.editBasis('xy', apply=True))

        self.xzAction = QAction('x&z  ', self)
        self.xzAction.setCheckable(True)
        self.xzAction.setShortcut('Alt+Z')
        self.xzAction.setToolTip('Change to xz basis')
        self.xzAction.setStatusTip('Change to xz basis')
        self.xzAction.triggered.connect(
            lambda: self.editBasis('xz', apply=True))

        self.yzAction = QAction('&yz  ', self)
        self.yzAction.setCheckable(True)
        self.yzAction.setShortcut('Alt+Y')
        self.yzAction.setToolTip('Change to yz basis')
        self.yzAction.setStatusTip('Change to yz basis')
        self.yzAction.triggered.connect(
            lambda: self.editBasis('yz', apply=True))

        self.basisMenu = self.editMenu.addMenu('&Basis')
        self.basisMenu.addAction(self.xyAction)
        self.basisMenu.addAction(self.xzAction)
        self.basisMenu.addAction(self.yzAction)
        self.basisMenu.aboutToShow.connect(self.updateBasisMenu)

        # Edit -> Color By Menu
        self.cellAction = QAction('&Cell', self)
        self.cellAction.setCheckable(True)
        self.cellAction.setShortcut('Alt+C')
        self.cellAction.setToolTip('Color by cell')
        self.cellAction.setStatusTip('Color plot by cell')
        self.cellAction.triggered.connect(
            lambda: self.editColorBy('cell', apply=True))

        self.materialAction = QAction('&Material', self)
        self.materialAction.setCheckable(True)
        self.materialAction.setShortcut('Alt+M')
        self.materialAction.setToolTip('Color by material')
        self.materialAction.setStatusTip('Color plot by material')
        self.materialAction.triggered.connect(
            lambda: self.editColorBy('material', apply=True))

        self.colorbyMenu = self.editMenu.addMenu('&Color By')
        self.colorbyMenu.addAction(self.cellAction)
        self.colorbyMenu.addAction(self.materialAction)
        self.colorbyMenu.aboutToShow.connect(self.updateColorbyMenu)

        self.editMenu.addSeparator()

        self.maskingAction = QAction('Enable &Masking', self)
        self.maskingAction.setShortcut('Ctrl+M')
        self.maskingAction.setCheckable(True)
        self.maskingAction.setToolTip('Toggle masking')
        self.maskingAction.setStatusTip('Toggle whether masking is enabled')
        self.maskingAction.triggered[bool].connect(
            lambda bool=bool: self.toggleMasking(bool, apply=True))
        self.editMenu.addAction(self.maskingAction)

        self.highlightingAct = QAction('Enable High&lighting', self)
        self.highlightingAct.setShortcut('Ctrl+L')
        self.highlightingAct.setCheckable(True)
        self.highlightingAct.setToolTip('Toggle highlighting')
        self.highlightingAct.setStatusTip(
            'Toggle whether highlighting is enabled')
        self.highlightingAct.triggered[bool].connect(
            lambda bool=bool: self.toggleHighlighting(bool, apply=True))
        self.editMenu.addAction(self.highlightingAct)

        # View Menu
        self.dockAction = QAction('Hide &Dock', self)
        self.dockAction.setShortcut("Ctrl+D")
        self.dockAction.setToolTip('Toggle dock visibility')
        self.dockAction.setStatusTip('Toggle dock visibility')
        self.dockAction.triggered.connect(self.toggleDockView)

        self.zoomAction = QAction('&Zoom...', self)
        self.zoomAction.setShortcut('Alt+Shift+Z')
        self.zoomAction.setToolTip('Edit zoom factor')
        self.zoomAction.setStatusTip('Edit zoom factor')
        self.zoomAction.triggered.connect(self.editZoomAct)

        self.viewMenu = self.mainMenu.addMenu('&View')
        self.viewMenu.addAction(self.dockAction)
        self.viewMenu.addSeparator()
        self.viewMenu.addAction(self.zoomAction)
        self.viewMenu.aboutToShow.connect(self.updateViewMenu)

        # Window Menu
        self.mainWindowAction = QAction('&Main Window', self)
        self.mainWindowAction.setShortcut('Alt+W')
        self.mainWindowAction.setCheckable(True)
        self.mainWindowAction.setToolTip('Bring main window to front')
        self.mainWindowAction.setStatusTip('Bring main window to front')
        self.mainWindowAction.triggered.connect(self.showMainWindow)

        self.colorDialogAction = QAction('Color &Options', self)
        self.colorDialogAction.setShortcut('Alt+D')
        self.colorDialogAction.setCheckable(True)
        self.colorDialogAction.setToolTip('Bring Color Dialog to front')
        self.colorDialogAction.setStatusTip('Bring Color Dialog to front')
        self.colorDialogAction.triggered.connect(self.showColorDialog)

        self.windowMenu = self.mainMenu.addMenu('&Window')
        self.windowMenu.addAction(self.mainWindowAction)
        self.windowMenu.addAction(self.colorDialogAction)
        self.windowMenu.aboutToShow.connect(self.updateWindowMenu)

    def updateEditMenu(self):
        changed = self.model.currentView != self.model.defaultView
        self.restoreAction.setDisabled(not changed)

        self.maskingAction.setChecked(self.model.currentView.masking)
        self.highlightingAct.setChecked(self.model.currentView.highlighting)

        self.undoAction.setText(f'&Undo ({len(self.model.previousViews)})')
        self.redoAction.setText(f'&Redo ({len(self.model.subsequentViews)})')

    def updateBasisMenu(self):
        self.xyAction.setChecked(self.model.currentView.basis == 'xy')
        self.xzAction.setChecked(self.model.currentView.basis == 'xz')
        self.yzAction.setChecked(self.model.currentView.basis == 'yz')

    def updateColorbyMenu(self):
        cv = self.model.currentView
        self.cellAction.setChecked(cv.colorby == 'cell')
        self.materialAction.setChecked(cv.colorby == 'material')

    def updateViewMenu(self):
        if self.dock.isVisible():
            self.dockAction.setText('Hide &Dock')
        else:
            self.dockAction.setText('Show &Dock')

    def updateWindowMenu(self):
        self.colorDialogAction.setChecked(self.colorDialog.isActiveWindow())
        self.mainWindowAction.setChecked(self.isActiveWindow())

    # Menu and shared methods:

    def saveImage(self):
        filename, ext = QFileDialog.getSaveFileName(self, "Save Plot Image",
                                                    "untitled",
                                                    "Images (*.png *.ppm)")
        if filename:
            if "." not in filename:
                filename += ".png"
            self.plotIm.figure.savefig(filename, transparent=True)
            self.statusBar().showMessage('Plot Image Saved', 5000)

    def saveView(self):
        filename, ext = QFileDialog.getSaveFileName(self, "Save View Settings",
                                                    "untitled",
                                                    "View Settings (*.pltvw)")
        if filename:
            if "." not in filename:
                filename += ".pltvw"

            saved = {
                'default': self.model.defaultView,
                'current': self.model.currentView
            }

            with open(filename, 'wb') as file:
                pickle.dump(saved, file)

    def openView(self):
        filename, ext = QFileDialog.getOpenFileName(self, "Open View Settings",
                                                    ".", "*.pltvw")
        if filename:
            try:
                with open(filename, 'rb') as file:
                    saved = pickle.load(file)
            except Exception:
                message = 'Error loading plot settings'
                saved = {'default': None, 'current': None}
            if saved['default'] == self.model.defaultView:
                self.model.activeView = saved['current']
                self.dock.updateDock()
                self.applyChanges()
                message = f'{filename} settings loaded'
            else:
                message = 'Error loading plot settings. Incompatible model.'
            self.statusBar().showMessage(message, 5000)

    def applyChanges(self):
        if self.model.activeView != self.model.currentView:
            self.statusBar().showMessage('Generating Plot...')
            QApplication.processEvents()

            self.model.storeCurrent()
            self.model.subsequentViews = []
            self.model.generatePlot()
            self.resetModels()
            self.showCurrentView()

        else:
            self.statusBar().showMessage('No changes to apply.', 3000)

    def undo(self):
        self.statusBar().showMessage('Generating Plot...')
        QApplication.processEvents()

        self.model.undo()
        self.resetModels()
        self.showCurrentView()
        self.dock.updateDock()
        self.colorDialog.updateDialogValues()

        if not self.model.previousViews:
            self.undoAction.setDisabled(True)
        self.redoAction.setDisabled(False)

    def redo(self):
        self.statusBar().showMessage('Generating Plot...')
        QApplication.processEvents()

        self.model.redo()
        self.resetModels()
        self.showCurrentView()
        self.dock.updateDock()
        self.colorDialog.updateDialogValues()

        if not self.model.subsequentViews:
            self.redoAction.setDisabled(True)
        self.undoAction.setDisabled(False)

    def restoreDefault(self):
        if self.model.currentView != self.model.defaultView:

            self.statusBar().showMessage('Generating Plot...')
            QApplication.processEvents()

            self.model.storeCurrent()
            self.model.activeView = copy.deepcopy(self.model.defaultView)
            self.model.generatePlot()
            self.resetModels()
            self.showCurrentView()
            self.dock.updateDock()
            self.colorDialog.updateDialogValues()

            self.model.subsequentViews = []

    def editBasis(self, basis, apply=False):
        self.model.activeView.basis = basis
        self.dock.updateBasis()
        if apply:
            self.applyChanges()

    def editColorBy(self, domain_kind, apply=False):
        self.model.activeView.colorby = domain_kind
        self.dock.updateColorBy()
        self.colorDialog.updateColorBy()
        if apply:
            self.applyChanges()

    def toggleMasking(self, state, apply=False):
        self.model.activeView.masking = bool(state)
        self.colorDialog.updateMasking()
        if apply:
            self.applyChanges()

    def toggleHighlighting(self, state, apply=False):
        self.model.activeView.highlighting = bool(state)
        self.colorDialog.updateHighlighting()
        if apply:
            self.applyChanges()

    def toggleDockView(self):
        if self.dock.isVisible():
            self.dock.hide()
            if not self.isMaximized() and not self.dock.isFloating():
                self.resize(self.width() - self.dock.width(), self.height())
        else:
            self.dock.setVisible(True)
            if not self.isMaximized() and not self.dock.isFloating():
                self.resize(self.width() + self.dock.width(), self.height())
        self.resizePixmap()
        self.showMainWindow()

    def editZoomAct(self):
        percent, ok = QInputDialog.getInt(self, "Edit Zoom", "Zoom Percent:",
                                          self.dock.zoomBox.value(), 25, 2000)
        if ok:
            self.dock.zoomBox.setValue(percent)

    def editZoom(self, value):
        self.zoom = value
        self.resizePixmap()
        self.dock.zoomBox.setValue(value)

    def showMainWindow(self):
        self.raise_()
        self.activateWindow()

    def showColorDialog(self):
        self.colorDialog.show()
        self.colorDialog.raise_()
        self.colorDialog.activateWindow()

    # Dock methods:

    def editSingleOrigin(self, value, dimension):
        self.model.activeView.origin[dimension] = value

    def editPlotAlpha(self, value):
        self.model.activeView.plotAlpha = value
        self.dock.updatePlotAlpha()

    def editWidth(self, value):
        self.model.activeView.width = value
        self.onRatioChange()
        self.dock.updateWidth()

    def editHeight(self, value):
        self.model.activeView.height = value
        self.onRatioChange()
        self.dock.updateHeight()

    def toggleAspectLock(self, state):
        self.model.activeView.aspectLock = bool(state)
        self.onRatioChange()
        self.dock.updateAspectLock()

    def editVRes(self, value):
        self.model.activeView.v_res = value
        self.dock.updateVRes()

    def editHRes(self, value):
        self.model.activeView.h_res = value
        self.onRatioChange()
        self.dock.updateHRes()

    # Color dialog methods:

    def editMaskingColor(self):
        current_color = self.model.activeView.maskBackground
        dlg = QColorDialog(self)

        dlg.setCurrentColor(QtGui.QColor.fromRgb(*current_color))
        if dlg.exec_():
            new_color = dlg.currentColor().getRgb()[:3]
            self.model.activeView.maskBackground = new_color
            self.colorDialog.updateMaskingColor()

    def editHighlightColor(self):
        current_color = self.model.activeView.highlightBackground
        dlg = QColorDialog(self)

        dlg.setCurrentColor(QtGui.QColor.fromRgb(*current_color))
        if dlg.exec_():
            new_color = dlg.currentColor().getRgb()[:3]
            self.model.activeView.highlightBackground = new_color
            self.colorDialog.updateHighlightColor()

    def editAlpha(self, value):
        self.model.activeView.highlightAlpha = value

    def editSeed(self, value):
        self.model.activeView.highlightSeed = value

    def editBackgroundColor(self, apply=False):
        current_color = self.model.activeView.plotBackground
        dlg = QColorDialog(self)

        dlg.setCurrentColor(QtGui.QColor.fromRgb(*current_color))
        if dlg.exec_():
            new_color = dlg.currentColor().getRgb()[:3]
            self.model.activeView.plotBackground = new_color
            self.colorDialog.updateBackgroundColor()

        if apply:
            self.applyChanges()

    # Plot image methods

    def editPlotOrigin(self, xOr, yOr, zOr=None, apply=False):
        if zOr != None:
            self.model.activeView.origin = [xOr, yOr, zOr]
        else:
            origin = [None, None, None]
            origin[self.xBasis] = xOr
            origin[self.yBasis] = yOr
            origin[self.zBasis] = self.model.activeView.origin[self.zBasis]
            self.model.activeView.origin = origin

        self.dock.updateOrigin()

        if apply:
            self.applyChanges()

    def revertDockControls(self):
        self.dock.revertToCurrent()

    def editDomainColor(self, kind, id):
        if kind == 'Cell':
            domain = self.model.activeView.cells
        else:
            domain = self.model.activeView.materials

        current_color = domain[id].color
        dlg = QColorDialog(self)

        if isinstance(current_color, tuple):
            dlg.setCurrentColor(QtGui.QColor.fromRgb(*current_color))
        elif isinstance(current_color, str):
            current_color = openmc.plots._SVG_COLORS[current_color]
            dlg.setCurrentColor(QtGui.QColor.fromRgb(*current_color))
        if dlg.exec_():
            new_color = dlg.currentColor().getRgb()[:3]
            domain[id].color = new_color

        self.applyChanges()

    def toggleDomainMask(self, state, kind, id):
        if kind == 'Cell':
            domain = self.model.activeView.cells
        else:
            domain = self.model.activeView.materials

        domain[id].masked = bool(state)
        self.applyChanges()

    def toggleDomainHighlight(self, state, kind, id):
        if kind == 'Cell':
            domain = self.model.activeView.cells
        else:
            domain = self.model.activeView.materials

        domain[id].highlighted = bool(state)
        self.applyChanges()

    # Helper methods:

    def restoreWindowSettings(self):
        settings = QtCore.QSettings()

        self.resize(settings.value("mainWindow/Size", QtCore.QSize(800, 600)))
        self.move(
            settings.value("mainWindow/Position", QtCore.QPoint(100, 100)))
        self.restoreState(settings.value("mainWindow/State"))

        self.colorDialog.resize(
            settings.value("colorDialog/Size", QtCore.QSize(400, 500)))
        self.colorDialog.move(
            settings.value("colorDialog/Position", QtCore.QPoint(600, 200)))
        self.colorDialog.setVisible(
            bool(settings.value("colorDialog/Visible", 0)))

    def restoreModelSettings(self):
        if os.path.isfile("plot_settings.pkl"):

            with open('plot_settings.pkl', 'rb') as file:
                model = pickle.load(file)

            if model.defaultView == self.model.defaultView:
                self.model.currentView = model.currentView
                self.model.activeView = copy.deepcopy(model.currentView)
                self.model.previousViews = model.previousViews
                self.model.subsequentViews = model.subsequentViews
                if os.path.isfile('plot_ids.binary') \
                    and os.path.isfile('plot.ppm'):
                    self.restored = True

    def resetModels(self):
        self.cellsModel = DomainTableModel(self.model.activeView.cells)
        self.materialsModel = DomainTableModel(self.model.activeView.materials)
        self.cellsModel.beginResetModel()
        self.cellsModel.endResetModel()
        self.materialsModel.beginResetModel()
        self.materialsModel.endResetModel()
        self.colorDialog.updateDomainTabs()

    def showCurrentView(self):
        self.resizePixmap()
        self.updateScale()
        self.updateRelativeBases()

        if self.model.previousViews:
            self.undoAction.setDisabled(False)
        if self.model.subsequentViews:
            self.redoAction.setDisabled(False)
        else:
            self.redoAction.setDisabled(True)

        self.statusBar().showMessage('Done', 1000)
        self.adjustWindow()

    def updateScale(self):
        cv = self.model.currentView
        self.scale = (cv.h_res / cv.width, cv.v_res / cv.height)

    def updateRelativeBases(self):
        cv = self.model.currentView
        self.xBasis = 0 if cv.basis[0] == 'x' else 1
        self.yBasis = 1 if cv.basis[1] == 'y' else 2
        self.zBasis = 3 - (self.xBasis + self.yBasis)

    def adjustWindow(self):
        self.screen = app.desktop().screenGeometry()
        self.setMaximumSize(self.screen.width(), self.screen.height())

    def onRatioChange(self):
        av = self.model.activeView
        if av.aspectLock:
            ratio = av.width / max(av.height, .001)
            av.v_res = int(av.h_res / ratio)
            self.dock.updateVRes()

    def showCoords(self, xPlotPos, yPlotPos):
        cv = self.model.currentView
        if cv.basis == 'xy':
            coords = (f"({round(xPlotPos, 2)}, {round(yPlotPos, 2)}, "
                      f"{round(cv.origin[2], 2)})")
        elif cv.basis == 'xz':
            coords = (f"({round(xPlotPos, 2)}, {round(cv.origin[1], 2)}, "
                      f"{round(yPlotPos, 2)})")
        else:
            coords = (f"({round(cv.origin[0], 2)}, {round(xPlotPos, 2)}, "
                      f"{round(yPlotPos, 2)})")
        self.coord_label.setText(f'{coords}')

    def resizePixmap(self):
        z = self.zoom / 100.
        self.plotIm.setPixmap(self.frame.width() * z, self.frame.height() * z)
        self.plotIm.adjustSize()

    def moveEvent(self, event):
        self.adjustWindow()

    def resizeEvent(self, event):
        if self.pixmap:
            self.adjustWindow()
            self.updateScale()

    def closeEvent(self, event):
        settings = QtCore.QSettings()
        settings.setValue("mainWindow/Size", self.size())
        settings.setValue("mainWindow/Position", self.pos())
        settings.setValue("mainWindow/State", self.saveState())

        settings.setValue("colorDialog/Size", self.colorDialog.size())
        settings.setValue("colorDialog/Position", self.colorDialog.pos())
        visible = int(self.colorDialog.isVisible())
        settings.setValue("colorDialog/Visible", visible)

        if len(self.model.previousViews) > 10:
            self.model.previousViews = self.model.previousViews[-10:]
        if len(self.model.subsequentViews) > 10:
            self.model.subsequentViews = self.model.subsequentViews[-10:]

        with open('plot_settings.pkl', 'wb') as file:
            pickle.dump(self.model, file)
Beispiel #4
0
                                            factory_map[PropertyID.CURSOR])
        group[count].addSubProperty(property_)

        browser_item = tree_browser.addProperty(group[count])
        tree_browser.setExpanded(browser_item, True)
        browser_item = box_browser.addProperty(group[count])
        browser_item = button_browser.addProperty(group[count])
        button_browser.setExpanded(browser_item, True)

    tree_browser.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
    tree_scroll_area.setWidgetResizable(True)
    tree_scroll_area.setWidget(tree_browser)
    layout.addWidget(QLabel("Tree Browser", parent=dialog), 0, 0)
    layout.addWidget(tree_scroll_area, 1, 0)

    tree_scroll_area.setSizePolicy(QSizePolicy.Expanding,
                                   QSizePolicy.Expanding)
    box_scroll_area.setWidgetResizable(True)
    box_scroll_area.setWidget(box_browser)
    layout.addWidget(QLabel("Box Browser", parent=dialog), 0, 1)
    layout.addWidget(box_scroll_area, 1, 1)

    button_scroll_area.setSizePolicy(QSizePolicy.Expanding,
                                     QSizePolicy.Expanding)
    button_scroll_area.setWidgetResizable(True)
    button_scroll_area.setWidget(button_browser)
    layout.addWidget(QLabel("Button Browser", parent=dialog), 0, 2)
    layout.addWidget(button_scroll_area, 1, 2)

    dialog.setLayout(layout)
    dialog.showMaximized()
    sys.exit(app.exec_())
Beispiel #5
0
class TabDisplays(QTabWidget):
    def __init__(self, parent=None):
        super(TabDisplays, self).__init__(parent)

        # Initialize logging
        logging_conf_file = os.path.join(os.path.dirname(__file__),
                                         'cfg/aecgviewer_aecg_logging.conf')
        logging.config.fileConfig(logging_conf_file)
        self.logger = logging.getLogger(__name__)

        self.studyindex_info = aecg.tools.indexer.StudyInfo()

        self.validator = QWidget()

        self.studyinfo = QWidget()

        self.statistics = QWidget()

        self.waveforms = QWidget()

        self.waveforms.setAccessibleName("Waveforms")

        self.scatterplot = QWidget()

        self.histogram = QWidget()

        self.trends = QWidget()

        self.xmlviewer = QWidget()
        self.xml_display = QTextEdit(self.xmlviewer)

        self.options = QWidget()

        self.aecg_display_area = QScrollArea()
        self.cbECGLayout = QComboBox()
        self.aecg_display = EcgDisplayWidget(self.aecg_display_area)
        self.aecg_display_area.setWidget(self.aecg_display)

        self.addTab(self.validator, "Study information")
        self.addTab(self.waveforms, "Waveforms")
        self.addTab(self.xmlviewer, "XML")
        self.addTab(self.options, "Options")

        self.setup_validator()
        self.setup_waveforms()
        self.setup_xmlviewer()
        self.setup_options()

        size = QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
        size.setHeightForWidth(False)
        self.setSizePolicy(size)

        # Initialized a threpool with 2 threads 1 for the GUI, 1 for long
        # tasks, so GUI remains responsive
        self.threadpool = QThreadPool()
        self.threadpool.setMaxThreadCount(2)
        self.validator_worker = None
        self.indexing_timer = QElapsedTimer()

    def setup_validator(self):
        self.directory_indexer = None  # aecg.indexing.DirectoryIndexer()
        self.validator_layout_container = QWidget()
        self.validator_layout = QFormLayout()
        self.validator_form_layout = QFormLayout(
            self.validator_layout_container)
        self.validator_grid_layout = QGridLayout()
        self.study_info_file = QLineEdit()
        self.study_info_file.setToolTip("Study index file")
        self.study_info_description = QLineEdit()
        self.study_info_description.setToolTip("Description")
        self.app_type = QLineEdit()
        self.app_type.setToolTip("Application type (e.g., NDA, IND, BLA, IDE)")
        self.app_num = QLineEdit()
        self.app_num.setToolTip("Six-digit application number")
        self.app_num.setValidator(QIntValidator(self.app_num))
        self.study_id = QLineEdit()
        self.study_id.setToolTip("Study identifier")
        self.study_sponsor = QLineEdit()
        self.study_sponsor.setToolTip("Sponsor of the study")

        self.study_annotation_aecg_cb = QComboBox()
        self.study_annotation_aecg_cb.addItems(
            ["Rhythm", "Derived beat", "Holter-rhythm", "Holter-derived"])
        self.study_annotation_aecg_cb.setToolTip(
            "Waveforms used to perform the ECG measurements (i.e., "
            "annotations)\n"
            "\tRhythm: annotations in a rhythm strip or discrete ECG "
            "extraction (e.g., 10-s strips)\n"
            "\tDerived beat: annotations in a representative beat derived "
            "from a rhythm strip\n"
            "\tHolter-rhythm: annotations in a the analysis window of a "
            "continuous recording\n"
            "\tHolter-derived: annotations in a representative beat derived "
            "from analysis window of a continuous recording\n")
        self.study_annotation_lead_cb = QComboBox()
        self.ui_leads = ["GLOBAL"] + aecg.STD_LEADS[0:12] +\
            [aecg.KNOWN_NON_STD_LEADS[1]] + aecg.STD_LEADS[12:15] + ["Other"]
        self.study_annotation_lead_cb.addItems(self.ui_leads)
        self.study_annotation_lead_cb.setToolTip(
            "Primary analysis lead annotated per protocol. There could be "
            "annotations in other leads also, but only the primary lead should"
            " be selected here.\n"
            "Select global if all leads were used at the "
            "same time (e.g., superimposed on screen).\n"
            "Select other if the primary lead used is not in the list.")

        self.study_numsubjects = QLineEdit()
        self.study_numsubjects.setToolTip(
            "Number of subjects with ECGs in the study")
        self.study_numsubjects.setValidator(
            QIntValidator(self.study_numsubjects))

        self.study_aecgpersubject = QLineEdit()
        self.study_aecgpersubject.setToolTip(
            "Number of scheduled ECGs (or analysis windows) per subject as "
            "specified in the study protocol.\n"
            "Enter average number of ECGs "
            "per subject if the protocol does not specify a fixed number of "
            "ECGs per subject.")
        self.study_aecgpersubject.setValidator(
            QIntValidator(self.study_aecgpersubject))

        self.study_numaecg = QLineEdit()
        self.study_numaecg.setToolTip(
            "Total number of aECG XML files in the study")
        self.study_numaecg.setValidator(QIntValidator(self.study_numaecg))

        self.study_annotation_numbeats = QLineEdit()
        self.study_annotation_numbeats.setToolTip(
            "Minimum number of beats annotated in each ECG or analysis window"
            ".\nEnter 1 if annotations were done in the derived beat.")
        self.study_annotation_numbeats.setValidator(
            QIntValidator(self.study_annotation_numbeats))

        self.aecg_numsubjects = QLineEdit()
        self.aecg_numsubjects.setToolTip(
            "Number of subjects found across the provided aECG XML files")
        self.aecg_numsubjects.setReadOnly(True)

        self.aecg_aecgpersubject = QLineEdit()
        self.aecg_aecgpersubject.setToolTip(
            "Average number of ECGs per subject found across the provided "
            "aECG XML files")
        self.aecg_aecgpersubject.setReadOnly(True)

        self.aecg_numaecg = QLineEdit()
        self.aecg_numaecg.setToolTip(
            "Number of aECG XML files found in the study aECG directory")
        self.aecg_numaecg.setReadOnly(True)

        self.subjects_less_aecgs = QLineEdit()
        self.subjects_less_aecgs.setToolTip(
            "Percentage of subjects with less aECGs than specified per "
            "protocol")
        self.subjects_less_aecgs.setReadOnly(True)

        self.subjects_more_aecgs = QLineEdit()
        self.subjects_more_aecgs.setToolTip(
            "Percentage of subjects with more aECGs than specified per "
            "protocol")
        self.subjects_more_aecgs.setReadOnly(True)

        self.aecgs_no_annotations = QLineEdit()
        self.aecgs_no_annotations.setToolTip(
            "Percentage of aECGs with no annotations")
        self.aecgs_no_annotations.setReadOnly(True)

        self.aecgs_less_qt_in_primary_lead = QLineEdit()
        self.aecgs_less_qt_in_primary_lead.setToolTip(
            "Percentage of aECGs with less QT intervals in the primary lead "
            "than specified per protocol")
        self.aecgs_less_qt_in_primary_lead.setReadOnly(True)

        self.aecgs_less_qts = QLineEdit()
        self.aecgs_less_qts.setToolTip(
            "Percentage of aECGs with less QT intervals than specified per "
            "protocol")
        self.aecgs_less_qts.setReadOnly(True)

        self.aecgs_annotations_multiple_leads = QLineEdit()
        self.aecgs_annotations_multiple_leads.setToolTip(
            "Percentage of aECGs with QT annotations in multiple leads")
        self.aecgs_annotations_multiple_leads.setReadOnly(True)

        self.aecgs_annotations_no_primary_lead = QLineEdit()
        self.aecgs_annotations_no_primary_lead.setToolTip(
            "Percentage of aECGs with QT annotations not in the primary lead")
        self.aecgs_annotations_no_primary_lead.setReadOnly(True)

        self.aecgs_with_errors = QLineEdit()
        self.aecgs_with_errors.setToolTip("Number of aECG files with errors")
        self.aecgs_with_errors.setReadOnly(True)

        self.aecgs_potentially_digitized = QLineEdit()
        self.aecgs_potentially_digitized.setToolTip(
            "Number of aECG files potentially digitized (i.e., with more than "
            "5% of samples missing)")
        self.aecgs_potentially_digitized.setReadOnly(True)

        self.study_dir = QLineEdit()
        self.study_dir.setToolTip("Directory containing the aECG files")
        self.study_dir_button = QPushButton("...")
        self.study_dir_button.clicked.connect(self.select_study_dir)
        self.study_dir_button.setToolTip("Open select directory dialog")

        self.validator_form_layout.addRow("Application Type", self.app_type)
        self.validator_form_layout.addRow("Application Number", self.app_num)
        self.validator_form_layout.addRow("Study name/ID", self.study_id)
        self.validator_form_layout.addRow("Sponsor", self.study_sponsor)
        self.validator_form_layout.addRow("Study description",
                                          self.study_info_description)

        self.validator_form_layout.addRow("Annotations in",
                                          self.study_annotation_aecg_cb)
        self.validator_form_layout.addRow("Annotations primary lead",
                                          self.study_annotation_lead_cb)

        self.validator_grid_layout.addWidget(QLabel(""), 0, 0)
        self.validator_grid_layout.addWidget(
            QLabel("Per study protocol or report"), 0, 1)
        self.validator_grid_layout.addWidget(QLabel("Found in aECG files"), 0,
                                             2)

        self.validator_grid_layout.addWidget(QLabel("Number of subjects"), 1,
                                             0)
        self.validator_grid_layout.addWidget(self.study_numsubjects, 1, 1)
        self.validator_grid_layout.addWidget(self.aecg_numsubjects, 1, 2)

        self.validator_grid_layout.addWidget(
            QLabel("Number of aECG per subject"), 2, 0)
        self.validator_grid_layout.addWidget(self.study_aecgpersubject, 2, 1)
        self.validator_grid_layout.addWidget(self.aecg_aecgpersubject, 2, 2)

        self.validator_grid_layout.addWidget(QLabel("Total number of aECG"), 3,
                                             0)
        self.validator_grid_layout.addWidget(self.study_numaecg, 3, 1)
        self.validator_grid_layout.addWidget(self.aecg_numaecg, 3, 2)

        self.validator_grid_layout.addWidget(
            QLabel("Number of beats per aECG"), 4, 0)
        self.validator_grid_layout.addWidget(self.study_annotation_numbeats, 4,
                                             1)

        self.validator_grid_layout.addWidget(
            QLabel("Subjects with fewer ECGs"), 5, 1)
        self.validator_grid_layout.addWidget(self.subjects_less_aecgs, 5, 2)
        self.validator_grid_layout.addWidget(QLabel("Subjects with more ECGs"),
                                             6, 1)
        self.validator_grid_layout.addWidget(self.subjects_more_aecgs, 6, 2)
        self.validator_grid_layout.addWidget(
            QLabel("aECGs without annotations"), 7, 1)
        self.validator_grid_layout.addWidget(self.aecgs_no_annotations, 7, 2)
        self.validator_grid_layout.addWidget(
            QLabel("aECGs without expected number of QTs in primary lead"), 8,
            1)
        self.validator_grid_layout.addWidget(
            self.aecgs_less_qt_in_primary_lead, 8, 2)
        self.validator_grid_layout.addWidget(
            QLabel("aECGs without expected number of QTs"), 9, 1)
        self.validator_grid_layout.addWidget(self.aecgs_less_qts, 9, 2)

        self.validator_grid_layout.addWidget(
            QLabel("aECGs annotated in multiple leads"), 10, 1)
        self.validator_grid_layout.addWidget(
            self.aecgs_annotations_multiple_leads, 10, 2)
        self.validator_grid_layout.addWidget(
            QLabel("aECGs with annotations not in primary lead"), 11, 1)
        self.validator_grid_layout.addWidget(
            self.aecgs_annotations_no_primary_lead, 11, 2)
        self.validator_grid_layout.addWidget(QLabel("aECGs with errors"), 12,
                                             1)
        self.validator_grid_layout.addWidget(self.aecgs_with_errors, 12, 2)

        self.validator_grid_layout.addWidget(
            QLabel("Potentially digitized aECGs"), 13, 1)
        self.validator_grid_layout.addWidget(self.aecgs_potentially_digitized,
                                             13, 2)

        self.validator_form_layout.addRow(self.validator_grid_layout)

        tmp = QHBoxLayout()
        tmp.addWidget(self.study_dir)
        tmp.addWidget(self.study_dir_button)
        self.validator_form_layout.addRow("Study aECGs directory", tmp)

        self.validator_form_layout.addRow("Study index file",
                                          self.study_info_file)

        self.validator_layout.addWidget(self.validator_layout_container)
        self.validator_effective_dirs = QLabel("")
        self.validator_effective_dirs.setWordWrap(True)
        self.validator_layout.addWidget(self.validator_effective_dirs)

        self.val_button = QPushButton("Generate/update study index")
        self.val_button.clicked.connect(self.importstudy_dialog)
        self.validator_layout.addWidget(self.val_button)
        self.cancel_val_button = QPushButton("Cancel study index generation")
        self.cancel_val_button.clicked.connect(self.cancel_validator)
        self.cancel_val_button.setEnabled(False)
        self.validator_layout.addWidget(self.cancel_val_button)
        self.validator_pl = QLabel("")
        self.validator_layout.addWidget(self.validator_pl)
        self.validator_pb = QProgressBar()
        self.validator_layout.addWidget(self.validator_pb)
        self.validator.setLayout(self.validator_layout)
        self.stop_indexing = False

        self.lastindexing_starttime = None

        self.update_validator_effective_dirs()

    def effective_aecgs_dir(self, navwidget, silent=False):
        aecgs_effective_dir = self.study_dir.text()
        if navwidget.project_loaded != '':
            # Path specified in the GUI
            potential_aecgs_dirs = [self.study_dir.text()]
            # StudyDir path from current working directory
            potential_aecgs_dirs += [self.studyindex_info.StudyDir]
            # StudyDir path from directory where the index is located
            potential_aecgs_dirs += [
                os.path.join(os.path.dirname(navwidget.project_loaded),
                             self.studyindex_info.StudyDir)
            ]
            # StudyDir replaced with the directory where the index is located
            potential_aecgs_dirs += [os.path.dirname(navwidget.project_loaded)]
            dir_found = False
            # Get xml and zip filenames from first element in the index
            aecg_xml_file = navwidget.data_index["AECGXML"][0]
            zipfile = ""
            if aecg_xml_file != "":
                zipfile = navwidget.data_index["ZIPFILE"][0]
            for p in potential_aecgs_dirs:
                testfn = os.path.join(p, aecg_xml_file)
                if zipfile != "":
                    testfn = os.path.join(p, zipfile)
                if os.path.isfile(testfn):
                    dir_found = True
                    aecgs_effective_dir = p
                    break
            if not silent:
                if not dir_found:
                    QMessageBox.warning(
                        self, f"Study aECGs directory not found",
                        f"The following paths were checked:"
                        f"{[','.join(p) for p in potential_aecgs_dirs]} and "
                        f"none of them is valid")
                elif p != self.study_dir.text():
                    QMessageBox.warning(
                        self, f"Study aECGs directory not found",
                        f"The path specified in the study aECGs directory is "
                        f"not valid and {p} is being used instead. Check and "
                        f"update the path in Study aECGs directory textbox if "
                        f"the suggested path is not the adequate path")
        return aecgs_effective_dir

    def update_validator_effective_dirs(self):
        msg = f"Working directory: {os.getcwd()}"
        if self.parent() is not None:
            if isinstance(self.parent(), QSplitter):
                navwidget = self.parent().parent()
            else:  # Tabs widget has not been allocated the QSplitter yet
                navwidget = self.parent()
            project_loaded = navwidget.project_loaded
            if project_loaded != '':
                msg = f"{msg}\nLoaded project index: "\
                      f"{navwidget.project_loaded}"
                effective_aecgs_path = self.effective_aecgs_dir(navwidget)
                msg = f"{msg}\nEffective study aECGs directory: "\
                      f"{effective_aecgs_path}"
            else:
                msg = f"{msg}\nLoaded project index: None"
        else:
            msg = f"{msg}\nLoaded project index: None"
        self.validator_effective_dirs.setText(msg)

    def load_study_info(self, fileName):
        self.study_info_file.setText(fileName)
        try:
            study_info = pd.read_excel(fileName, sheet_name="Info")
            self.studyindex_info = aecg.tools.indexer.StudyInfo()
            self.studyindex_info.__dict__.update(
                study_info.set_index("Property").transpose().reset_index(
                    drop=True).to_dict('index')[0])
            sponsor = ""
            description = ""
            if self.studyindex_info.Sponsor is not None and\
                    isinstance(self.studyindex_info.Sponsor, str):
                sponsor = self.studyindex_info.Sponsor
            if self.studyindex_info.Description is not None and\
                    isinstance(self.studyindex_info.Description, str):
                description = self.studyindex_info.Description
            self.study_sponsor.setText(sponsor)
            self.study_info_description.setText(description)
            self.app_type.setText(self.studyindex_info.AppType)
            self.app_num.setText(f"{int(self.studyindex_info.AppNum):06d}")
            self.study_id.setText(self.studyindex_info.StudyID)
            self.study_numsubjects.setText(str(self.studyindex_info.NumSubj))
            self.study_aecgpersubject.setText(
                str(self.studyindex_info.NECGSubj))
            self.study_numaecg.setText(str(self.studyindex_info.TotalECGs))

            anns_in = self.studyindex_info.AnMethod.upper()
            idx = 0
            if anns_in == "RHYTHM":
                idx = 0
            elif anns_in == "DERIVED":
                idx = 1
            elif anns_in == "HOLTER_RHYTHM":
                idx = 2
            elif anns_in == "HOLTER_MEDIAN_BEAT":
                idx = 3
            else:
                idx = int(anns_in) - 1
            self.study_annotation_aecg_cb.setCurrentIndex(idx)

            the_lead = self.studyindex_info.AnLead
            idx = self.study_annotation_lead_cb.findText(str(the_lead))
            if idx == -1:
                idx = self.study_annotation_lead_cb.findText("MDC_ECG_LEAD_" +
                                                             str(the_lead))
            if idx == -1:
                idx = int(the_lead)
            self.study_annotation_lead_cb.setCurrentIndex(idx)

            self.study_annotation_numbeats.setText(
                str(self.studyindex_info.AnNbeats))
            if self.studyindex_info.StudyDir == "":
                self.studyindex_info.StudyDir = os.path.dirname(fileName)
            self.study_dir.setText(self.studyindex_info.StudyDir)

            self.update_validator_effective_dirs()
            self.setCurrentWidget(self.validator)

        except Exception as ex:
            QMessageBox.critical(
                self, "Import study error",
                "Error reading the study information file: '" + fileName + "'")

    def setup_waveforms(self):
        wflayout = QVBoxLayout()

        # ECG plot layout selection box
        self.cbECGLayout.addItems(
            ['12-lead stacked', '3x4 + lead II rhythm', 'Superimposed'])
        self.cbECGLayout.currentIndexChanged.connect(
            self.ecgplotlayout_changed)

        # Zoom buttons
        blayout = QHBoxLayout()

        pb_zoomin = QPushButton()
        pb_zoomin.setText("Zoom +")
        pb_zoomin.clicked.connect(self.zoom_in)

        pb_zoomreset = QPushButton()
        pb_zoomreset.setText("Zoom 1:1")
        pb_zoomreset.clicked.connect(self.zoom_reset)

        pb_zoomout = QPushButton()
        pb_zoomout.setText("Zoom -")
        pb_zoomout.clicked.connect(self.zoom_out)

        blayout.addWidget(self.cbECGLayout)
        blayout.addWidget(pb_zoomout)
        blayout.addWidget(pb_zoomreset)
        blayout.addWidget(pb_zoomin)

        wflayout.addLayout(blayout)

        # Add QScrollArea to main layout of waveforms tab
        self.aecg_display_area.setWidgetResizable(False)
        wflayout.addWidget(self.aecg_display_area)
        self.waveforms.setLayout(wflayout)
        size = QSizePolicy(QSizePolicy.Preferred, QSizePolicy.Preferred)
        size.setHeightForWidth(False)
        self.aecg_display_area.setSizePolicy(size)
        self.waveforms.setSizePolicy(size)

    def setup_xmlviewer(self):
        wf_layout = QHBoxLayout()
        wf_layout.addWidget(self.xml_display)
        self.xmlviewer.setLayout(wf_layout)

    def setup_options(self):
        self.options_layout = QFormLayout()
        self.aecg_schema_filename = QLineEdit(aecg.get_aecg_schema_location())
        self.options_layout.addRow("aECG XML schema path",
                                   self.aecg_schema_filename)

        self.save_index_every_n_aecgs = QSpinBox()
        self.save_index_every_n_aecgs.setMinimum(0)
        self.save_index_every_n_aecgs.setMaximum(50000)
        self.save_index_every_n_aecgs.setValue(0)
        self.save_index_every_n_aecgs.setSingleStep(100)
        self.save_index_every_n_aecgs.setSuffix(" aECGs")
        self.save_index_every_n_aecgs.setToolTip(
            "Set o 0 to save the study index file only after its generation "
            "is completed.\nOtherwise, the file is saved everytime the "
            " specified number of ECGs have been appended to the index.")
        self.options_layout.addRow("Save index every ",
                                   self.save_index_every_n_aecgs)

        self.save_all_intervals_cb = QCheckBox("")
        self.save_all_intervals_cb.setChecked(False)
        self.options_layout.addRow("Save individual beat intervals",
                                   self.save_all_intervals_cb)

        self.parallel_processing_cb = QCheckBox("")
        self.parallel_processing_cb.setChecked(True)
        self.options_layout.addRow("Parallel processing of files",
                                   self.parallel_processing_cb)

        self.options.setLayout(self.options_layout)

    def zoom_in(self):
        self.aecg_display.apply_zoom(self.aecg_display.zoom_factor + 0.1)

    def zoom_out(self):
        self.aecg_display.apply_zoom(self.aecg_display.zoom_factor - 0.1)

    def zoom_reset(self):
        self.aecg_display.apply_zoom(1.0)

    def ecgplotlayout_changed(self, i):
        self.aecg_display.update_aecg_plot(
            ecg_layout=aecg.utils.ECG_plot_layout(i + 1))

    def update_search_progress(self, i, n):
        self.validator_pl.setText(
            f"Searching aECGs in directory ({n} XML files found)")

    def update_progress(self, i, n):
        j = i
        m = n
        if i <= 1:
            j = 1
            if self.validator_pb.value() > 0:
                j = self.validator_pb.value() + 1
            m = self.validator_pb.maximum()
        running_time = self.indexing_timer.elapsed() * 1e-3  # in seconds
        time_per_item = running_time / j
        # reamining = seconds per item so far * total pending items to process
        remaining_time = time_per_item * (m - j)
        eta = datetime.datetime.now() +\
            datetime.timedelta(seconds=round(remaining_time, 0))
        self.validator_pl.setText(
            f"Validating aECG {j}/{m} | "
            f"Execution time: "
            f"{str(datetime.timedelta(0,seconds=round(running_time)))} | "
            f"{round(1/time_per_item,2)} aECGs per second | "
            f"ETA: {eta.isoformat(timespec='seconds')}")
        self.validator_pb.setValue(j)
        if self.save_index_every_n_aecgs.value() > 0 and\
                len(self.directory_indexer.studyindex) % \
                self.save_index_every_n_aecgs.value() == 0:
            self.save_validator_results(
                pd.concat(self.directory_indexer.studyindex,
                          ignore_index=True))

    def save_validator_results(self, res):
        if res.shape[0] > 0:
            self.studyindex_info = aecg.tools.indexer.StudyInfo()
            self.studyindex_info.StudyDir = self.study_dir.text()
            self.studyindex_info.IndexFile = self.study_info_file.text()
            self.studyindex_info.Sponsor = self.study_sponsor.text()
            self.studyindex_info.Description =\
                self.study_info_description.text()
            self.studyindex_info.Date = self.lastindexing_starttime.isoformat()
            self.studyindex_info.End_date = datetime.datetime.now().isoformat()
            self.studyindex_info.Version = aecg.__version__
            self.studyindex_info.AppType = self.app_type.text()
            self.studyindex_info.AppNum = f"{int(self.app_num.text()):06d}"
            self.studyindex_info.StudyID = self.study_id.text()
            self.studyindex_info.NumSubj = int(self.study_numsubjects.text())
            self.studyindex_info.NECGSubj = int(
                self.study_aecgpersubject.text())
            self.studyindex_info.TotalECGs = int(self.study_numaecg.text())
            anmethod = aecg.tools.indexer.AnnotationMethod(
                self.study_annotation_aecg_cb.currentIndex())
            self.studyindex_info.AnMethod = anmethod.name
            self.studyindex_info.AnLead =\
                self.study_annotation_lead_cb.currentText()
            self.studyindex_info.AnNbeats = int(
                self.study_annotation_numbeats.text())

            # Calculate stats
            study_stats = aecg.tools.indexer.StudyStats(
                self.studyindex_info, res)

            # Save to file
            aecg.tools.indexer.save_study_index(self.studyindex_info, res,
                                                study_stats)

    validator_data_ready = Signal()

    def save_validator_results_and_load_index(self, res):
        self.save_validator_results(res)
        self.validator_data_ready.emit()

    def indexer_validator_results(self, res):
        self.studyindex_df = pd.concat([self.studyindex_df, res],
                                       ignore_index=True)

    def subindex_thread_complete(self):
        return

    def index_directory_thread_complete(self):
        tmp = self.validator_pl.text().replace("ETA:", "Completed: ").replace(
            "Validating", "Validated")
        self.validator_pl.setText(tmp)
        self.val_button.setEnabled(True)
        self.cancel_val_button.setEnabled(False)
        self.validator_layout_container.setEnabled(True)

    def index_directory(self, progress_callback):
        self.lastindexing_starttime = datetime.datetime.now()
        self.indexing_timer.start()

        studyindex_df = []
        n_cores = os.cpu_count()
        aecg_schema = None
        if self.aecg_schema_filename.text() != "":
            aecg_schema = self.aecg_schema_filename.text()
        if self.parallel_processing_cb.isChecked():
            studyindex_df = self.directory_indexer.index_directory(
                self.save_all_intervals_cb.isChecked(), aecg_schema, n_cores,
                progress_callback)
        else:
            studyindex_df = self.directory_indexer.index_directory(
                self.save_all_intervals_cb.isChecked(), aecg_schema, 1,
                progress_callback)

        return studyindex_df

    def importstudy_dialog(self):
        dirName = os.path.normpath(self.study_dir.text())
        if dirName != "":
            if os.path.exists(dirName):
                self.directory_indexer = aecg.indexing.DirectoryIndexer()
                self.directory_indexer.set_aecg_dir(
                    dirName, self.update_search_progress)
                self.validator_pb.setMaximum(self.directory_indexer.num_files)
                self.validator_pb.reset()
                self.stop_indexing = False
                self.validator_layout_container.setEnabled(False)
                self.val_button.setEnabled(False)
                self.cancel_val_button.setEnabled(True)
                self.validator_worker = Worker(self.index_directory)
                self.validator_worker.signals.result.connect(
                    self.save_validator_results_and_load_index)
                self.validator_worker.signals.finished.connect(
                    self.index_directory_thread_complete)
                self.validator_worker.signals.progress.connect(
                    self.update_progress)

                # Execute
                self.threadpool.start(self.validator_worker)
            else:
                QMessageBox.critical(
                    self, "Directory not found",
                    f"Specified study directory not found:\n{dirName}")
        else:
            QMessageBox.critical(self, "Import study error",
                                 "Study directory cannot be empty")

    def cancel_validator(self):
        self.cancel_val_button.setEnabled(False)
        self.stop_indexing = True
        self.directory_indexer.cancel_indexing = True
        self.threadpool.waitForDone(3000)
        self.val_button.setEnabled(True)

    def select_study_dir(self):
        cd = self.study_dir.text()
        if cd == "":
            cd = "."
        dir = QFileDialog.getExistingDirectory(
            self, "Open Directory", cd,
            QFileDialog.ShowDirsOnly | QFileDialog.DontResolveSymlinks)
        if dir != "":
            self.study_dir.setText(dir)
Beispiel #6
0
class MainWindow(QMainWindow):
    def __init__(self,
                 font=QtGui.QFontMetrics(QtGui.QFont()),
                 screen_size=QtCore.QSize()):
        super().__init__()

        self.screen = screen_size
        self.font_metric = font
        self.setWindowTitle('OpenMC Plot Explorer')

    def loadGui(self):

        self.pixmap = None
        self.zoom = 100

        self.loadModel()

        # Create viewing area
        self.frame = QScrollArea(self)
        cw = QWidget()
        self.frame.setCornerWidget(cw)
        self.frame.setAlignment(QtCore.Qt.AlignCenter)
        self.frame.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
        self.setCentralWidget(self.frame)

        # connect pinch gesture (OSX)
        self.grabGesture(QtCore.Qt.PinchGesture)

        # Create plot image
        self.plotIm = PlotImage(self.model, self.frame, self)
        self.frame.setWidget(self.plotIm)

        # Dock
        self.dock = DomainDock(self.model, self.font_metric, self)
        self.dock.setObjectName("Domain Options Dock")
        self.addDockWidget(QtCore.Qt.RightDockWidgetArea, self.dock)

        # Tally Dock
        self.tallyDock = TallyDock(self.model, self.font_metric, self)
        self.tallyDock.update()
        self.tallyDock.setObjectName("Tally Options Dock")
        self.addDockWidget(QtCore.Qt.RightDockWidgetArea, self.tallyDock)

        # Color DialogtallyDock
        self.colorDialog = ColorDialog(self.model, self.font_metric, self)
        self.colorDialog.hide()

        # Tools
        self.exportDataDialog = ExportDataDialog(self.model, self.font_metric,
                                                 self)

        # Restore Window Settings
        self.restoreWindowSettings()

        # Create menubar
        self.createMenuBar()
        self.updateEditMenu()

        # Status Bar
        self.coord_label = QLabel()
        self.statusBar().addPermanentWidget(self.coord_label)
        self.coord_label.hide()

        # Keyboard overlay
        self.shortcutOverlay = ShortcutsOverlay(self)
        self.shortcutOverlay.hide()

        # Load Plot
        self.statusBar().showMessage('Generating Plot...')
        self.dock.updateDock()
        self.tallyDock.update()
        self.colorDialog.updateDialogValues()
        self.statusBar().showMessage('')

        # Timer allows GUI to render before plot finishes loading
        QtCore.QTimer.singleShot(0, self.plotIm.generatePixmap)
        QtCore.QTimer.singleShot(0, self.showCurrentView)

    def event(self, event):
        # use pinch event to update zoom
        if isinstance(event, QGestureEvent):
            pinch = event.gesture(QtCore.Qt.PinchGesture)
            self.editZoom(self.zoom * pinch.scaleFactor())
        if isinstance(event, QKeyEvent) and hasattr(self, "shortcutOverlay"):
            self.shortcutOverlay.event(event)
        return super().event(event)

    def show(self):
        super().show()
        self.plotIm._resize()

    def toggleShortcuts(self):
        if self.shortcutOverlay.isVisible():
            self.shortcutOverlay.close()
        else:
            self.shortcutOverlay.move(0, 0)
            self.shortcutOverlay.resize(self.width(), self.height())
            self.shortcutOverlay.show()

    # Create and update menus:
    def createMenuBar(self):
        self.mainMenu = self.menuBar()

        # File Menu
        self.reloadModelAction = QAction("&Reload model...", self)
        self.reloadModelAction.setShortcut("Ctrl+Shift+R")
        self.reloadModelAction.setToolTip("Reload current model")
        self.reloadModelAction.setStatusTip("Reload current model")
        reload_connector = partial(self.loadModel, reload=True)
        self.reloadModelAction.triggered.connect(reload_connector)

        self.saveImageAction = QAction("&Save Image As...", self)
        self.saveImageAction.setShortcut("Ctrl+Shift+S")
        self.saveImageAction.setToolTip('Save plot image')
        self.saveImageAction.setStatusTip('Save plot image')
        self.saveImageAction.triggered.connect(self.saveImage)

        self.saveViewAction = QAction("Save &View...", self)
        self.saveViewAction.setShortcut(QtGui.QKeySequence.Save)
        self.saveViewAction.setStatusTip('Save current view settings')
        self.saveViewAction.triggered.connect(self.saveView)

        self.openAction = QAction("&Open View...", self)
        self.openAction.setShortcut(QtGui.QKeySequence.Open)
        self.openAction.setToolTip('Open saved view settings')
        self.openAction.setStatusTip('Open saved view settings')
        self.openAction.triggered.connect(self.openView)

        self.quitAction = QAction("&Quit", self)
        self.quitAction.setShortcut(QtGui.QKeySequence.Quit)
        self.quitAction.setToolTip('Quit OpenMC Plot Explorer')
        self.quitAction.setStatusTip('Quit OpenMC Plot Explorer')
        self.quitAction.triggered.connect(self.close)

        self.exportDataAction = QAction('E&xport...', self)
        self.exportDataAction.setToolTip('Export model and tally data VTK')
        self.setStatusTip('Export current model and tally data to VTK')
        self.exportDataAction.triggered.connect(self.exportTallyData)
        if not _HAVE_VTK:
            self.exportDataAction.setEnabled(False)
            self.exportDataAction.setToolTip(
                "Disabled: VTK Python module is not installed")

        self.fileMenu = self.mainMenu.addMenu('&File')
        self.fileMenu.addAction(self.reloadModelAction)
        self.fileMenu.addAction(self.saveImageAction)
        self.fileMenu.addAction(self.exportDataAction)
        self.fileMenu.addSeparator()
        self.fileMenu.addAction(self.saveViewAction)
        self.fileMenu.addAction(self.openAction)
        self.fileMenu.addSeparator()
        self.fileMenu.addAction(self.quitAction)

        # Data Menu
        self.openStatePointAction = QAction("&Open statepoint...", self)
        self.openStatePointAction.setToolTip('Open statepoint file')
        self.openStatePointAction.triggered.connect(self.openStatePoint)

        self.dataMenu = self.mainMenu.addMenu('D&ata')
        self.dataMenu.addAction(self.openStatePointAction)
        self.updateDataMenu()

        # Edit Menu
        self.applyAction = QAction("&Apply Changes", self)
        self.applyAction.setShortcut("Ctrl+Return")
        self.applyAction.setToolTip('Generate new view with changes applied')
        self.applyAction.setStatusTip('Generate new view with changes applied')
        self.applyAction.triggered.connect(self.applyChanges)

        self.undoAction = QAction('&Undo', self)
        self.undoAction.setShortcut(QtGui.QKeySequence.Undo)
        self.undoAction.setToolTip('Undo')
        self.undoAction.setStatusTip('Undo last plot view change')
        self.undoAction.setDisabled(True)
        self.undoAction.triggered.connect(self.undo)

        self.redoAction = QAction('&Redo', self)
        self.redoAction.setDisabled(True)
        self.redoAction.setToolTip('Redo')
        self.redoAction.setStatusTip('Redo last plot view change')
        self.redoAction.setShortcut(QtGui.QKeySequence.Redo)
        self.redoAction.triggered.connect(self.redo)

        self.restoreAction = QAction("&Restore Default Plot", self)
        self.restoreAction.setShortcut("Ctrl+R")
        self.restoreAction.setToolTip('Restore to default plot view')
        self.restoreAction.setStatusTip('Restore to default plot view')
        self.restoreAction.triggered.connect(self.restoreDefault)

        self.editMenu = self.mainMenu.addMenu('&Edit')
        self.editMenu.addAction(self.applyAction)
        self.editMenu.addSeparator()
        self.editMenu.addAction(self.undoAction)
        self.editMenu.addAction(self.redoAction)
        self.editMenu.addSeparator()
        self.editMenu.addAction(self.restoreAction)
        self.editMenu.addSeparator()
        self.editMenu.aboutToShow.connect(self.updateEditMenu)

        # Edit -> Basis Menu
        self.xyAction = QAction('&xy  ', self)
        self.xyAction.setCheckable(True)
        self.xyAction.setShortcut('Alt+X')
        self.xyAction.setToolTip('Change to xy basis')
        self.xyAction.setStatusTip('Change to xy basis')
        xy_connector = partial(self.editBasis, 'xy', apply=True)
        self.xyAction.triggered.connect(xy_connector)

        self.xzAction = QAction('x&z  ', self)
        self.xzAction.setCheckable(True)
        self.xzAction.setShortcut('Alt+Z')
        self.xzAction.setToolTip('Change to xz basis')
        self.xzAction.setStatusTip('Change to xz basis')
        xz_connector = partial(self.editBasis, 'xz', apply=True)
        self.xzAction.triggered.connect(xz_connector)

        self.yzAction = QAction('&yz  ', self)
        self.yzAction.setCheckable(True)
        self.yzAction.setShortcut('Alt+Y')
        self.yzAction.setToolTip('Change to yz basis')
        self.yzAction.setStatusTip('Change to yz basis')
        yz_connector = partial(self.editBasis, 'yz', apply=True)
        self.yzAction.triggered.connect(yz_connector)

        self.basisMenu = self.editMenu.addMenu('&Basis')
        self.basisMenu.addAction(self.xyAction)
        self.basisMenu.addAction(self.xzAction)
        self.basisMenu.addAction(self.yzAction)
        self.basisMenu.aboutToShow.connect(self.updateBasisMenu)

        # Edit -> Color By Menu
        self.cellAction = QAction('&Cell', self)
        self.cellAction.setCheckable(True)
        self.cellAction.setShortcut('Alt+C')
        self.cellAction.setToolTip('Color by cell')
        self.cellAction.setStatusTip('Color plot by cell')
        cell_connector = partial(self.editColorBy, 'cell', apply=True)
        self.cellAction.triggered.connect(cell_connector)

        self.materialAction = QAction('&Material', self)
        self.materialAction.setCheckable(True)
        self.materialAction.setShortcut('Alt+M')
        self.materialAction.setToolTip('Color by material')
        self.materialAction.setStatusTip('Color plot by material')
        material_connector = partial(self.editColorBy, 'material', apply=True)
        self.materialAction.triggered.connect(material_connector)

        self.temperatureAction = QAction('&Temperature', self)
        self.temperatureAction.setCheckable(True)
        self.temperatureAction.setShortcut('Alt+T')
        self.temperatureAction.setToolTip('Color by temperature')
        self.temperatureAction.setStatusTip('Color plot by temperature')
        temp_connector = partial(self.editColorBy, 'temperature', apply=True)
        self.temperatureAction.triggered.connect(temp_connector)

        self.densityAction = QAction('&Density', self)
        self.densityAction.setCheckable(True)
        self.densityAction.setShortcut('Alt+D')
        self.densityAction.setToolTip('Color by density')
        self.densityAction.setStatusTip('Color plot by density')
        density_connector = partial(self.editColorBy, 'density', apply=True)
        self.densityAction.triggered.connect(density_connector)

        self.colorbyMenu = self.editMenu.addMenu('&Color By')
        self.colorbyMenu.addAction(self.cellAction)
        self.colorbyMenu.addAction(self.materialAction)
        self.colorbyMenu.addAction(self.temperatureAction)
        self.colorbyMenu.addAction(self.densityAction)

        self.colorbyMenu.aboutToShow.connect(self.updateColorbyMenu)

        self.editMenu.addSeparator()

        # Edit -> Other Options
        self.maskingAction = QAction('Enable &Masking', self)
        self.maskingAction.setShortcut('Ctrl+M')
        self.maskingAction.setCheckable(True)
        self.maskingAction.setToolTip('Toggle masking')
        self.maskingAction.setStatusTip('Toggle whether masking is enabled')
        masking_connector = partial(self.toggleMasking, apply=True)
        self.maskingAction.toggled.connect(masking_connector)
        self.editMenu.addAction(self.maskingAction)

        self.highlightingAct = QAction('Enable High&lighting', self)
        self.highlightingAct.setShortcut('Ctrl+L')
        self.highlightingAct.setCheckable(True)
        self.highlightingAct.setToolTip('Toggle highlighting')
        self.highlightingAct.setStatusTip('Toggle whether '
                                          'highlighting is enabled')
        highlight_connector = partial(self.toggleHighlighting, apply=True)
        self.highlightingAct.toggled.connect(highlight_connector)
        self.editMenu.addAction(self.highlightingAct)

        self.overlapAct = QAction('Enable Overlap Coloring', self)
        self.overlapAct.setShortcut('Ctrl+P')
        self.overlapAct.setCheckable(True)
        self.overlapAct.setToolTip('Toggle overlapping regions')
        self.overlapAct.setStatusTip('Toggle display of overlapping '
                                     'regions when enabled')
        overlap_connector = partial(self.toggleOverlaps, apply=True)
        self.overlapAct.toggled.connect(overlap_connector)
        self.editMenu.addAction(self.overlapAct)

        self.outlineAct = QAction('Enable Domain Outlines', self)
        self.outlineAct.setShortcut('Ctrl+U')
        self.outlineAct.setCheckable(True)
        self.outlineAct.setToolTip('Display Cell/Material Boundaries')
        self.outlineAct.setStatusTip('Toggle display of domain '
                                     'outlines when enabled')
        outline_connector = partial(self.toggleOutlines, apply=True)
        self.outlineAct.toggled.connect(outline_connector)
        self.editMenu.addAction(self.outlineAct)

        # View Menu
        self.dockAction = QAction('Hide &Dock', self)
        self.dockAction.setShortcut("Ctrl+D")
        self.dockAction.setToolTip('Toggle dock visibility')
        self.dockAction.setStatusTip('Toggle dock visibility')
        self.dockAction.triggered.connect(self.toggleDockView)

        self.tallyDockAction = QAction('Tally &Dock', self)
        self.tallyDockAction.setShortcut("Ctrl+T")
        self.tallyDockAction.setToolTip('Toggle tally dock visibility')
        self.tallyDockAction.setStatusTip('Toggle tally dock visibility')
        self.tallyDockAction.triggered.connect(self.toggleTallyDockView)

        self.zoomAction = QAction('&Zoom...', self)
        self.zoomAction.setShortcut('Alt+Shift+Z')
        self.zoomAction.setToolTip('Edit zoom factor')
        self.zoomAction.setStatusTip('Edit zoom factor')
        self.zoomAction.triggered.connect(self.editZoomAct)

        self.viewMenu = self.mainMenu.addMenu('&View')
        self.viewMenu.addAction(self.dockAction)
        self.viewMenu.addAction(self.tallyDockAction)
        self.viewMenu.addSeparator()
        self.viewMenu.addAction(self.zoomAction)
        self.viewMenu.aboutToShow.connect(self.updateViewMenu)

        # Window Menu
        self.mainWindowAction = QAction('&Main Window', self)
        self.mainWindowAction.setCheckable(True)
        self.mainWindowAction.setToolTip('Bring main window to front')
        self.mainWindowAction.setStatusTip('Bring main window to front')
        self.mainWindowAction.triggered.connect(self.showMainWindow)

        self.colorDialogAction = QAction('Color &Options', self)
        self.colorDialogAction.setCheckable(True)
        self.colorDialogAction.setToolTip('Bring Color Dialog to front')
        self.colorDialogAction.setStatusTip('Bring Color Dialog to front')
        self.colorDialogAction.triggered.connect(self.showColorDialog)

        # Keyboard Shortcuts Overlay
        self.keyboardShortcutsAction = QAction("&Keyboard Shortcuts...", self)
        self.keyboardShortcutsAction.setShortcut("?")
        self.keyboardShortcutsAction.setToolTip("Display Keyboard Shortcuts")
        self.keyboardShortcutsAction.setStatusTip("Display Keyboard Shortcuts")
        self.keyboardShortcutsAction.triggered.connect(self.toggleShortcuts)

        self.windowMenu = self.mainMenu.addMenu('&Window')
        self.windowMenu.addAction(self.mainWindowAction)
        self.windowMenu.addAction(self.colorDialogAction)
        self.windowMenu.addAction(self.keyboardShortcutsAction)
        self.windowMenu.aboutToShow.connect(self.updateWindowMenu)

    def updateEditMenu(self):
        changed = self.model.currentView != self.model.defaultView
        self.restoreAction.setDisabled(not changed)

        self.maskingAction.setChecked(self.model.currentView.masking)
        self.highlightingAct.setChecked(self.model.currentView.highlighting)
        self.outlineAct.setChecked(self.model.currentView.outlines)

        num_previous_views = len(self.model.previousViews)
        self.undoAction.setText('&Undo ({})'.format(num_previous_views))
        num_subsequent_views = len(self.model.subsequentViews)
        self.redoAction.setText('&Redo ({})'.format(num_subsequent_views))

    def updateBasisMenu(self):
        self.xyAction.setChecked(self.model.currentView.basis == 'xy')
        self.xzAction.setChecked(self.model.currentView.basis == 'xz')
        self.yzAction.setChecked(self.model.currentView.basis == 'yz')

    def updateColorbyMenu(self):
        cv = self.model.currentView
        self.cellAction.setChecked(cv.colorby == 'cell')
        self.materialAction.setChecked(cv.colorby == 'material')
        self.temperatureAction.setChecked(cv.colorby == 'temperature')
        self.densityAction.setChecked(cv.colorby == 'density')

    def updateViewMenu(self):
        if self.dock.isVisible():
            self.dockAction.setText('Hide &Dock')
        else:
            self.dockAction.setText('Show &Dock')

    def updateWindowMenu(self):
        self.colorDialogAction.setChecked(self.colorDialog.isActiveWindow())
        self.mainWindowAction.setChecked(self.isActiveWindow())

    # Menu and shared methods
    def loadModel(self, reload=False):
        if reload:
            self.resetModels()
        else:
            # create new plot model
            self.model = PlotModel()
            self.restoreModelSettings()
            # update plot and model settings
            self.updateRelativeBases()

        self.cellsModel = DomainTableModel(self.model.activeView.cells)
        self.materialsModel = DomainTableModel(self.model.activeView.materials)

        if reload:
            loader_thread = Thread(target=_openmcReload)
            loader_thread.start()
            while loader_thread.is_alive():
                self.statusBar().showMessage("Reloading model...")
                QApplication.processEvents()

            self.plotIm.model = self.model
            self.applyChanges()

    def saveImage(self):
        filename, ext = QFileDialog.getSaveFileName(self, "Save Plot Image",
                                                    "untitled",
                                                    "Images (*.png)")
        if filename:
            if "." not in filename:
                filename += ".png"
            self.plotIm.figure.savefig(filename, transparent=True)
            self.statusBar().showMessage('Plot Image Saved', 5000)

    def saveView(self):
        filename, ext = QFileDialog.getSaveFileName(self, "Save View Settings",
                                                    "untitled",
                                                    "View Settings (*.pltvw)")
        if filename:
            if "." not in filename:
                filename += ".pltvw"

            saved = {
                'version': self.model.version,
                'current': self.model.currentView
            }
            with open(filename, 'wb') as file:
                pickle.dump(saved, file)

    def openView(self):
        filename, ext = QFileDialog.getOpenFileName(self, "Open View Settings",
                                                    ".", "*.pltvw")
        if filename:
            try:
                with open(filename, 'rb') as file:
                    saved = pickle.load(file)
            except Exception:
                message = 'Error loading plot settings'
                saved = {'version': None, 'current': None}
            if saved['version'] == self.model.version:
                self.model.activeView = saved['current']
                self.dock.updateDock()
                self.colorDialog.updateDialogValues()
                self.applyChanges()
                message = '{} settings loaded'.format(filename)
            else:
                message = 'Error loading plot settings. Incompatible model.'
            self.statusBar().showMessage(message, 5000)

    def openStatePoint(self):
        # check for an alread-open statepoint
        if self.model.statepoint:
            msg_box = QMessageBox()
            msg_box.setText("Please close the current statepoint file before "
                            "opening a new one.")
            msg_box.setIcon(QMessageBox.Information)
            msg_box.setStandardButtons(QMessageBox.Ok)
            msg_box.exec_()
            return
        filename, ext = QFileDialog.getOpenFileName(self, "Open StatePoint",
                                                    ".", "statepoint*.h5")
        if filename:
            try:
                self.model.openStatePoint(filename)
                message = 'Opened statepoint file: {}'
            except (FileNotFoundError, OSError):
                message = 'Error opening statepoint file: {}'
                msg_box = QMessageBox()
                msg = "Could open statepoint file: \n\n {} \n"
                msg_box.setText(msg.format(filename))
                msg_box.setIcon(QMessageBox.Warning)
                msg_box.setStandardButtons(QMessageBox.Ok)
                msg_box.exec_()
            finally:
                self.statusBar().showMessage(message.format(filename), 5000)
            self.updateDataMenu()
            self.tallyDock.update()

    def closeStatePoint(self):
        # remove the statepoint object and update the data menu
        filename = self.model.statepoint.filename
        self.model.statepoint = None
        self.model.currentView.selectedTally = None
        self.model.activeView.selectedTally = None

        msg = "Closed statepoint file {}".format(filename)
        self.statusBar().showMessage(msg)
        self.updateDataMenu()
        self.tallyDock.selectTally()
        self.tallyDock.update()
        self.plotIm.updatePixmap()

    def updateDataMenu(self):
        if self.model.statepoint:
            self.closeStatePointAction = QAction("&Close statepoint", self)
            self.closeStatePointAction.setToolTip("Close current statepoint")
            self.closeStatePointAction.triggered.connect(self.closeStatePoint)
            self.dataMenu.addAction(self.closeStatePointAction)
        elif hasattr(self, "closeStatePointAction"):
            self.dataMenu.removeAction(self.closeStatePointAction)

    def applyChanges(self):
        if self.model.activeView != self.model.currentView:
            self.statusBar().showMessage('Generating Plot...')
            QApplication.processEvents()
            self.model.storeCurrent()
            self.model.subsequentViews = []
            self.plotIm.generatePixmap()
            self.resetModels()
            self.showCurrentView()
            self.statusBar().showMessage('')
        else:
            self.statusBar().showMessage('No changes to apply.', 3000)

    def undo(self):
        self.statusBar().showMessage('Generating Plot...')
        QApplication.processEvents()

        self.model.undo()
        self.resetModels()
        self.showCurrentView()
        self.dock.updateDock()
        self.colorDialog.updateDialogValues()

        if not self.model.previousViews:
            self.undoAction.setDisabled(True)
        self.redoAction.setDisabled(False)
        self.statusBar().showMessage('')

    def redo(self):
        self.statusBar().showMessage('Generating Plot...')
        QApplication.processEvents()

        self.model.redo()
        self.resetModels()
        self.showCurrentView()
        self.dock.updateDock()
        self.colorDialog.updateDialogValues()

        if not self.model.subsequentViews:
            self.redoAction.setDisabled(True)
        self.undoAction.setDisabled(False)
        self.statusBar().showMessage('')

    def restoreDefault(self):
        if self.model.currentView != self.model.defaultView:

            self.statusBar().showMessage('Generating Plot...')
            QApplication.processEvents()

            self.model.storeCurrent()
            self.model.activeView.adopt_plotbase(self.model.defaultView)
            self.plotIm.generatePixmap()
            self.resetModels()
            self.showCurrentView()
            self.dock.updateDock()
            self.colorDialog.updateDialogValues()

            self.model.subsequentViews = []
            self.statusBar().showMessage('')

    def editBasis(self, basis, apply=False):
        self.model.activeView.basis = basis
        self.dock.updateBasis()
        if apply:
            self.applyChanges()

    def editColorBy(self, domain_kind, apply=False):
        self.model.activeView.colorby = domain_kind
        self.dock.updateColorBy()
        self.colorDialog.updateColorBy()
        if apply:
            self.applyChanges()

    def toggleOverlaps(self, state, apply=False):
        self.model.activeView.color_overlaps = bool(state)
        self.colorDialog.updateOverlap()
        if apply:
            self.applyChanges()

    def editColorMap(self, colormap_name, property_type, apply=False):
        self.model.activeView.colormaps[property_type] = colormap_name
        self.plotIm.updateColorMap(colormap_name, property_type)
        self.colorDialog.updateColorMaps()
        if apply:
            self.applyChanges()

    def editColorbarMin(self, min_val, property_type, apply=False):
        av = self.model.activeView
        current = av.user_minmax[property_type]
        av.user_minmax[property_type] = (min_val, current[1])
        self.colorDialog.updateColorMinMax()
        self.plotIm.updateColorMinMax(property_type)
        if apply:
            self.applyChanges()

    def editColorbarMax(self, max_val, property_type, apply=False):
        av = self.model.activeView
        current = av.user_minmax[property_type]
        av.user_minmax[property_type] = (current[0], max_val)
        self.colorDialog.updateColorMinMax()
        self.plotIm.updateColorMinMax(property_type)
        if apply:
            self.applyChanges()

    def toggleColorbarScale(self, state, property, apply=False):
        av = self.model.activeView
        av.color_scale_log[property] = bool(state)
        # temporary, should be resolved diferently in the future
        cv = self.model.currentView
        cv.color_scale_log[property] = bool(state)
        self.plotIm.updateColorbarScale()
        if apply:
            self.applyChanges()

    def toggleUserMinMax(self, state, property):
        av = self.model.activeView
        av.use_custom_minmax[property] = bool(state)
        if av.user_minmax[property] == (0.0, 0.0):
            av.user_minmax[property] = copy.copy(av.data_minmax[property])
        self.plotIm.updateColorMinMax('temperature')
        self.plotIm.updateColorMinMax('density')
        self.colorDialog.updateColorMinMax()

    def toggleDataIndicatorCheckBox(self, state, property, apply=False):
        av = self.model.activeView
        av.data_indicator_enabled[property] = bool(state)

        cv = self.model.currentView
        cv.data_indicator_enabled[property] = bool(state)

        self.plotIm.updateDataIndicatorVisibility()
        if apply:
            self.applyChanges()

    def toggleMasking(self, state, apply=False):
        self.model.activeView.masking = bool(state)
        self.colorDialog.updateMasking()
        if apply:
            self.applyChanges()

    def toggleHighlighting(self, state, apply=False):
        self.model.activeView.highlighting = bool(state)
        self.colorDialog.updateHighlighting()
        if apply:
            self.applyChanges()

    def toggleDockView(self):
        if self.dock.isVisible():
            self.dock.hide()
            if not self.isMaximized() and not self.dock.isFloating():
                self.resize(self.width() - self.dock.width(), self.height())
        else:
            self.dock.setVisible(True)
            if not self.isMaximized() and not self.dock.isFloating():
                self.resize(self.width() + self.dock.width(), self.height())
        self.resizePixmap()
        self.showMainWindow()

    def toggleTallyDockView(self):
        if self.tallyDock.isVisible():
            self.tallyDock.hide()
            if not self.isMaximized() and not self.tallyDock.isFloating():
                self.resize(self.width() - self.tallyDock.width(),
                            self.height())
        else:
            self.tallyDock.setVisible(True)
            if not self.isMaximized() and not self.tallyDock.isFloating():
                self.resize(self.width() + self.tallyDock.width(),
                            self.height())
        self.resizePixmap()
        self.showMainWindow()

    def editZoomAct(self):
        percent, ok = QInputDialog.getInt(self, "Edit Zoom", "Zoom Percent:",
                                          self.dock.zoomBox.value(), 25, 2000)
        if ok:
            self.dock.zoomBox.setValue(percent)

    def editZoom(self, value):
        self.zoom = value
        self.resizePixmap()
        self.dock.zoomBox.setValue(value)

    def showMainWindow(self):
        self.raise_()
        self.activateWindow()

    def showColorDialog(self):
        self.colorDialog.show()
        self.colorDialog.raise_()
        self.colorDialog.activateWindow()

    def showExportDialog(self):
        self.exportDataDialog.show()
        self.exportDataDialog.raise_()
        self.exportDataDialog.activateWindow()

    # Dock methods:

    def editSingleOrigin(self, value, dimension):
        self.model.activeView.origin[dimension] = value

    def editPlotAlpha(self, value):
        self.model.activeView.domainAlpha = value

    def editPlotVisibility(self, value):
        self.model.activeView.domainVisible = bool(value)

    def toggleOutlines(self, value, apply=False):
        self.model.activeView.outlines = bool(value)
        self.dock.updateOutlines()

        if apply:
            self.applyChanges()

    def editWidth(self, value):
        self.model.activeView.width = value
        self.onRatioChange()
        self.dock.updateWidth()

    def editHeight(self, value):
        self.model.activeView.height = value
        self.onRatioChange()
        self.dock.updateHeight()

    def toggleAspectLock(self, state):
        self.model.activeView.aspectLock = bool(state)
        self.onRatioChange()
        self.dock.updateAspectLock()

    def editVRes(self, value):
        self.model.activeView.v_res = value
        self.dock.updateVRes()

    def editHRes(self, value):
        self.model.activeView.h_res = value
        self.onRatioChange()
        self.dock.updateHRes()

    # Color dialog methods:

    def editMaskingColor(self):
        current_color = self.model.activeView.maskBackground
        dlg = QColorDialog(self)

        dlg.setCurrentColor(QtGui.QColor.fromRgb(*current_color))
        if dlg.exec_():
            new_color = dlg.currentColor().getRgb()[:3]
            self.model.activeView.maskBackground = new_color
            self.colorDialog.updateMaskingColor()

    def editHighlightColor(self):
        current_color = self.model.activeView.highlightBackground
        dlg = QColorDialog(self)

        dlg.setCurrentColor(QtGui.QColor.fromRgb(*current_color))
        if dlg.exec_():
            new_color = dlg.currentColor().getRgb()[:3]
            self.model.activeView.highlightBackground = new_color
            self.colorDialog.updateHighlightColor()

    def editAlpha(self, value):
        self.model.activeView.highlightAlpha = value

    def editSeed(self, value):
        self.model.activeView.highlightSeed = value

    def editOverlapColor(self, apply=False):
        current_color = self.model.activeView.overlap_color
        dlg = QColorDialog(self)
        dlg.setCurrentColor(QtGui.QColor.fromRgb(*current_color))
        if dlg.exec_():
            new_color = dlg.currentColor().getRgb()[:3]
            self.model.activeView.overlap_color = new_color
            self.colorDialog.updateOverlapColor()

        if apply:
            self.applyChanges()

    def editBackgroundColor(self, apply=False):
        current_color = self.model.activeView.domainBackground
        dlg = QColorDialog(self)

        dlg.setCurrentColor(QtGui.QColor.fromRgb(*current_color))
        if dlg.exec_():
            new_color = dlg.currentColor().getRgb()[:3]
            self.model.activeView.domainBackground = new_color
            self.colorDialog.updateBackgroundColor()

        if apply:
            self.applyChanges()

    def resetColors(self):
        self.model.resetColors()
        self.colorDialog.updateDialogValues()
        self.applyChanges()

    # Tally dock methods

    def editSelectedTally(self, event):
        av = self.model.activeView

        if event is None or event == "None" or event == "":
            av.selectedTally = None
        else:
            av.selectedTally = int(event.split()[1])
        self.tallyDock.selectTally(event)

    def editTallyValue(self, event):
        av = self.model.activeView
        av.tallyValue = event

    def updateScores(self, state):
        self.tallyDock.updateScores()

    def updateNuclides(self, state):
        self.tallyDock.updateNuclides()

    def toggleTallyVisibility(self, state, apply=False):
        av = self.model.activeView
        av.tallyDataVisible = bool(state)
        if apply:
            self.applyChanges()

    def toggleTallyLogScale(self, state, apply=False):
        av = self.model.activeView
        av.tallyDataLogScale = bool(state)
        if apply:
            self.applyChanges()

    def toggleTallyMaskZero(self, state):
        av = self.model.activeView
        av.tallyMaskZeroValues = bool(state)

    def editTallyAlpha(self, value, apply=False):
        av = self.model.activeView
        av.tallyDataAlpha = value
        if apply:
            self.applyChanges()

    def toggleTallyContours(self, state):
        av = self.model.activeView
        av.tallyContours = bool(state)

    def editTallyContourLevels(self, value):
        av = self.model.activeView
        av.tallyContourLevels = value

    def toggleTallyDataIndicator(self, state, apply=False):
        av = self.model.activeView
        av.tallyDataIndicator = bool(state)
        if apply:
            self.applyChanges()

    def toggleTallyDataClip(self, state):
        av = self.model.activeView
        av.clipTallyData = bool(state)

    def toggleTallyDataUserMinMax(self, state, apply=False):
        av = self.model.activeView
        av.tallyDataUserMinMax = bool(state)
        self.tallyDock.tallyColorForm.setMinMaxEnabled(bool(state))
        if apply:
            self.applyChanges()

    def editTallyDataMin(self, value, apply=False):
        av = self.model.activeView
        av.tallyDataMin = value
        if apply:
            self.applyChanges()

    def editTallyDataMax(self, value, apply=False):
        av = self.model.activeView
        av.tallyDataMax = value
        if apply:
            self.applyChanges()

    def editTallyDataColormap(self, cmap, apply=False):
        av = self.model.activeView
        av.tallyDataColormap = cmap
        if apply:
            self.applyChanges()

    def updateTallyMinMax(self):
        self.tallyDock.updateMinMax()

    # Plot image methods
    def editPlotOrigin(self, xOr, yOr, zOr=None, apply=False):
        if zOr is not None:
            self.model.activeView.origin = [xOr, yOr, zOr]
        else:
            origin = [None, None, None]
            origin[self.xBasis] = xOr
            origin[self.yBasis] = yOr
            origin[self.zBasis] = self.model.activeView.origin[self.zBasis]
            self.model.activeView.origin = origin

        self.dock.updateOrigin()

        if apply:
            self.applyChanges()

    def revertDockControls(self):
        self.dock.revertToCurrent()

    def editDomainColor(self, kind, id):
        if kind == 'Cell':
            domain = self.model.activeView.cells
        else:
            domain = self.model.activeView.materials

        current_color = domain[id].color
        dlg = QColorDialog(self)

        if isinstance(current_color, tuple):
            dlg.setCurrentColor(QtGui.QColor.fromRgb(*current_color))
        elif isinstance(current_color, str):
            current_color = openmc.plots._SVG_COLORS[current_color]
            dlg.setCurrentColor(QtGui.QColor.fromRgb(*current_color))
        if dlg.exec_():
            new_color = dlg.currentColor().getRgb()[:3]
            domain[id].color = new_color

        self.applyChanges()

    def toggleDomainMask(self, state, kind, id):
        if kind == 'Cell':
            domain = self.model.activeView.cells
        else:
            domain = self.model.activeView.materials

        domain[id].masked = bool(state)
        self.applyChanges()

    def toggleDomainHighlight(self, state, kind, id):
        if kind == 'Cell':
            domain = self.model.activeView.cells
        else:
            domain = self.model.activeView.materials

        domain[id].highlight = bool(state)
        self.applyChanges()

    # Helper methods:

    def restoreWindowSettings(self):
        settings = QtCore.QSettings()

        self.resize(settings.value("mainWindow/Size", QtCore.QSize(800, 600)))
        self.move(
            settings.value("mainWindow/Position", QtCore.QPoint(100, 100)))
        self.restoreState(settings.value("mainWindow/State"))

        self.colorDialog.resize(
            settings.value("colorDialog/Size", QtCore.QSize(400, 500)))
        self.colorDialog.move(
            settings.value("colorDialog/Position", QtCore.QPoint(600, 200)))
        is_visible = settings.value("colorDialog/Visible", 0)
        # some versions of PySide will return None rather than the default value
        if is_visible is None:
            is_visible = False
        else:
            is_visible = bool(int(is_visible))

        self.colorDialog.setVisible(is_visible)

    def restoreModelSettings(self):
        if os.path.isfile("plot_settings.pkl"):

            with open('plot_settings.pkl', 'rb') as file:
                model = pickle.load(file)

            # do not replace model if the version is out of date
            if model.version != self.model.version:
                print("WARNING: previous plot settings are for a different "
                      "version of the GUI. They will be ignored.")
                wrn_msg = "Existing version: {}, Current GUI version: {}"
                print(wrn_msg.format(model.version, self.model.version))
                return

            try:
                self.model.statepoint = model.statepoint
            except OSError:
                msg_box = QMessageBox()
                msg = "Could open statepoint file: \n\n {} \n"
                msg_box.setText(msg.format(self.model.statepoint.filename))
                msg_box.setIcon(QMessageBox.Warning)
                msg_box.setStandardButtons(QMessageBox.Ok)
                msg_box.exec_()
                self.model.statepoint = None

            self.model.currentView = model.currentView
            self.model.activeView = copy.deepcopy(model.currentView)
            self.model.previousViews = model.previousViews
            self.model.subsequentViews = model.subsequentViews

    def resetModels(self):
        self.cellsModel = DomainTableModel(self.model.activeView.cells)
        self.materialsModel = DomainTableModel(self.model.activeView.materials)
        self.cellsModel.beginResetModel()
        self.cellsModel.endResetModel()
        self.materialsModel.beginResetModel()
        self.materialsModel.endResetModel()
        self.colorDialog.updateDomainTabs()

    def showCurrentView(self):
        self.updateScale()
        self.updateRelativeBases()
        self.plotIm.updatePixmap()

        if self.model.previousViews:
            self.undoAction.setDisabled(False)
        if self.model.subsequentViews:
            self.redoAction.setDisabled(False)
        else:
            self.redoAction.setDisabled(True)

        self.adjustWindow()

    def updateScale(self):
        cv = self.model.currentView
        self.scale = (cv.h_res / cv.width, cv.v_res / cv.height)

    def updateRelativeBases(self):
        cv = self.model.currentView
        self.xBasis = 0 if cv.basis[0] == 'x' else 1
        self.yBasis = 1 if cv.basis[1] == 'y' else 2
        self.zBasis = 3 - (self.xBasis + self.yBasis)

    def adjustWindow(self):
        self.setMaximumSize(self.screen.width(), self.screen.height())

    def onRatioChange(self):
        av = self.model.activeView
        if av.aspectLock:
            ratio = av.width / max(av.height, .001)
            av.v_res = int(av.h_res / ratio)
            self.dock.updateVRes()

    def showCoords(self, xPlotPos, yPlotPos):
        cv = self.model.currentView
        if cv.basis == 'xy':
            coords = ("({}, {}, {})".format(round(xPlotPos, 2),
                                            round(yPlotPos, 2),
                                            round(cv.origin[2], 2)))
        elif cv.basis == 'xz':
            coords = ("({}, {}, {})".format(round(xPlotPos, 2),
                                            round(cv.origin[1], 2),
                                            round(yPlotPos, 2)))
        else:
            coords = ("({}, {}, {})".format(round(cv.origin[0], 2),
                                            round(xPlotPos, 2),
                                            round(yPlotPos, 2)))
        self.coord_label.setText('{}'.format(coords))

    def resizePixmap(self):
        self.plotIm._resize()
        self.plotIm.adjustSize()

    def moveEvent(self, event):
        self.adjustWindow()

    def resizeEvent(self, event):
        self.plotIm._resize()
        self.adjustWindow()
        self.updateScale()
        if self.shortcutOverlay.isVisible():
            self.shortcutOverlay.resize(self.width(), self.height())

    def closeEvent(self, event):
        settings = QtCore.QSettings()
        settings.setValue("mainWindow/Size", self.size())
        settings.setValue("mainWindow/Position", self.pos())
        settings.setValue("mainWindow/State", self.saveState())

        settings.setValue("colorDialog/Size", self.colorDialog.size())
        settings.setValue("colorDialog/Position", self.colorDialog.pos())
        visible = int(self.colorDialog.isVisible())
        settings.setValue("colorDialog/Visible", visible)

        openmc.lib.finalize()

        self.saveSettings()

    def saveSettings(self):

        if len(self.model.previousViews) > 10:
            self.model.previousViews = self.model.previousViews[-10:]
        if len(self.model.subsequentViews) > 10:
            self.model.subsequentViews = self.model.subsequentViews[-10:]

        with open('plot_settings.pkl', 'wb') as file:
            if self.model.statepoint:
                self.model.statepoint.close()
            pickle.dump(self.model, file)

    def exportTallyData(self):
        # show export tool dialog
        self.showExportDialog()
Beispiel #7
0
    def __init__(self, test_name: str, reference_image: QPixmap,
                 generated_image: QPixmap):
        super(ApprovalDialog, self).__init__()

        self.setWindowTitle(test_name)

        main_layout = QVBoxLayout(self)

        ref_image = QLabel()
        ref_image.setPixmap(reference_image)

        gen_image = QLabel()
        gen_image.setPixmap(generated_image)

        scroll_area = QScrollArea()

        self.layout().addWidget(scroll_area)

        screen_width, screen_height = QGuiApplication.primaryScreen().size(
        ).toTuple()

        if reference_image.width() + gen_image.width() >= screen_width:
            self.image_layout = QVBoxLayout()
        else:
            self.image_layout = QHBoxLayout()

        self.image_layout.addStretch()
        self.image_layout.addWidget(ref_image)
        self.image_layout.addWidget(gen_image)
        self.image_layout.addStretch()

        scroll_area.setWidget(QWidget())
        scroll_area.setWidgetResizable(True)

        scroll_area.widget().setSizePolicy(QSizePolicy.MinimumExpanding,
                                           QSizePolicy.MinimumExpanding)
        scroll_area.setSizePolicy(QSizePolicy.MinimumExpanding,
                                  QSizePolicy.Maximum)

        scroll_area.widget().setLayout(self.image_layout)

        def _sizeHint():
            orig_size = scroll_area.widget().sizeHint()

            orig_size.setHeight(orig_size.height() + 20)
            orig_size.setWidth(orig_size.width() + 20)

            if orig_size.width() > screen_width - 20:
                orig_size.setWidth(screen_width - 20)

            if orig_size.height() > screen_height - 20:
                orig_size.setHeight(screen_height - 20)

            return orig_size

        scroll_area.sizeHint = _sizeHint

        button_box = QDialogButtonBox()

        button_box.addButton(
            "Reject", QDialogButtonBox.RejectRole).clicked.connect(self.reject)
        button_box.addButton(QDialogButtonBox.Ignore).clicked.connect(
            self._on_ignore)

        button_box.addButton("Accept as new Reference",
                             QDialogButtonBox.ApplyRole).clicked.connect(
                                 self._on_overwrite)

        main_layout.addWidget(scroll_area)
        main_layout.addWidget(button_box, alignment=Qt.AlignCenter)