Exemplo n.º 1
0
        def _make_dock(name, widgets=[], tab_with=None):
            dock = QDockWidget(name)

            dock.setAllowedAreas(Qt.LeftDockWidgetArea
                                 | Qt.RightDockWidgetArea)

            dock_widget = QWidget()
            layout = QVBoxLayout()

            for widget in widgets:
                layout.addWidget(widget)

            dock_widget.setLayout(layout)
            dock.setWidget(dock_widget)

            key = f"hide {name.lower()} dock"
            if key in prefs and prefs[key]:
                dock.hide()

            self.addDockWidget(Qt.RightDockWidgetArea, dock)
            self.viewMenu.addAction(dock.toggleViewAction())

            if tab_with is not None:
                self.tabifyDockWidget(tab_with, dock)

            return layout
Exemplo n.º 2
0
 def init_tree_main_widget(self):
     """
     Inicijalizuje layout main window-a, layout strane, tree, tabove, dugmadi i labele
     """
     self.tabs = QTabWidget()
     self.tabs.setTabsClosable(True)
     self.tabs.tabCloseRequested.connect(self.closeMyTab)
     leftDock = QDockWidget()
     leftDock.setFixedWidth(250)
     leftDock.setWidget(self.tabs)
     leftDock.setAllowedAreas(Qt.LeftDockWidgetArea)
     leftDock.setFeatures(QDockWidget.NoDockWidgetFeatures)
     layout = QGridLayout()
     self.page = QVBoxLayout()
     self.central = QWidget()
     self.central.setFixedSize(730, 700)
     self.central.setLayout(self.page)
     self.central.setStyleSheet("background-color:white")
     self.LeftButton = QPushButton()
     self.PageLabel = QLabel()
     self.PageLabel.setAlignment(Qt.AlignCenter)
     self.PageLabel.setText("Nema trenutno otvorene strane")
     self.RightButton = QPushButton()
     self.LeftButton.setFixedSize(30, 30)
     self.RightButton.setFixedSize(30, 30)
     self.LeftButton.setIcon(QIcon("src/lbutton.png"))
     self.RightButton.setIcon(QIcon("src/rbutton.png"))
     layout.addWidget(self.central, 0, 0, 1, 5)
     layout.addWidget(self.LeftButton, 1, 1, 2, 1)
     layout.addWidget(self.PageLabel, 1, 2, 2, 1)
     layout.addWidget(self.RightButton, 1, 3, 2, 1)
     tmpWidget = QWidget()
     tmpWidget.setLayout(layout)
     self.setCentralWidget(tmpWidget)
     self.addDockWidget(Qt.LeftDockWidgetArea, leftDock)
Exemplo n.º 3
0
    def createDockWidget(self):
        """
        Criar dock
        """
        dock_wgt = QDockWidget()
        dock_wgt.setWindowTitle("Dock Exemplo")
        dock_wgt.setAllowedAreas(Qt.AllDockWidgetAreas)

        dock_wgt.setWidget(QTextEdit())

        self.addDockWidget(Qt.LeftDockWidgetArea, dock_wgt)
Exemplo n.º 4
0
    def __init__(self):
        super(MainWindowV2, self).__init__()

        # self.model - вместо трёх отдельных lists (arr_sheet, arr_Dash, arr_Tab)
        # в QStandardItemModel будем append-ить
        # один из трёх классов, наследующихся от QStandardItem

        self.model = QStandardItemModel(self)

        self.tabWidget = QTabWidget(self)
        self.tabWidget.setTabPosition(QTabWidget.South)
        self.tabWidget.setTabsClosable(True)  # можно премещать вкладки
        self.tabWidget.setMovable(
            True)  # но пока по нажатию ничего не происходит

        self.tabWidget.tabCloseRequested.connect(self.closeTabFromTabWidget)

        self.setCentralWidget(self.tabWidget)

        # summerfield "Rapid GUI Programming" ch6
        logDockWidget = QDockWidget("Tabs", self)
        logDockWidget.setTitleBarWidget(QWidget())

        # logDockWidget = QDockWidget(self)
        logDockWidget.setObjectName("LogDockWidget")
        logDockWidget.setAllowedAreas(Qt.LeftDockWidgetArea
                                      | Qt.RightDockWidgetArea)
        logDockWidget.setFeatures(QDockWidget.DockWidgetMovable)
        # logDockWidget.setMinimumSize(100, 0)

        self.navListView = QListView()
        self.navListView.setModel(self.model)

        logDockWidget.setWidget(self.navListView)
        self.addDockWidget(Qt.LeftDockWidgetArea, logDockWidget)

        self.createActions()

        tabsToolbar = self.addToolBar("Tabs")
        tabsToolbar.setObjectName("tabsToolBar")
        tabsToolbar.setToolButtonStyle(Qt.ToolButtonTextUnderIcon)

        for action in [
                self.newChartAction, self.newTableAction,
                self.newDashboardAction, self.closeTabAction
        ]:
            tabsToolbar.addAction(action)

        self.navListView.selectionModel().selectionChanged.connect(
            self.setActiveTab)

        # easy start
        self.newChart()
Exemplo n.º 5
0
 def createDockWindows(self):
     # Info
     dock = QDockWidget('Details', self)
     dock.setObjectName("DETAILS")
     dock.setAllowedAreas(Qt.AllDockWidgetAreas)
     self.detailsWidget = InfoPanelWidget(self)
     self.project_widget.treeCurrentItemChanged.connect(
         self.detailsWidget.setItem)
     self.project_widget.treeCurrentItemChanged.connect(
         self.currentItemChanged)
     dock.setWidget(self.detailsWidget)
     self.addDockWidget(Qt.RightDockWidgetArea, dock)
     self.viewMenu.addAction(dock.toggleViewAction())
Exemplo n.º 6
0
 def createDockWindows(self):
     dock = QDockWidget("Współrzędne Geograficzne Panelu: ", self)
     s = ephem.Sun()
     s.compute(epoch=ephem.now())
     dock.setAllowedAreas(Qt.LeftDockWidgetArea | Qt.RightDockWidgetArea)
     self.multiWidget3 = QWidget()
     font5 = QFont("Arial", 12)
     font6 = QFont("Arial", 17)
     self.vLayout3 = QGridLayout()
     self.result = QLabel(self.latlong)
     self.latitude = QLabel('Szerokość')
     self.longitude = QLabel('Długość')
     self.dateandtime = QLabel('Data i Czas')
     self.solarpanelcor = QLabel('WSPÓŁRZĘDNE PANELU SŁONECZNEGO: ')
     self.latitudeEdit = QLineEdit()
     self.latitudeEdit.setFixedHeight(28)
     self.latitudeEdit.setFixedWidth(386)
     self.latitudeEdit.setStatusTip(
         "Wprowadzona szerokość powinna być w stopniach dziesiętnych (np.: 51.100000)"
     )
     self.longitudeEdit = QLineEdit()
     self.longitudeEdit.setFixedHeight(28)
     self.longitudeEdit.setFixedWidth(386)
     self.longitudeEdit.setStatusTip(
         "Wprowadzona długość powinna być w stopniach dziesiętnych (np.: 17.03333)"
     )
     self.dateandtimeEdit = QLineEdit()
     self.dateandtimeEdit.setFixedHeight(28)
     self.dateandtimeEdit.setFixedWidth(386)
     self.dateandtimeEdit.setStatusTip(
         "Wprowadzona data powinna być w formacie: rok/mies/dzień<spacja>godz:min:sek (np.: 2022/12/4 8:12:7)"
     )
     self.button = QPushButton('Wylicz współrzędne / Przerwij liczenie',
                               self)
     self.button.clicked.connect(self.handleButton3)
     self.latitude.setFont(font5)
     self.longitude.setFont(font5)
     self.dateandtime.setFont(font5)
     self.button.setFont(font6)
     self.button.setStatusTip("Rozpoczyna Obliczenia")
     self.vLayout3.addWidget(self.latitude)
     self.vLayout3.addWidget(self.latitudeEdit)
     self.vLayout3.addWidget(self.longitude)
     self.vLayout3.addWidget(self.longitudeEdit)
     self.vLayout3.addWidget(self.dateandtime)
     self.vLayout3.addWidget(self.dateandtimeEdit)
     self.vLayout3.addWidget(self.button)
     self.multiWidget3.setLayout(self.vLayout3)
     dock.setWidget(self.multiWidget3)
     self.addDockWidget(Qt.RightDockWidgetArea, dock)
Exemplo n.º 7
0
    def createFileList(self):
        self.files = QListWidget()
        self.files.setSelectionMode(QAbstractItemView.SingleSelection)
        self.files.setSortingEnabled(False)
        self.files.itemSelectionChanged.connect(self.onSelect)
        self.files.itemDoubleClicked.connect(self.onToggle)
        self.files.setEnabled(False)

        dock = QDockWidget("Images", self)
        dock.setAllowedAreas(Qt.LeftDockWidgetArea)
        dock.setFixedWidth(320)
        dock.setFeatures(QDockWidget.NoDockWidgetFeatures)
        dock.setWidget(self.files)
        self.addDockWidget(Qt.LeftDockWidgetArea, dock)
Exemplo n.º 8
0
def make_dock_widget(parent,
                     name,
                     view,
                     area,
                     allowedAreas,
                     features=QDockWidget.DockWidgetClosable
                     | QDockWidget.DockWidgetMovable
                     | QDockWidget.DockWidgetFloatable):
    dock = QDockWidget(name, parent)
    dock.setObjectName(name)
    dock.setAllowedAreas(allowedAreas)
    dock.setFeatures(features)
    dock.setWidget(view)
    parent.addDockWidget(area, dock)
    return dock
Exemplo n.º 9
0
    def gui_init(self):

        pref_menu = menu_item(u'Preferences')
        ref_menu = menu_item(u'Reference')
        gen_menu = menu_item(u'Device')

        gen_menu.addChildren(menu_item(u'GRBL', self.showGrblDialog))

        file_menu = menu_item(self.qapp.tr(u'File'))
        file_menu.addChildren(menu_item(u'New', self.newProject),
                              menu_item(u'Open', self.openProject),
                              menu_item(u'Import...', self.openFile),
                              menu_item(u'Save', self.saveProject),
                              menu_item(u'Exit', self.qapp.exit))

        pref_menu.addChildren(gen_menu, menu_item(u'Settings'))

        ref_menu.addChildren(menu_item(u'Help'),
                             menu_item(u'About', self.showAbout))

        self.mainDialog.addMenuItems(file_menu, pref_menu, ref_menu)

        self.cnc_indicator = work_indicator(u'CNC')
        self.mainDialog.addStatusObj(self.cnc_indicator)

        self.progressBar = QProgressBar()
        self.progressBar.setTextVisible(True)
        self.progressBar.setAlignment(Qt.AlignCenter)
        self.progressBar.minimum = 1
        self.progressBar.maximum = 100
        self.mainDialog.addPermanentStatusObj(self.progressBar)
        '''Инициализациия таблицы содержания'''
        treeDock = QDockWidget(self.qapp.tr(u'Project tree'), self.mainDialog)
        treeDock.setAllowedAreas(Qt.LeftDockWidgetArea
                                 | Qt.RightDockWidgetArea)
        self.mainDialog.addDockWidget(Qt.LeftDockWidgetArea, treeDock)

        self.mainnodes = [Node("Samples"), Node("Other")]
        self.project_tree = ProjectTree(self, self.mainnodes)
        self.project_tree.doubleClicked.connect(self.on_tree_clicked)
        treeDock.setWidget(self.project_tree)
        '''Инициализациия центра'''
        jog = JogWidget(app=self.qapp,
                        axies=self.controls,
                        pinAcions=self.pinAcions,
                        motorActions=self.send_to_grbl)
        self.mainDialog.addToCenter(jog)
        jog.raise_()
Exemplo n.º 10
0
 def __create_dock_and_menu(self, name, view):
     # TODO: Check if name exists in docks
     if name in self.docks:
         dock_widget = self.docks[name]
         self.window.removeDockWidget(dock_widget)
     else:
         dock_widget = QDockWidget(name)
         new_action = QAction(name, self)
         new_action.setStatusTip("Create a new file")
         new_action.setCheckable(True)
         new_action.setChecked(True)
         new_action.triggered.connect(
             lambda state: self.switch_view(state, self.widgets[name]))
         self.view_menu.addAction(new_action)
         self.docks[name] = dock_widget
         self.widgets[name].dock = dock_widget
     dock_widget.setWidget(view)
     dock_widget.setAllowedAreas(Qt.AllDockWidgetAreas)
     self.window.addDockWidget(Qt.RightDockWidgetArea, dock_widget)
     dock_widget.raise_()
Exemplo n.º 11
0
    def createDockWindows(self):
        dock = QDockWidget("Customers", self)
        dock.setAllowedAreas(Qt.LeftDockWidgetArea | Qt.RightDockWidgetArea)
        self.customerList = QListWidget(dock)
        self.customerList.addItems(
            ("John Doe, Harmony Enterprises, 12 Lakeside, Ambleton",
             "Jane Doe, Memorabilia, 23 Watersedge, Beaton",
             "Tammy Shea, Tiblanka, 38 Sea Views, Carlton",
             "Tim Sheen, Caraba Gifts, 48 Ocean Way, Deal",
             "Sol Harvey, Chicos Coffee, 53 New Springs, Eccleston",
             "Sally Hobart, Tiroli Tea, 67 Long River, Fedula"))
        dock.setWidget(self.customerList)
        self.addDockWidget(Qt.RightDockWidgetArea, dock)
        self.viewMenu.addAction(dock.toggleViewAction())

        dock = QDockWidget("Paragraphs", self)
        self.paragraphsList = QListWidget(dock)
        self.paragraphsList.addItems(
            ("Thank you for your payment which we have received today.",
             "Your order has been dispatched and should be with you within "
             "28 days.",
             "We have dispatched those items that were in stock. The rest of "
             "your order will be dispatched once all the remaining items "
             "have arrived at our warehouse. No additional shipping "
             "charges will be made.",
             "You made a small overpayment (less than $5) which we will keep "
             "on account for you, or return at your request.",
             "You made a small underpayment (less than $1), but we have sent "
             "your order anyway. We'll add this underpayment to your next "
             "bill.",
             "Unfortunately you did not send enough money. Please remit an "
             "additional $. Your order will be dispatched as soon as the "
             "complete amount has been received.",
             "You made an overpayment (more than $5). Do you wish to buy more "
             "items, or should we return the excess to you?"))
        dock.setWidget(self.paragraphsList)
        self.addDockWidget(Qt.RightDockWidgetArea, dock)
        self.viewMenu.addAction(dock.toggleViewAction())

        self.customerList.currentTextChanged.connect(self.insertCustomer)
        self.paragraphsList.currentTextChanged.connect(self.addParagraph)
Exemplo n.º 12
0
    def createDockWindows(self):
        # Scale
        self.dynamic_scale_dock = QDockWidget("Dynamic Scale", self)
        self.dynamic_scale_dock.setObjectName("SCALE")
        self.dynamic_scale_dock.setAllowedAreas(Qt.LeftDockWidgetArea
                                                | Qt.RightDockWidgetArea
                                                | Qt.TopDockWidgetArea)
        self.scaleWidget = ScaleWidget(self,
                                       scales_model=self.scales_model,
                                       cmap_model=self.cmaps)
        self.dynamic_scale_dock.setWidget(self.scaleWidget)
        self.addDockWidget(Qt.RightDockWidgetArea, self.dynamic_scale_dock)
        self.viewMenu.addAction(self.dynamic_scale_dock.toggleViewAction())
        self.dynamic_scale_dock.setFloating(True)
        self.dynamic_scale_dock.hide()

        #radial profiles
        dock = QDockWidget("Radial Profile Fit", self)
        dock.setObjectName("RADIAL_PROFILE_IRAF")
        dock.setAllowedAreas(Qt.LeftDockWidgetArea | Qt.RightDockWidgetArea
                             | Qt.TopDockWidgetArea)
        self.radial_profile_iraf_widget = IRAFRadialProfileWidget(
            self.fits_image.data)
        dock.setWidget(self.radial_profile_iraf_widget)
        self.addDockWidget(Qt.RightDockWidgetArea, dock)
        self.viewMenu.addAction(dock.toggleViewAction())
        self.dockRadialFit = dock

        dock = QDockWidget("Radial Profile Curve", self)
        dock.setObjectName("RADIAL_PROFILE")
        dock.setAllowedAreas(Qt.LeftDockWidgetArea | Qt.RightDockWidgetArea
                             | Qt.TopDockWidgetArea)
        self.radial_profile_widget = RadialProfileWidget(self.fits_image.data)
        dock.setWidget(self.radial_profile_widget)
        self.addDockWidget(Qt.RightDockWidgetArea, dock)
        self.viewMenu.addAction(dock.toggleViewAction())
        dock.hide()

        #info panel
        dock = QDockWidget("Info", self)
        dock.setObjectName("INFO_PANEL")
        dock.setAllowedAreas(Qt.LeftDockWidgetArea | Qt.RightDockWidgetArea
                             | Qt.TopDockWidgetArea)
        self.info_widget = InfoWidget(self)
        dock.setWidget(self.info_widget)
        self.addDockWidget(Qt.TopDockWidgetArea, dock)
        self.viewMenu.addAction(dock.toggleViewAction())

        # FITS headers
        dock = QDockWidget("FITS header", self)
        dock.setObjectName("FTIS_DATA")
        dock.setAllowedAreas(Qt.LeftDockWidgetArea | Qt.RightDockWidgetArea
                             | Qt.TopDockWidgetArea)
        self.addDockWidget(Qt.TopDockWidgetArea, dock)
        self.viewMenu.addAction(dock.toggleViewAction())
        self.headerWidget = HeaderTableWidget(self)
        self.headerWidget.setColumnCount(2)
        self.headerWidget.setHorizontalHeaderItem(0, QTableWidgetItem("KEY"))
        self.headerWidget.setHorizontalHeaderItem(1, QTableWidgetItem("VALUE"))
        self.headerWidget.horizontalHeader().setStretchLastSection(1)
        self.headerWidget.setEditTriggers(
            QtWidgets.QTableWidget.NoEditTriggers)
        self.headerWidget.clearFocus()
        dock.setWidget(self.headerWidget)

        # full
        dock = QDockWidget("Full view", self)
        dock.setObjectName("FULL_VIEW")
        dock.setAllowedAreas(Qt.LeftDockWidgetArea | Qt.RightDockWidgetArea
                             | Qt.TopDockWidgetArea)
        self.full_view_widget = FullViewWidget(self.fits_image)
        self.full_view_widget.fits_image.set_scale_model(self.scales_model)
        self.full_view_widget.fits_image.set_cmap_model(self.cmaps)
        dock.setWidget(self.full_view_widget)
        self.addDockWidget(Qt.TopDockWidgetArea, dock)
        self.viewMenu.addAction(dock.toggleViewAction())
        # zoom
        dock = QDockWidget("Zoom view", self)
        dock.setObjectName("ZOOM_VIEW")
        dock.setAllowedAreas(Qt.LeftDockWidgetArea | Qt.RightDockWidgetArea
                             | Qt.TopDockWidgetArea)
        self.zoom_view_widget = ZoomViewWidget(self.fits_image)
        self.zoom_view_widget.fits_image.set_scale_model(self.scales_model)
        self.zoom_view_widget.fits_image.set_cmap_model(self.cmaps)
        dock.setWidget(self.zoom_view_widget)
        self.addDockWidget(Qt.TopDockWidgetArea, dock)
        self.viewMenu.addAction(dock.toggleViewAction())

        # fileSelector
        dock = QDockWidget("Directory view", self)
        dock.setObjectName("DIRECTORY_VIEW")
        dock.setAllowedAreas(Qt.LeftDockWidgetArea)
        self.file_widget = FileSystemWidget(self)
        dock.setWidget(self.file_widget)
        self.addDockWidget(Qt.LeftDockWidgetArea, dock)
        self.viewMenu.addAction(dock.toggleViewAction())

        self.viewMenu.addSeparator()
Exemplo n.º 13
0
class MainWindow(QMainWindow):
    def __init__(self, tedaCommandLine):
        super().__init__()
        self.tedaCommandLine = tedaCommandLine
        self.cmaps = ColorMaps()
        self.combobox = QComboBox()
        self.filename = None
        self.isMousePressed = False
        self.isCmdPressed = False
        self.cursor_coords = CoordinatesModel()
        self.scales_model = ScalesModel()
        fig = Figure(figsize=(14, 10))
        fig.tight_layout()
        self.fits_image = FitsPlotter(figure=fig)
        fig.subplots_adjust(left=0,
                            bottom=0.001,
                            right=1,
                            top=1,
                            wspace=None,
                            hspace=None)

        self.fits_image = FitsPlotterFitsFile(figure=fig,
                                              cmap_model=self.cmaps,
                                              scale_model=self.scales_model)
        self.central_widget = FigureCanvas(fig)
        self.setCentralWidget(self.central_widget)

        self.current_x_coord = 0
        self.current_y_coord = 0

        self.fullWidgetXcord = 0
        self.fullWidgetYcord = 0
        self.centralWidgetcordX = 0
        self.centralWidgetcordY = 0

        self.painterComponent = PainterComponent(self.fits_image)
        # self.painterComponent.startMovingEvents(self.central_widget)
        self.painterComponent.setCanvas(self.central_widget)
        self.scanObject = ScanToolbar(self)
        self.createActions()
        self.createMenus()
        self.createToolBars()
        self.createStatusBar()
        self.createDockWindows()
        if not self.tedaCommandLine.ignoreSettings:
            self.scaleWidget.readSlidersValues()
        # self.defineButtonsActions()
        self.setWindowTitle("TeDa")

        self.painterComponent.observe(
            lambda change: self.onAutoCenterChange(change), ['auto_center'])

        self.readWindowSettings()
        self.readAppState()

        self.updateHeaderData()
        self.dragging = draggingComponent.Dragging(
            widget=self, scale_widget=self.scaleWidget)
        self.activeLinearAdjustmentByMouseMovement()

        # Observing here may be to late for values loaded from settings e.g. via readAppState
        self.painterComponent.observe(
            lambda change: self.onCenterCircleChange(change),
            ['ccenter_x', 'ccenter_y'])
        self.painterComponent.observe(
            lambda change: self.onCenterCircleRadiusChange(change),
            ['cradius'])
        self.fits_image.observe(lambda change: self.onMouseMoveOnImage(change),
                                ['mouse_xdata', 'mouse_ydata'])
        # self.cmaps.observe(lambda change: self.on_colormap_change(change))
        self.full_view_widget.painterComponent.observe(
            lambda change: self.onRectangleInWidgetMove(change),
            ['viewX', 'viewY'])
        self.painterComponent.observe(
            lambda change: self.movingCentralWidget(change),
            ['movingViewX', 'movingViewY'])
        self.fits_image.observe(lambda change: self.onMouseZoomOnImage(change),
                                ['viewBounaries_versionno'])

        # open last fits
        try:
            self.openLastFits()
        except FileNotFoundError:
            print('Błąd w odczycie lub brak ostatio wczytanego pliku')

    def closeEvent(self, event: PySide2.QtGui.QCloseEvent):
        self.writeAppState()
        self.writeWindowSettings()
        if not self.tedaCommandLine.ignoreSettings:
            self.scaleWidget.writeSlidersValues()
        super().closeEvent(event)

    def keyPressEvent(self, e):
        if e.key() == Qt.Key_Delete:
            self.deleteSelected()
        if e.key() == Qt.Key_R:
            action = self.dockRadialFit.toggleViewAction()
            if not action.isChecked():
                action.trigger()
            if (self.cursor_coords.img_x != 0 and self.cursor_coords.img_x !=
                    None) and (self.cursor_coords.img_y != 0
                               and self.cursor_coords.img_y != None):
                self.painterComponent.add(self.cursor_coords.img_x,
                                          self.cursor_coords.img_y,
                                          type="circleCenter")
                self.painterComponent.paintAllShapes(
                    self.central_widget.figure.axes[0])
        if e.key() == Qt.Key_Control:
            self.isCmdPressed = True

    def keyReleaseEvent(self, event: PySide2.QtGui.QKeyEvent):
        if event.key() == Qt.Key_Control:
            self.isCmdPressed = False

    def canvasMousePressEvent(self, event):
        self.isMousePressed = not self.isMousePressed

    def mouseMoveEventOnCanvas(self, event):
        if self.isCmdPressed:
            if self.isMousePressed:
                self.dragging.mouseMoveEvent(event)

    def print_(self):
        document = self.textEdit.document()
        printer = QPrinter()

        dlg = QPrintDialog(printer, self)
        if dlg.exec_() != QDialog.Accepted:
            return

        document.print_(printer)

        self.statusBar().showMessage("Ready", 2000)

    def open_dialog(self):
        fileName, _ = QFileDialog.getOpenFileName(self, "Open Image", ".",
                                                  "Fits files (*.fits)")
        if fileName:
            self.open_fits(fileName)

    def save(self):
        filename, _ = QFileDialog.getSaveFileName(self, "Choose a file name",
                                                  '.', "HTML (*.html *.htm)")
        if not filename:
            return

        file = QFile(filename)
        if not file.open(QFile.WriteOnly | QFile.Text):
            QMessageBox.warning(
                self, "Dock Widgets",
                "Cannot write file %s:\n%s." % (filename, file.errorString()))
            return

        out = QTextStream(file)
        QApplication.setOverrideCursor(Qt.WaitCursor)
        out << self.textEdit.toHtml()
        QApplication.restoreOverrideCursor()

        self.statusBar().showMessage("Saved '%s'" % filename, 2000)

    def save_dialog(self):
        figure = self.central_widget.figure
        filetypes = figure.canvas.get_supported_filetypes_grouped()
        filterstr = ';;'.join([
            k + ' (' + ' '.join(['*.' + ext for ext in v]) + ')'
            for k, v in filetypes.items()
        ])
        dialog = QFileDialog.getSaveFileName(
            self, "Save Image As...",
            os.path.splitext(self.filename)[0], filterstr)
        if dialog[0] != "":
            try:
                self.central_widget.figure.savefig(dialog[0])
            except ValueError:
                print("Unsupported format")

    def open_fits(self, fileName):
        """Opens specified FITS file and loads it to user interface"""
        self.fits_image.set_file(fileName)
        self.filename = fileName
        self.cursor_coords.set_wcs_from_fits(
            self.fits_image.header
        )  # TODO: one up and extract and set wcs in fits_image before plot
        self.fits_image.set_wcs(self.cursor_coords.wcs)

        self.fits_image.plot()

        self.radial_profile_widget.set_data(self.fits_image.data)
        self.radial_profile_iraf_widget.set_data(self.fits_image.data)

        self.updateHeaderData()

        self.zoom_view_widget.updateFits(self.fits_image)
        self.full_view_widget.updateFits(self.fits_image)
        self.saveLastFits()

    def saveLastFits(self):
        if self.tedaCommandLine.ignoreSettings:
            return
        settings = QSettings()
        settings.beginGroup("Files")
        settings.setValue("lastFile", self.filename)
        settings.endGroup()

    def openLastFits(self):
        if (self.tedaCommandLine.openFile is None):
            if self.tedaCommandLine.ignoreSettings:
                return
            settings = QSettings()
            settings.beginGroup("Files")
            filename = settings.value("lastFile")
            settings.endGroup()
        else:
            filename = self.tedaCommandLine.openFile
        if filename:
            self.open_fits(filename)

    def readAppState(self):
        if self.tedaCommandLine.ignoreSettings:
            return
        settings = QSettings()
        settings.beginGroup("WCS")
        self.wcsSexAct.setChecked(bool(settings.value("sexagesimal", True)))
        self.wcsGridAct.setChecked(bool(settings.value("grid", False)))
        settings.endGroup()
        settings.beginGroup("paint")
        self.painterComponent.auto_center = bool(
            settings.value("auto_center", True))
        settings.endGroup()

    def writeAppState(self):
        if self.tedaCommandLine.ignoreSettings:
            return
        settings = QSettings()
        settings.beginGroup("WCS")
        settings.setValue("sexagesimal", self.wcsSexAct.isChecked())
        settings.setValue("grid", self.wcsGridAct.isChecked())
        settings.endGroup()
        settings.beginGroup("paint")
        settings.setValue("auto_center", self.painterComponent.auto_center)
        settings.endGroup()

    def undo(self):
        document = self.textEdit.document()
        document.undo()

    def insertCustomer(self, customer):
        if not customer:
            return

    def addParagraph(self, paragraph):
        if not paragraph:
            return

    def about(self):
        QMessageBox.about(
            self, "TeDa FITS Viewer", f"TeDa FITS Viewer {__version__} <br/>"
            "Authors: <ul> "
            "<li>Michał Brodniak</li>"
            "<li>Konrad Górski</li>"
            "<li>Mikołaj Kałuszyński</li>"
            "<li>Edward Lis</li>"
            "<li>Grzegorz Mroczkowski</li>"
            "</ul>"
            "Created by <a href='https://akond.com'>Akond Lab</a> for The "
            "<a href='https://araucaria.camk.edu.pl'>Araucaria Project</a><br/>"
            "Licence: MIT <br/>"
            "3rd party work used: "
            "<a href='https://material.io/resources/icons/'> Google Material Icons</a>, "
            "<a href='https://www.astropy.org'> AstroPy</a>, "
            "<a href='https://doc.qt.io/qtforpython/'> Qt5/PySide2</a>, "
            "<a href='https://www.scipy.org'> SciPy</a>, and other..."
            "<br/><br/>"
            "Visit the <a href='https://github.com/majkelx/teda'>project's GitHub  page</a> for help"
            " and the issue tracker")

    def on_console_show(self):
        console.show(ax=self.fits_image.ax,
                     window=self,
                     data=self.fits_image.data,
                     header=self.fits_image.header,
                     wcs=self.cursor_coords.wcs)

    def on_sex_toggle(self):
        print('sex toggled to :', self.wcsSexAct.isChecked())
        self.cursor_coords.wcs_sexagesimal = self.wcsSexAct.isChecked()

    def on_grid_toggle(self):
        self.fits_image.plot_grid = self.wcsGridAct.isChecked()

    def createActions(self):
        # ico1 = QPixmap('/Users/mka/projects/astro/teda/icons/png.png')
        # self.openAct = QAction(ico1, "&Open", self, shortcut=QKeySequence.Open, statusTip="Open FITS file", triggered=self.open)
        self.openAct = QAction(IconFactory.getIcon('note_add'),
                               "&Open",
                               self,
                               shortcut=QKeySequence.Open,
                               statusTip="Open FITS file",
                               triggered=self.open_dialog)
        self.saveAct = QAction(IconFactory.getIcon('save'),
                               "&Save",
                               self,
                               shortcut=QKeySequence.Save,
                               statusTip="Save FITS view",
                               triggered=self.save_dialog)
        self.quitAct = QAction("&Quit",
                               self,
                               shortcut="Ctrl+Q",
                               statusTip="Quit the application",
                               triggered=self.close)
        self.aboutAct = QAction("&About",
                                self,
                                statusTip="Show the application's About box",
                                triggered=self.about)
        self.aboutQtAct = QAction("About &Qt",
                                  self,
                                  statusTip="Show the Qt library's About box",
                                  triggered=QApplication.instance().aboutQt)

        self.qtConsoleAct = QAction('Python Console',
                                    self,
                                    statusTip="Open IPython console window",
                                    triggered=self.on_console_show)

        self.wcsSexAct = QAction(
            'Sexagesimal',
            self,
            statusTip=
            "Format WCS coordinates as sexagesimal (RA in hour angle) instead of decimal deg"
        )
        self.wcsSexAct.toggled.connect(self.on_sex_toggle)
        self.wcsSexAct.setCheckable(True)

        self.wcsGridAct = QAction(
            'Show Grid',
            self,
            statusTip="Overlay WCS coordinates grid over image",
        )
        self.wcsGridAct.setCheckable(True)
        self.wcsGridAct.toggled.connect(self.on_grid_toggle)

        self.prevHDUAct = QAction(IconFactory.getIcon('skip_previous'),
                                  'Prev HDU',
                                  self,
                                  statusTip="Previous HDU",
                                  triggered=self.prevHDU)
        self.nextHDUAct = QAction(IconFactory.getIcon('skip_next'),
                                  'Next HDU',
                                  self,
                                  statusTip="Next HDU",
                                  triggered=self.nextHDU)

        self.zoom4Act = QAction(IconFactory.getIcon("x4"),
                                'Zoom ×4',
                                self,
                                statusTip="Zoom ×4",
                                triggered=self.setZoomButton4)
        self.zoom2Act = QAction(IconFactory.getIcon("x2"),
                                'Zoom ×2',
                                self,
                                statusTip="Zoom ×2",
                                triggered=self.setZoomButton2)
        self.zoomHomeAct = QAction(IconFactory.getIcon('home'),
                                   'Home',
                                   self,
                                   statusTip="Reset zoom an position",
                                   triggered=self.setZoomButtonHome)
        self.zoom05Act = QAction(IconFactory.getIcon("1-2"),
                                 'Zoom 1/2',
                                 self,
                                 statusTip="Zoom 1/2",
                                 triggered=self.setZoomButton05)
        self.zoom025Act = QAction(IconFactory.getIcon("1-4"),
                                  'Zoom 1/4',
                                  self,
                                  statusTip="Zoom 1/4",
                                  triggered=self.setZoomButton025)

        self.panningAct = QAction(IconFactory.getIcon('panning'),
                                  'Panning',
                                  self,
                                  statusTip="Panning",
                                  triggered=self.changePanningStatus)
        self.circleAct = QAction(IconFactory.getIcon('circle'),
                                 'Add Region',
                                 self,
                                 statusTip="Add Region",
                                 triggered=self.changeAddCircleStatus)
        self.centerCircleAct = QAction(
            IconFactory.getIcon('add_circle_outline'),
            'Radial profile',
            self,
            statusTip="Radial profile with gaussoide fit [R]-key",
            triggered=self.changeAddCenterCircleStatus)
        self.autoCenterAct = QAction(
            'Auto Center',
            self,
            statusTip="Automatically center cursor on star centroid",
            triggered=self.changeAutoCenter)
        self.deleteAct = QAction(IconFactory.getIcon('delete_forever'),
                                 'Delete selected',
                                 self,
                                 statusTip="Delete selected [Del]-key",
                                 triggered=self.deleteSelected)

        self.slidersAct = QAction(
            IconFactory.getIcon('slider'),
            'Dynamic Scale Sliders',
            self,
            statusTip='Show/Hide Dynamic Scale',
            triggered=self.dynamicScaleDockWidgetTriggerActions)

        self.panningAct.setCheckable(True)
        self.panningAct.setChecked(True)
        self.circleAct.setCheckable(True)
        self.autoCenterAct.setCheckable(True)
        self.autoCenterAct.setChecked(self.painterComponent.auto_center)
        self.centerCircleAct.setCheckable(True)

    def createMenus(self):
        self.fileMenu = self.menuBar().addMenu("&File")
        self.fileMenu.addAction(self.openAct)
        self.fileMenu.addAction(self.scanObject.scanAct)
        self.fileMenu.addAction(self.scanObject.stopAct)
        self.fileMenu.addAction(self.scanObject.pauseAct)
        self.fileMenu.addAction(self.scanObject.resumeAct)
        self.fileMenu.addAction(self.scanObject.autopauseAct)
        self.fileMenu.addAction(self.scanObject.disabledautopauseAct)
        self.fileMenu.addAction(self.saveAct)
        self.fileMenu.addSeparator()
        self.fileMenu.addAction(self.quitAct)

        self.editMenu = self.menuBar().addMenu("&Edit")
        self.editMenu.addAction(self.panningAct)
        self.editMenu.addAction(self.circleAct)
        self.editMenu.addAction(self.centerCircleAct)
        self.editMenu.addSeparator()
        self.editMenu.addAction(self.autoCenterAct)
        self.editMenu.addSeparator()
        self.editMenu.addAction(self.deleteAct)

        self.hduMenu = self.menuBar().addMenu("HDU")
        self.hduMenu.addAction(self.prevHDUAct)
        self.hduMenu.addAction(self.nextHDUAct)
        self.hduMenu.addSeparator()

        self.zoomMenu = self.menuBar().addMenu("Zoom")
        self.zoomMenu.addAction(self.zoom4Act)
        self.zoomMenu.addAction(self.zoom2Act)
        self.zoomMenu.addAction(self.zoomHomeAct)
        self.zoomMenu.addAction(self.zoom05Act)
        self.zoomMenu.addAction(self.zoom025Act)

        self.WcsMenu = self.menuBar().addMenu("W&CS")
        self.WcsMenu.addAction(self.wcsSexAct)
        self.WcsMenu.addSeparator()
        self.WcsMenu.addAction(self.wcsGridAct)

        self.viewMenu = self.menuBar().addMenu("&View")
        self.viewMenu.addAction(self.qtConsoleAct)
        self.viewMenu.addSeparator()

        self.menuBar().addSeparator()

        self.helpMenu = self.menuBar().addMenu("&Help")
        self.helpMenu.addAction(self.aboutAct)
        self.helpMenu.addAction(self.aboutQtAct)

    def createToolBars(self):
        self.fileToolBar = self.addToolBar("File Toolbar")
        self.fileToolBar.addAction(self.openAct)
        self.fileToolBar.addAction(self.saveAct)

        self.hduToolBar = self.addToolBar("HDU Toolbar")
        self.hduToolBar.addAction(self.prevHDUAct)
        self.hduToolBar.addAction(self.nextHDUAct)

        self.scanToolBar = self.addToolBar("Scan Toolbar")
        self.scanToolBar.addAction(self.scanObject.scanAct)
        self.scanToolBar.addAction(self.scanObject.stopAct)
        self.scanToolBar.addAction(self.scanObject.pauseAct)
        self.scanToolBar.addAction(self.scanObject.resumeAct)
        self.scanToolBar.addAction(self.scanObject.autopauseAct)
        self.scanToolBar.addAction(self.scanObject.disabledautopauseAct)
        self.scanToolBar.hide()

        # self.infoToolBar = self.addToolBar("Info Toolbar")
        # self.mouse_x_label = QLabel('100.1')
        # self.mouse_y_label = QLabel('100.145')
        # self.infoToolBar.addWidget(QLabel('image x:'))
        # self.infoToolBar.addWidget(self.mouse_x_label)
        # self.infoToolBar.addWidget(QLabel('y:'))
        # self.infoToolBar.addWidget(self.mouse_y_label)
        # self.infoToolBar.hide()

        self.zoomToolBar = self.addToolBar("Zoom Toolbar")
        self.zoomToolBar.addAction(self.zoom4Act)
        self.zoomToolBar.addAction(self.zoom2Act)
        self.zoomToolBar.addAction(self.zoomHomeAct)
        self.zoomToolBar.addAction(self.zoom05Act)
        self.zoomToolBar.addAction(self.zoom025Act)

        self.mouseActionToolBar = self.addToolBar("Mouse Task Toolbar")
        self.mouseActionToolBar.addAction(self.panningAct)
        self.mouseActionToolBar.addAction(self.circleAct)
        self.mouseActionToolBar.addAction(self.centerCircleAct)
        self.mouseActionToolBar.addAction(self.deleteAct)

        self.sliderToolBar = self.addToolBar("Slider Toolbar")
        self.slidersAct.setChecked(True)
        self.sliderToolBar.addAction(self.slidersAct)

        self.viewMenu.addAction(self.fileToolBar.toggleViewAction())
        self.viewMenu.addAction(self.hduToolBar.toggleViewAction())
        self.viewMenu.addAction(self.scanToolBar.toggleViewAction())
        # self.viewMenu.addAction(self.infoToolBar.toggleViewAction())
        self.viewMenu.addAction(self.zoomToolBar.toggleViewAction())
        self.viewMenu.addAction(self.mouseActionToolBar.toggleViewAction())
        self.viewMenu.addAction(self.sliderToolBar.toggleViewAction())
        self.viewMenu.addSeparator()

    def nextHDU(self):
        self.fits_image.changeHDU(True, 1)
        self.updateHeaderData()

    def prevHDU(self):
        self.fits_image.changeHDU(True, -1)
        self.updateHeaderData()

    def updateHeaderData(self):
        self.headerWidget.setHeader()
        self.prevHDUAct.setEnabled(self.fits_image._huds is not None
                                   and self.fits_image.hdu != 0)
        self.nextHDUAct.setEnabled(
            self.fits_image._huds is not None
            and self.fits_image.hdu != len(self.fits_image._huds) - 1)

    def setZoomButton4(self):
        self.setZoomButton(4, False)

    def setZoomButton2(self):
        self.setZoomButton(2, False)

    def setZoomButtonHome(self):
        self.setZoomButton(1, True)

    def setZoomButton05(self):
        self.setZoomButton(0.5, False)

    def setZoomButton025(self):
        self.setZoomButton(0.25, False)

    def setZoomButton(self, zoom: float, reset: bool):
        if self.fits_image.ax != None:
            self.fits_image.setZoom(zoom, reset)
            self.full_view_widget.updateMiniatureShape(self.fits_image.viewX,
                                                       self.fits_image.viewY,
                                                       self.fits_image.viewW,
                                                       self.fits_image.viewH)

    def changePanningStatus(self):
        if self.panningAct.isChecked():
            self.toogleOffRegionButtons()
            self.panningAct.toggle()
            self.painterComponent.stopPainting(self.central_widget)
            self.painterComponent.startMovingEvents(self.central_widget)
        else:
            self.painterComponent.stopPainting(self.central_widget)
            self.painterComponent.stopMovingEvents(self.central_widget)

    def changeAddCircleStatus(self):
        if self.circleAct.isChecked():
            self.toogleOffRegionButtons()
            self.circleAct.toggle()
            self.painterComponent.startPainting(self.central_widget, "circle")
        else:
            self.painterComponent.stopPainting(self.central_widget)
            self.painterComponent.startMovingEvents(self.central_widget)
            self.panningAct.toggle()

    def changeAddCenterCircleStatus(self):
        if self.centerCircleAct.isChecked():
            self.toogleOffRegionButtons()
            self.centerCircleAct.toggle()
            self.painterComponent.startPainting(self.central_widget,
                                                "circleCenter")
        else:
            self.painterComponent.stopPainting(self.central_widget)
            self.painterComponent.startMovingEvents(self.central_widget)
            self.panningAct.toggle()

    def changeAutoCenter(self):
        self.painterComponent.auto_center = self.autoCenterAct.isChecked()

    def deleteSelected(self):
        self.painterComponent.deleteSelectedShapes(
            self.central_widget.figure.axes[0])

    def toogleOffRegionButtons(self):
        if self.panningAct.isChecked():
            self.panningAct.toggle()
        if self.circleAct.isChecked():
            self.circleAct.toggle()
        if self.centerCircleAct.isChecked():
            self.centerCircleAct.toggle()
        self.painterComponent.stopPainting(self.central_widget)

    def createStatusBar(self):
        self.statusBar().showMessage("Ready")

    def createDockWindows(self):
        # Scale
        self.dynamic_scale_dock = QDockWidget("Dynamic Scale", self)
        self.dynamic_scale_dock.setObjectName("SCALE")
        self.dynamic_scale_dock.setAllowedAreas(Qt.LeftDockWidgetArea
                                                | Qt.RightDockWidgetArea
                                                | Qt.TopDockWidgetArea)
        self.scaleWidget = ScaleWidget(self,
                                       scales_model=self.scales_model,
                                       cmap_model=self.cmaps)
        self.dynamic_scale_dock.setWidget(self.scaleWidget)
        self.addDockWidget(Qt.RightDockWidgetArea, self.dynamic_scale_dock)
        self.viewMenu.addAction(self.dynamic_scale_dock.toggleViewAction())
        self.dynamic_scale_dock.setFloating(True)
        self.dynamic_scale_dock.hide()

        #radial profiles
        dock = QDockWidget("Radial Profile Fit", self)
        dock.setObjectName("RADIAL_PROFILE_IRAF")
        dock.setAllowedAreas(Qt.LeftDockWidgetArea | Qt.RightDockWidgetArea
                             | Qt.TopDockWidgetArea)
        self.radial_profile_iraf_widget = IRAFRadialProfileWidget(
            self.fits_image.data)
        dock.setWidget(self.radial_profile_iraf_widget)
        self.addDockWidget(Qt.RightDockWidgetArea, dock)
        self.viewMenu.addAction(dock.toggleViewAction())
        self.dockRadialFit = dock

        dock = QDockWidget("Radial Profile Curve", self)
        dock.setObjectName("RADIAL_PROFILE")
        dock.setAllowedAreas(Qt.LeftDockWidgetArea | Qt.RightDockWidgetArea
                             | Qt.TopDockWidgetArea)
        self.radial_profile_widget = RadialProfileWidget(self.fits_image.data)
        dock.setWidget(self.radial_profile_widget)
        self.addDockWidget(Qt.RightDockWidgetArea, dock)
        self.viewMenu.addAction(dock.toggleViewAction())
        dock.hide()

        #info panel
        dock = QDockWidget("Info", self)
        dock.setObjectName("INFO_PANEL")
        dock.setAllowedAreas(Qt.LeftDockWidgetArea | Qt.RightDockWidgetArea
                             | Qt.TopDockWidgetArea)
        self.info_widget = InfoWidget(self)
        dock.setWidget(self.info_widget)
        self.addDockWidget(Qt.TopDockWidgetArea, dock)
        self.viewMenu.addAction(dock.toggleViewAction())

        # FITS headers
        dock = QDockWidget("FITS header", self)
        dock.setObjectName("FTIS_DATA")
        dock.setAllowedAreas(Qt.LeftDockWidgetArea | Qt.RightDockWidgetArea
                             | Qt.TopDockWidgetArea)
        self.addDockWidget(Qt.TopDockWidgetArea, dock)
        self.viewMenu.addAction(dock.toggleViewAction())
        self.headerWidget = HeaderTableWidget(self)
        self.headerWidget.setColumnCount(2)
        self.headerWidget.setHorizontalHeaderItem(0, QTableWidgetItem("KEY"))
        self.headerWidget.setHorizontalHeaderItem(1, QTableWidgetItem("VALUE"))
        self.headerWidget.horizontalHeader().setStretchLastSection(1)
        self.headerWidget.setEditTriggers(
            QtWidgets.QTableWidget.NoEditTriggers)
        self.headerWidget.clearFocus()
        dock.setWidget(self.headerWidget)

        # full
        dock = QDockWidget("Full view", self)
        dock.setObjectName("FULL_VIEW")
        dock.setAllowedAreas(Qt.LeftDockWidgetArea | Qt.RightDockWidgetArea
                             | Qt.TopDockWidgetArea)
        self.full_view_widget = FullViewWidget(self.fits_image)
        self.full_view_widget.fits_image.set_scale_model(self.scales_model)
        self.full_view_widget.fits_image.set_cmap_model(self.cmaps)
        dock.setWidget(self.full_view_widget)
        self.addDockWidget(Qt.TopDockWidgetArea, dock)
        self.viewMenu.addAction(dock.toggleViewAction())
        # zoom
        dock = QDockWidget("Zoom view", self)
        dock.setObjectName("ZOOM_VIEW")
        dock.setAllowedAreas(Qt.LeftDockWidgetArea | Qt.RightDockWidgetArea
                             | Qt.TopDockWidgetArea)
        self.zoom_view_widget = ZoomViewWidget(self.fits_image)
        self.zoom_view_widget.fits_image.set_scale_model(self.scales_model)
        self.zoom_view_widget.fits_image.set_cmap_model(self.cmaps)
        dock.setWidget(self.zoom_view_widget)
        self.addDockWidget(Qt.TopDockWidgetArea, dock)
        self.viewMenu.addAction(dock.toggleViewAction())

        # fileSelector
        dock = QDockWidget("Directory view", self)
        dock.setObjectName("DIRECTORY_VIEW")
        dock.setAllowedAreas(Qt.LeftDockWidgetArea)
        self.file_widget = FileSystemWidget(self)
        dock.setWidget(self.file_widget)
        self.addDockWidget(Qt.LeftDockWidgetArea, dock)
        self.viewMenu.addAction(dock.toggleViewAction())

        self.viewMenu.addSeparator()

    # def changeColor(self, color):
    #     self.cmaps.set_active_color_map(color)

    # def on_colormap_change(self, change):
    #     self.fits_image.cmap = self.cmaps.get_active_color_map()
    #     self.fits_image.plot()
    #     self.updateFitsInWidgets()

    def onAutoCenterChange(self, change):
        self.autoCenterAct.setChecked(change.new)

    def dynamicScaleDockWidgetTriggerActions(self):
        if self.dynamic_scale_dock.isHidden():
            self.dynamic_scale_dock.show()
        else:
            self.dynamic_scale_dock.hide()

    def onCenterCircleChange(self, change):
        self.radial_profile_widget.set_centroid(
            self.painterComponent.ccenter_x, self.painterComponent.ccenter_y)
        self.radial_profile_iraf_widget.set_centroid(
            self.painterComponent.ccenter_x, self.painterComponent.ccenter_y)

    def onCenterCircleRadiusChange(self, change):
        self.radial_profile_widget.set_radius(self.painterComponent.cradius)
        self.radial_profile_iraf_widget.set_radius(
            self.painterComponent.cradius)

    def onRectangleInWidgetMove(self, change):
        changed = False
        if change.new is not None:
            changed = True
        if change.name == 'viewX':
            self.fullWidgetXcord = change.new
        elif change.name == 'viewY':
            self.fullWidgetYcord = change.new
        if changed:
            self.fits_image.moveToXYcords(self.fullWidgetXcord,
                                          self.fullWidgetYcord)

    def movingCentralWidget(self, change):
        changed = False
        if change.new is not None:
            changed = True
        if change.name == 'movingViewX':
            self.centralWidgetcordX = change.new
        elif change.name == 'movingViewY':
            self.centralWidgetcordY = change.new
        if changed:
            self.full_view_widget.updateMiniatureShapeXYonly(
                self.centralWidgetcordX, self.centralWidgetcordY)

    def onMouseMoveOnImage(self, change):
        display = ''
        val = 0
        if change.new is not None:
            display = f'{change.new:f}'
            val = change.new
        if change.name == 'mouse_xdata':
            # self.mouse_x_label.setText(display)
            self.current_x_coord = val
            self.cursor_coords.set_img_x(change.new)
        elif change.name == 'mouse_ydata':
            # self.mouse_y_label.setText(display)
            self.current_y_coord = val
            self.cursor_coords.set_img_y(change.new)
        if display != '':
            self.zoom_view_widget.setXYofZoom(self.fits_image,
                                              self.current_x_coord,
                                              self.current_y_coord,
                                              self.fits_image.zoom)
            if not self.hasFocus():
                self.setFocus()
            if self.scanObject.activeScan and self.scanObject.enableAutopause:  #reser autopause
                if not self.scanObject.obserwableValue.autopauseFlag:
                    self.scanObject.obserwableValue.autopauseFlag = True

    def activeLinearAdjustmentByMouseMovement(self):
        self.central_widget.mpl_connect('motion_notify_event',
                                        self.mouseMoveEventOnCanvas)
        self.central_widget.mpl_connect('button_press_event',
                                        self.canvasMousePressEvent)
        self.central_widget.mpl_connect('button_release_event',
                                        self.canvasMousePressEvent)

    def onMouseZoomOnImage(self, change):
        changed = False
        if change.new is not None:
            changed = True
        if changed:
            self.full_view_widget.updateMiniatureShape(self.fits_image.viewX,
                                                       self.fits_image.viewY,
                                                       self.fits_image.viewW,
                                                       self.fits_image.viewH)

    def readWindowSettings(self):
        if self.tedaCommandLine.ignoreSettings:
            return
        settings = QSettings()
        settings.beginGroup("MainWindow")
        size, pos = settings.value("size"), settings.value("pos")
        settings.endGroup()
        if size is not None and pos is not None:
            print('settings: resize to {} and move to {}', size, pos)
            self.move(pos)
            # self.resize(size)
            print('Size reported ', self.size())
            print('Size set ', size)
            self.resize(size)
            print('Size reported ', self.size())
        else:
            self.resize(800, 600)

        geometry = settings.value("geometry")
        if geometry is not None:
            self.restoreGeometry(geometry)
            self.restoreState(settings.value("windowState"))

        self.headerWidget.readSettings(settings)
        self.file_widget.readSettings(settings)

    def writeWindowSettings(self):
        if self.tedaCommandLine.ignoreSettings:
            return
        settings = QSettings()
        settings.beginGroup("MainWindow")
        settings.setValue("size", self.size())
        settings.setValue("pos", self.pos())
        settings.endGroup()

        settings.setValue('geometry', self.saveGeometry())
        settings.setValue('windowState', self.saveState())

        self.headerWidget.writeSettings(settings)
        self.file_widget.writeSettings(settings)
Exemplo n.º 14
0
class SequenceEditor(SchemeEditor):
    def __init__(self, parent=None):
        super().__init__(parent)
        self._sequences = []
        """All sequences as a list of tuples (seq_as_graph, seq_as_list, radio_button)"""

        self.selector_placeholder = QLabel("(none)")
        self.selector_placeholder.setAlignment(Qt.AlignCenter)

        self.selector = None
        self.selector_button_group = None

        self.selector_scroll_area = QScrollArea()
        self.selector_dock = QDockWidget("Active Sequence", self)
        self.selector_dock.setWidget(self.selector_scroll_area)
        self.selector_dock.setAllowedAreas(Qt.LeftDockWidgetArea | Qt.RightDockWidgetArea)
        self.selector_dock.setFeatures(QDockWidget.DockWidgetMovable | QDockWidget.DockWidgetFloatable)
        self.addDockWidget(Qt.LeftDockWidgetArea, self.selector_dock)

    def setScheme(self, scheme):
        super().setScheme(scheme)
        self.scheme().graphChanged.connect(self._updateSelector)
        self._updateSelector()

    def sequences(self):
        """Return a list of tuples (sequence_as_graph, sequence_as_list, sequence_button)."""
        return self._sequences
    
    def selectedSequence(self):
        """Return a tuple (sequence_as_graph, sequence_as_list, sequence_button) of the selected sequence."""
        checked = [s for s in self.sequences() if s[2].isChecked()]
        
        assert len(checked) <= 1
        if len(checked) == 1:
            return checked[0]
        return None

    def _updateSelector(self):
        sequences = list(self._possibleSequences())
        
        # Build a new widget with all the new options
        selector = QWidget()
        layout = QVBoxLayout()
        layout.setSizeConstraint(layout.SetMinAndMaxSize)
        layout.addWidget(self.selector_placeholder)
        selector.setLayout(layout)
        self.selector_scroll_area.setWidget(selector)
        self.selector_button_group = QButtonGroup()
        self.selector = selector
        
        if len(sequences) == 0:
            self.selector_placeholder.show()
            return
        self.selector_placeholder.hide()

        # Some black magic because of problems with scopes
        def selectSequence(seq):
            self.scheme().clearSelection()
            seq[0].selectAll()

        def makeSelectSequence(seq):
            return lambda: selectSequence(seq)
        # End of black magic

        self._sequences = []
        for sgraph, slist in sequences:
            label = " → ".join([node.title() for node in slist])

            button = QRadioButton(label)
            button.clicked.connect(makeSelectSequence((sgraph, slist)))

            self.selector_button_group.addButton(button)
            layout.addWidget(button)
            self._sequences.append((sgraph, slist, button))
        
        # Now that the new widget was built, determine which point should be selected considering previous selection
        selected = self.selectedSequence()

        if selected is None:
            # If no previous selection, select the first one
            self._sequences[0][2].setChecked(True)
            return

        candidates = []
        for sgraph, slist, button in self._sequences:
            # Analyze every new option
            if selected[0] <= sgraph:
                # If old selection is a subset of a new option, that means a new node was attached and current selection
                # should be expanded
                button.setChecked(True)
                return
            
            if sgraph <= selected[0]:
                # This option is a subset of previous option, which means that something was deleted. If an edge was
                # removed, there may be other parts of previous options, add them all to candidates for new selection.
                candidates.append((sgraph, slist, button))

        if len(candidates) == 0:
            # If there are no candidates, select the first option.
            self._sequences[0][2].setChecked(True)
            return

        for node in selected[1]:
            # For each node in order in prev. selection, see if any candidates have it. Select the first mathcing one.
            for sgraph, slist, button in candidates:
                if node in sgraph:
                    button.setChecked(True)
                    return

    def _possibleSequences(self):
        """Return a list of tuples, where each tuple represents a possible experiment sequence.
        Tuple contains 2 items: a graph-view of the sequence, and a list of nodes of that sequence in order.
        """
        g = self.scheme().graph

        for node in g.nodes:
            if len(node.inputs) == 0 or len(list(node.inputs[0].edges)) == 0:
                for sequence in self._possibleSequencesFrom(node):
                    yield sequence

    def _possibleSequencesFrom(self, node):
        """Return a list of tuples, where each tuple represents a possible experiment sequence starting from this node.
        Tuple contains 2 items: a graph-view of the sequence, and a list of nodes of that sequence in order.
        """
        has_sequences = False

        for out in node.outputs:
            for edge in out.edges:
                connected = edge.targetNode()

                if connected is None:
                    # Filter for edges with no target nodes. These are usually fake edges that the user drags.
                    # When they drop and become real, they will have a node.
                    continue

                for sequence_graph, sequence_list in self._possibleSequencesFrom(connected):
                    sequence_graph.add(node)
                    sequence_graph.add(edge)
                    sequence_list.insert(0, node)
                    
                    yield (sequence_graph, sequence_list)
                    has_sequences = True
        
        if not has_sequences:
            # If no sequences could be built, this node is the final node.
            # In this case, just yield itself.
            result = (Graph(), list())
            result[0].add(node)
            result[1].append(node)
            yield result
Exemplo n.º 15
0
class View(QMainWindow):
    def __init__(self, model, controller):
        super().__init__()

        self._model = model
        self._controller = controller
        self.segmentcursor = False
        self.togglecolors = {"#1f77b4": "m", "m": "#1f77b4"}

        #################################################################
        # define GUI layout and connect input widgets to external slots #
        #################################################################

        self.setWindowTitle("biopeaks")
        self.setGeometry(50, 50, 1750, 750)
        self.setWindowIcon(QIcon(":/python_icon.png"))

        # figure0 for signal
        self.figure0 = Figure()
        self.canvas0 = FigureCanvas(self.figure0)
        # Enforce minimum height, otherwise resizing with self.splitter causes
        # mpl to throw an error because figure is resized to height 0. The
        # widget can still be fully collapsed with self.splitter-
        self.canvas0.setMinimumHeight(1)  # in pixels
        self.ax00 = self.figure0.add_subplot(1, 1, 1)
        self.ax00.set_frame_on(False)
        self.figure0.subplots_adjust(left=0.04, right=0.98, bottom=0.25)
        self.line00 = None
        self.scat = None
        self.segmentspan = None

        # figure1 for marker
        self.figure1 = Figure()
        self.canvas1 = FigureCanvas(self.figure1)
        self.canvas1.setMinimumHeight(1)
        self.ax10 = self.figure1.add_subplot(1, 1, 1, sharex=self.ax00)
        self.ax10.get_xaxis().set_visible(False)
        self.ax10.set_frame_on(False)
        self.figure1.subplots_adjust(left=0.04, right=0.98)
        self.line10 = None

        # figure2 for statistics
        self.figure2 = Figure()
        self.canvas2 = FigureCanvas(self.figure2)
        self.canvas2.setMinimumHeight(1)
        self.ax20 = self.figure2.add_subplot(3, 1, 1, sharex=self.ax00)
        self.ax20.get_xaxis().set_visible(False)
        self.ax20.set_frame_on(False)
        self.line20 = None
        self.ax21 = self.figure2.add_subplot(3, 1, 2, sharex=self.ax00)
        self.ax21.get_xaxis().set_visible(False)
        self.ax21.set_frame_on(False)
        self.line21 = None
        self.ax22 = self.figure2.add_subplot(3, 1, 3, sharex=self.ax00)
        self.ax22.get_xaxis().set_visible(False)
        self.ax22.set_frame_on(False)
        self.line22 = None
        self.figure2.subplots_adjust(left=0.04, right=0.98)

        # navigation bar
        self.navitools = CustomNavigationToolbar(self.canvas0, self)

        # peak editing
        self.editcheckbox = QCheckBox("editable", self)
        self.editcheckbox.stateChanged.connect(self._model.set_peakseditable)

        # peak saving batch
        self.savecheckbox = QCheckBox("save during batch processing", self)
        self.savecheckbox.stateChanged.connect(self._model.set_savebatchpeaks)

        # peak auto-correction batch
        self.correctcheckbox = QCheckBox("correct during batch processing",
                                         self)
        self.correctcheckbox.stateChanged.connect(
            self._model.set_correctbatchpeaks)

        # selecting stats for saving
        self.periodcheckbox = QCheckBox("period", self)
        self.periodcheckbox.stateChanged.connect(
            lambda: self.select_stats("period"))
        self.ratecheckbox = QCheckBox("rate", self)
        self.ratecheckbox.stateChanged.connect(
            lambda: self.select_stats("rate"))
        self.tidalampcheckbox = QCheckBox("tidal amplitude", self)
        self.tidalampcheckbox.stateChanged.connect(
            lambda: self.select_stats("tidalamp"))

        # channel selection
        self.sigchanmenulabel = QLabel("biosignal")
        self.sigchanmenu = QComboBox(self)
        self.sigchanmenu.addItem("A1")
        self.sigchanmenu.addItem("A2")
        self.sigchanmenu.addItem("A3")
        self.sigchanmenu.addItem("A4")
        self.sigchanmenu.addItem("A5")
        self.sigchanmenu.addItem("A6")
        self.sigchanmenu.currentTextChanged.connect(self._model.set_signalchan)
        # initialize with default value
        self._model.set_signalchan(self.sigchanmenu.currentText())

        self.markerchanmenulabel = QLabel("marker")
        self.markerchanmenu = QComboBox(self)
        self.markerchanmenu.addItem("none")
        self.markerchanmenu.addItem("I1")
        self.markerchanmenu.addItem("I2")
        self.markerchanmenu.addItem("A1")
        self.markerchanmenu.addItem("A2")
        self.markerchanmenu.addItem("A3")
        self.markerchanmenu.addItem("A4")
        self.markerchanmenu.addItem("A5")
        self.markerchanmenu.addItem("A6")
        self.markerchanmenu.currentTextChanged.connect(
            self._model.set_markerchan)
        # initialize with default value
        self._model.set_markerchan(self.markerchanmenu.currentText())

        # processing mode (batch or single file)
        self.batchmenulabel = QLabel("mode")
        self.batchmenu = QComboBox(self)
        self.batchmenu.addItem("single file")
        self.batchmenu.addItem("multiple files")
        self.batchmenu.currentTextChanged.connect(self._model.set_batchmode)
        self.batchmenu.currentTextChanged.connect(self.toggle_options)
        # initialize with default value
        self._model.set_batchmode(self.batchmenu.currentText())
        self.toggle_options(self.batchmenu.currentText())

        # modality selection
        self.modmenulabel = QLabel("modality")
        self.modmenu = QComboBox(self)
        self.modmenu.addItem("ECG")
        self.modmenu.addItem("PPG")
        self.modmenu.addItem("RESP")
        self.modmenu.currentTextChanged.connect(self._model.set_modality)
        self.modmenu.currentTextChanged.connect(self.toggle_options)
        # initialize with default value
        self._model.set_modality(self.modmenu.currentText())
        self.toggle_options(self.modmenu.currentText())

        # segment selection; this widget can be openend / set visible from
        # the menu and closed from within itself (see mapping of segmentermap);
        # it provides utilities to select a segment from the signal
        self.segmentermap = QSignalMapper(self)
        self.segmenter = QDockWidget("select a segment", self)
        # disable closing such that widget can only be closed by confirming
        # selection or custom button
        self.segmenter.setFeatures(QDockWidget.NoDockWidgetFeatures)
        # Limit number of decimals to four.
        regex = QRegExp("[0-9]*\.?[0-9]{4}")
        validator = QRegExpValidator(regex)

        self.startlabel = QLabel("start")
        self.startedit = QLineEdit()
        self.startedit.setValidator(validator)

        self.endlabel = QLabel("end")
        self.endedit = QLineEdit()
        self.endedit.setValidator(validator)

        segmentfromcursor = QAction(QIcon(":/mouse_icon.png"),
                                    "select with mouse", self)
        segmentfromcursor.triggered.connect(self.enable_segmentedit)
        self.startedit.addAction(segmentfromcursor, QLineEdit.TrailingPosition)
        self.endedit.addAction(segmentfromcursor, QLineEdit.TrailingPosition)

        self.previewedit = QPushButton("preview segment")
        lambdafn = lambda: self._model.set_segment(
            [self.startedit.text(), self.endedit.text()])
        self.previewedit.clicked.connect(lambdafn)

        self.confirmedit = QPushButton("confirm segment")
        self.confirmedit.clicked.connect(self._controller.segment_signal)
        self.confirmedit.clicked.connect(self.segmentermap.map)
        self.segmentermap.setMapping(self.confirmedit, 0)

        self.abortedit = QPushButton("abort segmentation")
        self.abortedit.clicked.connect(self.segmentermap.map)
        # reset the segment to None
        self.segmentermap.setMapping(self.abortedit, 2)

        self.segmenterlayout = QFormLayout()
        self.segmenterlayout.addRow(self.startlabel, self.startedit)
        self.segmenterlayout.addRow(self.endlabel, self.endedit)
        self.segmenterlayout.addRow(self.previewedit)
        self.segmenterlayout.addRow(self.confirmedit)
        self.segmenterlayout.addRow(self.abortedit)
        self.segmenterwidget = QWidget()
        self.segmenterwidget.setLayout(self.segmenterlayout)
        self.segmenter.setWidget(self.segmenterwidget)

        self.segmenter.setVisible(False)
        self.segmenter.setAllowedAreas(Qt.RightDockWidgetArea)
        self.addDockWidget(Qt.RightDockWidgetArea, self.segmenter)

        # Set up dialog to gather user input for custom files.

        regex = QRegExp("[1-9][0-9]")
        validator = QRegExpValidator(regex)

        self.signallabel = QLabel("biosignal column")
        self.signaledit = QLineEdit()
        self.signaledit.setValidator(validator)

        self.markerlabel = QLabel("marker column")
        self.markeredit = QLineEdit()
        self.markeredit.setValidator(validator)

        regex = QRegExp("[0-9]{2}")
        validator = QRegExpValidator(regex)

        self.headerrowslabel = QLabel("number of header rows")
        self.headerrowsedit = QLineEdit()
        self.headerrowsedit.setValidator(validator)

        regex = QRegExp("[0-9]{5}")
        validator = QRegExpValidator(regex)

        self.sfreqlabel = QLabel("sampling rate")
        self.sfreqedit = QLineEdit()
        self.sfreqedit.setValidator(validator)

        self.separatorlabel = QLabel("column separator")
        self.separatormenu = QComboBox(self)
        self.separatormenu.addItem("comma")
        self.separatormenu.addItem("tab")
        self.separatormenu.addItem("colon")
        self.separatormenu.addItem("space")

        self.continuecustomfile = QPushButton("continue loading file")
        self.continuecustomfile.clicked.connect(self.set_customheader)

        self.customfiledialog = QDialog()
        self.customfiledialog.setWindowTitle("custom file info")
        self.customfiledialog.setWindowIcon(QIcon(":/file_icon.png"))
        self.customfiledialog.setWindowFlags(
            Qt.WindowCloseButtonHint
        )  # remove help button by only setting close button
        self.customfilelayout = QFormLayout()
        self.customfilelayout.addRow(self.signallabel, self.signaledit)
        self.customfilelayout.addRow(self.markerlabel, self.markeredit)
        self.customfilelayout.addRow(self.separatorlabel, self.separatormenu)
        self.customfilelayout.addRow(self.headerrowslabel, self.headerrowsedit)
        self.customfilelayout.addRow(self.sfreqlabel, self.sfreqedit)
        self.customfilelayout.addRow(self.continuecustomfile)
        self.customfiledialog.setLayout(self.customfilelayout)

        # set up menubar
        menubar = self.menuBar()

        # signal menu
        signalmenu = menubar.addMenu("biosignal")

        openSignal = signalmenu.addMenu("load")
        openEDF = QAction("EDF", self)
        openEDF.triggered.connect(lambda: self._model.set_filetype("EDF"))
        openEDF.triggered.connect(self._controller.get_fpaths)
        openSignal.addAction(openEDF)
        openOpenSignals = QAction("OpenSignals", self)
        openOpenSignals.triggered.connect(
            lambda: self._model.set_filetype("OpenSignals"))
        openOpenSignals.triggered.connect(self._controller.get_fpaths)
        openSignal.addAction(openOpenSignals)
        openCustom = QAction("Custom", self)
        openCustom.triggered.connect(
            lambda: self._model.set_filetype("Custom"))
        openCustom.triggered.connect(lambda: self.customfiledialog.exec_())
        openSignal.addAction(openCustom)

        segmentSignal = QAction("select segment", self)
        segmentSignal.triggered.connect(self.segmentermap.map)
        self.segmentermap.setMapping(segmentSignal, 1)
        signalmenu.addAction(segmentSignal)

        self.segmentermap.mapped.connect(self.toggle_segmenter)

        saveSignal = QAction("save", self)
        saveSignal.triggered.connect(self._controller.get_wpathsignal)
        signalmenu.addAction(saveSignal)

        # peak menu
        peakmenu = menubar.addMenu("peaks")

        findPeaks = QAction("find", self)
        findPeaks.triggered.connect(self._controller.find_peaks)
        peakmenu.addAction(findPeaks)

        autocorrectPeaks = QAction("autocorrect", self)
        autocorrectPeaks.triggered.connect(self._controller.autocorrect_peaks)
        peakmenu.addAction(autocorrectPeaks)

        savePeaks = QAction("save", self)
        savePeaks.triggered.connect(self._controller.get_wpathpeaks)
        peakmenu.addAction(savePeaks)

        loadPeaks = QAction("load", self)
        loadPeaks.triggered.connect(self._controller.get_rpathpeaks)
        peakmenu.addAction(loadPeaks)

        # stats menu
        statsmenu = menubar.addMenu("statistics")

        calculateStats = QAction("calculate", self)
        calculateStats.triggered.connect(self._controller.calculate_stats)
        statsmenu.addAction(calculateStats)

        saveStats = QAction("save", self)
        saveStats.triggered.connect(self._controller.get_wpathstats)
        statsmenu.addAction(saveStats)

        # set up status bar to display error messages and current file path
        self.statusBar = QStatusBar()
        self.setStatusBar(self.statusBar)
        self.progressBar = QProgressBar(self)
        self.progressBar.setRange(0, 1)
        self.statusBar.addPermanentWidget(self.progressBar)
        self.currentFile = QLabel()
        self.statusBar.addPermanentWidget(self.currentFile)

        # set up the central widget containing the plot and navigationtoolbar
        self.centwidget = QWidget()
        self.setCentralWidget(self.centwidget)

        # connect canvas0 to keyboard and mouse input for peak editing;
        # only widgets (e.g. canvas) that currently have focus capture
        # keyboard input: "You must enable keyboard focus for a widget if
        # it processes keyboard events."
        self.canvas0.setFocusPolicy(Qt.ClickFocus)
        self.canvas0.setFocus()
        self.canvas0.mpl_connect("key_press_event",
                                 self._controller.edit_peaks)
        self.canvas0.mpl_connect("button_press_event", self.get_xcursor)

        # arrange the three figure canvases in splitter object
        self.splitter = QSplitter(Qt.Vertical)
        # setting opaque resizing to false is important, since resizing gets
        # very slow otherwise once axes are populated
        self.splitter.setOpaqueResize(False)
        self.splitter.addWidget(self.canvas0)
        self.splitter.addWidget(self.canvas1)
        self.splitter.addWidget(self.canvas2)
        self.splitter.setChildrenCollapsible(False)

        # define GUI layout
        self.vlayout0 = QVBoxLayout(self.centwidget)
        self.vlayout1 = QVBoxLayout()
        self.vlayoutA = QFormLayout()
        self.vlayoutB = QFormLayout()
        self.vlayoutC = QVBoxLayout()
        self.vlayoutD = QVBoxLayout()
        self.hlayout0 = QHBoxLayout()

        self.optionsgroupA = QGroupBox("processing options")
        self.vlayoutA.addRow(self.modmenulabel, self.modmenu)
        self.vlayoutA.addRow(self.batchmenulabel, self.batchmenu)
        self.optionsgroupA.setLayout(self.vlayoutA)

        self.optionsgroupB = QGroupBox("channels")
        self.vlayoutB.addRow(self.sigchanmenulabel, self.sigchanmenu)
        self.vlayoutB.addRow(self.markerchanmenulabel, self.markerchanmenu)
        self.optionsgroupB.setLayout(self.vlayoutB)

        self.optionsgroupC = QGroupBox("peaks")
        self.vlayoutC.addWidget(self.editcheckbox)
        self.vlayoutC.addWidget(self.savecheckbox)
        self.vlayoutC.addWidget(self.correctcheckbox)
        self.optionsgroupC.setLayout(self.vlayoutC)

        self.optionsgroupD = QGroupBox("select statistics for saving")
        self.vlayoutD.addWidget(self.periodcheckbox)
        self.vlayoutD.addWidget(self.ratecheckbox)
        self.vlayoutD.addWidget(self.tidalampcheckbox)
        self.optionsgroupD.setLayout(self.vlayoutD)

        self.vlayout1.addWidget(self.optionsgroupA)
        self.vlayout1.addWidget(self.optionsgroupB)
        self.vlayout1.addWidget(self.optionsgroupC)
        self.vlayout1.addWidget(self.optionsgroupD)
        self.optionsgroupwidget = QWidget()
        self.optionsgroupwidget.setLayout(self.vlayout1)
        self.optionsgroup = QDockWidget("configurations", self)
        self.optionsgroup.setAllowedAreas(Qt.LeftDockWidgetArea)
        self.toggleoptionsgroup = self.optionsgroup.toggleViewAction()
        self.toggleoptionsgroup.setText("show/hide configurations")
        menubar.addAction(self.toggleoptionsgroup)
        self.optionsgroup.setWidget(self.optionsgroupwidget)
        self.addDockWidget(Qt.LeftDockWidgetArea, self.optionsgroup)

        self.vlayout0.addWidget(self.splitter)

        self.hlayout0.addWidget(self.navitools)
        self.vlayout0.addLayout(self.hlayout0)

        ##############################################
        # connect output widgets to external signals #
        ##############################################
        self._model.signal_changed.connect(self.plot_signal)
        self._model.marker_changed.connect(self.plot_marker)
        self._model.peaks_changed.connect(self.plot_peaks)
        self._model.period_changed.connect(self.plot_period)
        self._model.rate_changed.connect(self.plot_rate)
        self._model.tidalamp_changed.connect(self.plot_tidalamp)
        self._model.path_changed.connect(self.display_path)
        self._model.segment_changed.connect(self.plot_segment)
        self._model.status_changed.connect(self.display_status)
        self._model.progress_changed.connect(self.display_progress)
        self._model.model_reset.connect(self.reset_plot)

    ###########
    # methods #
    ###########

    def plot_signal(self, value):
        self.ax00.clear()
        self.ax00.relim()
        # reset navitools history
        self.navitools.update()
        self.line00 = self.ax00.plot(self._model.sec, value, zorder=1)
        self.ax00.set_xlabel("seconds", fontsize="large", fontweight="heavy")
        self.canvas0.draw()
#        print("plot_signal listening")
#        print(self.ax0.collections, self.ax0.patches, self.ax0.artists)

    def plot_peaks(self, value):
        # self.scat is listed in ax.collections
        if self.ax00.collections:
            self.ax00.collections[0].remove()
        self.scat = self.ax00.scatter(self._model.sec[value],
                                      self._model.signal[value],
                                      c="m",
                                      zorder=2)
        self.canvas0.draw()
#        print("plot_peaks listening")
#        print(self.ax0.collections, self.ax0.patches, self.ax0.artists)

    def plot_segment(self, value):
        # If an invalid signal has been selected reset the segmenter interface.
        if value is None:
            self.toggle_segmenter(1)
            return
        if self.ax00.patches:  # self.segementspan is listed in ax.patches
            self.ax00.patches[0].remove()
        self.segmentspan = self.ax00.axvspan(value[0],
                                             value[1],
                                             color="m",
                                             alpha=0.25)
        self.canvas0.draw()
        self.confirmedit.setEnabled(True)
#        print(self.ax0.collections, self.ax0.patches, self.ax0.artists)

    def plot_marker(self, value):
        self.ax10.clear()
        self.ax10.relim()
        self.line10 = self.ax10.plot(value[0], value[1])
        self.canvas1.draw()
#        print("plot_marker listening")

    def plot_period(self, value):
        self.ax20.clear()
        self.ax20.relim()
        self.navitools.home()
        if self._model.savestats["period"]:
            self.line20 = self.ax20.plot(self._model.sec, value, c="m")
        else:
            self.line20 = self.ax20.plot(self._model.sec, value)
        self.ax20.set_ylim(bottom=min(value), top=max(value))
        self.ax20.set_title("period", pad=0, fontweight="heavy")
        self.ax20.grid(True, axis="y")
        self.navitools.update()
        self.canvas2.draw()
#        print("plot_period listening")

    def plot_rate(self, value):
        self.ax21.clear()
        self.ax21.relim()
        self.navitools.home()
        if self._model.savestats["rate"]:
            self.line21 = self.ax21.plot(self._model.sec, value, c="m")
        else:
            self.line21 = self.ax21.plot(self._model.sec, value)
        self.ax21.set_ylim(bottom=min(value), top=max(value))
        self.ax21.set_title("rate", pad=0, fontweight="heavy")
        self.ax21.grid(True, axis="y")
        self.navitools.update()
        self.canvas2.draw()
#        print("plot_rate listening")

    def plot_tidalamp(self, value):
        self.ax22.clear()
        self.ax22.relim()
        self.navitools.home()
        if self._model.savestats["tidalamp"]:
            self.line22 = self.ax22.plot(self._model.sec, value, c="m")
        else:
            self.line22 = self.ax22.plot(self._model.sec, value)
        self.ax22.set_ylim(bottom=min(value), top=max(value))
        self.ax22.set_title("amplitude", pad=0, fontweight="heavy")
        self.ax22.grid(True, axis="y")
        self.navitools.update()
        self.canvas2.draw()
#        print("plot_tidalamp listening")

    def display_path(self, value):
        self.currentFile.setText(value)

    def display_status(self, status):
        # display status until new status is set
        self.statusBar.showMessage(status)

    def display_progress(self, value):
        # if value is 0, the progressbar indicates a busy state
        self.progressBar.setRange(0, value)

    def toggle_segmenter(self, value):
        if not self._model.loaded:
            return
        # Open segmenter when called from signalmenu or clear segmenter
        # upon selection of invalid segment.
        if value == 1:
            self.segmenter.setVisible(True)
            self.confirmedit.setEnabled(False)
            self.startedit.clear()
            self.endedit.clear()
            if self.ax00.patches:
                self.ax00.patches[0].remove()
                self.canvas0.draw()
        # Close segmenter after segment has been confirmed.
        elif value == 0:
            self.segmenter.setVisible(False)
            if self.ax00.patches:
                self.ax00.patches[0].remove()
                self.canvas0.draw()
        # Close segmenter after segmentation has been aborted (reset
        # segment).
        elif value == 2:
            self._model.set_segment([0,
                                     0])  # This will reset the model to None
            self.segmenter.setVisible(False)
            if self.ax00.patches:
                self.ax00.patches[0].remove()
                self.canvas0.draw()

    def enable_segmentedit(self):
        # disable peak editing to avoid interference
        self.editcheckbox.setChecked(False)
        if self.startedit.hasFocus():
            self.segmentcursor = "start"
        elif self.endedit.hasFocus():
            self.segmentcursor = "end"

    def set_customheader(self):
        """Populate the customheader with inputs from the customfiledialog"""

        # Check if one of the mandatory fields is missing.
        mandatoryfields = self.signaledit.text() and self.headerrowsedit.text(
        ) and self.sfreqedit.text()

        if not mandatoryfields:
            self._model.status = (
                "Please provide values for 'biosignal column'"
                ", 'number of header rows' and 'sampling"
                " rate'.")
            return

        seps = {"comma": ",", "tab": "\t", "colon": ":", "space": " "}
        self._model.customheader = dict.fromkeys(
            self._model.customheader, None
        )  # reset header here since it cannot be reset in controller.get_fpaths()

        self._model.customheader["signalidx"] = int(self.signaledit.text())
        self._model.customheader["skiprows"] = int(self.headerrowsedit.text())
        self._model.customheader["sfreq"] = int(self.sfreqedit.text())
        self._model.customheader["separator"] = seps[
            self.separatormenu.currentText()]
        if self.markeredit.text():  # not mandatory
            self._model.customheader["markeridx"] = int(self.markeredit.text())

        self.customfiledialog.done(QDialog.Accepted)  # close the dialog window
        self._controller.get_fpaths()  # move on to file selection

    def get_xcursor(self, event):
        # event.button 1 corresponds to left mouse button
        if event.button != 1:
            return
        # limit number of decimal places to two
        if self.segmentcursor == "start":
            self.startedit.selectAll()
            self.startedit.insert("{:.2f}".format(event.xdata))
        elif self.segmentcursor == "end":
            self.endedit.selectAll()
            self.endedit.insert("{:.2f}".format(event.xdata))
        # disable segment cursor again after value has been set
        self.segmentcursor = False

    def select_stats(self, event):
        """
        select or deselect statistics to be saved; toggle boolean with xor
        operator ^=, toggle color with dictionary
        """
        self._model.savestats[event] ^= True
        line = None
        if event == "period":
            if self.line20:
                line = self.line20[0]
        elif event == "rate":
            if self.line21:
                line = self.line21[0]
        elif event == "tidalamp":
            if self.line22:
                line = self.line22[0]
        if line:
            line.set_color(self.togglecolors[line.get_color()])
        self.canvas2.draw()

    def toggle_options(self, event):
        if event in ["ECG", "PPG"]:
            self.tidalampcheckbox.setEnabled(False)
            self.tidalampcheckbox.setChecked(False)
            self.ax22.set_visible(False)
            self.canvas2.draw()
        elif event == "RESP":
            self.tidalampcheckbox.setEnabled(True)
            self.ax22.set_visible(True)
            self.canvas2.draw()
        elif event == "multiple files":
            self.editcheckbox.setEnabled(False)
            self.editcheckbox.setChecked(False)
            self.savecheckbox.setEnabled(True)
            self.correctcheckbox.setEnabled(True)
            self.markerchanmenu.setEnabled(False)
        elif event == "single file":
            self.editcheckbox.setEnabled(True)
            self.markerchanmenu.setEnabled(True)
            self.savecheckbox.setEnabled(False)
            self.savecheckbox.setChecked(False)
            self.correctcheckbox.setEnabled(False)
            self.correctcheckbox.setChecked(False)

    def reset_plot(self):
        self.ax00.clear()
        self.ax00.relim()
        self.line00 = None
        self.scat = None
        self.segmentspan = None
        self.ax10.clear()
        self.ax10.relim()
        self.line10 = None
        self.ax20.clear()
        self.ax20.relim()
        self.line20 = None
        self.ax21.clear()
        self.ax21.relim()
        self.line21 = None
        self.ax22.clear()
        self.ax22.relim()
        self.line22 = None
        self.canvas0.draw()
        self.canvas1.draw()
        self.canvas2.draw()
        self.navitools.update()
        self.currentFile.clear()
class ExperimentView(QMainWindow):
    """View widget for Experiment class and the main window of this application."""
    def __init__(self, parent=None):
        super().__init__(parent=parent)

        self._model = None
        self._save_path = None
        """Save path of the experiment that is being edited."""

        self._processes = []
        """Process handles for experiments that were started."""

        # Exceptions ---------------------------------------------------------------------------------------------------
        self.__excepthook__ = sys.excepthook
        sys.excepthook = self.excepthook

        # Menu bar -----------------------------------------------------------------------------------------------------
        menubar = self.menuBar()
        filemenu = menubar.addMenu("File")

        action_new = filemenu.addAction("New")
        action_new.triggered.connect(self.actionNew)
        action_new.setShortcut(QKeySequence.New)

        action_open = filemenu.addAction("Open")
        action_open.triggered.connect(self.actionOpen)
        action_open.setShortcut(QKeySequence.Open)

        action_import = filemenu.addAction("Import")
        action_import.triggered.connect(self.actionImport)

        filemenu.addSeparator()

        action_save = filemenu.addAction("Save")
        action_save.triggered.connect(self.actionSave)
        action_save.setShortcut(QKeySequence.Save)

        action_save_as = filemenu.addAction("Save As...")
        action_save_as.triggered.connect(self.actionSaveAs)
        action_save_as.setShortcut(QKeySequence.SaveAs)

        action_export = filemenu.addAction("Export...")
        action_export.triggered.connect(self.actionExport)

        filemenu.addSeparator()

        action_exit = filemenu.addAction("Exit")
        action_exit.triggered.connect(self.close)

        runmenu = menubar.addMenu("Run")

        action_start_ex = runmenu.addAction("Start Experiment")
        action_start_ex.triggered.connect(self.actionStartExperiment)
        action_start_ex.setShortcut("F9")

        # Property tree ------------------------------------------------------------------------------------------------
        self.tree = PropertyTree()
        self.tree_view = self.tree.getView()
        self.tree_view.currentIndexChanged.connect(self.displayTreeItem)

        # Property tree dock widget ------------------------------------------------------------------------------------
        self.property_tree_dock = QDockWidget("Properties", self)
        self.property_tree_dock.setWidget(self.tree_view)
        self.property_tree_dock.setAllowedAreas(Qt.LeftDockWidgetArea
                                                | Qt.RightDockWidgetArea)
        self.property_tree_dock.setFeatures(QDockWidget.DockWidgetMovable
                                            | QDockWidget.DockWidgetFloatable)
        self.addDockWidget(Qt.LeftDockWidgetArea, self.property_tree_dock)

        # Editing widgets ----------------------------------------------------------------------------------------------
        self.general_view = GeneralView()

        self.signal_editor = SchemeEditor()

        for name in node_types:
            self.signal_editor.toolbox().addItem(name, node_types[name]())

        self.blocks = StackedDictWidget()
        self.groups = StackedDictWidget()

        # Sequence editor ----------------------------------------------------------------------------------------------
        self.sequence_editor = SequenceEditor()

        # Central widget -----------------------------------------------------------------------------------------------
        self.central_widget = QStackedWidget()
        self.setCentralWidget(self.central_widget)

        scrollarea = QScrollArea()
        scrollarea.setWidget(self.general_view)
        self.central_widget.addWidget(scrollarea)
        self.central_widget.addWidget(self.signal_editor)
        self.central_widget.addWidget(self.blocks)
        self.central_widget.addWidget(self.groups)
        self.central_widget.addWidget(self.sequence_editor)

        # New experiment view is created with a new experiment ---------------------------------------------------------
        self.actionNew()

    # Get/Set methods ==================================================================================================
    def model(self) -> Optional[Experiment]:
        """Return the experiment assosiated with this view."""
        return self._model

    def projectTitle(self) -> str:
        """Return document title, i.e. name of file this experiment is saved as.
        If no file has been associated with this experiment, returns "Untitled".
        """
        if self.savePath():
            return Path(self.savePath()).stem
        return "Untitled"

    def savePath(self) -> Optional[str]:
        """Return path of the file from which the """
        return self._save_path

    def setModel(self, ex: Experiment, /):
        self._model = ex

        self.tree.setExperiment(ex)
        self.signal_editor.setScheme(ex.signal_scheme)

        self.sequence_editor.setScheme(ex.sequence_scheme)

        ex.blocks.itemAdded.connect(self._onBlockAdded)
        ex.blocks.itemRenamed.connect(self._onBlockRenamed)
        ex.blocks.itemRemoved.connect(self._onBlockRemoved)
        ex.groups.itemAdded.connect(self._onGroupAdded)
        ex.groups.itemRenamed.connect(self._onGroupRenamed)
        ex.groups.itemRemoved.connect(self._onGroupRemoved)

        self.updateView()

    # Experiment syncronization ========================================================================================
    def updateModel(self):
        """Update the experiment when data in the view was changed by the user."""
        ex = self.model()
        if ex is None:
            return

        # For each block, write it's data to the experiment
        for name in self.blocks.keys():
            block_view: BlockView = self.blocks.widget(name).widget()
            block_view.updateModel()

        # For each group, write its data to the experiment
        for name in self.groups.keys():
            group_view: GroupView = self.groups.widget(name).widget()
            group_view.updateModel()

        # Write general experiment data
        self.general_view.updateModel(ex)

        # Write the selected sequence
        ex.sequence = [
            node.title() for node in self.sequence_editor.selectedSequence()[1]
        ]

    def updateView(self):
        ex = self.model()
        if ex is None:
            return

        # General properties -------------------------------------------------------------------------------------------
        general = self.general_view

        general.name.setText(ex.name)
        general.inlet_type.setCurrentText(
            general.inlet_type_import_values[ex.inlet])
        general.lsl_stream_name.setCurrentText(ex.lsl_stream_name)
        general.lsl_filename.setText(ex.raw_data_path)
        general.hostname_port.setText(ex.hostname_port)
        general.dc.setChecked(ex.dc)

        if ex.prefilter_band[0] is None:
            general.prefilter_lower_bound_enable.setChecked(False)
            general.prefilter_lower_bound.setValue(0)
        else:
            general.prefilter_lower_bound_enable.setChecked(True)
            general.prefilter_lower_bound.setValue(ex.prefilter_band[0])

        if ex.prefilter_band[1] is None:
            general.prefilter_upper_bound_enable.setChecked(False)
            general.prefilter_upper_bound.setValue(0)
        else:
            general.prefilter_upper_bound_enable.setChecked(True)
            general.prefilter_upper_bound.setValue(ex.prefilter_band[1])

        general.plot_raw.setChecked(ex.plot_raw)
        general.plot_signals.setChecked(ex.plot_signals)
        general.discard_channels.setText(ex.discard_channels)
        general.reference_sub.setText(ex.reference_sub)
        general.show_photo_rectangle.setChecked(ex.show_photo_rectangle)
        general.show_notch_filters.setChecked(ex.show_notch_filters)

        # Blocks and groups --------------------------------------------------------------------------------------------
        while self.tree.blocks.rowCount() > 0:
            name = self.tree.blocks.child(0).text()
            self.tree.blocks.removeRow(0)
            self.blocks.removeWidget(name)
            self.sequence_editor.toolbox().removeItem(name)

        while self.tree.groups.rowCount() > 0:
            name = self.tree.groups.child(0).text()
            self.tree.groups.removeRow(0)
            self.groups.removeWidget(name)
            self.sequence_editor.toolbox().removeItem(name)

        for name in ex.blocks:
            ex.blocks.itemAdded.emit(name)

        for name in ex.groups:
            ex.groups.itemAdded.emit(name)

        # Sequence -----------------------------------------------------------------------------------------------------
        for sgraph, slist, button in self.sequence_editor.sequences():
            if ex.sequence == [node.title() for node in slist]:
                button.setChecked(True)
                break

    def _onBlockAdded(self, name):
        """Function that gets called when a new block has been added to the experiment."""
        # Add an item to the property tree
        tree_item = QStandardItem(name)
        tree_item.setFlags(Qt.ItemIsEnabled | Qt.ItemIsSelectable
                           | Qt.ItemIsEditable)
        self.tree.blocks.appendRow(tree_item)

        # Add a view to the widget stack
        block_view = BlockView()
        block_view.setModel(self.model().blocks[name])
        scrollarea = QScrollArea()
        scrollarea.setWidget(block_view)
        self.blocks.addWidget(name, scrollarea)

        # Add a node to draw the sequence
        node = BlockNode()
        node.setTitle(name)
        self.sequence_editor.toolbox().addItem(name, node)

        # Select this item
        self.tree_view.setCurrentIndex(self.tree.indexFromItem(tree_item))

    def _onGroupAdded(self, name):
        """Function that gets called when a new group has been added to the experiment."""
        # Add an item to the property tree
        tree_item = QStandardItem(name)
        tree_item.setFlags(Qt.ItemIsEnabled | Qt.ItemIsSelectable
                           | Qt.ItemIsEditable)
        self.tree.groups.appendRow(tree_item)

        # Add a view to the widget stack
        group_view = GroupView()
        group_view.setModel(self.model().groups[name])
        scrollarea = QScrollArea()
        scrollarea.setWidget(group_view)
        self.groups.addWidget(name, scrollarea)

        # Add a node to draw the sequence
        node = GroupNode()
        node.setTitle(name)
        self.sequence_editor.toolbox().addItem(name, node)

        # Select this item
        self.tree_view.setCurrentIndex(self.tree.indexFromItem(tree_item))

    def _onBlockRenamed(self, old_name, new_name):
        """Function that gets called when a block has been renamed."""
        # Rename it in the property tree
        for i in range(self.tree.blocks.rowCount()):
            item = self.tree.blocks.child(i)
            if item.text() == old_name:
                item.setText(new_name)
                break

        # Rename in widget stack
        current_key = self.blocks.currentKey()

        w = self.blocks.removeWidget(old_name)
        self.blocks.addWidget(new_name, w)

        if current_key == old_name:
            self.blocks.setCurrentKey(new_name)

        # Rename in the sequence editor
        node = self.sequence_editor.toolbox().removeItem(old_name)
        node.setTitle(new_name)
        self.sequence_editor.toolbox().addItem(new_name, node)

        for node in self.sequence_editor.scheme().graph.nodes:
            if node.title() == old_name:
                node.setTitle(new_name)

        # Rename it in the sequence editor's current sequence widget
        for _1, _2, button in self.sequence_editor.sequences():
            label = button.text()
            new_label = " → ".join(
                [new_name if x == old_name else x for x in label.split(" → ")])
            button.setText(new_label)

    def _onGroupRenamed(self, old_name, new_name):
        """Function that gets called when a group has been renamed."""
        # Rename it in the property tree
        for i in range(self.tree.groups.rowCount()):
            item = self.tree.groups.child(i)
            if item.text() == old_name:
                item.setText(new_name)
                break

        # Rename in widget stack
        current_key = self.groups.currentKey()

        w = self.groups.removeWidget(old_name)
        self.groups.addWidget(new_name, w)

        if current_key == old_name:
            self.groups.setCurrentKey(new_name)

        # Rename in the sequence editor
        node = self.sequence_editor.toolbox().removeItem(old_name)
        node.setTitle(new_name)
        self.sequence_editor.toolbox().addItem(new_name, node)

        for node in self.sequence_editor.scheme().graph.nodes:
            if node.title() == old_name:
                node.setTitle(new_name)

        # Rename it in the sequence editor's current sequence widget
        for _1, _2, button in self.sequence_editor.sequences():
            label = button.text()
            new_label = " → ".join(
                [new_name if x == old_name else x for x in label.split(" → ")])
            button.setText(new_label)

    def _onBlockRemoved(self, name):
        """Function that gets called when a block has been removed from the experiment."""
        # Find and remove it from the property tree
        for i in range(self.tree.blocks.rowCount()):
            item = self.tree.blocks.child(i)
            if item.text() == name:
                self.tree.blocks.removeRow(i)
                break

        # Remove from widget stack and the sequence editor toolbox
        self.blocks.removeWidget(name)
        self.sequence_editor.toolbox().removeItem(name)

    def _onGroupRemoved(self, name):
        """Function that gets called when a block has been removed from the experiment."""
        # Find and remove it from the property tree
        for i in range(self.tree.groups.rowCount()):
            item = self.tree.groups.child(i)
            if item.text() == name:
                self.tree.groups.removeRow(i)
                break

        # Remove from widget stack and the sequence editor toolbox
        self.groups.removeWidget(name)
        self.sequence_editor.toolbox().removeItem(name)

    # Property tree syncronization =====================================================================================
    def displayTreeItem(self, index: QModelIndex):
        """Display the widget corresponding to the item in the property tree."""
        item = self.tree.itemFromIndex(index)

        if item is self.tree.general:
            # General
            scrollarea = self.general_view.parent().parent()
            assert type(scrollarea) == QScrollArea
            self.central_widget.setCurrentWidget(scrollarea)
        elif item is self.tree.signals:
            # Signal editor
            self.central_widget.setCurrentWidget(self.signal_editor)
        elif item.parent() is self.tree.blocks:
            # A block
            self.central_widget.setCurrentWidget(self.blocks)
            self.blocks.setCurrentKey(item.text())
        elif item.parent() is self.tree.groups:
            # A group
            self.central_widget.setCurrentWidget(self.groups)
            self.groups.setCurrentKey(item.text())
        elif item is self.tree.sequence:
            # Sequence editor
            self.central_widget.setCurrentWidget(self.sequence_editor)

    # User actions =====================================================================================================
    def actionNew(self) -> bool:
        if self.model() and not self.promptSaveChanges():
            return False  # Action cancelled during prompt

        self.setModel(Experiment())
        self._save_path = None
        self.setWindowTitle(self.projectTitle() + " - NFB Studio")
        return True

    def actionExport(self) -> bool:
        self.updateModel()

        if (len(self.model().sequence_scheme.graph.nodes) == 0):
            # No nodes in sequence scheme, cancel operation
            # TODO: A better way to signal to the user that he needs to create a sequence?
            self.central_widget.setCurrentWidget(self.sequence_editor)
            return False

        data = self.model().export()

        file_path = QFileDialog.getSaveFileName(filter="XML Files (*.xml)")[0]
        if file_path == "":
            return False  # Action was cancelled

        if os.path.splitext(file_path)[1] == "":  # No extension
            file_path = file_path + ".xml"

        with open(file_path, "w", encoding="utf-8") as file:
            file.write(data)
        return True

    def actionSave(self) -> bool:
        """User action "Save". Saves file to its location, or promts user if no location exists yet.
        Returns True if file was saved, and False if action was cancelled.
        """
        if self.savePath() is None:
            return self.actionSaveAs()

        self.fileSave(self.savePath())
        self.setWindowTitle(self.projectTitle() + " - NFB Studio")
        return True

    def actionSaveAs(self) -> bool:
        """User action "Save As". Promts user to save file as.
        Returns True if file was saved, and False if action was cancelled.
        """
        path = QFileDialog.getSaveFileName(
            filter="Experiment Files (*.nfbex)")[0]
        if path == "":
            return False  # Action was cancelled

        if os.path.splitext(path)[1] == "":  # No extension
            path = path + ".nfbex"

        self._save_path = path
        self.fileSave(self.savePath())
        self.setWindowTitle(self.projectTitle() + " - NFB Studio")
        return True

    def actionOpen(self) -> bool:
        """User action "Open". Promts user to open a file.
        Returns True if file was opened, and False if action was cancelled.
        """
        if self.model() and not self.promptSaveChanges():
            return False  # Action cancelled during prompt

        path = QFileDialog.getOpenFileName(
            filter="Experiment Files (*.nfbex)")[0]
        if path == "":
            return False

        self.fileOpen(path)
        self.setWindowTitle(self.projectTitle() + " - NFB Studio")
        return True

    def actionImport(self) -> bool:
        """User action "Import". Promts user to import a file.
        Returns True if file was imported, and False if action was cancelled or failed.
        """
        if self.model() and not self.promptSaveChanges():
            return False  # Action cancelled during prompt

        file_path = QFileDialog.getOpenFileName(filter="XML Files (*.xml)")[0]
        if file_path == "":
            return False

        try:
            with open(file_path, encoding="utf-8") as file:
                data = file.read()
        except UnicodeDecodeError:
            try:
                with open(file_path, encoding="cp1251") as file:
                    data = file.read()
            except:
                QMessageBox.critical(
                    title="Unable to read the file",
                    text="NFB Studio was unable to read this file.")
                return False

        ex = Experiment.import_xml(data)
        self.setModel(ex)
        return True

    def actionStartExperiment(self):
        self.updateModel()

        if (len(self.model().sequence_scheme.graph.nodes) == 0):
            # No nodes in sequence scheme, cancel operation
            # TODO: A better way to signal to the user that he needs to create a sequence?
            self.central_widget.setCurrentWidget(self.sequence_editor)
            return

        results_path = QFileDialog.getExistingDirectory(
            caption="Select a folder to save experiment results",
            options=QFileDialog.ShowDirsOnly | QFileDialog.DontResolveSymlinks)
        if results_path == "":
            return False  # Action was cancelled

        data = self.model().export()

        temp_dir = QDir.tempPath() + "/nfb_studio"
        os.makedirs(temp_dir, exist_ok=True)

        timestamp = datetime.now()
        file_path = "{}/experiment ({:04d}-{:02d}-{:02d} {:02d}-{:02d}-{:02d}).xml".format(
            temp_dir, timestamp.year, timestamp.month, timestamp.day,
            timestamp.hour, timestamp.minute, timestamp.second)

        with open(file_path, "w", encoding="utf-8") as file:
            file.write(data)

        proc = Process(target=run, args=(file_path, results_path))
        proc.start()

        self._processes.append(proc)

    def promptSaveChanges(self) -> bool:
        """Prompt the user to save changes to current project.
        Display a message box asking the user if they want to save changes. If user selects Save, execute actionSave.
        Returns True if the user decided to discard changes or saved the file, and False if the user cancelled operation
        either during prompt or during save.
        """
        prompt = QMessageBox()
        prompt.setWindowTitle("NFB Studio")
        prompt.setIcon(QMessageBox.Warning)
        prompt.setText("Save changes to \"{}\" before closing?".format(
            self.projectTitle()))
        prompt.setStandardButtons(QMessageBox.Save | QMessageBox.Discard
                                  | QMessageBox.Cancel)
        prompt.setDefaultButton(QMessageBox.Save)

        answer = prompt.exec_()
        if answer == QMessageBox.Cancel:
            return False
        if answer == QMessageBox.Save:
            return self.actionSave()
        return True

    def closeEvent(self, event):
        if self.model():
            event.setAccepted(self.promptSaveChanges())
        else:
            event.accept()

    # File operations ==================================================================================================
    def fileOpen(self, path):
        with open(path, encoding="utf-8") as file:
            data = file.read()

        ex = Experiment.load(data)
        self.setModel(ex)
        self._save_path = path

    def fileSave(self, path):
        self.updateModel()
        data = self.model().save()

        with open(path, "w", encoding="utf-8") as file:
            file.write(data)

    # Exception handling ===============================================================================================
    def excepthook(self, etype, value, tb):
        message = QMessageBox(self)
        message.setWindowTitle("Critical Error")
        message.setText(
            "An unknown critical error occured. It is recommended to save your work and restart NFB Studio.\n\n"
            "Please inform the developer, describing what you were doing before the error, and attach the text below."
        )
        message.setIcon(message.Icon.Critical)

        exception_field = QTextEdit()
        exception_field.setText("".join(
            traceback.format_exception(etype, value, tb)))
        exception_field.setReadOnly(True)
        message.layout().addWidget(exception_field, 1, 0, 1, -1)
        message.exec_()

        self.__excepthook__(etype, value, tb)
Exemplo n.º 17
0
class MainWindow(QMainWindow):
    def __init__(self):
        super(MainWindow, self).__init__()
        self.re_editing = False
        self.initUI()
        self.setup_shortcuts()
        self._tmp = NamedTemporaryFile(suffix=".srt", delete=False)
        self.tmp = self._tmp.name
        self.importing = False

    def initUI(self):
        self.setMinimumSize(50, 70)
        self.setWindowTitle(f"Angel SubTitle Pro ({__version__})")
        self.sb = self.statusBar()
        self.updateStatusBar(__version__)
        # Dockable Widgets -----------------------------------------------------------
        # self.dock1 = QDockWidget("Video Player", self) # Coverted to Central Widget
        self.dock2 = QDockWidget("Text Editing View", self)
        self.dock3 = QDockWidget("Table View", self)
        self.dock4 = QDockWidget("Waveform View", self)
        # Title Widgets    -----------------------------------------------------------
        # self.oldD1Title = self.dock1.titleBarWidget()  # Dock1
        self.oldD2Title = self.dock2.titleBarWidget()
        self.oldD3Title = self.dock3.titleBarWidget()
        self.oldD4Title = self.dock4.titleBarWidget()
        # self.noTitle1 = QWidget()  # Dock1
        self.noTitle2 = QWidget()
        self.noTitle3 = QWidget()
        self.noTitle4 = QWidget()
        # self.dock1.setTitleBarWidget(self.noTitle1)  # Dock1
        self.dock2.setTitleBarWidget(self.noTitle2)
        self.dock3.setTitleBarWidget(self.noTitle3)
        self.dock4.setTitleBarWidget(self.noTitle4)
        # Dockable Areas
        self.dock2.setAllowedAreas(Qt.AllDockWidgetAreas)
        self.dock3.setAllowedAreas(Qt.AllDockWidgetAreas)
        self.dock4.setAllowedAreas(Qt.AllDockWidgetAreas)
        # Adding Dockable Widgets ----------------------------------------------------
        # self.addDockWidget(Qt.RightDockWidgetArea, self.dock1)  # Video Player Panel
        self.addDockWidget(Qt.BottomDockWidgetArea,
                           self.dock3)  # Subtitle Table Panel
        self.addDockWidget(Qt.RightDockWidgetArea,
                           self.dock2)  # Subtitle Editing Panel
        self.addDockWidget(Qt.BottomDockWidgetArea,
                           self.dock4)  # Waveform Panel
        self.tabifyDockWidget(self.dock3, self.dock4)
        self.dock3.raise_()
        # Panel Instances ------------------------------------------------------------
        self.videoPanel = vlcPlayer()
        self.editPanel = subTitleEdit()
        self.subTablePanel = subTitleTable()
        self.setCentralWidget(self.videoPanel)
        self.waveFormPanel = waveform(self)
        # self.dock1.setWidget(self.videoPanel)
        self.dock2.setWidget(self.editPanel)
        self.dock3.setWidget(self.subTablePanel)
        # self.dock4.setWidget(QLabel("Under Construction"))
        self.dock4.setWidget(self.waveFormPanel)
        # Right Click Actions --------------------------------------------------------
        self.actShowT = QAction("Show TitleBar", self)
        self.actShowT.triggered.connect(self.showTitleBar)
        self.actHideT = QAction("Hide TitleBar", self)
        self.actHideT.triggered.connect(self.hideTitleBar)
        # Signals & Slot Connections
        self.videoPanel.message.connect(self.updateStatusBar)
        self.subTablePanel.verticalHeader().sectionDoubleClicked.connect(
            self.edit_row)
        self.subTablePanel.row_deleted.connect(self.row_deleted_update)
        self.subTablePanel.row_added.connect(self.row_added_update)
        self.waveFormPanel.file_loaded.connect(self.updateStatusBar)
        self.waveFormPanel.selectionCtrl.sigRegionChangeFinished.connect(
            self.processWaveformSelection)

        # Final Cleanup before show
        self.editPanel.subtitle.setFocus()

    def isVideoParsed(self):
        return self.videoPanel.fileParsed

    def getVideoFilePath(self):
        if self.isVideoParsed():
            return self.videoPanel.currVideoFile
        else:
            return None

    def setup_shortcuts(self):
        shortcut_open = QShortcut(QKeySequence(Qt.CTRL + Qt.Key_O), self)
        shortcut_open.activated.connect(self.open_project)
        shortcut_save = QShortcut(QKeySequence(Qt.CTRL + Qt.Key_S), self)
        shortcut_save.activated.connect(self.save_project)
        shortcut_export = QShortcut(QKeySequence(Qt.CTRL + Qt.Key_E), self)
        shortcut_export.activated.connect(self.export_project)
        shortcut_import = QShortcut(QKeySequence(Qt.CTRL + Qt.Key_I), self)
        shortcut_import.activated.connect(self.import_project)
        # Subtitle Editing Shortcuts
        shortcut_setIn = QShortcut(QKeySequence(Qt.CTRL + Qt.Key_BracketLeft),
                                   self)
        shortcut_setIn.activated.connect(self.set_intime)
        shortcut_setOut = QShortcut(
            QKeySequence(Qt.CTRL + Qt.Key_BracketRight), self)
        shortcut_setOut.activated.connect(self.set_outtime)
        shortcut_insert = QShortcut(QKeySequence(Qt.CTRL + Qt.Key_Equal), self)
        shortcut_insert.activated.connect(self.insert_new_subtitle)
        # Video Player ShortCuts
        shortcut_rewind = QShortcut(QKeySequence(Qt.CTRL + Qt.Key_J), self)
        shortcut_rewind.activated.connect(self.videoPanel.rewind)
        shortcut_play_pause = QShortcut(QKeySequence(Qt.CTRL + Qt.Key_K), self)
        shortcut_play_pause.activated.connect(self.videoPanel.play_pause)
        shortcut_forward = QShortcut(QKeySequence(Qt.CTRL + Qt.Key_L), self)
        shortcut_forward.activated.connect(self.videoPanel.fastforward)
        shortcut_nf = QShortcut(QKeySequence(Qt.CTRL + Qt.Key_Slash), self)
        shortcut_nf.activated.connect(self.videoPanel.nextFrame)
        shortcut_pf = QShortcut(QKeySequence(Qt.CTRL + Qt.Key_Comma), self)
        shortcut_pf.activated.connect(self.videoPanel.previousFrame)
        shortcut_loadV = QShortcut(QKeySequence(Qt.CTRL + Qt.Key_P), self)
        shortcut_loadV.activated.connect(self.videoPanel.load_video)
        shortcut_safezone = QShortcut(QKeySequence(Qt.CTRL + Qt.Key_T), self)
        shortcut_safezone.activated.connect(
            self.videoPanel.set_overlay_safezone)

    @Slot(str)
    def updateStatusBar(self, message):
        self.sb.showMessage(message)

    def contextMenuEvent(self, event):
        menu = QMenu(self)
        menu.addAction(self.actShowT)
        menu.addAction(self.actHideT)
        menu.exec_(event.globalPos())

    def showTitleBar(self):
        # self.dock1.setTitleBarWidget(self.oldD1Title)
        self.dock2.setTitleBarWidget(self.oldD2Title)
        self.dock3.setTitleBarWidget(self.oldD3Title)
        self.dock4.setTitleBarWidget(self.oldD4Title)

    def hideTitleBar(self):
        # self.dock1.setTitleBarWidget(self.noTitle1)
        self.dock2.setTitleBarWidget(self.noTitle2)
        self.dock3.setTitleBarWidget(self.noTitle3)
        self.dock4.setTitleBarWidget(self.noTitle4)
        self.repaint()

    @Slot()
    def insert_new_subtitle(self):
        index = self.editPanel.no.value() - 1  # Starts at 1
        numRows = self.subTablePanel.rowCount()
        tcIn = QTableWidgetItem(self.editPanel.tcIn.text())
        tcOut = QTableWidgetItem(self.editPanel.tcOut.text())
        sub = QTableWidgetItem(self.editPanel.subtitle.toPlainText())
        if not self.re_editing:
            # print(index, numRows)
            if index >= numRows:
                # print("Everything is ok, can Insert")
                self.subTablePanel.setRowCount(index + 1)
                # Insert Row Data
                self.subTablePanel.setItem(numRows, 0, tcIn)
                self.subTablePanel.setItem(numRows, 1, tcOut)
                self.subTablePanel.setItem(numRows, 2, sub)
                self.editPanel.no.setValue(index + 2)  # Increment Number
                self.editPanel.tcIn.setText(tcOut.text())
        else:
            self.subTablePanel.setItem(index, 0, tcIn)
            self.subTablePanel.setItem(index, 1, tcOut)
            self.subTablePanel.setItem(index, 2, sub)
            self.editPanel.subtitle.clear()
            self.editPanel.no.setValue(numRows + 1)
            self.editPanel.tcIn.setText(
                self.subTablePanel.item(numRows - 1, 1).text())
            self.re_editing = False
        self.editPanel.subtitle.clear()
        self.editPanel.tcOut.setText("00000000")
        self.editPanel.tcDur.setText("00000000")
        if not self.importing:
            self.setup_temp_subtitles()

    def setup_temp_subtitles(self):
        self.saveSrt(self.tmp)
        print("Temp_File:", self.tmp)
        if self.videoPanel.fileParsed:
            self.videoPanel.set_subtitle_file(self.tmp)

    @Slot()
    def open_project(self):
        # print("Opening project!")
        file_dialog = QFileDialog(self, "Open Project")
        file_dialog.setNameFilter("AngelSubTitle Project Files (*.asp)")
        file_dialog.setDefaultSuffix("asp")
        selected_file, valid = file_dialog.getOpenFileName()
        if valid:
            filename, ext = splitext(selected_file)
            if ext == ".asp":
                project = xmlET.parse(selected_file)
                video_root = project.find("./video")
                subtitle_root = project.find("./subtitle")
                self.videoPanel.loadVideoFile(video_root.text)
                self.clear_table()
                self.editPanel.no.setValue(1)
                for i, sub in enumerate(subtitle_root.findall("./en/sub")):
                    inTime, outTime, data = list(sub)
                    # print(i, inTime.text, outTime.text, data.text)
                    self.editPanel.tcIn.setText(inTime.text)
                    self.editPanel.tcOut.setText(outTime.text)
                    self.editPanel.subtitle.setText(data.text)
                    self.insert_new_subtitle()
                videofile, videoext = splitext(video_root.text)
                if not exists(f"{videofile}.srt"):
                    self.setup_temp_subtitles()
            else:
                self.updateStatusBar("Please select a valid Project File!")

    def clear_table(self):
        for i in range(self.subTablePanel.rowCount()):
            # print("removing, row", i)
            self.subTablePanel.removeCellWidget(i, 0)
            self.subTablePanel.removeCellWidget(i, 1)
            self.subTablePanel.removeCellWidget(i, 2)
        self.subTablePanel.setRowCount(0)

    @Slot()
    def row_deleted_update(self):
        self.editPanel.no.setValue(self.editPanel.no.value() - 1)

    @Slot()
    def row_added_update(self):
        self.editPanel.no.setValue(self.editPanel.no.value() + 1)

    @Slot()
    def save_project(self):
        # print("Saving project!")
        file_dialog = QFileDialog(self, "Save as")
        file_dialog.setNameFilter("AngelSubTitle Project Files (*.asp)")
        file_dialog.setDefaultSuffix("asp")
        selected_file, valid = file_dialog.getSaveFileName()
        if valid:
            filename, ext = splitext(selected_file)
            if ext != ".asp":
                # print(join(filename+".asp"))
                selected_file = f"{filename}.asp"
            project = xmlET.Element("Angel_Subtitle_Pro_Project")
            project.text = __version__
            video_root = xmlET.SubElement(project, "video")
            video_root.text = self.videoPanel.currVideoFile
            subtitle_root = xmlET.SubElement(project, "subtitle")
            sub_en = xmlET.SubElement(subtitle_root, "en")
            sub_en.text = "English"
            for i in range(self.subTablePanel.rowCount()):
                curRow = xmlET.SubElement(sub_en, "sub")
                tcIn = xmlET.SubElement(curRow, "intime")
                tcIn.text = self.subTablePanel.item(i, 0).text()
                tcOut = xmlET.SubElement(curRow, "outtime")
                tcOut.text = self.subTablePanel.item(i, 1).text()
                sub = xmlET.SubElement(curRow, "data")
                sub.text = self.subTablePanel.item(i, 2).text()
            with open(selected_file, 'w', encoding='utf-8') as fp:
                fp.write(
                    xmlET.tostring(project, encoding="unicode", method="xml"))
            self.updateStatusBar(f"File saved {selected_file}")

    @Slot()
    def export_project(self):  # Testing SubRip (SRT)
        # print("Exporting current project!")
        file_dialog = QFileDialog(self, "Save as")
        selected_file, valid = file_dialog.getSaveFileName()
        if valid:
            fileName, ext = splitext(selected_file)
            if ext == ".txt":
                self.saveAvidTxt(selected_file)
            elif ext == ".srt":
                self.saveSrt(selected_file)
            self.updateStatusBar(f"{selected_file} Exported!")

    def saveAvidTxt(self, filename):
        with open(filename, 'w', encoding='utf-8') as fp:
            fp.write("<begin subtitles>\n\n")
            for i in range(self.subTablePanel.rowCount()):
                tcIn = self.subTablePanel.item(i, 0).text()
                tcOut = self.subTablePanel.item(i, 1).text()
                sub = self.subTablePanel.item(i, 2).text()
                fp.write(f"{tcIn} {tcOut}\n")
                fp.write(f"{sub}\n")
                fp.write("\n")
            fp.write("<end subtitles>")

    def saveSrt(self, filename):
        with open(filename, 'w', encoding='utf-8') as fp:
            for i in range(self.subTablePanel.rowCount()):
                fp.write(f"{i+1}\n")
                tcIn = TimeCode(self.subTablePanel.item(i, 0).text())
                tcOut = TimeCode(self.subTablePanel.item(i, 1).text())
                sub = self.subTablePanel.item(i, 2).text()
                fp.write(f"{tcIn.get_mstc()} --> {tcOut.get_mstc()}\n")
                fp.write(f"{sub}\n")
                fp.write("\n")

    @Slot()
    def import_project(self):
        file_dialog = QFileDialog(self, "Import File")
        selected_file, valid = file_dialog.getOpenFileName()
        if valid:
            self.importing = True
            # print(f"Importing {selected_file}")
            fileName, ext = splitext(selected_file)
            if ext == ".xml":
                self.clear_table()
                self.editPanel.no.setValue(1)
                project = xmlET.parse(selected_file)
                # print(project.findall('.//generatoritem'))
                for card in project.findall('.//generatoritem'):
                    tcIn = TimeCode()
                    tcOut = TimeCode()
                    tcIn.setFrames(int(card[5].text))
                    tcOut.setFrames(int(card[6].text))
                    # print(tcIn.timecode, tcOut.timecode, card[10][6][2].text)
                    try:
                        sub = deepcopy(
                            card[10][6][2].text)  # Standard FCP Outline Text
                    except IndexError:
                        sub = deepcopy(
                            card[13][6][2].text)  # Texte avec bordure
                    self.editPanel.tcIn.setText(tcIn.timecode)
                    self.editPanel.tcOut.setText(tcOut.timecode)
                    self.editPanel.subtitle.setText(sub)
                    self.insert_new_subtitle()
                videofile = project.find(
                    ".//media/video/track/clipitem/file/pathurl")
                p = urlparse(videofile.text)
                finalPath = abspath(join(p.netloc, p.path))
                if exists(finalPath):
                    # print(f"Loading {finalPath}")
                    self.videoPanel.loadVideoFile(finalPath)
                else:
                    # print(f"File not found {finalPath}")
                    self.updateStatusBar(f"File not found {finalPath}")
                videofile, videoext = splitext(finalPath)
                if not exists(f"{videofile}.srt"):
                    self.setup_temp_subtitles()
            self.importing = False
            self.saveSrt(self.tmp)

    @Slot()
    def set_intime(self):
        if not self.videoPanel.isPlaying and self.videoPanel.fileParsed:
            self.editPanel.tcIn.setText(self.videoPanel.currPos.timecode)

    @Slot()
    def set_outtime(self):
        if not self.videoPanel.isPlaying and self.videoPanel.fileParsed:
            self.editPanel.tcOut.setText(self.videoPanel.currPos.timecode)
            self.editPanel.calculate_duration()

    @Slot()
    def edit_row(self, row_number):
        self.editPanel.no.setValue(row_number + 1)
        self.editPanel.tcIn.setText(
            self.subTablePanel.item(row_number, 0).text())
        self.editPanel.tcOut.setText(
            self.subTablePanel.item(row_number, 1).text())
        self.editPanel.calculate_duration()
        self.editPanel.subtitle.clear()
        self.editPanel.subtitle.setText(
            self.subTablePanel.item(row_number, 2).text())
        self.editPanel.clearStyles()
        self.re_editing = True

    def map(self, value, low1, high1, low2, high2):  # Value Remap function
        return low2 + (value - low1) * (high2 - low2) / (high1 - low1)

    def processWaveformSelection(self):
        try:
            wavIn, wavOut = self.waveFormPanel.selectionCtrl.getRegion()
            total_audio_frames = self.waveFormPanel.getTotalAudioFrames()
            total_video_frames = self.videoPanel.video_duration.frames
            x = self.map(int(wavIn), 0, total_audio_frames / 100, 0,
                         total_video_frames)  # Because audio filtering
            y = self.map(
                int(wavOut), 0, total_audio_frames / 100, 0,
                total_video_frames)  # of 100 in wavformPanel.py Line 83
            selIn = TimeCode()
            selIn.setFrames(int(x))
            selOut = TimeCode()
            selOut.setFrames(int(y))
            # print("Selected ->", wavIn, wavOut, "->", x, y, "->",selIn, selOut, "->", selIn.frames, selOut.frames)
            self.editPanel.tcIn.setText(selIn.timecode)
            self.editPanel.tcOut.setText(selOut.timecode)
            self.editPanel.calculate_duration()
        except AttributeError:
            print("please make sure you have a video and audio file loaded!")

    def closeEvent(self, event):
        # print("closing now!")
        self._tmp.close()
        os.unlink(self._tmp.name)
Exemplo n.º 18
0
class MainWindow(QMainWindow):
    def __init__(self, parent=None):
        # super().__init__()
        QMainWindow.__init__(self, parent)
        # const value
        self.dequeMax = 1000
        self.notchCutOff = 60
        self.notchQualityFactor = 15
        self.lowPassCutOff = 50
        self.lowPassOrder = 8
        self.samplingRate = 500
        self.two_16 = pow(2, 16)
        self.two_8 = pow(2, 8)
        self.max_uv = 407
        self.two_resolution = 8388607
        self.rawGraphFrame = 25
        self.update_num = 20
        self.timerCounter = 0

        # value
        self.measure_time = 0
        self.timerCount = 0
        self.printIndex = 0
        self.headerCount = 0
        self.ch1_1_value = 0
        self.ch1_2_value = 0
        self.ch1_3_value = 0
        self.ch2_1_value = 0
        self.ch2_2_value = 0
        self.ch2_3_value = 0
        self.dataIndex = 0
        self.read_state = parsingState.header1
        self.ptr = 0
        self.ptrFilter = 0
        self.ptrTime = 0
        self.boolPaused = True

        # UI
        self.pgWidget = QWidget()
        self.setCentralWidget(self.pgWidget)
        self.setGeometry(QRect(250, 120, 1600, 820))

        self.dockingWidget = QDockWidget("개발용 텍스트")
        self.listWidget = QListWidget()
        self.listWidget.setFont("Courier")
        self.dockingWidget.setWidget(self.listWidget)
        self.dockingWidget.setAllowedAreas(Qt.LeftDockWidgetArea
                                           | Qt.RightDockWidgetArea)
        self.dockingWidget.setFloating(False)
        self.addDockWidget(Qt.RightDockWidgetArea, self.dockingWidget)

        self.vBoxLayout2 = QVBoxLayout(self.pgWidget)
        self.lLabel = QLabel("블루투스 자동연결을 실행합니다.")
        self.lLabel.resize(200, 45)
        self.lLabel.setMaximumHeight(45)
        self.lLabel.setMinimumHeight(30)
        self.lLabel.setStyleSheet("color: blue;"
                                  "border-style: solid;"
                                  "border-width: 1px;"
                                  "border-color: #c1f7fe;"
                                  "border-radius: 3px;"
                                  "background-color: #F7FFFE;")
        font1 = self.lLabel.font()
        font1.setPointSize(30)
        font1.setFamily('Times New Roman')
        font1.setBold(True)
        self.lLabel.setFont(font1)

        self.setWindowTitle('Brint Monitor')
        self.setWindowIcon(QIcon("./images/brainGreen.png"))

        # grpah
        self.ax3 = pg.PlotWidget()
        self.ax3.setMaximumHeight(340)
        self.ax3.setMinimumHeight(250)
        self.ax3.setDownsampling(mode='peak')
        self.ax3.setTitle("좌측 뇌파", color='w')
        self.ax3.setClipToView(True)
        self.ax3.setLabel('left', "뇌파 [uV]", color='white')
        self.ax3.setLabel('bottom', '시간 [초]', color='white')
        self.ax3.setRange(xRange=[-10, 0], yRange=[-150, 150])

        self.ax4 = pg.PlotWidget()
        self.ax4.setMaximumHeight(340)
        self.ax4.setMinimumHeight(250)
        self.ax4.setDownsampling(mode='peak')
        self.ax4.setTitle("우측 뇌파", color='w')
        self.ax4.setClipToView(True)
        self.ax4.setRange(xRange=[-10, 0], yRange=[-150, 150])
        self.ax4.setLabel('left', '뇌파 [uV]', color='white')
        self.ax4.setLabel('bottom', '시간 [초]', color='white', size=30)
        self.vBoxLayout2.addWidget(self.ax3)
        self.vBoxLayout2.addWidget(self.ax4)
        self.pen = pg.mkPen(color=(255, 0, 0))
        self.line3 = self.ax3.plot(pen=self.pen)
        self.line4 = self.ax4.plot()
        self.data3 = np.zeros(500)
        self.data3_x = np.linspace(0, 499, 500) * 0.002
        self.data4 = np.zeros(500)
        self.data4_x = np.linspace(0, 499, 500) * 0.002
        self.vBoxLayout2.addWidget(self.lLabel)
        # database
        self.fData = deque(np.zeros(self.dequeMax), maxlen=self.dequeMax)
        self.fData2 = deque(np.zeros(self.dequeMax), maxlen=self.dequeMax)
        self.buffer = []
        self.ch1_int_buffer = []
        self.ch2_int_buffer = []

        # xml
        self.user = ET.Element("userName")

        # bluetooth
        self.scanner = BleakScanner()
        self.macAddress = " "
        self.Read_UUID = "0000fff1-0000-1000-8000-00805f9b34fb"
        self.Rx_UUID = "a9da6040-0823-4995-94ec-9ce41ca28833"
        self.Tx_UUID = "a73e9a10-628f-4494-a099-12efaf72258f"
        self.client = None
        # "74:F0:7D:C0:52:0C"
        self.panaxAddress = "PAPA"  # "BGX-76DE"
        self.find_device = False
        self.noexcept = False
        self.conBool = False
        self.autoScan()
        # event
        # create actions, file menu action
        self.save = QAction("&Save", self)
        self.save.setIcon(QIcon("./images/saveBlue.png"))
        self.save.setShortcut("Ctrl+S")
        self.save.setStatusTip("Save .xml file")
        self.save.triggered.connect(self.save_rx)

        self.exitAction = QAction("E&xit", self)
        self.exitAction.setIcon(QIcon("./images/Quit.png"))
        self.exitAction.setShortcut("Ctrl+Q")
        self.exitAction.setStatusTip("Exit the application")
        self.exitAction.triggered.connect(self.close)

        # control menu action
        self.start = QAction("S&tart", self)
        self.start.setIcon(QIcon("./images/recordRed.png"))
        self.start.setStatusTip("측정을 시작합니다")
        self.start.triggered.connect(self.startDialog)

        self.paused = QAction("P&ause", self)
        self.paused.setIcon(QIcon("./images/pauseBlue.png"))
        self.paused.setStatusTip("측정을 정지합니다")
        self.paused.triggered.connect(self.pausedMeasure)

        self.stop = QAction("&Stop", self)
        self.stop.setIcon(QIcon("./images/stopBlue.png"))
        self.stop.setStatusTip("측정을 멈춤니다")
        self.stop.triggered.connect(self.rx_stop)

        # view menu action

        # about
        self.aboutAction = QAction("&About", self)
        self.aboutAction.setStatusTip("Show the application's About box")
        self.aboutAction.triggered.connect(self.about)

        # createMenus
        fileMenu = self.menuBar().addMenu("&File")
        fileMenu.addAction(self.save)
        # fileMenu.addAction(self.load)
        fileMenu.addAction(self.exitAction)
        ControlMenu = self.menuBar().addMenu("&Control")
        ControlMenu.addAction(self.start)
        ControlMenu.addAction(self.paused)
        ControlMenu.addAction(self.stop)

        helpMenu = self.menuBar().addMenu("&Help")
        helpMenu.addAction(self.aboutAction)

        # createToolBar
        pgToolBar = self.addToolBar("&PG")
        pgToolBar.setObjectName("PGToolBar")
        pgToolBar.addAction(self.save)
        pgToolBar.addSeparator()

        pgToolBar.addAction(self.start)
        pgToolBar.addAction(self.paused)
        pgToolBar.addAction(self.stop)

        pgToolBar.addSeparator()
        pgToolBar.addAction(self.aboutAction)

        pgToolBar2 = self.addToolBar("PG2")
        pgToolBar2.setObjectName("PGToolBar2")

        # createStatusBar
        '''
        lLabel = QLabel("status bar")
        #lLabel.setAlignment(Qt.AlignHCenter)
        #lLabel.setMinimumSize(lLabel.sizeHint())
        self.locationLabel = QLabel("test")
        self.locationLabel.setMinimumWidth(100)
        self.statusBar().setMinimumHeight(50)
        self.statusBar().addWidget(lLabel)
        self.statusBar().addWidget(self.locationLabel)
        '''

    def readSettings(self):
        settings = QSettings("Panaxtos", "newneuroSpec_Demo")  # modify

        self.restoreGeometry(settings.value("geometry"))
        self.restoreState(settings.value("state"))

    def writeSettings(self):
        settings = QSettings("Qt5Programming Inc.", "Shape")
        self.saveGeometry()
        settings.setValue("geometry", self.saveGeometry())
        settings.setValue("state", self.saveState())

    def autoScan(self):
        asyncio.ensure_future(self.scan_start(), loop=loop)
        print('btnCallback returns...')

    def detection_callback(*args):
        print(args)

    async def scan_start(self):
        self.scanner.register_detection_callback(self.detection_callback)
        self.lLabel.setText("PANAXTOS 기기를 Scan 중....")
        run_time = 0
        start_time = time()
        while run_time < 16:
            await self.scanner.start()
            await asyncio.sleep(5.0)
            await self.scanner.stop()
            devices = await self.scanner.get_discovered_devices()
            if not devices:
                self.lLabel.setText("bluetooth를 켜주세요")
            for d in devices:
                if d.name == self.panaxAddress:
                    self.find_device = True
                    self.macAddress = d.address
                    break
            if self.find_device:
                print("True")
                self.lLabel.setText("기기를 찾았습니다. connection을 진행합니다.")
                await self.connect_panax(self.macAddress, loop)
                break
            end_time = time()
            run_time = end_time - start_time
        if not self.find_device:
            self.lLabel.setText("기기가 켜져있는지 확인해주시고 프로그램을 다시 시작해주세요")

    async def connect_panax(self, address, loop):
        self.client = BleakClient(address, loop=loop)
        while not self.noexcept:
            try:
                await self.client.connect()
                await self.isConnected()
                self.start.setEnabled(True)
                self.noexcept = True
                self.lLabel.setText("연결이 완료 되었습니다. 측정시작 버튼을 눌러주세요")
            except Exception as e:
                print(e)
                self.lLabel.setText("Connection 중")

    async def isConnected(self):
        self.conBool = await self.client.is_connected()

    async def disconnect_panax(self):
        # await self.client.disconnect()
        await self.client.stop_notify(self.Read_UUID)

    async def start_panax(self):
        if self.conBool:
            await self.client.start_notify(self.Read_UUID,
                                           self.tx_data_received)
            # await self.client.start_notify(self.Rx_UUID, self.rx_data_received)
            # await asyncio.sleep(0.2)
            # await self.client.stop_notify(self.Rx_UUID)
            # await self.client.start_notify(self.Tx_UUID, self.tx_data_received)

    async def start_rx(self):
        if self.conBool:
            await self.client.start_notify(self.Rx_UUID, self.rx_data_received)

    async def stop_rx(self):
        await self.client.stop_notify(self.Read_UUID)

    def measureStart(self):
        asyncio.ensure_future(self.isConnected())
        asyncio.ensure_future(self.start_panax(), loop=loop)
        self.start.setDisabled(True)
        self.save.setEnabled(True)
        self.lLabel.setText("뇌파를 측정중입니다.")

    # event
    def save_xml(self):
        xml_write.indent(self.user)
        now = datetime.datetime.now()
        nowDate = now.strftime('%Y-%m-%d.%H.%M')
        nowXml = nowDate + '.xml'
        ET.ElementTree(self.user).write(nowXml)
        nowXml = "저장이 완료 되었습니다" + nowXml
        self.lLabel.setText(nowXml)

    def save_rx(self):
        asyncio.ensure_future(self.start_rx(), loop=loop)

    def rx_stop(self):
        asyncio.ensure_future(self.stop_rx(), loop=loop)

    def startDialog(self):
        sd = startdialog.Ui_dialog(self)
        if sd.exec():
            self.measure_time = sd.time_info()
            self.measureStart()

    def stopDialog(self):
        sd = stopdialog.Ui_dialog(self)
        if sd.exec():
            self.plotInit()

    def pausedMeasure(self):
        self.boolPaused = not self.boolPaused
        if not self.boolPaused:
            asyncio.ensure_future(self.disconnect_panax())
            self.paused.setIcon(QIcon("./images/playBlue.png"))
            self.paused.setStatusTip("측정을 재개합니다")
            self.lLabel.setText("측정을 정지합니다.")
        else:
            self.measureStart()
            self.paused.setIcon(QIcon("./images/pauseBlue.png"))
            self.paused.setStatusTip("측정을 정지합니다")
            self.lLabel.setText("측정을 재개합니다.")

    def plotInit(self):
        self.lLabel.setText("측정을 초기화 하였습니다. 다시 시작하려면 시작버튼을 누르시오")
        if self.boolPaused:
            self.pausedMeasure()
        self.fData.clear()
        self.fData2.clear()
        self.fData.extend(np.zeros(500))
        self.fData2.extend(np.zeros(500))
        self.ptr = 0
        self.ptrTime = 0
        self.ptrFilter = 0
        self.timerCount = 0
        self.line3.setData(np.empty(1))
        self.line4.setData(np.empty(1))
        self.ax3.setRange(xRange=[-10, 0], yRange=[-150, 150])
        self.ax4.setRange(xRange=[-10, 0], yRange=[-150, 150])
        self.boolPaused = not self.boolPaused
        self.paused.setIcon(QIcon("./images/pauseBlue.png"))
        self.paused.setStatusTip("측정을 정지합니다")
        self.start.setEnabled(True)
        self.paused.setDisabled(True)
        self.stop.setDisabled(True)

    def about(self):
        QMessageBox.about(
            self, "About Shape", "<h2>Brint Monitor 1.0</h2>"
            "<p>Copyright &copy; 2020 Panaxtos Inc."
            "<p>Shape is a small application that "
            "demonstrates QAction, QMainWindow, QMenuBar, "
            "QStatusBar, QToolBar, and many other ")

    # data_received -> parsing -> int -> 20 -> print

    def rx_data_received(self, sender, data):
        print("RX {}".format(data))

    def tx_data_received(self, sender, data):
        data_len = len(data)
        for rep in range(data_len):
            self.buffer.append(data[rep])
        self.print_data()
        '''data_len = len(data)
        for rep in range(data_len):
            self.buffer.append(data[rep])
        self.read_data()
        if len(self.ch1_int_buffer) >= self.update_num and len(self.ch2_int_buffer) >= self.update_num:
            self.print_graph()
        '''

    def print_data(self):
        while len(self.buffer) > 0:
            temp = self.buffer.pop(0)
            if self.read_state == parsingState.header1:
                if temp == 255:
                    self.read_state = parsingState.header2
            elif self.read_state == parsingState.header2:
                if temp == 119:
                    self.read_state = parsingState.header3
            elif self.read_state == parsingState.header3:
                self.headerCount = temp
                self.read_state = parsingState.ch1_1
            elif self.read_state == parsingState.ch1_1:
                self.ch1_1_value = temp
                self.read_state = parsingState.ch1_2
            elif self.read_state == parsingState.ch1_2:
                self.ch1_2_value = temp
                self.read_state = parsingState.ch1_3
            elif self.read_state == parsingState.ch1_3:
                self.ch1_3_value = temp
                self.read_state = parsingState.ch2_1
            elif self.read_state == parsingState.ch2_1:
                self.ch2_1_value = temp
                self.read_state = parsingState.ch2_2
            elif self.read_state == parsingState.ch2_2:
                self.ch2_2_value = temp
                self.read_state = parsingState.ch2_3
            elif self.read_state == parsingState.ch2_3:
                self.ch2_3_value = temp
                stringFF = hex(255)
                string77 = hex(119)
                hCount = "0x{:02x}".format(self.headerCount)
                ch1_1 = "0x{:02x}".format(self.ch1_1_value)
                ch1_2 = "0x{:02x}".format(self.ch1_2_value)
                ch1_3 = "0x{:02x}".format(self.ch1_3_value)
                ch2_1 = "0x{:02x}".format(self.ch2_1_value)
                ch2_2 = "0x{:02x}".format(self.ch2_2_value)
                ch2_3 = "0x{:02x}".format(self.ch2_3_value)
                ss = stringFF + " " + string77 + " " + hCount + " " + ch1_1 + " " + ch1_2 + " " + ch1_3 + " " + ch2_1 \
                    + " " + ch2_2 + " " + ch2_3
                self.listWidget.addItem(ss)
                self.printIndex += 1
                if self.printIndex > 40:
                    self.listWidget.takeItem(0)
                self.read_state = parsingState.header1

    def read_data(self):
        while len(self.buffer) > 0:
            temp = self.buffer.pop(0)
            if self.read_state == parsingState.header1:
                if temp == 255:
                    self.read_state = parsingState.header2
            elif self.read_state == parsingState.header2:
                if temp == 119:
                    self.read_state = parsingState.header3
            elif self.read_state == parsingState.header3:
                if temp == 255:
                    self.read_state = parsingState.ch1_1
            elif self.read_state == parsingState.ch1_1:
                self.ch1_1_value = temp
                self.read_state = parsingState.ch1_2
            elif self.read_state == parsingState.ch1_2:
                self.ch1_2_value = temp
                self.read_state = parsingState.ch1_3
            elif self.read_state == parsingState.ch1_3:
                self.ch1_3_value = temp
                self.read_state = parsingState.ch2_1
            elif self.read_state == parsingState.ch2_1:
                self.ch2_1_value = temp
                self.read_state = parsingState.ch2_2
            elif self.read_state == parsingState.ch2_2:
                self.ch2_2_value = temp
                self.read_state = parsingState.ch2_3
            elif self.read_state == parsingState.ch2_3:
                self.ch2_3_value = temp
                ch1_int = (self.ch1_1_value * self.two_16) + (
                    self.ch1_2_value * self.two_8) + self.ch1_3_value
                ch1_int = tc.twos_comp(ch1_int, 24)
                ch1_int = (ch1_int * self.max_uv) / self.two_resolution
                self.ch1_int_buffer.append(ch1_int)
                ch2_int = (self.ch2_1_value * self.two_16) + (
                    self.ch2_2_value * self.two_8) + self.ch2_3_value
                ch2_int = tc.twos_comp(ch2_int, 24)
                ch2_int = (ch2_int * self.max_uv) / self.two_resolution
                self.ch2_int_buffer.append(ch2_int)
                self.dataIndex += 1
                data = xml_write.makeXML(self.dataIndex, ch1_int, ch2_int)
                self.user.append(data)
                self.read_state = parsingState.header1

    def print_graph(self):
        ch1 = []
        ch2 = []
        for rep in range(0, self.update_num):
            temp = self.ch1_int_buffer.pop(0)
            temp2 = self.ch2_int_buffer.pop(0)
            ch1.append(temp)
            ch2.append(temp2)
        self.fData.extend(ch1)
        self.fData2.extend(ch2)

        notch_ch1 = nf.notch_filter(self.fData, self.notchCutOff,
                                    self.samplingRate, self.notchQualityFactor)
        notch_ch2 = nf.notch_filter(self.fData2, self.notchCutOff,
                                    self.samplingRate, self.notchQualityFactor)
        notch_ch1 = nf.notch_filter(notch_ch1, self.notchCutOff,
                                    self.samplingRate, self.notchQualityFactor)
        notch_ch2 = nf.notch_filter(notch_ch2, self.notchCutOff,
                                    self.samplingRate, self.notchQualityFactor)
        notchLowPass_ch1 = lf.butter_lowpass_filter(notch_ch1,
                                                    self.lowPassCutOff,
                                                    self.samplingRate,
                                                    self.lowPassOrder)
        notchLowPass_ch2 = lf.butter_lowpass_filter(notch_ch2,
                                                    self.lowPassCutOff,
                                                    self.samplingRate,
                                                    self.lowPassOrder)
        filtering_ch1 = lf.butter_lowpass_filter(notchLowPass_ch1,
                                                 self.lowPassCutOff,
                                                 self.samplingRate,
                                                 self.lowPassOrder)
        filtering_ch2 = lf.butter_lowpass_filter(notchLowPass_ch2,
                                                 self.lowPassCutOff,
                                                 self.samplingRate,
                                                 self.lowPassOrder)
        # self.fData3.extend(filtering_ch1[-self.update_num:])
        # self.fData4.extend(filtering_ch2[-self.update_num:])
        self.data3[self.ptrFilter:self.ptrFilter +
                   self.update_num] = filtering_ch1[-self.update_num:]
        self.data4[self.ptrFilter:self.ptrFilter +
                   self.update_num] = filtering_ch2[-self.update_num:]
        self.ptrFilter += self.update_num
        if self.ptrFilter >= self.data3.shape[0]:
            tmp = self.data3
            tmp2 = self.data4
            self.data3 = np.zeros(self.data3.shape[0] * 2)
            self.data4 = np.zeros(self.data3.shape[0] * 2)
            self.data3[:tmp.shape[0]] = tmp
            self.data4[:tmp2.shape[0]] = tmp2
            self.data3_x = np.linspace(0, self.data3.shape[0] - 1,
                                       self.data3.shape[0]) * 0.002
            self.data4_x = np.linspace(0, self.data3.shape[0] - 1,
                                       self.data3.shape[0]) * 0.002
        self.line3.setData(x=self.data3_x[:self.ptrFilter],
                           y=self.data3[:self.ptrFilter])
        self.line3.setPos(-self.ptrFilter * 0.002, 0)
        self.line4.setData(x=self.data4_x[:self.ptrFilter],
                           y=self.data4[:self.ptrFilter])
        self.line4.setPos(-self.ptrFilter * 0.002, 0)
        self.timerCount += 0.04
        if self.timerCount > self.measure_time:
            self.pausedMeasure()
            self.paused.setDisabled(True)
            self.lLabel.setText("측정이 끝났습니다. 수고하셨습니다.")
Exemplo n.º 19
0
class View(QMainWindow):
    """View component of the MVC application.

    Presents the state of the application as well as the available means of
    interaction. Receives updates about the state from the Model and informs
    Controller about user interactions.

    """
    def __init__(self, model, controller):
        """Define GUI elements and their layout.

        Parameters
        ----------
        model : QObject
            Model component of the MVC application.
        controller : QObject
            Controller component of the MVC application.
        """
        super().__init__()

        self._model = model
        self._controller = controller
        self.segmentcursor = False
        self.togglecolors = {"#1f77b4": "m", "m": "#1f77b4"}

        self.setWindowTitle("biopeaks")
        self.setGeometry(50, 50, 1750, 750)
        self.setWindowIcon(QIcon(":/python_icon.png"))

        # Figure for biosignal.
        self.figure0 = Figure()
        self.canvas0 = FigureCanvas(self.figure0)
        # Enforce minimum height, otherwise resizing with self.splitter causes
        # mpl to throw an error because figure is resized to height 0. The
        # widget can still be fully collapsed with self.splitter.
        self.canvas0.setMinimumHeight(1)  # in pixels
        self.ax00 = self.figure0.add_subplot(1, 1, 1)
        self.ax00.set_frame_on(False)
        self.figure0.subplots_adjust(left=0.04, right=0.98, bottom=0.25)
        self.line00 = None
        self.scat = None
        self.segmentspan = None

        # Figure for marker.
        self.figure1 = Figure()
        self.canvas1 = FigureCanvas(self.figure1)
        self.canvas1.setMinimumHeight(1)
        self.ax10 = self.figure1.add_subplot(1, 1, 1, sharex=self.ax00)
        self.ax10.get_xaxis().set_visible(False)
        self.ax10.set_frame_on(False)
        self.figure1.subplots_adjust(left=0.04, right=0.98)
        self.line10 = None

        # Figure for statistics.
        self.figure2 = Figure()
        self.canvas2 = FigureCanvas(self.figure2)
        self.canvas2.setMinimumHeight(1)
        self.ax20 = self.figure2.add_subplot(3, 1, 1, sharex=self.ax00)
        self.ax20.get_xaxis().set_visible(False)
        self.ax20.set_frame_on(False)
        self.line20 = None
        self.ax21 = self.figure2.add_subplot(3, 1, 2, sharex=self.ax00)
        self.ax21.get_xaxis().set_visible(False)
        self.ax21.set_frame_on(False)
        self.line21 = None
        self.ax22 = self.figure2.add_subplot(3, 1, 3, sharex=self.ax00)
        self.ax22.get_xaxis().set_visible(False)
        self.ax22.set_frame_on(False)
        self.line22 = None
        self.figure2.subplots_adjust(left=0.04, right=0.98)

        self.navitools = CustomNavigationToolbar(self.canvas0, self)

        # Peak editing.
        self.editcheckbox = QCheckBox("editable", self)
        self.editcheckbox.stateChanged.connect(self._model.set_peakseditable)

        # Peak saving during batch processing.
        self.savecheckbox = QCheckBox("save during batch processing", self)
        self.savecheckbox.stateChanged.connect(self._model.set_savebatchpeaks)

        # Peak auto-correction during batch processing.
        self.correctcheckbox = QCheckBox("correct during batch processing",
                                         self)
        self.correctcheckbox.stateChanged.connect(
            self._model.set_correctbatchpeaks)

        # Selection of stats for saving.
        self.periodcheckbox = QCheckBox("period", self)
        self.periodcheckbox.stateChanged.connect(
            lambda: self.select_stats("period"))
        self.ratecheckbox = QCheckBox("rate", self)
        self.ratecheckbox.stateChanged.connect(
            lambda: self.select_stats("rate"))
        self.tidalampcheckbox = QCheckBox("tidal amplitude", self)
        self.tidalampcheckbox.stateChanged.connect(
            lambda: self.select_stats("tidalamp"))

        # Channel selection.
        self.sigchanmenulabel = QLabel("biosignal")
        self.sigchanmenu = QComboBox(self)
        self.sigchanmenu.addItem("A1")
        self.sigchanmenu.addItem("A2")
        self.sigchanmenu.addItem("A3")
        self.sigchanmenu.addItem("A4")
        self.sigchanmenu.addItem("A5")
        self.sigchanmenu.addItem("A6")
        self.sigchanmenu.currentTextChanged.connect(self._model.set_signalchan)
        self._model.set_signalchan(
            self.sigchanmenu.currentText())  # initialize with default value

        self.markerchanmenulabel = QLabel("marker")
        self.markerchanmenu = QComboBox(self)
        self.markerchanmenu.addItem("none")
        self.markerchanmenu.addItem("I1")
        self.markerchanmenu.addItem("I2")
        self.markerchanmenu.addItem("A1")
        self.markerchanmenu.addItem("A2")
        self.markerchanmenu.addItem("A3")
        self.markerchanmenu.addItem("A4")
        self.markerchanmenu.addItem("A5")
        self.markerchanmenu.addItem("A6")
        self.markerchanmenu.currentTextChanged.connect(
            self._model.set_markerchan)
        self._model.set_markerchan(self.markerchanmenu.currentText())

        # Processing mode.
        self.batchmenulabel = QLabel("mode")
        self.batchmenu = QComboBox(self)
        self.batchmenu.addItem("single file")
        self.batchmenu.addItem("multiple files")
        self.batchmenu.currentTextChanged.connect(self._model.set_batchmode)
        self.batchmenu.currentTextChanged.connect(self.toggle_options)
        self._model.set_batchmode(self.batchmenu.currentText())
        self.toggle_options(self.batchmenu.currentText())

        # Modality selection.
        self.modmenulabel = QLabel("modality")
        self.modmenu = QComboBox(self)
        self.modmenu.addItem("ECG")
        self.modmenu.addItem("PPG")
        self.modmenu.addItem("RESP")
        self.modmenu.currentTextChanged.connect(self._model.set_modality)
        self.modmenu.currentTextChanged.connect(self.toggle_options)
        self._model.set_modality(self.modmenu.currentText())
        self.toggle_options(self.modmenu.currentText())

        # Segment selection. This widget can be openend / set visible from
        # the menu and closed from within itself (see mapping of segmentermap).
        self.segmentermap = QSignalMapper(self)
        self.segmenter = QDockWidget("select a segment", self)
        self.segmenter.setFeatures(
            QDockWidget.NoDockWidgetFeatures
        )  # disable closing such that widget can only be closed by confirming selection or custom button
        regex = QRegExp(
            "[0-9]*\.?[0-9]{4}")  # Limit number of decimals to four

        validator = QRegExpValidator(regex)

        self.startlabel = QLabel("start")
        self.startedit = QLineEdit()
        self.startedit.setValidator(validator)

        self.endlabel = QLabel("end")
        self.endedit = QLineEdit()
        self.endedit.setValidator(validator)

        segmentfromcursor = QAction(QIcon(":/mouse_icon.png"),
                                    "select with mouse", self)
        segmentfromcursor.triggered.connect(self.enable_segmentedit)
        self.startedit.addAction(segmentfromcursor, QLineEdit.TrailingPosition)
        self.endedit.addAction(segmentfromcursor, QLineEdit.TrailingPosition)

        self.previewedit = QPushButton("preview segment")
        lambdafn = lambda: self._model.set_segment(
            [self.startedit.text(), self.endedit.text()])
        self.previewedit.clicked.connect(lambdafn)

        self.confirmedit = QPushButton("confirm segment")
        self.confirmedit.clicked.connect(self._controller.segment_dataset)
        self.confirmedit.clicked.connect(self.segmentermap.map)
        self.segmentermap.setMapping(self.confirmedit, 0)

        self.abortedit = QPushButton("abort segmentation")
        self.abortedit.clicked.connect(self.segmentermap.map)
        self.segmentermap.setMapping(self.abortedit,
                                     2)  # resets the segment to None

        self.segmenterlayout = QFormLayout()
        self.segmenterlayout.addRow(self.startlabel, self.startedit)
        self.segmenterlayout.addRow(self.endlabel, self.endedit)
        self.segmenterlayout.addRow(self.previewedit)
        self.segmenterlayout.addRow(self.confirmedit)
        self.segmenterlayout.addRow(self.abortedit)
        self.segmenterwidget = QWidget()
        self.segmenterwidget.setLayout(self.segmenterlayout)
        self.segmenter.setWidget(self.segmenterwidget)

        self.segmenter.setVisible(False)
        self.segmenter.setAllowedAreas(Qt.RightDockWidgetArea)
        self.addDockWidget(Qt.RightDockWidgetArea, self.segmenter)

        # Custom file dialog.
        regex = QRegExp("[1-9][0-9]")
        validator = QRegExpValidator(regex)

        self.signallabel = QLabel("biosignal column")
        self.signaledit = QLineEdit()
        self.signaledit.setValidator(validator)

        self.markerlabel = QLabel("marker column")
        self.markeredit = QLineEdit()
        self.markeredit.setValidator(validator)

        regex = QRegExp("[0-9]{2}")
        validator = QRegExpValidator(regex)

        self.headerrowslabel = QLabel("number of header rows")
        self.headerrowsedit = QLineEdit()
        self.headerrowsedit.setValidator(validator)

        regex = QRegExp("[0-9]{5}")
        validator = QRegExpValidator(regex)

        self.sfreqlabel = QLabel("sampling rate")
        self.sfreqedit = QLineEdit()
        self.sfreqedit.setValidator(validator)

        self.separatorlabel = QLabel("column separator")
        self.separatormenu = QComboBox(self)
        self.separatormenu.addItem("comma")
        self.separatormenu.addItem("tab")
        self.separatormenu.addItem("colon")
        self.separatormenu.addItem("space")

        self.continuecustomfile = QPushButton("continue loading file")
        self.continuecustomfile.clicked.connect(self.set_customheader)

        self.customfiledialog = QDialog()
        self.customfiledialog.setWindowTitle("custom file info")
        self.customfiledialog.setWindowIcon(QIcon(":/file_icon.png"))
        self.customfiledialog.setWindowFlags(
            Qt.WindowCloseButtonHint
        )  # remove help button by only setting close button
        self.customfilelayout = QFormLayout()
        self.customfilelayout.addRow(self.signallabel, self.signaledit)
        self.customfilelayout.addRow(self.markerlabel, self.markeredit)
        self.customfilelayout.addRow(self.separatorlabel, self.separatormenu)
        self.customfilelayout.addRow(self.headerrowslabel, self.headerrowsedit)
        self.customfilelayout.addRow(self.sfreqlabel, self.sfreqedit)
        self.customfilelayout.addRow(self.continuecustomfile)
        self.customfiledialog.setLayout(self.customfilelayout)

        # Layout.
        menubar = self.menuBar()

        signalmenu = menubar.addMenu("biosignal")

        openSignal = signalmenu.addMenu("load")
        openEDF = QAction("EDF", self)
        openEDF.triggered.connect(lambda: self._model.set_filetype("EDF"))
        openEDF.triggered.connect(self._controller.load_channels)
        openSignal.addAction(openEDF)
        openOpenSignals = QAction("OpenSignals", self)
        openOpenSignals.triggered.connect(
            lambda: self._model.set_filetype("OpenSignals"))
        openOpenSignals.triggered.connect(self._controller.load_channels)
        openSignal.addAction(openOpenSignals)
        openCustom = QAction("Custom", self)
        openCustom.triggered.connect(
            lambda: self._model.set_filetype("Custom"))
        openCustom.triggered.connect(lambda: self.customfiledialog.exec_())
        openSignal.addAction(openCustom)

        segmentSignal = QAction("select segment", self)
        segmentSignal.triggered.connect(self.segmentermap.map)
        self.segmentermap.setMapping(segmentSignal, 1)
        signalmenu.addAction(segmentSignal)

        self.segmentermap.mapped.connect(self.toggle_segmenter)

        saveSignal = QAction("save", self)
        saveSignal.triggered.connect(self._controller.save_channels)
        signalmenu.addAction(saveSignal)

        peakmenu = menubar.addMenu("peaks")

        findPeaks = QAction("find", self)
        findPeaks.triggered.connect(self._controller.find_peaks)
        peakmenu.addAction(findPeaks)

        autocorrectPeaks = QAction("autocorrect", self)
        autocorrectPeaks.triggered.connect(self._controller.autocorrect_peaks)
        peakmenu.addAction(autocorrectPeaks)

        savePeaks = QAction("save", self)
        savePeaks.triggered.connect(self._controller.save_peaks)
        peakmenu.addAction(savePeaks)

        loadPeaks = QAction("load", self)
        loadPeaks.triggered.connect(self._controller.load_peaks)
        peakmenu.addAction(loadPeaks)

        statsmenu = menubar.addMenu("statistics")

        calculateStats = QAction("calculate", self)
        calculateStats.triggered.connect(self._controller.calculate_stats)
        statsmenu.addAction(calculateStats)

        saveStats = QAction("save", self)
        saveStats.triggered.connect(self._controller.save_stats)
        statsmenu.addAction(saveStats)

        self.statusBar = QStatusBar()
        self.setStatusBar(self.statusBar)
        self.progressBar = QProgressBar(self)
        self.progressBar.setRange(0, 1)
        self.statusBar.addPermanentWidget(self.progressBar)
        self.currentFile = QLabel()
        self.statusBar.addPermanentWidget(self.currentFile)

        self.centwidget = QWidget()  # contains figures and navigationtoolbar
        self.setCentralWidget(self.centwidget)

        self.canvas0.setFocusPolicy(
            Qt.ClickFocus
        )  # only widgets (e.g. canvas) that currently have focus capture keyboard input
        self.canvas0.setFocus()
        self.canvas0.mpl_connect(
            "key_press_event", self._controller.edit_peaks
        )  # connect canvas to keyboard input for peak editing
        self.canvas0.mpl_connect(
            "button_press_event",
            self.get_xcursor)  # connect canvas to mouse input for peak editing

        self.splitter = QSplitter(
            Qt.Vertical
        )  # arrange the three figure canvases in splitter object
        self.splitter.setOpaqueResize(
            False)  # resizing gets very slow otherwise once axes are populated
        self.splitter.addWidget(self.canvas0)
        self.splitter.addWidget(self.canvas1)
        self.splitter.addWidget(self.canvas2)
        self.splitter.setChildrenCollapsible(False)

        self.vlayout0 = QVBoxLayout(self.centwidget)
        self.vlayout1 = QVBoxLayout()
        self.vlayoutA = QFormLayout()
        self.vlayoutB = QFormLayout()
        self.vlayoutC = QVBoxLayout()
        self.vlayoutD = QVBoxLayout()
        self.hlayout0 = QHBoxLayout()

        self.optionsgroupA = QGroupBox("processing options")
        self.vlayoutA.addRow(self.modmenulabel, self.modmenu)
        self.vlayoutA.addRow(self.batchmenulabel, self.batchmenu)
        self.optionsgroupA.setLayout(self.vlayoutA)

        self.optionsgroupB = QGroupBox("channels")
        self.vlayoutB.addRow(self.sigchanmenulabel, self.sigchanmenu)
        self.vlayoutB.addRow(self.markerchanmenulabel, self.markerchanmenu)
        self.optionsgroupB.setLayout(self.vlayoutB)

        self.optionsgroupC = QGroupBox("peaks")
        self.vlayoutC.addWidget(self.editcheckbox)
        self.vlayoutC.addWidget(self.savecheckbox)
        self.vlayoutC.addWidget(self.correctcheckbox)
        self.optionsgroupC.setLayout(self.vlayoutC)

        self.optionsgroupD = QGroupBox("select statistics for saving")
        self.vlayoutD.addWidget(self.periodcheckbox)
        self.vlayoutD.addWidget(self.ratecheckbox)
        self.vlayoutD.addWidget(self.tidalampcheckbox)
        self.optionsgroupD.setLayout(self.vlayoutD)

        self.vlayout1.addWidget(self.optionsgroupA)
        self.vlayout1.addWidget(self.optionsgroupB)
        self.vlayout1.addWidget(self.optionsgroupC)
        self.vlayout1.addWidget(self.optionsgroupD)
        self.optionsgroupwidget = QWidget()
        self.optionsgroupwidget.setLayout(self.vlayout1)
        self.optionsgroup = QDockWidget("configurations", self)
        self.optionsgroup.setAllowedAreas(Qt.LeftDockWidgetArea)
        self.toggleoptionsgroup = self.optionsgroup.toggleViewAction()
        self.toggleoptionsgroup.setText("show/hide configurations")
        menubar.addAction(self.toggleoptionsgroup)
        self.optionsgroup.setWidget(self.optionsgroupwidget)
        self.addDockWidget(Qt.LeftDockWidgetArea, self.optionsgroup)

        self.vlayout0.addWidget(self.splitter)

        self.hlayout0.addWidget(self.navitools)
        self.vlayout0.addLayout(self.hlayout0)

        # Subscribe to updates from the Model.
        self._model.signal_changed.connect(self.plot_signal)
        self._model.marker_changed.connect(self.plot_marker)
        self._model.peaks_changed.connect(self.plot_peaks)
        self._model.period_changed.connect(self.plot_period)
        self._model.rate_changed.connect(self.plot_rate)
        self._model.tidalamp_changed.connect(self.plot_tidalamp)
        self._model.path_changed.connect(self.display_path)
        self._model.segment_changed.connect(self.plot_segment)
        self._model.status_changed.connect(self.display_status)
        self._model.progress_changed.connect(self.display_progress)
        self._model.model_reset.connect(self.reset_plot)

    def plot_signal(self, signal):
        """Plot the biosignal.

        Receives updates in signal from Model.

        Parameters
        ----------
        signal : ndarray of float
            Vector representing the biosignal.

        See Also
        --------
        model.Model.signal
        """
        self.ax00.clear()
        self.ax00.relim()
        self.navitools.update()  # reset navitools history
        self.line00 = self.ax00.plot(self._model.sec, signal, zorder=1)
        self.ax00.set_xlabel("seconds", fontsize="large", fontweight="heavy")
        self.canvas0.draw()

    def plot_peaks(self, peaks):
        """Plot the extrema.

        Receives updates in peaks from Model.

        Parameters
        ----------
        peaks : ndarray of int
            Vector representing the extrema.

        See Also
        --------
        model.Model.peaks
        """
        if self.ax00.collections:  # self.scat is listed in ax.collections
            self.ax00.collections[0].remove()
        self.scat = self.ax00.scatter(self._model.sec[peaks],
                                      self._model.signal[peaks],
                                      c="m",
                                      zorder=2)
        self.canvas0.draw()

    def plot_segment(self, segment):
        """Show preview of segment.

        Receives updates in segment from Model.

        Parameters
        ----------
        segment : list of float
            The start and end of the segment in seconds.

        See Also
        --------
        model.Model.segment
        """
        if segment is None:  # if an invalid segment has been selected reset the segmenter interface
            self.toggle_segmenter(1)
            return
        if self.ax00.patches:  # self.segementspan is listed in ax.patches
            self.ax00.patches[0].remove()
        self.segmentspan = self.ax00.axvspan(segment[0],
                                             segment[1],
                                             color="m",
                                             alpha=0.25)
        self.canvas0.draw()
        self.confirmedit.setEnabled(True)

    def plot_marker(self, marker):
        """Plot the marker channel.

        Receives updates in marker from Model.

        Parameters
        ----------
        marker : list of ndarray
            Seconds element is vector representing the marker channel and first
            element is a vector representing the seconds associated with each
            sample in the marker channel.

        See Also
        --------
        model.Model.marker
        """
        self.ax10.clear()
        self.ax10.relim()
        self.line10 = self.ax10.plot(marker[0], marker[1])
        self.canvas1.draw()

    def plot_period(self, period):
        """Plot instantaneous period.

        Receives updates in period from Model.

        Parameters
        ----------
        period : ndarray of float
            Vector representing the instantaneous period.

        See Also
        --------
        model.Model.periodintp
        """
        self.ax20.clear()
        self.ax20.relim()
        self.navitools.home()
        if self._model.savestats["period"]:
            self.line20 = self.ax20.plot(self._model.sec, period, c="m")
        else:
            self.line20 = self.ax20.plot(self._model.sec, period)
        self.ax20.set_ylim(bottom=min(period), top=max(period))
        self.ax20.set_title("period", pad=0, fontweight="heavy")
        self.ax20.grid(True, axis="y")
        self.navitools.update()
        self.canvas2.draw()

    def plot_rate(self, rate):
        """Plot instantaneous rate.

        Receives updates in rate from Model.

        Parameters
        ----------
        rate : ndarray of float
            Vector representing the instantaneous rate.

        See Also
        --------
        model.Model.rateintp
        """
        self.ax21.clear()
        self.ax21.relim()
        self.navitools.home()
        if self._model.savestats["rate"]:
            self.line21 = self.ax21.plot(self._model.sec, rate, c="m")
        else:
            self.line21 = self.ax21.plot(self._model.sec, rate)
        self.ax21.set_ylim(bottom=min(rate), top=max(rate))
        self.ax21.set_title("rate", pad=0, fontweight="heavy")
        self.ax21.grid(True, axis="y")
        self.navitools.update()
        self.canvas2.draw()

    def plot_tidalamp(self, tidalamp):
        """Plot instantaneous tidal amplitude.

        Receives updates in tidal amplitude from Model.

        Parameters
        ----------
        tidalamp : ndarray of float
            Vector representing the instantaneous tidal amplitude.

        See Also
        --------
        model.Model.tidalampintp
        """
        self.ax22.clear()
        self.ax22.relim()
        self.navitools.home()
        if self._model.savestats["tidalamp"]:
            self.line22 = self.ax22.plot(self._model.sec, tidalamp, c="m")
        else:
            self.line22 = self.ax22.plot(self._model.sec, tidalamp)
        self.ax22.set_ylim(bottom=min(tidalamp), top=max(tidalamp))
        self.ax22.set_title("amplitude", pad=0, fontweight="heavy")
        self.ax22.grid(True, axis="y")
        self.navitools.update()
        self.canvas2.draw()

    def display_path(self, path):
        """Display the path to the current dataset.

        Receives update in path from Model.

        Parameters
        ----------
        path : str
            The path to the file containing the current dataset.

        See Also
        --------
        model.Model.rpathsignal
        """
        self.currentFile.setText(path)

    def display_status(self, status):
        """Display a status message.

        Receives updates in status message from Model.

        Parameters
        ----------
        status : str
            A status message.

        See Also
        --------
        model.Model.status
        """
        self.statusBar.showMessage(status)

    def display_progress(self, progress):
        """Display task progress.

        Receives updates in progress from Model.

        Parameters
        ----------
        progress : int
            Integer indicating the current task progress.

        See Also
        --------
        model.Model.progress, controller.Worker, controller.threaded
        """
        self.progressBar.setRange(
            0, progress)  # indicates busy state if progress is 0

    def toggle_segmenter(self, visibility_state):
        """Toggle visibility of segmenter widget.

        Parameters
        ----------
        visibility_state : int
            Update in state of the segmenter widget's visibility.
        """
        if not self._model.loaded:
            return
        if visibility_state == 1:  # open segmenter when called from signalmenu or clear segmenter upon selection of invalid segment
            self.segmenter.setVisible(True)
            self.confirmedit.setEnabled(False)
            self.startedit.clear()
            self.endedit.clear()
        elif visibility_state == 0:  # close segmenter after segment has been confirmed
            self.segmenter.setVisible(False)
        elif visibility_state == 2:  # close segmenter after segmentation has been aborted (reset segment)
            self._model.set_segment([0, 0])
            self.segmenter.setVisible(False)
        if self.ax00.patches:
            self.ax00.patches[0].remove()
            self.canvas0.draw()

    def enable_segmentedit(self):
        """Associate cursor position with a specific segmenter text field.

        Regulate if cursor position is associated with editing the start or
        end of a segment.
        """
        self.editcheckbox.setChecked(
            False)  # disable peak editing to avoid interference
        if self.startedit.hasFocus():
            self.segmentcursor = "start"
        elif self.endedit.hasFocus():
            self.segmentcursor = "end"

    def get_xcursor(self, mouse_event):
        """Retrieve input to segmenter text fields from cursor position.

        Retrieve the start or end of a segment in seconds from the current
        cursor position.

        Parameters
        ----------
        mouse_event : MouseEvent
            Event containing information about the current cursor position
            in data coordinates.

        See Also
        --------
        matplotlib.backend_bases.MouseEvent
        """
        if mouse_event.button != 1:  # 1 = left mouse button
            return
        if self.segmentcursor == "start":
            self.startedit.selectAll()
            self.startedit.insert("{:.2f}".format(
                mouse_event.xdata))  # limit number of decimal places to two
        elif self.segmentcursor == "end":
            self.endedit.selectAll()
            self.endedit.insert("{:.2f}".format(mouse_event.xdata))
        self.segmentcursor = False  # disable segment cursor again after value has been set

    def set_customheader(self):
        """Populate the customheader with inputs from the customfiledialog."""
        mandatoryfields = self.signaledit.text() and self.headerrowsedit.text(
        ) and self.sfreqedit.text(
        )  # check if one of the mandatory fields is missing

        if not mandatoryfields:
            self._model.status = (
                "Please provide values for 'biosignal column'"
                ", 'number of header rows' and 'sampling"
                " rate'.")
            return

        seps = {"comma": ",", "tab": "\t", "colon": ":", "space": " "}
        self._model.customheader = dict.fromkeys(
            self._model.customheader, None
        )  # reset header here since it cannot be reset in controller.load_chanels

        self._model.customheader["signalidx"] = int(self.signaledit.text())
        self._model.customheader["skiprows"] = int(self.headerrowsedit.text())
        self._model.customheader["sfreq"] = int(self.sfreqedit.text())
        self._model.customheader["separator"] = seps[
            self.separatormenu.currentText()]
        if self.markeredit.text():  # not mandatory
            self._model.customheader["markeridx"] = int(self.markeredit.text())

        self.customfiledialog.done(QDialog.Accepted)  # close the dialog window
        self._controller.load_channels()  # move on to file selection

    def select_stats(self, statistic):
        """Select statistics to be saved.

        Parameters
        ----------
        statistic : str
            The selected statistic.
        """
        self._model.savestats[
            statistic] ^= True  # toggle boolean with xor operator
        line = None
        if statistic == "period":
            if self.line20:
                line = self.line20[0]
        elif statistic == "rate":
            if self.line21:
                line = self.line21[0]
        elif statistic == "tidalamp":
            if self.line22:
                line = self.line22[0]
        if line:
            line.set_color(self.togglecolors[line.get_color()])
        self.canvas2.draw()

    def toggle_options(self, state):
        """Toggle availability of configuration options.

        Based on current state.

        Parameters
        ----------
        state : str
            The aspect of the current state to which the availability of
            configuration options needs to be adapted.
        """
        if state in ["ECG", "PPG"]:
            self.tidalampcheckbox.setEnabled(False)
            self.tidalampcheckbox.setChecked(False)
            self.ax22.set_visible(False)
            self.canvas2.draw()
        elif state == "RESP":
            self.tidalampcheckbox.setEnabled(True)
            self.ax22.set_visible(True)
            self.canvas2.draw()
        elif state == "multiple files":
            self.editcheckbox.setEnabled(False)
            self.editcheckbox.setChecked(False)
            self.savecheckbox.setEnabled(True)
            self.correctcheckbox.setEnabled(True)
            self.markerchanmenu.setEnabled(False)
        elif state == "single file":
            self.editcheckbox.setEnabled(True)
            self.markerchanmenu.setEnabled(True)
            self.savecheckbox.setEnabled(False)
            self.savecheckbox.setChecked(False)
            self.correctcheckbox.setEnabled(False)
            self.correctcheckbox.setChecked(False)

    def reset_plot(self):
        """Reset plot elements associated with the current dataset."""
        self.ax00.clear()
        self.ax00.relim()
        self.line00 = None
        self.scat = None
        self.segmentspan = None
        self.ax10.clear()
        self.ax10.relim()
        self.line10 = None
        self.ax20.clear()
        self.ax20.relim()
        self.line20 = None
        self.ax21.clear()
        self.ax21.relim()
        self.line21 = None
        self.ax22.clear()
        self.ax22.relim()
        self.line22 = None
        self.canvas0.draw()
        self.canvas1.draw()
        self.canvas2.draw()
        self.navitools.update()
        self.currentFile.clear()
Exemplo n.º 20
0
class SchemeEditor(QMainWindow):
    """Scheme editor is the widget responsible for the scheme editing experience.  
    As its main components, SchemeEditor contains the following widgets:
    - scheme - the canvas for dragging and connecting nodes to form signals;
    - toolbox - a list of draggable nodes to be put on the scheme;
    - (optionally) config widget - a widget for manipulating properties of nodes.
    """
    def __init__(self, parent=None):
        super().__init__(parent)

        self._scheme = None
        self._scheme_view = Scheme.View()
        self._toolbox = None

        self.setCentralWidget(self._scheme_view)

        self.toolbox_dock = QDockWidget("Node Toolbox", self)
        self.toolbox_dock.setAllowedAreas(Qt.LeftDockWidgetArea
                                          | Qt.RightDockWidgetArea)
        self.toolbox_dock.setFeatures(QDockWidget.DockWidgetMovable
                                      | QDockWidget.DockWidgetFloatable)
        self.addDockWidget(Qt.LeftDockWidgetArea, self.toolbox_dock)
        self.setToolbox(Toolbox(self))

        self.config_widget_dock = QDockWidget("Configuration", self)
        self.config_widget_dock.setAllowedAreas(Qt.LeftDockWidgetArea
                                                | Qt.RightDockWidgetArea)
        self.addDockWidget(Qt.RightDockWidgetArea, self.config_widget_dock)
        self.config_widget_dock.hide()

    def scheme(self):
        return self._scheme

    def toolbox(self):
        return self._toolbox

    def schemeView(self):
        return self._scheme_view

    def toolboxView(self):
        return self.toolbox_dock.widget()

    def setScheme(self, scheme: Scheme):
        if self._scheme is not None:
            self._scheme_view.configRequested.disconnect(self.showConfigWidget)
            self._scheme.setCustomDropEvent(self.toolbox().DragMimeType, None)

        self._scheme = scheme

        if self._scheme is not None:
            self._scheme_view.setScene(self._scheme)

            # Conect the signal for config widget
            self._scheme_view.configRequested.connect(self.showConfigWidget)

            # Add a custom drop event for the scheme from the toolbox
            self._scheme.setCustomDropEvent(self.toolbox().DragMimeType,
                                            self.toolbox().schemeDropEvent)

    def setToolbox(self, toolbox):
        self._toolbox = toolbox

        self.toolbox_dock.setWidget(self._toolbox.getView())

    def showConfigWidget(self, node):
        """Show config widget for a node.
        This function is automatically connected as a slot to the scheme view's configRequested signal.
        """
        self.config_widget_dock.setWidget(node.configWidget())
        self.config_widget_dock.show()
Exemplo n.º 21
0
    def createDockWindows(self):
        dock = QDockWidget("Program", self)
        dock.setFeatures(dock.NoDockWidgetFeatures)
        dock.DockWidgetMovable = False
        dock.setAllowedAreas(Qt.LeftDockWidgetArea)
        self.multiWidget = QWidget()
        font1 = QFont("Courier New", 10)
        self.title = QLabel("SOLAR PANEL Program")
        font2 = QFont("Courier New", 10)
        font2.setBold(True)
        self.author = QLabel("Tomasz Dróżdż")
        self.author.setFont(font2)
        self.other = QLabel("Politechnika Wrocławska")
        self.other2 = QLabel("Automatyka i Robotyka")
        self.vLayout = QVBoxLayout()
        self.vLayout.addWidget(self.title)
        self.vLayout.addWidget(self.author)
        self.vLayout.addWidget(self.other)
        self.vLayout.addWidget(self.other2)
        self.multiWidget.setLayout(self.vLayout)
        dock.setWidget(self.multiWidget)
        self.addDockWidget(Qt.LeftDockWidgetArea, dock)

        dock = QDockWidget("Zegar", self)
        dock.setAllowedAreas(Qt.LeftDockWidgetArea | Qt.RightDockWidgetArea)
        self.customerList = QLabel(dock)
        self.customerList.setText(
            ("John Doe, Harmony Enterprises, 12 Lakeside, Ambleton"))
        dock.setWidget(self.customerList)
        self.addDockWidget(Qt.LeftDockWidgetArea, dock)

        #        dock = QDockWidget("Współrzędne dla domyślnej lokacji", self)
        #        dock.setAllowedAreas(Qt.LeftDockWidgetArea | Qt.RightDockWidgetArea)
        #        self.multiWidget2 = QWidget()
        #        self.vLayout2 = QVBoxLayout()
        #        self.result = QLabel(self.latlong)
        #        self.latitude = QTextEdit()
        #        self.latitude.setFixedHeight(24)
        #        self.longitude = QTextEdit()
        #        self.longitude.setFixedHeight(24)
        #        self.button = QPushButton('Test', self)
        #        self.button.clicked.connect(self.handleButton)
        #        self.vLayout2.addWidget(self.latitude)
        #        self.vLayout2.addWidget(self.longitude)
        #        self.vLayout2.addWidget(self.button)
        #        self.vLayout2.addWidget(self.result)
        #        self.multiWidget2.setLayout(self.vLayout2);
        #        dock.setWidget(self.multiWidget2);
        #        self.addDockWidget(Qt.RightDockWidgetArea, dock)

        #    def handleButton(self):
        #        self.result.setText(self.latitude.toPlainText()+self.longitude.toPlainText())

        dock = QDockWidget("Współrzędne", self)
        s = ephem.Sun()
        s.compute(epoch=ephem.now())
        dock.setAllowedAreas(Qt.LeftDockWidgetArea | Qt.RightDockWidgetArea)
        self.multiWidget3 = QWidget()
        self.vLayout3 = QGridLayout()
        self.result = QLabel(self.latlong)
        self.latitude = QLabel('Latitude')
        self.longitude = QLabel('longitude')
        self.result = QLabel('Result')
        self.rightascension = QLabel('R.A.')
        self.latitudeEdit = QTextEdit()
        self.longitudeEdit = QTextEdit()
        self.resultEdit = QTextEdit()
        #        self.latitude = QTextEdit()
        #        self.latitude.setFixedHeight(24)
        #        self.longitude = QTextEdit()
        #        self.longitude.setFixedHeight(24)
        self.button = QPushButton('Test', self)
        self.button.clicked.connect(self.handleButton3)
        self.vLayout3.addWidget(self.latitude)
        self.vLayout3.addWidget(self.latitudeEdit)
        self.vLayout3.addWidget(self.longitude)
        self.vLayout3.addWidget(self.longitudeEdit)
        self.vLayout3.addWidget(self.rightascension)
        self.vLayout3.addWidget(self.button)
        self.vLayout3.addWidget(self.result)
        #        self.vLayout3.addWidget(self.resultEdit)
        self.multiWidget3.setLayout(self.vLayout3)
        dock.setWidget(self.multiWidget3)
        self.addDockWidget(Qt.RightDockWidgetArea, dock)
Exemplo n.º 22
0
class AsemblerIDE(QMainWindow):
    def __init__(self):
        super(AsemblerIDE, self).__init__()
        self.workspace = None
        self.backupTimer = 300000
        PathManager.START_DIRECTORY = os.getcwd()
        self.workspaceConfiguration = WorkspaceConfiguration.loadConfiguration(
        )
        self.snippetManager = SnippetManager.loadSnippetConfiguration()
        self.tooltipManager = TooltipManager.loadTooltipConfiguration()
        self.configurationManager = ConfigurationManager()
        self.editorTabs = EditorTabWidget(self.snippetManager,
                                          self.tooltipManager)
        self.menuBar = MenuBar()
        self.terminal = Terminal()
        self.toolBar = ToolBar(self.configurationManager)
        self.statusBar = StatusBar()
        self.treeView = TreeView(self.configurationManager)
        self.help = HelpWidget()
        self.ascii = AsciiTableWidget()
        self.setStatusBar(self.statusBar)
        self.addToolBar(self.toolBar)
        self.addDockWidget(Qt.BottomDockWidgetArea, self.terminal)
        self.addDockWidget(Qt.RightDockWidgetArea, self.help)
        self.addDockWidget(Qt.RightDockWidgetArea, self.ascii)
        self.splitDockWidget(self.help, self.ascii, Qt.Vertical)
        self.treeDock = QDockWidget()
        self.treeDock.setAllowedAreas(Qt.LeftDockWidgetArea
                                      | Qt.RightDockWidgetArea)
        self.treeDock.setStyleSheet("background-color: #2D2D30; color: white;")
        self.treeDock.setFeatures(QDockWidget.DockWidgetMovable
                                  | QDockWidget.DockWidgetClosable)
        self.treeDock.setWindowTitle("Workspace explorer")
        self.treeDock.setWidget(self.treeView)
        header = QLabel("Workspace explorer")
        # header.setStyleSheet("background-color: #007ACC;")
        self.treeDock.setTitleBarWidget(header)
        self.addDockWidget(Qt.LeftDockWidgetArea, self.treeDock)
        self.setMenuBar(self.menuBar)
        self.setMinimumSize(1200, 800)
        self.setWindowTitle("i386 Assembly Integrated Development Environment")
        self.setCentralWidget(self.editorTabs)
        self.setStyleSheet("background-color: #3E3E42; color: white;")
        self.setWindowIcon(QIcon(resource_path("resources/app_icon.ico")))

        self.addTabWidgetEventHandlers()
        self.addMenuBarEventHandlers()
        self.addToolBarEventHandlers()
        self.addTreeViewEventHandlers()
        self.checkWorkspaceConfiguration()
        #self.populateTreeView()
        #self.statusBar.comboBox.currentTextChanged.connect(self.changeEditorSyntax)
        self.statusBar.tabWidthComboBox.currentTextChanged.connect(
            self.changeEditorTabWidth)
        self.timer = QTimer()
        self.timer.start(self.backupTimer)
        self.timer.timeout.connect(self.makeBackupSave)

        self.terminal.console.setFocus()
        self.tabSwitcher = TabSwitcher(self.editorTabs)
        self.tabSwitcher.hide()
        self.projectSwitcher = ProjectSwitcher(self.configurationManager,
                                               self.toolBar.projectComboBox)
        self.projectSwitcher.hide()
        self.terminal.projectSwitchRequested.connect(self.showProjectSwitcher)
        self.terminal.tabSwitchRequested.connect(self.showTabSwitcher)
        self.editorTabs.tabSwitchRequested.connect(self.showTabSwitcher)
        self.editorTabs.projectSwitchRequested.connect(
            self.showProjectSwitcher)

        self.helpDocsDialog = GettingStartedDialog()
        self.helpDocsDialog.hide()

    def makeBackupSave(self):
        self.workspace.saveBackup()
        self.timer.setInterval(
            self.backupTimer
        )  # interval has to be reset cause the timer may have been paused

    def changeEditorTabWidth(self, text):
        currentTab: EditorTab = self.editorTabs.getCurrentTab()
        if currentTab:
            currentTab.widget.editor.tabSize = int(text)

    def changeEditorSyntax(self, text):
        currentTab: EditorTab = self.editorTabs.getCurrentTab()
        if currentTab:
            if text == "Assembly":
                currentTab.widget.editor.sintaksa = AsemblerSintaksa(
                    currentTab.widget.editor.document())
            elif text == "C":
                currentTab.widget.editor.sintaksa = CSyntax(
                    currentTab.widget.editor.document())
            currentTab.widget.editor.update()

    def checkWorkspaceConfiguration(self):
        defaultWorkspace = self.workspaceConfiguration.getDefaultWorkspace()
        if defaultWorkspace:
            if self.openWorkspaceAction(defaultWorkspace):
                self.show()
                return
            else:
                self.workspaceConfiguration.removeWorkspace(defaultWorkspace)
        dialog = WorkspaceConfigurationEditor(self.workspaceConfiguration,
                                              self)
        if dialog.exec_():
            self.show()
        else:
            sys.exit(0)

    def addTabWidgetEventHandlers(self):
        self.editorTabs.currentChanged.connect(self.activeTabChanged)

    def addTreeViewEventHandlers(self):
        self.treeView.fileDoubleCliked.connect(self.loadFileText)
        self.treeView.workspaceReload.connect(
            lambda wsProxy: self.openWorkspaceAction(wsProxy.path,
                                                     updateWorkspace=True))
        self.treeView.workspaceRename.connect(
            lambda oldPath, wsProxy: self.workspaceConfiguration.
            replaceWorkpsace(oldPath, wsProxy.path))
        self.treeView.newProjectAdded.connect(
            lambda: self.toolBar.updateComboBox())
        self.treeView.projectCompile.connect(
            lambda proxy: self.compileAction(proxy))
        self.treeView.projectDebug.connect(
            lambda proxy: self.debugAction(proxy))
        self.treeView.projectRun.connect(lambda proxy: self.runAction(proxy))
        self.treeView.projectRemove.connect(
            lambda proxy: self.removeProject(proxy))
        self.treeView.projectRename.connect(
            lambda oldPath, project: self.renameProject(oldPath, project))
        self.treeView.fileRemove.connect(
            lambda fileProxy: self.removeFile(fileProxy))
        self.treeView.fileRename.connect(
            lambda oldPath, fileProxy: self.renameFile(oldPath, fileProxy))
        self.treeView.fileSave.connect(
            lambda fileProxy: self.updateEditorTrie(fileProxy))
        self.treeView.invalidWorkspace.connect(self.invalidWorkspace)
        self.treeView.projectSave.connect(self.saveProject)
        self.treeView.quickAssemblyFile.connect(self.loadFileText)
        self.treeView.newFile.connect(self.loadFileText)

    def saveProject(self, projectProxy: ProjectProxy):
        self.saveAllFiles(projectProxy)

    def invalidWorkspace(self, workspace: WorkspaceNode):
        workspace.deleted = True
        msg = QMessageBox()
        msg.setStyleSheet("background-color: #2D2D30; color: white;")
        msg.setModal(True)
        msg.setIcon(QMessageBox.Critical)
        msg.setText("Workspace '{}' has been deleted from the disk.".format(
            workspace.path))
        msg.setWindowTitle("Invalid workspace")
        msg.exec_()
        if workspace.path in self.workspaceConfiguration.getWorkspaces():
            self.workspaceConfiguration.removeWorkspace(workspace.path)
        self.switchWorkspaceAction()

    def renameFile(self, oldPath: str, fileProxy: FileProxy):
        # fileProxy.text = None
        key = "{}/{}".format(fileProxy.parent.path, oldPath)
        if key in self.editorTabs.projectTabs:
            newKey = "{}/{}".format(fileProxy.parent.path, fileProxy.path)
            tab = self.editorTabs.projectTabs.pop(key)
            self.editorTabs.projectTabs[newKey] = tab
            tab.tabName = newKey
            index = self.editorTabs.tabs.index(fileProxy)
            tabText = newKey + "*" if fileProxy.hasUnsavedChanges else newKey
            self.editorTabs.setTabText(index, tabText)

    def renameProject(self, oldPath: str, project: ProjectNode):
        self.toolBar.updateComboBox()
        for fileProxy in project.proxy.files:
            oldKey = "{}/{}".format(oldPath, fileProxy.path)
            if oldKey in self.editorTabs.projectTabs:
                newKey = "{}/{}".format(project.proxy.path, fileProxy.path)
                tab = self.editorTabs.projectTabs.pop(oldKey)
                self.editorTabs.projectTabs[newKey] = tab
                tab.tabName = newKey
                index = self.editorTabs.tabs.index(fileProxy)
                tabText = newKey + "*" if fileProxy.hasUnsavedChanges else newKey
                self.editorTabs.setTabText(index, tabText)

    def removeFile(self, proxy: FileProxy):
        key = "{}/{}".format(proxy.parent.path, proxy.path)
        if key in self.editorTabs.projectTabs:
            self.editorTabs.closeTab(self.editorTabs.tabs.index(proxy),
                                     askToSave=False)

    def removeProject(self, proxy: ProjectProxy):
        for file in proxy.files:
            if file in self.editorTabs.tabs:
                self.editorTabs.closeTab(self.editorTabs.tabs.index(file),
                                         askToSave=False)
        self.toolBar.updateComboBox()

    def checkIfWorkspaceDeleted(self):
        if not os.path.exists(self.workspace.path):
            self.workspace.deleted = True

    def activeTabChanged(self, index):
        if index == -1:
            self.statusBar.tabWidthComboBox.setCurrentText('4')
            return
        syntax = "Assembly" if self.editorTabs.tabs[index].path[-1].lower(
        ) == "s" else "C"
        proxy = self.editorTabs.tabs[index]
        key = "{}/{}".format(proxy.parent.path, proxy.path)
        self.statusBar.comboBox.setCurrentText(syntax)
        self.statusBar.tabWidthComboBox.setCurrentText(
            str(self.editorTabs.projectTabs[key].widget.editor.tabSize))
        #self.changeEditorSyntax(syntax)

    def populateTreeView(self):
        workspace = WorkspaceNode()
        workspace.setText(0, "My workspace")
        self.treeView.setRoot(workspace)
        for i in range(5):
            project = ProjectNode()
            project.setText(0, "My Project {}".format(i + 1))
            assemblyFile = AssemblyFileNode()
            assemblyFile.setText(0, "procedure_{}.S".format(i + 1))
            cFile = CFileNode()
            cFile.setText(0, "main_{}.c".format(i + 1))
            self.treeView.addNode(workspace, project)
            self.treeView.addNode(project, assemblyFile)
            self.treeView.addNode(project, cFile)
            project.setExpanded(True)
        self.workspace = workspace

    def closeEvent(self, event):
        if self.tabSwitcher.isActiveWindow():
            self.tabSwitcher.hide()
        if self.helpDocsDialog.isVisible():
            self.helpDocsDialog.hide()
        for proxy in self.editorTabs.tabs:
            if proxy.hasUnsavedChanges:
                msg = QMessageBox()
                msg.setStyleSheet("background-color: #2D2D30; color: white;")
                msg.setParent(None)
                msg.setModal(True)
                msg.setWindowTitle("Confirm Exit")
                msg.setText("The file {}/{} has been modified.".format(
                    proxy.parent.path, proxy.path))
                msg.setInformativeText("Do you want to save the changes?")
                msg.setStandardButtons(QMessageBox.Save | QMessageBox.Discard
                                       | QMessageBox.Cancel)
                msg.setDefaultButton(QMessageBox.Save)
                retValue = msg.exec_()
                if retValue == QMessageBox.Save:
                    if not self.saveFileAction():
                        event.ignore()
                        return
                elif retValue == QMessageBox.Discard:
                    pass
                else:
                    event.ignore()
                    return
        self.workspaceConfiguration.saveConfiguration()
        self.snippetManager.saveConfiguration()
        self.tooltipManager.saveConfiguration()
        self.checkIfWorkspaceDeleted()
        if not self.workspace.deleted:
            self.workspace.proxy.closedNormally = True
            self.saveWorkspaceAction()
        else:
            if self.workspace.path in self.workspaceConfiguration.getWorkspaces(
            ):
                self.workspaceConfiguration.removeWorkspace(
                    self.workspace.path)
        super(AsemblerIDE, self).closeEvent(event)

    def addMenuBarEventHandlers(self):
        self.menuBar.quickAssemblyProjectAction.triggered.connect(
            self.quickAssemblyProject)
        self.menuBar.newWorkspaceAction.triggered.connect(
            self.newWorkspaceAction)
        self.menuBar.saveWorkspaceAction.triggered.connect(
            self.saveWorkpsaceAllFiles)
        self.menuBar.openWorkspaceAction.triggered.connect(
            self.openWorkspaceAction)
        self.menuBar.switchWorkspaceAction.triggered.connect(
            self.switchWorkspaceAction)

        self.menuBar.saveAction.triggered.connect(self.saveFileAction)
        self.menuBar.findAction.triggered.connect(self.findAction)
        self.menuBar.editDefaultWorkspace.triggered.connect(
            self.editDefaultWorkspaceConfiguration)
        self.menuBar.editCodeSnippets.triggered.connect(self.editCodeSnippets)
        self.menuBar.editSettings.triggered.connect(self.editSettings)

        self.menuBar.aboutAction.triggered.connect(self.showAbout)
        self.menuBar.helpAction.triggered.connect(self.showGettingStarted)

        self.menuBar.view.addAction(self.terminal.toggleViewAction())
        self.menuBar.view.addAction(self.treeDock.toggleViewAction())
        self.menuBar.view.addAction(self.help.toggleViewAction())
        self.menuBar.view.addAction(self.ascii.toggleViewAction())
        self.menuBar.view.addAction(self.toolBar.toggleViewAction())

    def quickAssemblyProject(self):
        if self.workspace:
            self.workspace.createQuickAssemblyProject()

    def showAbout(self):
        dialog = AboutDialog()
        dialog.exec_()

    def showGettingStarted(self):
        if self.helpDocsDialog.isHidden():
            self.helpDocsDialog.show()

    def findAction(self):
        currentTab: EditorTabWidget = self.editorTabs.getCurrentTab()
        if not currentTab:
            msg = QMessageBox()
            msg.setStyleSheet("background-color: #2D2D30; color: white;")
            msg.setModal(True)
            msg.setIcon(QMessageBox.Critical)
            msg.setText(
                "Cannot open file and replace window because there is no open file at the moment."
            )
            msg.setWindowTitle("Find & Replace error")
            msg.exec_()
            return
        currentTab.widget.find.setVisible(True)
        if currentTab.widget.editor.lastFind:
            currentTab.widget.find.findLabel.setText(
                currentTab.widget.editor.lastFind)
        currentTab.widget.find.findLabel.setFocus()

    def switchWorkspaceAction(self):
        remaining = self.timer.remainingTime()
        self.timer.stop(
        )  # timer for creating backups needs to be paused when switching ws
        dialog = WorkspaceConfigurationEditor(self.workspaceConfiguration,
                                              self,
                                              switch=True)
        if dialog.exec_() and dialog.workspaceDirectory:
            self.workspace.proxy.closedNormally = True
            self.saveWorkspaceAction()
            if not self.editorTabs.closeAllTabs():
                self.timer.start(
                    remaining)  # timer for saving backups is resumed
                return
            self.openWorkspaceAction(dialog.workspaceDirectory)
        self.timer.start(remaining)  # timer for saving backups is resumed

    def editDefaultWorkspaceConfiguration(self):
        editor = DefaultWorkspaceEditor(self.workspaceConfiguration)
        if editor.exec_():
            self.workspaceConfiguration.saveConfiguration()

    def editCodeSnippets(self):
        editor = SnippetEditor(self.snippetManager)
        if editor.exec_():
            self.snippetManager.saveConfiguration()

    def editSettings(self):
        editor = SettingsEditor(self.tooltipManager)
        if editor.exec_():
            self.tooltipManager.saveConfiguration()

    def newWorkspaceAction(self):
        remaining = self.timer.remainingTime()
        self.timer.stop(
        )  # timer for creating backups needs to be paused when switching ws
        if not self.editorTabs.closeAllTabs():
            self.timer.start(remaining)  # timer for saving backups is resumed
            return False
        self.workspace.proxy.closedNormally = True
        self.saveWorkspaceAction()
        workspace = WorkspaceNode()
        name = QFileDialog.getExistingDirectory(
            self, "New workspace", "select new workspace directory")
        if name:
            path = os.path.join(name, ".metadata")
            backup_path = os.path.join(name, ".backup")
            if os.path.isdir(path) or os.path.isdir(backup_path):
                self.msgInvalidFolderError(name)
                self.timer.start(
                    remaining)  # timer for saving backups is resumed
                return
            wsname = name[name.rindex(os.path.sep) + 1:]
            regex = re.compile('[@!#$%^&*()<>?/\|}{~:]')
            if ' ' in name or regex.search(wsname):
                msg = QMessageBox()
                msg.setStyleSheet("background-color: #2D2D30; color: white;")
                msg.setModal(True)
                msg.setIcon(QMessageBox.Critical)
                msg.setText(
                    "Workspace path/name cannot contain whitespace or special characters."
                )
                msg.setWindowTitle("Workspace creation error")
                msg.exec_()
                self.timer.start(
                    remaining)  # timer for saving backups is resumed
                return False
            workspace.path = name
            proxy = WorkspaceProxy()
            proxy.path = name
            workspace.proxy = proxy
            workspace.setIcon(0,
                              QIcon(resource_path("resources/workspace.png")))
            workspace.setText(0, wsname)
            self.workspace = workspace
            self.treeView.setRoot(self.workspace)
            self.saveWorkspaceAction()
            self.configurationManager.allProjects = []
            self.configurationManager.currentProject = None
            self.toolBar.updateComboBox()
            self.terminal.executeCommand("cd {}".format(self.workspace.path))
            self.workspaceConfiguration.addWorkspace(self.workspace.proxy.path)
            self.timer.start(remaining)  # timer for saving backups is resumed
            return True
        self.timer.start(remaining)  # timer for saving backups is resumed
        return False

    def saveWorkspaceAction(self, workspacePath=None):
        if self.workspace:
            self.workspace.saveWorkspace(workspacePath)

    def saveWorkpsaceAllFiles(self):
        self.saveAllFiles()
        self.saveWorkspaceAction()

    def updateProjectList(self):
        self.configurationManager.allProjects = []
        self.configurationManager.currentProject = None
        projects = self.treeView.getProjects()
        if len(projects):
            self.configurationManager.allProjects = self.treeView.getProjects()
            self.configurationManager.currentProject = projects[0]
        self.toolBar.updateComboBox()

    def openWorkspaceAction(self, workspacePath=None, updateWorkspace=False):
        if not self.editorTabs.closeAllTabs():
            return
        if not workspacePath:
            workspacePath = QFileDialog.getExistingDirectory(
                self, "Open workspace", "select new workspace directory")
            if not workspacePath:
                return
        regex = re.compile('[@!#$%^&*()<>?/\|}{~:]')
        if ' ' in workspacePath or regex.search(
                os.path.basename(workspacePath)):
            msg = QMessageBox()
            msg.setStyleSheet("background-color: #2D2D30; color: white;")
            msg.setModal(True)
            msg.setIcon(QMessageBox.Critical)
            msg.setText(
                "Workspace path/name cannot contain whitespace or special characters."
            )
            msg.setWindowTitle("Workspace creation error")
            msg.exec_()
            return False
        path = os.path.join(workspacePath, ".metadata")
        backup_path = os.path.join(workspacePath, ".backup")
        if os.path.isdir(path) or os.path.isdir(backup_path):
            self.msgInvalidFolderError(workspacePath)
            return
        workspace = WorkspaceProxy()
        self.workspace = WorkspaceNode()
        if os.path.exists(path):
            try:  # in try block in case there is a corrupted .metadata file on the path
                with open(path, 'rb') as file:
                    workspace = pickle.load(file)
            except:
                workspace.closedNormally = False  # set it to false to trigger backup msg in case .metadata is corrupted
        elif not os.path.exists(
                backup_path
        ):  # creates a .metadata file for a clean new workspace
            self.workspace.proxy = workspace
            self.applyWsCompatibilityFix(workspacePath)
            self.saveWorkspaceAction(workspacePath)
        self.workspace.proxy = workspace
        self.applyWsCompatibilityFix(workspacePath)
        attempted_backup = False
        if not self.workspace.proxy.closedNormally:
            if self.restoreBackupMessage(workspacePath,
                                         updateWorkspace=updateWorkspace):
                attempted_backup = True
                if self.loadWorkspaceAction(
                        workspacePath, backup=True):  # attempt to load backup
                    self.updateProjectList()
                    return True
                else:
                    self.messageBackupError("closedAbruptly")

        if self.loadWorkspaceAction(
                workspacePath,
                backup=False):  # attempt to load regular ws file
            self.updateProjectList()
            return True
        # If the regular file won't load for some reason and there was no backup attempt, ask to load the backup file
        elif not attempted_backup and self.restoreBackupMessage(
                workspacePath, failedToLoad=True):
            if self.loadWorkspaceAction(
                    workspacePath,
                    backup=True):  # attempt to load the backup file
                self.updateProjectList()
                return True
            else:
                self.messageBackupError()
        return False

    def msgInvalidFolderError(self, path):
        msg = QMessageBox()
        msg.setStyleSheet("background-color: #2D2D30; color: white;")
        msg.setModal(True)
        msg.setIcon(QMessageBox.Critical)
        msg.setText(
            "Invalid folder for a workspace."
            "\nEnsure there are no .metadata and .backup folders in \n{}".
            format(path))
        msg.setWindowTitle("Failed to load workspace.")
        msg.exec_()

    def messageBackupError(self, msgType=None):
        msg = QMessageBox()
        msg.setStyleSheet("background-color: #2D2D30; color: white;")
        msg.setModal(True)
        msg.setIcon(QMessageBox.Critical)
        if msgType == "closedAbruptly":
            msg.setText("Failed to load {}."
                        "\nRegular workspace save will be restored.".format(
                            ".backup workspace file"))
        else:
            msg.setText("Failed to load {}.".format(".backup workspace file"))
        msg.setWindowTitle("Failed to load backup workspace.")
        msg.exec_()

    def applyWsCompatibilityFix(self, workspacePath):
        try:
            closedNormally = self.workspace.proxy.closedNormally
        except AttributeError:
            closedNormally = True
        self.workspace.proxy.closedNormally = closedNormally  # adds attribute to old ws files
        self.workspace.path = workspacePath  # changes the path to currently selected dir, in case it was moved
        self.workspace.proxy.path = workspacePath

    def restoreBackupMessage(self,
                             wsName,
                             failedToLoad=False,
                             updateWorkspace=False):
        try:
            msg = QMessageBox()
            msg.setStyleSheet("background-color: #2D2D30; color: white;")
            msg.setParent(None)
            msg.setModal(True)
            msg.setWindowTitle("Workspace recovery")
            time = strftime(
                '%m/%d/%Y %H:%M:%S',
                localtime(os.path.getmtime(os.path.join(wsName, ".backup"))))
            if failedToLoad:
                msg.setText("The workplace {} could not be loaded.\n"
                            "\nTime the backup was created: {}".format(
                                wsName, time))
            elif updateWorkspace:
                msg.setText(
                    "Choose if you want to reload workspace or to recover from backup.\n"
                    "\nTime the backup was created: {}".format(wsName, time))
            else:
                msg.setText("The workplace {} was closed unexpectedly.\n"
                            "\nTime the backup was created: {}".format(
                                wsName, time))
            if not updateWorkspace:
                msg.setInformativeText(
                    "Would you like to recover from backup?")
            else:
                msg.setInformativeText(
                    "Would you like to recover from backup? Select No if you just want to update the workspace."
                )
            msg.setStandardButtons(QMessageBox.Yes | QMessageBox.No)
            msg.setDefaultButton(QMessageBox.Yes)
            retValue = msg.exec_()
            if retValue == QMessageBox.Yes:
                return True
            else:
                return
        except:
            return False

    def loadWorkspaceAction(self, workspacePath, backup=False):
        if backup:
            path = os.path.join(workspacePath, ".backup")
        else:
            path = os.path.join(workspacePath, ".metadata")
        if os.path.exists(path):
            try:  # in try block in case there is a corrupted .metadata file on the path
                with open(path, 'rb') as file:
                    workspace = pickle.load(file)
            except:
                return False
        else:
            return False
        self.workspace = WorkspaceNode()
        self.workspace.proxy = workspace
        self.applyWsCompatibilityFix(workspacePath)
        self.workspace.setIcon(0,
                               QIcon(resource_path("resources/workspace.png")))
        self.workspace.setText(
            0, workspacePath[workspacePath.rindex(os.path.sep) + 1:])
        self.workspace.proxy.closedNormally = False
        if backup:
            success = self.workspace.loadBackupWorkspace(workspacePath)
        else:
            success = self.workspace.loadWorkspace()
        if not success:
            return False
        self.treeView.setRoot(self.workspace)
        projects = self.treeView.getProjects()
        if projects:
            self.configurationManager.allProjects.clear()
            self.configurationManager.allProjects.extend(projects)
        self.toolBar.updateComboBox()
        #self.treeView.expandAll()
        self.terminal.executeCommand("cd {}".format(self.workspace.path))
        self.workspaceConfiguration.addWorkspace(self.workspace.proxy.path)
        if workspacePath:
            self.saveWorkspaceAction(workspacePath)
        return True

    def addToolBarEventHandlers(self):
        self.toolBar.compile.triggered.connect(self.compileAction)
        self.toolBar.run.triggered.connect(self.runAction)
        self.toolBar.debug.triggered.connect(self.debugAction)

    def debugAction(self, projectProxy=None):
        currentProject: ProjectNode = self.configurationManager.currentProject
        if not currentProject:
            self.showNoCurrentProjectMessage("Debug")
            return
        if not os.path.exists(currentProject.proxy.getProjectPath()):
            currentProject.eventManager.invalidProject.emit(currentProject)
            return
        proxy = None
        if projectProxy:
            proxy = projectProxy
        else:
            if currentProject:
                proxy = currentProject.proxy
        if proxy:
            commandString = proxy.getProjectDebugCommand()
            self.terminal.console.setFocus()
            if self.terminal.executeCommand(proxy.getProjectCompileCommand()):
                copmileString = proxy.getProjectCompileCommand()
                if ' -g ' not in copmileString:
                    msg = QMessageBox()
                    msg.setStyleSheet(
                        "background-color: #2D2D30; color: white;")
                    msg.setModal(True)
                    msg.setIcon(QMessageBox.Warning)
                    msg.setText(
                        "Please set '-g' option in compiler configuration to be able to debug your project."
                    )
                    msg.setWindowTitle("Debug warning")
                    msg.exec_()
                    return False
                self.terminal.executeCommand(commandString)
            self.toolBar.projectComboBox.setCurrentText(proxy.path)

    def runAction(self, projectProxy=None):
        currentProject: ProjectNode = self.configurationManager.currentProject
        if not currentProject:
            self.showNoCurrentProjectMessage("Run")
            return
        if not os.path.exists(currentProject.proxy.getProjectPath()):
            currentProject.eventManager.invalidProject.emit(currentProject)
            return
        proxy = None
        if projectProxy:
            proxy = projectProxy
        else:
            if currentProject:
                proxy = currentProject.proxy
        if proxy:
            commandString = proxy.getProjectRunCommand()
            self.terminal.console.setFocus()
            if self.terminal.executeCommand(proxy.getProjectCompileCommand()):
                self.terminal.executeCommand(commandString)
            self.toolBar.projectComboBox.setCurrentText(proxy.path)

    def compileAction(self, projectProxy=None):
        currentProject: ProjectNode = self.configurationManager.currentProject
        if not currentProject:
            self.showNoCurrentProjectMessage("Compile")
            return
        if not os.path.exists(currentProject.proxy.getProjectPath()):
            currentProject.eventManager.invalidProject.emit(currentProject)
            return
        proxy = None
        if projectProxy:
            proxy = projectProxy
        else:
            if currentProject:
                proxy = currentProject.proxy
        if proxy:
            commandString = proxy.getProjectCompileCommand()
            self.terminal.console.setFocus()
            self.terminal.executeCommand(commandString)
            self.toolBar.projectComboBox.setCurrentText(proxy.path)

    def showNoCurrentProjectMessage(self, action: str):
        msg = QMessageBox()
        msg.setStyleSheet("background-color: #2D2D30; color: white;")
        msg.setModal(True)
        msg.setIcon(QMessageBox.Critical)
        msg.setText("You have to select a project first.")
        msg.setWindowTitle("{} error".format(action.capitalize()))
        msg.exec_()

    def checkExecutable(self):
        if self.editor.filePath:
            destination = self.editor.filePath[:-1] + "out"
            return os.path.exists(destination)
        return None

    def loadFileText(self, fileProxy):
        key = "{}/{}".format(fileProxy.parent.path, fileProxy.path)
        if key in self.editorTabs.projectTabs:
            self.editorTabs.setCurrentIndex(
                self.editorTabs.tabs.index(fileProxy))
            return
        text = self.openFileAction(fileProxy)
        fileProxy.text = text
        fileProxy.hasUnsavedChanges = False
        if fileProxy.getFilePath()[-1].lower() == "c":
            currentText = "C"
        else:
            currentText = "Assembly"
        update = True
        if len(self.editorTabs.tabs) == 0:
            self.editorTabs.tabs.append(fileProxy)
            update = False
        self.editorTabs.addNewTab(fileProxy, update)
        self.statusBar.comboBox.setCurrentText(currentText)
        if currentText == "Assembly":
            self.editorTabs.projectTabs[
                key].widget.editor.sintaksa = AsemblerSintaksa(
                    self.editorTabs.projectTabs[key].widget.editor.document())
        elif currentText == "C":
            self.editorTabs.projectTabs[key].widget.editor.sintaksa = CSyntax(
                self.editorTabs.projectTabs[key].widget.editor.document())

    def updateEditorTrie(self, proxy: FileProxy):
        key = "{}/{}".format(proxy.parent.path, proxy.path)
        if key in self.editorTabs.projectTabs:
            self.editorTabs.projectTabs[key].widget.editor.updateTrie()
            self.editorTabs.removeChangeIdentificator(proxy)

    def saveAllFiles(self, projectProxy=None):
        couldNotBeSaved = []
        for i in range(len(self.editorTabs.tabs)):
            fileProxy = self.editorTabs.tabs[i]
            try:
                if fileProxy and fileProxy.hasUnsavedChanges:
                    if projectProxy and fileProxy.parent.path != projectProxy.path:
                        continue
                    with open(fileProxy.getFilePath(), 'w') as file:
                        file.write(fileProxy.text)
                        fileProxy.hasUnsavedChanges = False
                    self.updateEditorTrie(fileProxy)
            except:
                couldNotBeSaved.append(fileProxy.path)
        self.saveWorkspaceAction()
        if couldNotBeSaved:
            msg = QMessageBox()
            msg.setStyleSheet("background-color: #2D2D30; color: white;")
            msg.setModal(True)
            msg.setIcon(QMessageBox.Critical)
            msg.setText("The following file(s) could not be saved: {}".format(
                ','.join(couldNotBeSaved)))
            msg.setWindowTitle("File save error")
            msg.exec_()

    def saveFileAction(self):
        if len(self.editorTabs.tabs):
            proxy = self.editorTabs.getCurrentFileProxy()
            if proxy and proxy.hasUnsavedChanges:
                try:
                    with open(proxy.getFilePath(), 'w') as file:
                        file.write(proxy.text)
                        proxy.hasUnsavedChanges = False
                    self.updateEditorTrie(proxy)
                except:
                    msg = QMessageBox()
                    msg.setStyleSheet(
                        "background-color: #2D2D30; color: white;")
                    msg.setModal(True)
                    msg.setIcon(QMessageBox.Critical)
                    msg.setText(
                        "The following file could not be saved: {}".format(
                            proxy.path))
                    msg.setWindowTitle("File save error")
                    msg.exec_()
            self.saveWorkspaceAction()
            return True

    def openFileAction(self, fileName: FileProxy):
        text = None
        # if fileName.text:
        #     return fileName.text
        with open(fileName.getFilePath(), 'r') as file:
            text = file.read()
        return text

    def keyPressEvent(self, event):
        if event.modifiers() == Qt.ControlModifier:
            if event.key() == Qt.Key_Tab:
                self.showTabSwitcher()
            elif event.key() == Qt.Key_E:
                self.showProjectSwitcher()
        super(AsemblerIDE, self).keyPressEvent(event)

    def showProjectSwitcher(self):
        if self.projectSwitcher.isHidden() and len(
                self.configurationManager.allProjects):
            self.projectSwitcher.showSwitcher()
            self.projectSwitcher.setFocus()

    def showTabSwitcher(self):
        if self.tabSwitcher.isHidden() and len(self.editorTabs.tabs):
            self.tabSwitcher.showSwitcher()
            self.tabSwitcher.setFocus()
Exemplo n.º 23
0
    def createDockWindows(self):
        # dock = QDockWidget('n', self)
        # dock.setFeatures(dock.NoDockWidgetFeatures)
        # dock.DockWidgetMovable = False
        # dock.setAllowedAreas(Qt.TopDockWidgetArea)
        # self.addDockWidget(Qt.TopDockWidgetArea, dock)

        dock = QDockWidget("Program", self)
        dock.setFeatures(dock.NoDockWidgetFeatures)
        dock.DockWidgetMovable = False
        dock.setAllowedAreas(Qt.LeftDockWidgetArea)
        self.multiWidget = QWidget()
        font1 = QFont("Courier New", 10)
        self.title = QLabel("SOLAR PANEL Program")
        font2 = QFont("Courier New", 10)
        font2.setBold(True)
        self.author = QLabel("Tomasz Dróżdż")
        self.author.setFont(font2)
        self.other = QLabel("Politechnika Wrocławska")
        self.other2 = QLabel("Automatyka i Robotyka")
        self.vLayout = QVBoxLayout()
        self.vLayout.addWidget(self.title)
        self.vLayout.addWidget(self.author)
        self.vLayout.addWidget(self.other)
        self.vLayout.addWidget(self.other2)
        self.multiWidget.setLayout(self.vLayout)
        dock.setWidget(self.multiWidget)
        self.addDockWidget(Qt.LeftDockWidgetArea, dock)

        dock = QDockWidget("Zegar", self)
        dock.setAllowedAreas(Qt.LeftDockWidgetArea | Qt.RightDockWidgetArea)
        self.multiWidget2 = QWidget()
        font3 = QFont("Arial", 13)
        font4 = QFont("Arial", 20)
        self.date = QLabel(QDate.currentDate().toString("d MMMM yyyy:  "))
        self.clock = QLabel(QTime.currentTime().toString("hh:mm:ss"))
        self.date.setFont(font3)
        self.clock.setFont(font4)
        font4.setBold(True)
        self.vLayout2 = QVBoxLayout()
        self.vLayout2.addWidget(self.date)
        self.vLayout2.addWidget(self.clock)
        self.multiWidget2.setLayout(self.vLayout2)
        dock.setWidget(self.multiWidget2)
        self.addDockWidget(Qt.LeftDockWidgetArea, dock)
        self.viewMenu.addAction(dock.toggleViewAction())

        dock = QDockWidget("Współrzędne Geograficzne Panelu: ", self)
        s = ephem.Sun()
        s.compute(epoch=ephem.now())
        dock.setAllowedAreas(Qt.LeftDockWidgetArea | Qt.RightDockWidgetArea)
        self.multiWidget3 = QWidget()
        font5 = QFont("Arial", 12)
        font6 = QFont("Arial", 17)
        self.vLayout3 = QGridLayout()
        self.result = QLabel(self.latlong)
        self.name = QLabel('Nazwa')
        self.latitude = QLabel('Szerokość')
        self.longitude = QLabel('Długość')
        self.dateandtime = QLabel('Data i Czas')
        # self.result = QLabel('')
        # self.result2 = QLabel('')
        # self.result3 = QLabel('')
        self.solarpanelcor = QLabel('WSPÓŁRZĘDNE PANELU SŁONECZNEGO: ')
        self.nameEdit = QLineEdit()
        self.nameEdit.setFixedHeight(28)
        self.nameEdit.setFixedWidth(386)
        self.nameEdit.setStatusTip(
            "Wprowadź nazwę dla konfiguracji współrzędnych i czasu")
        self.latitudeEdit = QLineEdit()
        self.latitudeEdit.setFixedHeight(28)
        self.latitudeEdit.setFixedWidth(386)
        self.latitudeEdit.setStatusTip(
            "Wprowadzona szerokość powinna być w stopniach dziesiętnych (np.: 51.100000)"
        )
        self.longitudeEdit = QLineEdit()
        self.longitudeEdit.setFixedHeight(28)
        self.longitudeEdit.setFixedWidth(386)
        self.longitudeEdit.setStatusTip(
            "Wprowadzona długość powinna być w stopniach dziesiętnych (np.: 17.03333)"
        )
        self.dateandtimeEdit = QLineEdit()
        self.dateandtimeEdit.setFixedHeight(28)
        self.dateandtimeEdit.setFixedWidth(386)
        self.dateandtimeEdit.setStatusTip(
            "Wprowadzona data powinna być w formacie: rok/mies/dzień<spacja>godz:min:sek (np.: 2022/12/4 8:12:7)"
        )
        self.button = QPushButton('Wylicz współrzędne / Przerwij liczenie',
                                  self)
        self.button.clicked.connect(self.handleButton4)
        self.name.setFont(font5)
        self.latitude.setFont(font5)
        self.longitude.setFont(font5)
        self.dateandtime.setFont(font5)
        self.button.setFont(font6)
        self.button.setStatusTip("Rozpoczyna Obliczenia")
        # self.button.addAction(self.buttonAct)
        # self.solarpanelcor.setFont(font5)
        # self.result.setFont(font6)
        # self.result2.setFont(font6)
        # self.result3.setFont(font6)
        self.vLayout3.addWidget(self.name)
        self.vLayout3.addWidget(self.nameEdit)
        self.vLayout3.addWidget(self.latitude)
        self.vLayout3.addWidget(self.latitudeEdit)
        self.vLayout3.addWidget(self.longitude)
        self.vLayout3.addWidget(self.longitudeEdit)
        self.vLayout3.addWidget(self.dateandtime)
        self.vLayout3.addWidget(self.dateandtimeEdit)
        self.vLayout3.addWidget(self.button)
        # self.vLayout3.addWidget(self.solarpanelcor)
        # self.vLayout3.addWidget(self.result)
        # self.vLayout3.addWidget(self.result2)
        # self.vLayout3.addWidget(self.result3)
        self.multiWidget3.setLayout(self.vLayout3)
        dock.setWidget(self.multiWidget3)
        self.addDockWidget(Qt.RightDockWidgetArea, dock)
        self.viewMenu.addAction(dock.toggleViewAction())
Exemplo n.º 24
0
class PhotoEditor(QMainWindow):  #Ok
    def __init__(self):
        super().__init__()
        self.iniciaUI()

    def iniciaUI(self):
        """
        Inicializa a janela e mostra seu conteuda na tela
        """

        self.setFixedSize(650, 650)
        #self.setGeometry(100,100, 400, 230)
        self.setWindowTitle("Photo Editor")
        self.centerMainWindow()
        self.createToolsDockWidget()
        self.createMenu()
        self.createToolBar()
        self.photoEditorWidgets()

        self.show()

    def centerMainWindow(self):  #Ok
        """
        Use a classe QDesktopWidget para acessar informações sobre 
        sua tela e use-a para centralizar a janela do aplicativo.
        """
        desktop = QDesktopWidget().screenGeometry()
        screen_width = desktop.width()
        screen_height = desktop.height()
        self.move((screen_width - self.width()) / 2,
                  (screen_height - self.height()) / 2)

    def createMenu(self):  #Ok
        """
        Criar menu para editor de fotos
        """

        self.abre_act = QAction(QIcon('Imagens/open_file.png'), "Abrir", self)
        self.abre_act.setShortcut('Ctrl+O')
        self.abre_act.setStatusTip('Abrir uma nova imagem')
        self.abre_act.triggered.connect(self.openImage)

        self.salv_act = QAction(QIcon('Imagens/save_file.png'), 'Salvar', self)
        self.salv_act.setShortcut('Ctrl+S')
        self.salv_act.setStatusTip('Salvar imagem')
        self.salv_act.triggered.connect(self.saveToFile)

        self.prnt_act = QAction(QIcon('Imagens/print.png'), "Imprimir", self)
        self.prnt_act.setShortcut('Ctrl+P')
        self.prnt_act.setStatusTip('Imprimir imagem')
        self.prnt_act.triggered.connect(self.printImage)
        self.prnt_act.setEnabled(False)

        self.sair_act = QAction(QIcon('Imagens/exit.png'), 'Sair', self)
        self.sair_act.setShortcut('Ctrl+Q')
        self.sair_act.setStatusTip('Sair do programa')
        self.sair_act.triggered.connect(self.close)

        self.rt90_act = QAction("Girar 90°", self)
        self.rt90_act.setStatusTip('Girar imagem 90 ° no sentido horário')
        self.rt90_act.triggered.connect(self.rotateImage90)

        self.rt180_act = QAction("Girar 180°", self)
        self.rt180_act.setStatusTip('Girar imagem 180° no sentido horário')
        self.rt180_act.triggered.connect(self.rotateImage180)

        self.flph_act = QAction("Espelhar na Horizontal", self)
        self.flph_act.setStatusTip('Espelhar imagem no eixo horizontal')
        self.flph_act.triggered.connect(self.flipImageHorizontal)

        self.flpv_act = QAction("Espelhar na Vertical", self)
        self.flpv_act.setStatusTip('Espelhar imagem no eixo vertical')
        self.flpv_act.triggered.connect(self.flipImageVertical)

        self.redm_act = QAction("Redimensionar metade", self)
        self.redm_act.setStatusTip(
            'Redimensionar imagem para metade do tamanho original')
        self.redm_act.triggered.connect(self.resizeImageHalf)

        self.limp_act = QAction(QIcon('Imagens/clear.png'), "Limpar Imagem",
                                self)
        self.limp_act.setShortcut("Ctrl+D")
        self.limp_act.setStatusTip('Limpar a imagem atual')
        self.limp_act.triggered.connect(self.clearImage)

        menu_bar = self.menuBar()
        menu_bar.setNativeMenuBar(False)

        arqv_menu = menu_bar.addMenu('Arquivo')
        arqv_menu.addAction(self.abre_act)
        arqv_menu.addAction(self.salv_act)
        arqv_menu.addSeparator()
        arqv_menu.addAction(self.prnt_act)
        arqv_menu.addSeparator()
        arqv_menu.addAction(self.sair_act)

        edit_menu = menu_bar.addMenu('Editar')
        edit_menu.addAction(self.rt90_act)
        edit_menu.addAction(self.rt180_act)
        edit_menu.addSeparator()
        edit_menu.addAction(self.flph_act)
        edit_menu.addAction(self.flpv_act)
        edit_menu.addSeparator()
        edit_menu.addAction(self.redm_act)
        edit_menu.addSeparator()
        edit_menu.addAction(self.limp_act)

        #???????????????????
        view_menu = menu_bar.addMenu('Exibir')
        view_menu.addAction(self.toggle_dock_tools_act)

        self.setStatusBar(QStatusBar(self))

    def openImage(self):  #Ok
        """
        Abrir um arquivo de imagem e exiba seu conteúdo 
        no label.
        Exibir mensagem de erro se a imagem não puder ser aberta.
        """
        arq_img, _ = QFileDialog.getOpenFileName(
            self, "Abrir Imagem", "",
            "Arquivos JPG (*.jpeg *.jpg );;Arquivos PNG (*.png)\
                            ;;Arquivos  Bitmap (*.bmp);;Arquivos  GIF (*.gif)")

        if arq_img:
            self.img = QPixmap(arq_img)
            self.img_lbl.setPixmap(
                self.img.scaled(self.img_lbl.size(), Qt.KeepAspectRatio,
                                Qt.SmoothTransformation))
        else:
            QMessageBox.information(self, "Erro",
                                    "Não é possível abrir imagem.",
                                    QMessageBox.Ok)

        self.prnt_act.setEnabled(True)

    def saveToFile(self):  #Ok
        """
        Salvar a imagem.
        Exibir mensagem de erro se a imagem não puder ser salva.
        """

        arq_img, _ = QFileDialog.getSaveFileName(
            self, "Salvar Imagem", "",
            "Arquivos JPG (*.jpeg *.jpg );;Arquivos PNG (*.png)\
                            ;;Arquivos  Bitmap (*.bmp);;Arquivos  GIF (*.gif)")

        if arq_img and self.img.isNull() == False:
            self.img.save(image_file)
        else:
            QMessageBox.information(self, "Não é possível salvar imagem.",
                                    QMessageBox.Ok)

    def printImage(self):  #Ok
        """Imprimir Imagem
        """
        printer = QPrinter()
        printer.setOutputFormat(QPrinter.NativeFormat)

        prnt_dlg = QPrintDialog(printer)

        if (prnt_dlg.exec_() == QPrintDialog.Accepted):

            painter = QPainter()

            painter.begin(printer)

            rect = QRect(painter.viewport())

            size = QSize(self.img_lbl.pixmap().size())
            size.scale(rect.size(), Qt.KeepAspectRatio)

            painter.setViewport(rect.x(), rect.y(), size.width(),
                                size.height())
            painter.setWindow(self.img_lbl.pixmap().rect())

            painter.drawPixmap(0, 0, self.img_lbl.pixmap())

            painter.end()

    def rotateImage90(self):  #Ok
        """
        Girar imagem 90° no sentido horário
        """
        if self.img.isNull() == False:
            transform90 = QTransform().rotate(90)
            pixmap = QPixmap(self.img)
            rotated = pixmap.transformed(transform90,
                                         mode=Qt.SmoothTransformation)
            self.img_lbl.setPixmap(
                rotated.scaled(self.img_lbl.size(), Qt.KeepAspectRatio,
                               Qt.SmoothTransformation))
            self.img = QPixmap(rotated)
            self.img_lbl.repaint()  # repaint the child widget
        else:
            # No image to rotate
            pass

    def rotateImage180(self):  #Ok
        """
        Girar imagem 180° no sentido horário
        """
        if self.img.isNull() == False:
            transform180 = QTransform().rotate(180)
            pixmap = QPixmap(self.img)
            rotated = pixmap.transformed(transform180,
                                         mode=Qt.SmoothTransformation)
            self.img_lbl.setPixmap(
                rotated.scaled(self.img_lbl.size(), Qt.KeepAspectRatio,
                               Qt.SmoothTransformation))
            self.img = QPixmap(rotated)
            self.img_label.repaint()  # repaint the child widget
        else:
            # No image to rotate
            pass

    def flipImageHorizontal(self):  #Ok
        """
        Espelhar a imagem no eixo horizontal
        """
        if self.img.isNull() == False:
            flip_h = QTransform().scale(-1, 1)
            pixmap = QPixmap(self.img)
            flipped = pixmap.transformed(flip_h)
            self.img_lbl.setPixmap(
                flipped.scaled(self.img_lbl.size(), Qt.KeepAspectRatio,
                               Qt.SmoothTransformation))
            self.img = QPixmap(flipped)
            self.img_lbl.repaint()
        else:
            pass

    def flipImageVertical(self):  #Ok
        """
        Espelhar a imagem no eixo vertical
        """
        if self.img.isNull() == False:
            flip_v = QTransform().scale(1, -1)
            pixmap = QPixmap(self.img)
            flipped = pixmap.transformed(flip_v)
            self.img_lbl.setPixmap(
                flipped.scaled(self.img_lbl.size(), Qt.KeepAspectRatio,
                               Qt.SmoothTransformation))
            self.img = QPixmap(flipped)
            self.img_lbl.repaint()
        else:
            pass

    def resizeImageHalf(self):  #Ok
        """
        Redimensione a imagem para a metade do tamanho atual.
        """
        if self.img.isNull() == False:
            resize = QTransform().scale(0.5, 0.5)
            pixmap = QPixmap(self.img)
            resized = pixmap.transformed(resize)
            self.img_lbl.setPixmap(
                resized.scaled(self.img_lbl.size(), Qt.KeepAspectRatio,
                               Qt.SmoothTransformation))
            self.img = QPixmap(resized)
            self.img_lbl.repaint()
        else:
            pass

    def clearImage(self):  #Ok
        """
        Limpar a imagem atual no widget QLabel
        """
        self.img_lbl.clear()
        self.img = QPixmap()

    def createToolBar(self):  #Ok
        """
        Criar barra de ferramentas para editor de fotos
        """
        tool_bar = QToolBar("Barra de Ferramentas do Editor de Fotos")
        tool_bar.setIconSize(QSize(24, 24))
        self.addToolBar(tool_bar)

        tool_bar.addAction(self.abre_act)
        tool_bar.addAction(self.salv_act)
        tool_bar.addAction(self.prnt_act)
        tool_bar.addAction(self.limp_act)
        tool_bar.addSeparator()
        tool_bar.addAction(self.sair_act)

    def createToolsDockWidget(self):  #Ok
        """
        Use o menu Exibir -> Editar Ferramentas de Imagem e clique
        no widget dock para ligar ou desligar. 
        O dock de ferramentas pode ser colocado à esquerda ou à 
        direita da janela principal.
        """

        self.dock_tools_view = QDockWidget()
        self.dock_tools_view.setWindowTitle("Ferramentas Edição de Imagem")
        self.dock_tools_view.setAllowedAreas(Qt.LeftDockWidgetArea
                                             | Qt.RightDockWidgetArea)

        self.img_tool = QWidget()

        self.rt90_btn = QPushButton("Girar 90°")
        self.rt90_btn.setMinimumSize(QSize(130, 40))
        self.rt90_btn.setStatusTip('Girar imagem 90° no sentido horário')
        self.rt90_btn.clicked.connect(self.rotateImage90)

        self.rt180_btn = QPushButton("Girar 180°")
        self.rt180_btn.setMinimumSize(QSize(130, 40))
        self.rt180_btn.setStatusTip('Girar imagem 180° no sentido horário')
        self.rt180_btn.clicked.connect(self.rotateImage180)

        self.flph_btn = QPushButton("Espelhar na Horizontal")
        self.flph_btn.setMinimumSize(QSize(130, 40))
        self.flph_btn.setStatusTip('Espelhar imagem no eixo horizontal')
        self.flph_btn.clicked.connect(self.flipImageHorizontal)

        self.flpv_btn = QPushButton("Espelhar na Vertical")
        self.flpv_btn.setMinimumSize(QSize(130, 40))
        self.flpv_btn.setStatusTip('Espelhar imagem no eixo vertical')
        self.flpv_btn.clicked.connect(self.flipImageVertical)

        self.redm_btn = QPushButton("Redimensionar metade")
        self.redm_btn.setMinimumSize(QSize(130, 40))
        self.redm_btn.setStatusTip(
            'Redimensionar imagem para metade do tamanho original')
        self.redm_btn.clicked.connect(self.resizeImageHalf)

        vbox = QVBoxLayout()
        vbox.addWidget(self.rt90_btn)
        vbox.addWidget(self.rt180_btn)
        vbox.addStretch(1)
        vbox.addWidget(self.flph_btn)
        vbox.addWidget(self.flpv_btn)
        vbox.addStretch(1)
        vbox.addWidget(self.redm_btn)
        vbox.addStretch(6)

        self.img_tool.setLayout(vbox)
        self.dock_tools_view.setWidget(self.img_tool)

        self.addDockWidget(Qt.RightDockWidgetArea, self.dock_tools_view)

        self.toggle_dock_tools_act = self.dock_tools_view.toggleViewAction()

    def photoEditorWidgets(self):  #Ok
        """
        Configurar instâncias de widgets para editor de fotos
        """

        self.img = QPixmap()
        self.img_lbl = QLabel()
        self.img_lbl.setAlignment(Qt.AlignCenter)
        self.img_lbl.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Ignored)

        self.setCentralWidget(self.img_lbl)
Exemplo n.º 25
0
 def showEditor(self):
     dock = QDockWidget("Scene Parameters", self)
     dock.setAllowedAreas(Qt.RightDockWidgetArea)
     dock.setFixedWidth(640)
     dock.setWidget(self.editor)
     self.addDockWidget(Qt.RightDockWidgetArea, dock)
Exemplo n.º 26
0
    def create_ui(self):
        """Setup main UI elements, dock widgets, UI-related elements, etc.
        """

        log.debug('Loading UI')

        # Undo Stack
        self.undo_stack = QUndoStack(self)
        self.undo_stack.setUndoLimit(100)

        # Object navigation history
        self.obj_history = deque([], config.MAX_OBJ_HISTORY)

        app = QApplication.instance()
        base_font = QFont()
        base_font.fromString(self.prefs['base_font'])
        app.setFont(base_font)

        # Object class table widget
        # classTable = QTableView(self)
        classTable = classtable.TableView(self)
        classTable.setObjectName("classTable")
        classTable.setAlternatingRowColors(True)
        classTable.setFrameShape(QFrame.StyledPanel)
        classTable_font = QFont()
        classTable_font.fromString(self.prefs['class_table_font'])
        classTable.setFont(classTable_font)
        fm = classTable.fontMetrics()
        classTable.setWordWrap(True)
        classTable.setEditTriggers(QAbstractItemView.EditKeyPressed |
                                   QAbstractItemView.DoubleClicked |
                                   QAbstractItemView.AnyKeyPressed |
                                   QAbstractItemView.SelectedClicked)
        # classTable.horizontalHeader().setMovable(True)
        # classTable.verticalHeader().setMovable(False)
        classTable.horizontalHeader().setSectionResizeMode(QHeaderView.Interactive)
        classTable.verticalHeader().setSectionResizeMode(QHeaderView.Interactive)
        classTable.horizontalHeader().setDefaultSectionSize(self.prefs['default_column_width'])
        classTable.verticalHeader().setDefaultSectionSize(fm.height() + 0)
        classTable.setSelectionMode(QAbstractItemView.ExtendedSelection)
        classTable.setContextMenuPolicy(Qt.CustomContextMenu)
        classTable.customContextMenuRequested.connect(self.custom_table_context_menu)

        # Create table model and proxy layers for transposing and filtering
        self.classTableModel = classtable.IDFObjectTableModel(classTable)
        self.transposeableModel = classtable.TransposeProxyModel(self.classTableModel)
        self.transposeableModel.setSourceModel(self.classTableModel)
        self.sortableModel = classtable.SortFilterProxyModel(self.transposeableModel)
        self.sortableModel.setSourceModel(self.transposeableModel)

        # Assign model to table (enable sorting FIRST)
        # table.setSortingEnabled(True) # Disable for now, CRUD actions won't work!
        classTable.setModel(self.sortableModel)

        # Connect some signals
        selection_model = classTable.selectionModel()
        selection_model.selectionChanged.connect(self.table_selection_changed)
        scroll_bar = classTable.verticalScrollBar()
        scroll_bar.valueChanged.connect(self.scroll_changed)

        # These are currently broken
        # classTable.horizontalHeader().sectionMoved.connect(self.moveObject)
        # classTable.verticalHeader().sectionMoved.connect(self.moveObject)

        # Object class tree widget
        classTreeDockWidget = QDockWidget("Object Classes and Counts", self)
        classTreeDockWidget.setObjectName("classTreeDockWidget")
        classTreeDockWidget.setAllowedAreas(Qt.AllDockWidgetAreas)

        classTree = QTreeView(classTreeDockWidget)
        classTree.setUniformRowHeights(True)
        classTree.setAllColumnsShowFocus(True)
        classTree.setRootIsDecorated(False)
        classTree.setExpandsOnDoubleClick(True)
        classTree.setIndentation(15)
        classTree.setAnimated(True)
        classTree_font = QFont()
        classTree_font.fromString(self.prefs['class_tree_font'])
        classTree.setFont(classTree_font)
        classTree.setAlternatingRowColors(True)
        classTree.setHorizontalScrollMode(QAbstractItemView.ScrollPerPixel)
        palette = classTree.palette()
        palette.setColor(QPalette.Highlight, Qt.darkCyan)
        classTree.setPalette(palette)

        class_tree_window = QWidget(classTreeDockWidget)
        class_tree_dock_layout_v = QVBoxLayout()
        class_tree_dock_layout_h = QHBoxLayout()
        class_tree_dock_layout_v.setContentsMargins(0, 8, 0, 0)
        class_tree_dock_layout_h.setContentsMargins(0, 0, 0, 0)

        class_tree_filter_edit = QLineEdit(classTreeDockWidget)
        class_tree_filter_edit.setPlaceholderText("Filter Classes")
        class_tree_filter_edit.textChanged.connect(self.treeFilterRegExpChanged)

        class_tree_filter_cancel = QPushButton("Clear", classTreeDockWidget)
        class_tree_filter_cancel.setMaximumWidth(45)
        class_tree_filter_cancel.clicked.connect(self.clearTreeFilterClicked)

        class_tree_dock_layout_h.addWidget(class_tree_filter_edit)
        class_tree_dock_layout_h.addWidget(class_tree_filter_cancel)
        class_tree_dock_layout_v.addLayout(class_tree_dock_layout_h)
        class_tree_dock_layout_v.addWidget(classTree)
        class_tree_window.setLayout(class_tree_dock_layout_v)

        classTreeDockWidget.setWidget(class_tree_window)
        classTreeDockWidget.setContentsMargins(0,0,0,0)

        # Comments widget
        commentDockWidget = QDockWidget("Comments", self)
        commentDockWidget.setObjectName("commentDockWidget")
        commentDockWidget.setAllowedAreas(Qt.AllDockWidgetAreas)
        commentView = UndoRedoTextEdit(commentDockWidget, self)
        commentView.setLineWrapMode(QTextEdit.FixedColumnWidth)
        commentView.setLineWrapColumnOrWidth(499)
        commentView.setFrameShape(QFrame.StyledPanel)
        commentView_font = QFont()
        commentView_font.fromString(self.prefs['comments_font'])
        commentView.setFont(commentView_font)
        commentDockWidget.setWidget(commentView)

        # Info and help widget
        infoDockWidget = QDockWidget("Info", self)
        infoDockWidget.setObjectName("infoDockWidget")
        infoDockWidget.setAllowedAreas(Qt.AllDockWidgetAreas)
        infoView = QTextEdit(infoDockWidget)
        infoView.setFrameShape(QFrame.StyledPanel)
        infoView.setReadOnly(True)
        infoDockWidget.setWidget(infoView)

        # Node list and jump menu widget
        refDockWidget = QDockWidget("Field References", self)
        refDockWidget.setObjectName("refDockWidget")
        refDockWidget.setAllowedAreas(Qt.AllDockWidgetAreas)
        ref_model = reftree.ReferenceTreeModel(None, refDockWidget)
        refView = QTreeView(refDockWidget)
        refView.setModel(ref_model)
        refView.setUniformRowHeights(True)
        refView.setRootIsDecorated(False)
        refView.setIndentation(15)
        refView.setColumnWidth(0, 160)
        refView.setFrameShape(QFrame.StyledPanel)
        refDockWidget.setWidget(refView)
        refView.doubleClicked.connect(self.ref_tree_double_clicked)

        # Logging and debugging widget
        logDockWidget = QDockWidget("Log Viewer", self)
        logDockWidget.setObjectName("logDockWidget")
        logDockWidget.setAllowedAreas(Qt.AllDockWidgetAreas)
        logView = QPlainTextEdit(logDockWidget)
        logView.setLineWrapMode(QPlainTextEdit.NoWrap)
        logView.setReadOnly(True)
        logView_font = QFont()
        logView_font.fromString(self.prefs['base_font'])
        logView.setFont(logView_font)
        logView.ensureCursorVisible()
        logDockWidget.setWidget(logView)

        # Undo view widget
        undoDockWidget = QDockWidget("Undo History", self)
        undoDockWidget.setObjectName("undoDockWidget")
        undoDockWidget.setAllowedAreas(Qt.AllDockWidgetAreas)
        undoView = QUndoView(self.undo_stack)
        undoDockWidget.setWidget(undoView)

        # Define corner docking behaviour
        self.setDockNestingEnabled(True)
        self.setCorner(Qt.TopLeftCorner,
                       Qt.LeftDockWidgetArea)
        self.setCorner(Qt.TopRightCorner,
                       Qt.RightDockWidgetArea)
        self.setCorner(Qt.BottomLeftCorner,
                       Qt.LeftDockWidgetArea)
        self.setCorner(Qt.BottomRightCorner,
                       Qt.RightDockWidgetArea)

        # Assign main widget and dock widgets to QMainWindow
        self.setCentralWidget(classTable)
        self.addDockWidget(Qt.LeftDockWidgetArea, classTreeDockWidget)
        self.addDockWidget(Qt.RightDockWidgetArea, commentDockWidget)
        self.addDockWidget(Qt.RightDockWidgetArea, infoDockWidget)
        self.addDockWidget(Qt.RightDockWidgetArea, refDockWidget)
        self.addDockWidget(Qt.RightDockWidgetArea, logDockWidget)
        self.addDockWidget(Qt.RightDockWidgetArea, undoDockWidget)

        # Store widgets for access by other objects
        self.classTable = classTable
        self.commentView = commentView
        self.infoView = infoView
        self.classTree = classTree
        self.logView = logView
        self.undoView = undoView
        self.refView = refView
        self.filterTreeBox = class_tree_filter_edit

        # Store docks for access by other objects
        self.commentDockWidget = commentDockWidget
        self.infoDockWidget = infoDockWidget
        self.classTreeDockWidget = classTreeDockWidget
        self.logDockWidget = logDockWidget
        self.undoDockWidget = undoDockWidget
        self.refDockWidget = refDockWidget

        # Perform other UI-related initialization tasks
        self.center()
        self.setUnifiedTitleAndToolBarOnMac(True)
        self.setWindowIcon(QIcon(':/images/logo.png'))

        # Status bar setup
        self.statusBar().showMessage('Status: Ready')
        self.unitsLabel = QLabel()
        self.unitsLabel.setAlignment(Qt.AlignCenter)
        self.unitsLabel.setMinimumSize(self.unitsLabel.sizeHint())
        self.unitsLabel.setFrameStyle(QFrame.StyledPanel | QFrame.Sunken)
        self.statusBar().addPermanentWidget(self.unitsLabel)
        self.pathLabel = QLabel()
        self.pathLabel.setAlignment(Qt.AlignCenter)
        self.pathLabel.setMinimumSize(self.pathLabel.sizeHint())
        self.pathLabel.setFrameStyle(QFrame.StyledPanel | QFrame.Sunken)
        self.statusBar().addPermanentWidget(self.pathLabel)
        self.versionLabel = QLabel()
        self.versionLabel.setAlignment(Qt.AlignCenter)
        self.versionLabel.setMinimumSize(self.versionLabel.sizeHint())
        self.versionLabel.setFrameStyle(QFrame.StyledPanel | QFrame.Sunken)
        self.statusBar().addPermanentWidget(self.versionLabel)
        self.progressBarIDF = QProgressBar()
        self.progressBarIDF.setAlignment(Qt.AlignCenter)
        self.progressBarIDF.setMaximumWidth(200)
        self.statusBar().addPermanentWidget(self.progressBarIDF)

        self.clipboard = QApplication.instance().clipboard()
        self.obj_clipboard = []

        self.setStyleSheet("""
            QToolTip {
               background-color: gray;
               color: white;
               border: black solid 1px
            } 
            # QMenu {
            #     background-color: rgbf(0.949020, 0.945098, 0.941176);
            #     color: rgb(255,255,255);
            # }
            # QMenu::item::selected {
            #     background-color: rgbf(0.949020, 0.945098, 0.941176);
            # }
            """)
Exemplo n.º 27
0
    def __init__(self, parent=None):
        super(MainWindow, self).__init__(parent)
        QApplication.setApplicationName('Sherloq')
        QApplication.setOrganizationName('Guido Bartoli')
        QApplication.setOrganizationDomain('www.guidobartoli.com')
        QApplication.setApplicationVersion(ToolTree().version)
        QApplication.setWindowIcon(QIcon('icons/sherloq_white.png'))
        self.setWindowTitle('{} {}'.format(QApplication.applicationName(),
                                           QApplication.applicationVersion()))
        self.mdi_area = QMdiArea()
        self.setCentralWidget(self.mdi_area)
        self.filename = None
        self.image = None
        modify_font(self.statusBar(), bold=True)

        tree_dock = QDockWidget(self.tr('TOOLS'), self)
        tree_dock.setObjectName('tree_dock')
        tree_dock.setAllowedAreas(Qt.LeftDockWidgetArea
                                  | Qt.RightDockWidgetArea)
        self.addDockWidget(Qt.LeftDockWidgetArea, tree_dock)
        self.tree_widget = ToolTree()
        self.tree_widget.setObjectName('tree_widget')
        self.tree_widget.itemDoubleClicked.connect(self.open_tool)
        tree_dock.setWidget(self.tree_widget)

        tools_action = tree_dock.toggleViewAction()
        tools_action.setText(self.tr('Show tools'))
        tools_action.setToolTip(self.tr('Toggle toolset visibility'))
        tools_action.setShortcut(QKeySequence(Qt.Key_Tab))
        tools_action.setObjectName('tools_action')
        tools_action.setIcon(QIcon('icons/tools.svg'))

        help_action = QAction(self.tr('Show help'), self)
        help_action.setToolTip(self.tr('Toggle online help'))
        help_action.setShortcut(QKeySequence.HelpContents)
        help_action.setObjectName('help_action')
        help_action.setIcon(QIcon('icons/help.svg'))
        help_action.setCheckable(True)
        help_action.setEnabled(False)

        load_action = QAction(self.tr('&Load image...'), self)
        load_action.setToolTip(self.tr('Load an image to analyze'))
        load_action.setShortcut(QKeySequence.Open)
        load_action.triggered.connect(self.load_file)
        load_action.setObjectName('load_action')
        load_action.setIcon(QIcon('icons/load.svg'))

        quit_action = QAction(self.tr('&Quit'), self)
        quit_action.setToolTip(self.tr('Exit from Sherloq'))
        quit_action.setShortcut(QKeySequence.Quit)
        quit_action.triggered.connect(self.close)
        quit_action.setObjectName('quit_action')
        quit_action.setIcon(QIcon('icons/quit.svg'))

        tabbed_action = QAction(self.tr('&Tabbed'), self)
        tabbed_action.setToolTip(self.tr('Toggle tabbed view for window area'))
        tabbed_action.setShortcut(QKeySequence(Qt.Key_F10))
        tabbed_action.setCheckable(True)
        tabbed_action.triggered.connect(self.toggle_view)
        tabbed_action.setObjectName('tabbed_action')
        tabbed_action.setIcon(QIcon('icons/tabbed.svg'))

        prev_action = QAction(self.tr('&Previous'), self)
        prev_action.setToolTip(self.tr('Select the previous tool window'))
        prev_action.setShortcut(QKeySequence.PreviousChild)
        prev_action.triggered.connect(self.mdi_area.activatePreviousSubWindow)
        prev_action.setObjectName('prev_action')
        prev_action.setIcon(QIcon('icons/previous.svg'))

        next_action = QAction(self.tr('&Next'), self)
        next_action.setToolTip(self.tr('Select the next tool window'))
        next_action.setShortcut(QKeySequence.NextChild)
        next_action.triggered.connect(self.mdi_area.activateNextSubWindow)
        next_action.setObjectName('next_action')
        next_action.setIcon(QIcon('icons/next.svg'))

        tile_action = QAction(self.tr('&Tile'), self)
        tile_action.setToolTip(
            self.tr('Arrange windows into non-overlapping views'))
        tile_action.setShortcut(QKeySequence(Qt.Key_F11))
        tile_action.triggered.connect(self.mdi_area.tileSubWindows)
        tile_action.setObjectName('tile_action')
        tile_action.setIcon(QIcon('icons/tile.svg'))

        cascade_action = QAction(self.tr('&Cascade'), self)
        cascade_action.setToolTip(
            self.tr('Arrange windows into overlapping views'))
        cascade_action.setShortcut(QKeySequence(Qt.Key_F12))
        cascade_action.triggered.connect(self.mdi_area.cascadeSubWindows)
        cascade_action.setObjectName('cascade_action')
        cascade_action.setIcon(QIcon('icons/cascade.svg'))

        close_action = QAction(self.tr('Close &All'), self)
        close_action.setToolTip(self.tr('Close all open tool windows'))
        close_action.setShortcut(QKeySequence(Qt.CTRL + Qt.SHIFT + Qt.Key_W))
        close_action.triggered.connect(self.mdi_area.closeAllSubWindows)
        close_action.setObjectName('close_action')
        close_action.setIcon(QIcon('icons/close.svg'))

        self.full_action = QAction(self.tr('Full screen'), self)
        self.full_action.setToolTip(self.tr('Switch to full screen mode'))
        self.full_action.setShortcut(QKeySequence.FullScreen)
        self.full_action.triggered.connect(self.change_view)
        self.full_action.setObjectName('full_action')
        self.full_action.setIcon(QIcon('icons/full.svg'))

        self.normal_action = QAction(self.tr('Normal view'), self)
        self.normal_action.setToolTip(self.tr('Back to normal view mode'))
        self.normal_action.setShortcut(QKeySequence(Qt.CTRL + Qt.Key_F12))
        self.normal_action.triggered.connect(self.change_view)
        self.normal_action.setObjectName('normal_action')
        self.normal_action.setIcon(QIcon('icons/normal.svg'))

        about_action = QAction(self.tr('&About...'), self)
        about_action.setToolTip(self.tr('Information about this program'))
        about_action.triggered.connect(self.show_about)
        about_action.setObjectName('about_action')
        about_action.setIcon(QIcon('icons/sherloq_alpha.png'))

        about_qt_action = QAction(self.tr('About &Qt'), self)
        about_qt_action.setToolTip(
            self.tr('Information about the Qt Framework'))
        about_qt_action.triggered.connect(QApplication.aboutQt)
        about_qt_action.setIcon(QIcon('icons/Qt.svg'))

        file_menu = self.menuBar().addMenu(self.tr('&File'))
        file_menu.addAction(load_action)
        file_menu.addSeparator()
        self.recent_actions = [None] * self.max_recent
        for i in range(len(self.recent_actions)):
            self.recent_actions[i] = QAction(self)
            self.recent_actions[i].setVisible(False)
            self.recent_actions[i].triggered.connect(self.open_recent)
            file_menu.addAction(self.recent_actions[i])
        file_menu.addSeparator()
        file_menu.addAction(quit_action)

        view_menu = self.menuBar().addMenu(self.tr('&View'))
        view_menu.addAction(tools_action)
        view_menu.addAction(help_action)
        view_menu.addSeparator()
        view_menu.addAction(self.full_action)
        view_menu.addAction(self.normal_action)

        window_menu = self.menuBar().addMenu(self.tr('&Window'))
        window_menu.addAction(prev_action)
        window_menu.addAction(next_action)
        window_menu.addSeparator()
        window_menu.addAction(tile_action)
        window_menu.addAction(cascade_action)
        window_menu.addAction(tabbed_action)
        window_menu.addSeparator()
        window_menu.addAction(close_action)

        help_menu = self.menuBar().addMenu(self.tr('&Help'))
        help_menu.addAction(help_action)
        help_menu.addSeparator()
        help_menu.addAction(about_action)
        help_menu.addAction(about_qt_action)

        main_toolbar = self.addToolBar(self.tr('&Toolbar'))
        main_toolbar.setToolButtonStyle(Qt.ToolButtonTextBesideIcon)
        main_toolbar.addAction(load_action)
        main_toolbar.addSeparator()
        main_toolbar.addAction(tools_action)
        main_toolbar.addAction(help_action)
        main_toolbar.addSeparator()
        main_toolbar.addAction(prev_action)
        main_toolbar.addAction(next_action)
        main_toolbar.addSeparator()
        main_toolbar.addAction(tile_action)
        main_toolbar.addAction(cascade_action)
        main_toolbar.addAction(tabbed_action)
        main_toolbar.addAction(close_action)
        # main_toolbar.addSeparator()
        # main_toolbar.addAction(self.normal_action)
        # main_toolbar.addAction(self.full_action)
        main_toolbar.setAllowedAreas(Qt.TopToolBarArea | Qt.BottomToolBarArea)
        main_toolbar.setObjectName('main_toolbar')

        settings = QSettings()
        settings.beginGroup('main_window')
        self.restoreGeometry(settings.value('geometry'))
        self.restoreState(settings.value('state'))
        self.recent_files = settings.value('recent_files')
        if self.recent_files is None:
            self.recent_files = []
        elif not isinstance(self.recent_files, list):
            self.recent_files = [self.recent_files]
        self.update_recent()
        settings.endGroup()

        prev_action.setEnabled(False)
        next_action.setEnabled(False)
        tile_action.setEnabled(False)
        cascade_action.setEnabled(False)
        close_action.setEnabled(False)
        tabbed_action.setEnabled(False)
        self.tree_widget.setEnabled(False)
        self.showNormal()
        self.normal_action.setEnabled(False)
        self.show_message(self.tr('Ready'))
Exemplo n.º 28
0
    def __init__(self, parent=None):
        super(MainWindow, self).__init__(parent)
        QApplication.setApplicationName("Sherloq")
        QApplication.setOrganizationName("Guido Bartoli")
        QApplication.setOrganizationDomain("www.guidobartoli.com")
        QApplication.setApplicationVersion(ToolTree().version)
        QApplication.setWindowIcon(QIcon("icons/sherloq_white.png"))
        self.setWindowTitle(
            f"{QApplication.applicationName()} {QApplication.applicationVersion()}"
        )
        self.mdi_area = QMdiArea()
        self.setCentralWidget(self.mdi_area)
        self.filename = None
        self.image = None
        modify_font(self.statusBar(), bold=True)

        tree_dock = QDockWidget(self.tr("TOOLS"), self)
        tree_dock.setObjectName("tree_dock")
        tree_dock.setAllowedAreas(Qt.LeftDockWidgetArea
                                  | Qt.RightDockWidgetArea)
        self.addDockWidget(Qt.LeftDockWidgetArea, tree_dock)
        self.tree_widget = ToolTree()
        self.tree_widget.setObjectName("tree_widget")
        self.tree_widget.itemDoubleClicked.connect(self.open_tool)
        tree_dock.setWidget(self.tree_widget)

        tools_action = tree_dock.toggleViewAction()
        tools_action.setText(self.tr("Show tools"))
        tools_action.setToolTip(self.tr("Toggle toolset visibility"))
        tools_action.setShortcut(QKeySequence(Qt.Key_Tab))
        tools_action.setObjectName("tools_action")
        tools_action.setIcon(QIcon("icons/tools.svg"))

        help_action = QAction(self.tr("Show help"), self)
        help_action.setToolTip(self.tr("Toggle online help"))
        help_action.setShortcut(QKeySequence.HelpContents)
        help_action.setObjectName("help_action")
        help_action.setIcon(QIcon("icons/help.svg"))
        help_action.setCheckable(True)
        help_action.setEnabled(False)

        load_action = QAction(self.tr("&Load image..."), self)
        load_action.setToolTip(self.tr("Load an image to analyze"))
        load_action.setShortcut(QKeySequence.Open)
        load_action.triggered.connect(self.load_file)
        load_action.setObjectName("load_action")
        load_action.setIcon(QIcon("icons/load.svg"))

        quit_action = QAction(self.tr("&Quit"), self)
        quit_action.setToolTip(self.tr("Exit from Sherloq"))
        quit_action.setShortcut(QKeySequence.Quit)
        quit_action.triggered.connect(self.close)
        quit_action.setObjectName("quit_action")
        quit_action.setIcon(QIcon("icons/quit.svg"))

        tabbed_action = QAction(self.tr("&Tabbed"), self)
        tabbed_action.setToolTip(self.tr("Toggle tabbed view for window area"))
        tabbed_action.setShortcut(QKeySequence(Qt.Key_F10))
        tabbed_action.setCheckable(True)
        tabbed_action.triggered.connect(self.toggle_view)
        tabbed_action.setObjectName("tabbed_action")
        tabbed_action.setIcon(QIcon("icons/tabbed.svg"))

        prev_action = QAction(self.tr("&Previous"), self)
        prev_action.setToolTip(self.tr("Select the previous tool window"))
        prev_action.setShortcut(QKeySequence.PreviousChild)
        prev_action.triggered.connect(self.mdi_area.activatePreviousSubWindow)
        prev_action.setObjectName("prev_action")
        prev_action.setIcon(QIcon("icons/previous.svg"))

        next_action = QAction(self.tr("&Next"), self)
        next_action.setToolTip(self.tr("Select the next tool window"))
        next_action.setShortcut(QKeySequence.NextChild)
        next_action.triggered.connect(self.mdi_area.activateNextSubWindow)
        next_action.setObjectName("next_action")
        next_action.setIcon(QIcon("icons/next.svg"))

        tile_action = QAction(self.tr("&Tile"), self)
        tile_action.setToolTip(
            self.tr("Arrange windows into non-overlapping views"))
        tile_action.setShortcut(QKeySequence(Qt.Key_F11))
        tile_action.triggered.connect(self.mdi_area.tileSubWindows)
        tile_action.setObjectName("tile_action")
        tile_action.setIcon(QIcon("icons/tile.svg"))

        cascade_action = QAction(self.tr("&Cascade"), self)
        cascade_action.setToolTip(
            self.tr("Arrange windows into overlapping views"))
        cascade_action.setShortcut(QKeySequence(Qt.Key_F12))
        cascade_action.triggered.connect(self.mdi_area.cascadeSubWindows)
        cascade_action.setObjectName("cascade_action")
        cascade_action.setIcon(QIcon("icons/cascade.svg"))

        close_action = QAction(self.tr("Close &All"), self)
        close_action.setToolTip(self.tr("Close all open tool windows"))
        close_action.setShortcut(QKeySequence(Qt.CTRL + Qt.SHIFT + Qt.Key_W))
        close_action.triggered.connect(self.mdi_area.closeAllSubWindows)
        close_action.setObjectName("close_action")
        close_action.setIcon(QIcon("icons/close.svg"))

        self.full_action = QAction(self.tr("Full screen"), self)
        self.full_action.setToolTip(self.tr("Switch to full screen mode"))
        self.full_action.setShortcut(QKeySequence.FullScreen)
        self.full_action.triggered.connect(self.change_view)
        self.full_action.setObjectName("full_action")
        self.full_action.setIcon(QIcon("icons/full.svg"))

        self.normal_action = QAction(self.tr("Normal view"), self)
        self.normal_action.setToolTip(self.tr("Back to normal view mode"))
        self.normal_action.setShortcut(QKeySequence(Qt.CTRL + Qt.Key_F12))
        self.normal_action.triggered.connect(self.change_view)
        self.normal_action.setObjectName("normal_action")
        self.normal_action.setIcon(QIcon("icons/normal.svg"))

        about_action = QAction(self.tr("&About..."), self)
        about_action.setToolTip(self.tr("Information about this program"))
        about_action.triggered.connect(self.show_about)
        about_action.setObjectName("about_action")
        about_action.setIcon(QIcon("icons/sherloq_alpha.png"))

        about_qt_action = QAction(self.tr("About &Qt"), self)
        about_qt_action.setToolTip(
            self.tr("Information about the Qt Framework"))
        about_qt_action.triggered.connect(QApplication.aboutQt)
        about_qt_action.setIcon(QIcon("icons/Qt.svg"))

        file_menu = self.menuBar().addMenu(self.tr("&File"))
        file_menu.addAction(load_action)
        file_menu.addSeparator()
        self.recent_actions = [None] * self.max_recent
        for i in range(len(self.recent_actions)):
            self.recent_actions[i] = QAction(self)
            self.recent_actions[i].setVisible(False)
            self.recent_actions[i].triggered.connect(self.open_recent)
            file_menu.addAction(self.recent_actions[i])
        file_menu.addSeparator()
        file_menu.addAction(quit_action)

        view_menu = self.menuBar().addMenu(self.tr("&View"))
        view_menu.addAction(tools_action)
        view_menu.addAction(help_action)
        view_menu.addSeparator()
        view_menu.addAction(self.full_action)
        view_menu.addAction(self.normal_action)

        window_menu = self.menuBar().addMenu(self.tr("&Window"))
        window_menu.addAction(prev_action)
        window_menu.addAction(next_action)
        window_menu.addSeparator()
        window_menu.addAction(tile_action)
        window_menu.addAction(cascade_action)
        window_menu.addAction(tabbed_action)
        window_menu.addSeparator()
        window_menu.addAction(close_action)

        help_menu = self.menuBar().addMenu(self.tr("&Help"))
        help_menu.addAction(help_action)
        help_menu.addSeparator()
        help_menu.addAction(about_action)
        help_menu.addAction(about_qt_action)

        main_toolbar = self.addToolBar(self.tr("&Toolbar"))
        main_toolbar.setToolButtonStyle(Qt.ToolButtonTextBesideIcon)
        main_toolbar.addAction(load_action)
        main_toolbar.addSeparator()
        main_toolbar.addAction(tools_action)
        main_toolbar.addAction(help_action)
        main_toolbar.addSeparator()
        main_toolbar.addAction(prev_action)
        main_toolbar.addAction(next_action)
        main_toolbar.addSeparator()
        main_toolbar.addAction(tile_action)
        main_toolbar.addAction(cascade_action)
        main_toolbar.addAction(tabbed_action)
        main_toolbar.addAction(close_action)
        # main_toolbar.addSeparator()
        # main_toolbar.addAction(self.normal_action)
        # main_toolbar.addAction(self.full_action)
        main_toolbar.setAllowedAreas(Qt.TopToolBarArea | Qt.BottomToolBarArea)
        main_toolbar.setObjectName("main_toolbar")

        settings = QSettings()
        settings.beginGroup("main_window")
        self.restoreGeometry(settings.value("geometry"))
        self.restoreState(settings.value("state"))
        self.recent_files = settings.value("recent_files")
        if self.recent_files is None:
            self.recent_files = []
        elif not isinstance(self.recent_files, list):
            self.recent_files = [self.recent_files]
        self.update_recent()
        settings.endGroup()

        prev_action.setEnabled(False)
        next_action.setEnabled(False)
        tile_action.setEnabled(False)
        cascade_action.setEnabled(False)
        close_action.setEnabled(False)
        tabbed_action.setEnabled(False)
        self.tree_widget.setEnabled(False)
        self.showNormal()
        self.normal_action.setEnabled(False)
        self.show_message(self.tr("Ready"))
class Window(QMainWindow, TopMenu):
    def __init__(self, parent=None):
        self.app = QApplication(sys.argv)
        self.app.setWindowIcon(QIcon("icon_maybe_BIG.ico"))
        self.app.setStyle("Fusion")
        super(Window, self).__init__(parent)

        # Main Layout for Editor
        self._main_widget = QWidget(self)
        self._main_layout = QGridLayout(self._main_widget)

        self._scroll = QScrollArea()
        self._tilemap_scroll = QScrollArea()

        self.setWindowTitle("Game Editor")

        self._gallery = Gallery()
        self._gallery_frame = QFrame(self)
        self._world_canvas = WorldCanvas(self._gallery, self)

        gallery_layout = QGridLayout(self._gallery_frame)
        gallery_layout.addWidget(self._gallery)

        buttons = QWidget(self)
        buttons_layout = QGridLayout(buttons)
        buttons_layout.addWidget(self._world_canvas._last_tilemap_button, 0, 0)
        buttons_layout.addWidget(self._world_canvas._next_tilemap_button, 0, 1)
        buttons_layout.addWidget(self._world_canvas._gallery_grid_chkbox, 0, 2)
        buttons_layout.addWidget(HorizontalFiller(), 0, 3)
        buttons_layout.addWidget(self._world_canvas._load_tilemap_button, 0, 4)
        gallery_layout.addWidget(buttons)
        gallery_layout.addWidget(VerticalFiller())
        self._gallery_dock = QDockWidget("Tilemaps", self)
        self._gallery_dock.setAllowedAreas(Qt.LeftDockWidgetArea
                                           | Qt.RightDockWidgetArea)
        self._tilemap_scroll.setWidget(self._gallery_frame)
        self._gallery_dock.setWidget(self._tilemap_scroll)
        self.addDockWidget(Qt.LeftDockWidgetArea, self._gallery_dock)

        self._layers_dock = QDockWidget("Map Layers", self)
        self._layers_dock.setAllowedAreas(Qt.LeftDockWidgetArea
                                          | Qt.RightDockWidgetArea)
        self._layers_dock.setWidget(self._world_canvas._layers._layers_widget)
        self.addDockWidget(Qt.LeftDockWidgetArea, self._layers_dock)

        # Add top menu
        TopMenu.__init__(self)
        self._world_canvas.setPaintTools(self._paint_tools)

        # World Canvas Layout

        # Canvas Top Section
        canvas_bot_section = QWidget(self)
        canvas_bot_section_layout = QGridLayout(canvas_bot_section)
        canvas_bot_section_layout.addWidget(QLabel(" Map Coordinates: "), 0, 0)
        canvas_bot_section_layout.addWidget(
            self._world_canvas._map_coords_label, 0, 1)
        canvas_bot_section_layout.addWidget(HorizontalFiller(), 0, 2)

        self._world_canvas_widget = QWidget(self)
        self._world_canvas_layout = QGridLayout(self._world_canvas_widget)
        self._world_canvas_layout.addWidget(self._world_canvas, 0, 0)
        self._world_canvas_layout.addWidget(canvas_bot_section, 1, 0)

        self._world_canvas_layout.addWidget(HorizontalFiller(), 1, 1)
        self._world_canvas_layout.addWidget(VerticalFiller(), 2, 0)

        self._main_layout.addWidget(self._world_canvas_widget, 0, 0)

        self._scroll.setWidget(self._main_widget)
        self.setCentralWidget(self._scroll)

    def start(self):
        self.showMaximized()

        sys.exit(self.app.exec_())
Exemplo n.º 30
0
class Window(QMainWindow, WindowMenu, Plots):
    def __init__(self, parent=None):
        super(Window, self).__init__(parent)
        self.setWindowTitle("Leak Detection Evaluation Form")
        self.main_style_qss = "Eclippy.qss"
        self._main_widget = QWidget(self)
        self._main_layout = QVBoxLayout(self._main_widget)
        self._form_widget = QWidget(self)
        self._form_layout = QVBoxLayout(self._form_widget)
        self._database_widget = QWidget(self)
        self._database_layout = QVBoxLayout(self._database_widget)
        self._database_table_label = QLabel()
        self._database_table_label.setObjectName("dbtablelabel")
        self._database_table_label.setStyleSheet(
            'QLabel[objectName^="dbtablelabel"] {font-size: 12pt; font-weight: bold;}'
        )
        self._database_table_export_button = QPushButton("Export Table (CSV)")
        self._database_table_export_button.clicked.connect(
            self.exportDatabaseTable)
        database_table_export_row = QWidget()
        database_table_export_lay = QHBoxLayout(database_table_export_row)
        database_table_export_lay.setContentsMargins(0, 0, 0, 0)
        database_table_export_lay.addWidget(HorizontalFiller())
        database_table_export_lay.addWidget(self._database_table_export_button)

        self._database_layout.addWidget(self._database_table_label)
        self._database_table = DataTable(use_max_height=False)
        self._database_layout.addWidget(self._database_table)
        self._database_layout.addWidget(database_table_export_row)
        self._main_tabs = QTabWidget(self)
        self._main_layout.addWidget(self._main_tabs)
        self._main_tabs.addTab(self._form_widget, "Form")
        self._main_tabs.addTab(self._database_widget, "Database")

        snwa_logo = QPixmap("snwa_logo.png")
        self._logo = QLabel(self)
        self._logo.setPixmap(snwa_logo)
        search_row = QWidget(self)
        search_lay = QHBoxLayout(search_row)
        search_lay.addWidget(self._logo)

        self._search_toolbar = QToolBar(self)
        self._new_rec_button = QPushButton("Create New Record")
        self._new_rec_button.clicked.connect(self.createNewRecord)
        self._new_rec_button.setIcon(QIcon(QPixmap("plus.png")))

        self._search_toolbar.addWidget(self._new_rec_button)

        # Database
        DB_LOC = "LeakDetectionDatabase.db"
        self._database = SQLiteLib(DB_LOC)

        self.setTabs()
        Plots.__init__(self)

        self._data_table_label = QLabel("Client Table")
        self._data_table = DataTable(1, 1, self)

        search_bars = self.setSearchBars()
        search_bars.setContentsMargins(0, 0, 0, 0)
        search_lay.setContentsMargins(0, 0, 0, 0)
        search_lay.addWidget(search_bars)
        search_lay.addWidget(HorizontalFiller(self))
        self.readDataFromDatabase()

        self._ap_phase_widget = Client.AP_PHASE_ID.value
        self._address_widget = Client.ADDRESS.value
        self._main_tabs.currentChanged.connect(self.mainTabChange)
        self._ap_phase_widget.returnPressed.connect(
            self.checkApPhaseIDsInDatabase)

        # Layout -----------------------------------

        # Docks
        self._form_menu_dock = QDockWidget("Form Selections", self)
        self._form_menu_dock.setAllowedAreas(Qt.LeftDockWidgetArea
                                             | Qt.RightDockWidgetArea)
        self._form_menu_dock.setFeatures(self._form_menu_dock.features()
                                         & ~QDockWidget.DockWidgetClosable)
        self._form_menu_dock.setWidget(self._form_menu)
        self.addDockWidget(Qt.LeftDockWidgetArea, self._form_menu_dock)

        self._plot_menu_dock = QDockWidget(
            "Plotted Data (Double Click to Activate Chart)", self)
        self._plot_menu_dock.setAllowedAreas(Qt.LeftDockWidgetArea
                                             | Qt.RightDockWidgetArea)
        self._plot_menu_dock.setWidget(self._plot_menu)
        self.addDockWidget(Qt.RightDockWidgetArea, self._plot_menu_dock)

        WindowMenu.__init__(self)

        # Tabs
        form_area = QWidget(self)
        form_lay = QVBoxLayout(form_area)
        form_lay.addWidget(search_row)
        form_lay.addWidget(self._search_toolbar)
        form_lay.addWidget(BreakLine(self))
        form_lay.addWidget(self._tab_label)
        form_lay.addWidget(self._tabs)
        form_lay.addWidget(self._data_table_label)
        form_lay.addWidget(self._data_table)
        form_lay.setContentsMargins(0, 0, 0, 0)

        self._form_layout.addWidget(form_area)
        self.updateDataTable()

        self.setCentralWidget(self._main_widget)

    def closeEvent(self, event):
        for plot_name in self._plots:
            if self._plots[plot_name].isVisible():
                self._plots[plot_name].close()
        if self._search_window != None and self._search_window.isVisible():
            self._search_window.close()

    def setSearchBars(self):
        self._database.openDatabase()

        self._search_apPhase_combo = ExtendedComboBox(self)
        self._search_address_combo = ExtendedComboBox(self)
        search_data = getAddresses(
            self._database.getValuesFrom2Fields(Tables.CLIENT.name,
                                                Client.ADDRESS.name,
                                                Client.AP_PHASE_ID.name))
        self._search_ap_phase_ids = [value[1] for value in search_data]
        search_addresses = [value[0] for value in search_data]
        self._search_apPhase_combo.addItems(self._search_ap_phase_ids)
        self._search_address_combo.addItems(search_addresses)
        self._search_apPhase_combo.currentIndexChanged.connect(
            self.apPhaseComboChange)
        self._search_apPhase_combo.currentTextChanged.connect(
            self.newSearchApCombo)
        self._search_address_combo.currentIndexChanged.connect(
            self.addressComboChange)
        self._search_address_combo.currentTextChanged.connect(
            self.newSearchAdrCombo)
        self._new = False
        self._ap_phase_change = False
        self._address_change = False

        self._database.closeDatabase()

        bot_menu = QWidget(self)
        bot_menu_layout = QFormLayout(bot_menu)
        bot_menu.setStyleSheet("QLabel { font-weight: bold; }")
        bot_menu_layout.addRow("Search ApPhase ID:",
                               self._search_apPhase_combo)
        bot_menu_layout.addRow("Search Address:", self._search_address_combo)
        return bot_menu

    def setTabs(self):
        #self._tabs = QTabWidget(self)
        self._tab_label = QLabel("Client", self)
        self._tab_label.setObjectName("tablabel")
        self._tab_label.setStyleSheet(
            'QLabel[objectName^="tablabel"] {font-size: 12pt; font-weight: bold;}'
        )
        self._tabs = QStackedWidget(self)
        table_names = []
        for table in Tables:
            table_name = table.value.getTabName()
            table_names.append(table_name)
            table.value.setTab(self, self._database)
            table.value.setFormParent(self)

            #self._tabs.addTab(table.value, table_name)
            self._tabs.addWidget(table.value)
        self._form_menu = FormMenu(item_names=table_names, parent=self)
        self._form_menu.currentRowChanged.connect(self.selectMenuItemChange)

    def selectMenuItemChange(self, index):
        self._tabs.setCurrentIndex(index)
        text = self._form_menu.currentItem().text()
        self._tab_label.setText(text)
        self._data_table_label.setText(text + " Table")
        self.updateDataTable()
        self.updateDatabaseTable()

    def updateDataTable(self):
        self._data_table.setRowCount(0)
        self._data_table.setRowCount(1)
        data = self._tabs.currentWidget()

        fields = data.getFields()
        values = data.getValues()
        col_count = len(values)
        self._data_table.setColumnCount(col_count)
        for col, (field, value) in enumerate(zip(fields, values)):
            header = QTableWidgetItem(field)
            item = QTableWidgetItem(str(value))
            self._data_table.setHorizontalHeaderItem(col, header)
            self._data_table.setItem(0, col, item)

        #self._data_table.resizeColumnsToContents()
        self._data_table.verticalHeader().setStretchLastSection(True)

    def readDataFromDatabase(self):
        for table in Tables:
            table.value.readDataIntoForm(
                self._search_apPhase_combo.currentText())

    def setApPhaseChangeCheck(self, change):
        self._ap_phase_change = change
        for table in Tables:
            table.value._ap_phase_change = change

    def setAddressChangeCheck(self, change):
        self._address_change = change
        for table in Tables:
            table.value._address_change = change

    def setNewForTables(self, new_val):
        self._new = new_val
        for table in Tables:
            table.value._new = new_val

    def apPhaseComboChange(self):
        if not self._address_change:
            self.setApPhaseChangeCheck(True)
            self.clearForm()
            self.readDataFromDatabase()
            self._search_address_combo.setCurrentIndex(
                self._search_apPhase_combo.currentIndex())
            self.updateDataTable()
            self.setNewForTables(False)
            self.setApPhaseChangeCheck(False)

    def addressComboChange(self):
        if not self._ap_phase_change:
            self.setAddressChangeCheck(True)
            self.clearForm()
            self._search_apPhase_combo.setCurrentIndex(
                self._search_address_combo.currentIndex())
            self.readDataFromDatabase()
            self.updateDataTable()
            self.setNewForTables(False)
            self.setAddressChangeCheck(False)

    def newSearchApCombo(self):
        if not self._address_change and self._search_apPhase_combo.currentText(
        ) == "":
            self._ap_phase_change = True
            Tables.CLIENT.value._ap_phase_change = True
            self._search_address_combo.setCurrentText("")
            self._ap_phase_widget.setPlaceholderText("Enter new ApPhase ID...")
            self._address_widget.setPlaceholderText("Enter new Address...")
            Tables.CLIENT.value.clearValues()
            self.setNewForTables(True)
            Tables.CLIENT.value._ap_phase_change = False
            self._ap_phase_change = False

    def newSearchAdrCombo(self):
        if not self._ap_phase_change and self._search_address_combo.currentText(
        ) == "":
            self._address_change = True
            self._search_apPhase_combo.setCurrentText("")
            self._ap_phase_widget.setPlaceholderText("Enter new ApPhase ID...")
            self._address_widget.setPlaceholderText("Enter new Address...")
            self.setNewForTables(True)
            Tables.CLIENT.value.clearValues()
            self._address_change = False

    def updateSearchCombos(self):
        ap_phase_id = self._ap_phase_widget.text()
        search_data = getAddresses(
            self._database.getValuesFrom2Fields(Tables.CLIENT.name,
                                                Client.ADDRESS.name,
                                                Client.AP_PHASE_ID.name))
        search_ap_phase_ids = [value[1] for value in search_data]
        search_addresses = [value[0] for value in search_data]
        self._search_apPhase_combo.clear()
        self._search_address_combo.clear()
        self._search_apPhase_combo.addItems(search_ap_phase_ids)
        self._search_address_combo.addItems(search_addresses)
        index = self._search_apPhase_combo.findText(ap_phase_id)
        self._search_apPhase_combo.setCurrentIndex(index)

    def updateDatabaseTable(self):
        if self._main_tabs.currentIndex() == 1:
            table_name = self._tabs.currentWidget().getTableName()
            tab_name = self._tabs.currentWidget().getTabName()
            self._database_table_label.setText("Table: {tn} ({tbn})".format(
                tn=table_name, tbn=tab_name))
            self._database.openDatabase()
            data = self._database.readTable(table_name)
            self._database.closeDatabase()
            headers = getFieldNames(table_name)
            data_count = len(data)
            self._database_table.setRowCount(0)
            self._database_table.setColumnCount(len(headers))
            self._database_table.setRowCount(data_count)

            for col, header_text in enumerate(headers):
                header = QTableWidgetItem(header_text)
                self._database_table.setHorizontalHeaderItem(col, header)

            for row, data_row in enumerate(data):
                data_row = list(data_row)
                data_row.pop(0)
                for col, data_value in enumerate(data_row):
                    item = QTableWidgetItem(str(data_value))
                    self._database_table.setItem(row, col, item)

    def exportDatabaseTable(self):
        file_name = self.exportCSVDialog()
        temp_row = []
        headers = []
        for col in range(self._database_table.columnCount()):
            headers.append(
                self._database_table.horizontalHeaderItem(col).text())

        if file_name:
            with open(file_name, 'w', newline='') as n_f:
                writer = csv.writer(n_f)
                writer.writerow(headers)

                for row in range(self._database_table.rowCount()):
                    for col in range(self._database_table.columnCount()):
                        temp_row.append(
                            self._database_table.item(row, col).text())

                    writer.writerow(temp_row)
                    temp_row.clear()

    def exportCSVDialog(self):
        file_dialog = QFileDialog()
        file_name, ext = file_dialog.getSaveFileName(self, 'Export CSV File',
                                                     "", "CSV (*.csv)")
        return file_name

    def mainTabChange(self, index):
        if index == 1:
            self.updateDatabaseTable()

    def start(self):
        with open(self.main_style_qss, 'r') as main_qss:
            main_style = main_qss.read()
            app.setStyleSheet(main_style)
        self.showMaximized()
        sys.exit(app.exec_())