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)
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()
class Main(QMainWindow): """ MainWindow which contains all widgets of POSM. """ def __init__(self, parent=None): super(Main, self).__init__(parent) self.setWindowTitle("POSM") # All widgets should be destryed when the main window is closed. This the widgets can use the destroyed widget # to allow clean up. E.g. save the database of the TileLoader. self.setAttribute(QtCore.Qt.WA_DeleteOnClose, True) self.resize(config.config.window_size[0], config.config.window_size[1]) self.elements_loader = ElementsLoader() # Element Viewer as DockWidget self.element_viewer = ElementViewer(self) self.dock_element_viewer = QDockWidget() self.dock_element_viewer.setWindowTitle("Element Viewer") self.dock_element_viewer.setFeatures(QDockWidget.DockWidgetFloatable | QDockWidget.DockWidgetMovable) self.dock_element_viewer.setWidget(self.element_viewer) self.addDockWidget(QtCore.Qt.RightDockWidgetArea, self.dock_element_viewer) # LayerManger as DockWidget self.layer_manager = LayerManager(self) self.dock_layer_manager = QDockWidget() self.dock_layer_manager.setWindowTitle("Layer Manager") self.dock_layer_manager.setFeatures(QDockWidget.DockWidgetFloatable | QDockWidget.DockWidgetMovable) self.dock_layer_manager.setWidget(self.layer_manager) self.addDockWidget(QtCore.Qt.RightDockWidgetArea, self.dock_layer_manager) self.viewer = Viewer.Viewer(self) self.setCentralWidget(self.viewer) self.viewer.setFocus() self.viewer.setFocusPolicy(QtCore.Qt.StrongFocus) self.changeset = Changeset(self) self.changset_form = ChangesetForm(self) self.toolbar = QToolBar() self.toolbar.addAction("Load Elements", self.viewer.load_elements) self.toolbar.addAction("Undo Changes", self.viewer.undo_changes) self.toolbar.addAction("Create Node", partial(self.viewer.change_mode, "new_node")) self.toolbar.addAction("Upload Changes", self.changset_form.show) self.toolbar.addAction("Open Configuration", partial(os.startfile, str(config.path_config))) self.addToolBar(self.toolbar) self.statusBar().showMessage("Welcome to POSM!")
class JobSearchAggregator: def __init__(self, job_service): self.app = QApplication(sys.argv) self.job_service = job_service self.list_view = ListView() self.info_panel = JobInfoPanel() self.dock = QDockWidget() self.dock.setWidget(self.list_view) self.dock.setFeatures(self.dock.NoDockWidgetFeatures) self.dock.setTitleBarWidget(QWidget()) self.main_window = MainWindow() self.main_window.setCentralWidget(self.info_panel) self.main_window.addDockWidget(Qt.LeftDockWidgetArea, self.dock) self.list_view.selectionModel().currentChanged.connect(self.list_item_selected) def run(self): self.__populate_item_model() self.list_view.select(0) self.main_window.show() sys.exit(self.app.exec_()) def __populate_item_model(self): jobs = self.job_service.gather_all_jobs() self.uids = [job.uid for job in jobs] [self.list_view.append(job) for job in jobs] def list_item_selected(self, index): job_id = self.uids[index.row()] job = self.job_service.consult_job(job_id) self.info_panel.title.setText(job.title) self.info_panel.description.setPlainText(job.description) self.info_panel.about.setPlainText(job.about) restrictions = '\n\n'.join(job.restrictions) self.info_panel.restrictions.setPlainText(restrictions) requirements = '\n\n'.join(job.requirements) self.info_panel.requirements.setPlainText(requirements) add_marker = 'try { add_marker(' + f'{job.location.lat}, {job.location.lng})' + '} catch(e) {}' self.info_panel.webpage.runJavaScript(add_marker) self.info_panel.set_contact_info(job.contact_info.contact, job.contact_info.email, job.contact_info.website, job.company)
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)
class MainWindow(QMainWindow): def __init__(self,application,parent=None): super(MainWindow,self).__init__(parent) #private attributes self.menuBar_ = QMenuBar() self.fileMenu_ = FileMenu(self) self.editMenu_ = EditMenu(self) self.toolbarDoc_ = QDockWidget(self) self.toolbarWidget_ = Toolbar(self) self.setGeometry(0,0,1280,720) #public attributes self.application = application self.contentTab = ContentTab() self.statusBar = QStatusBar() #menuBar self.menuBar_.addMenu(self.fileMenu_) self.menuBar_.addMenu(self.editMenu_) self.menuBar_.addAction("Help",self.helpFunc) self.setMenuBar(self.menuBar_) #statusBar self.statusBar.showMessage("status bar") self.setStatusBar(self.statusBar) #toolBar self.toolbarDoc_.setFeatures(QDockWidget.NoDockWidgetFeatures ) self.addDockWidget(Qt.LeftDockWidgetArea,self.toolbarDoc_) self.toolbarDoc_.setWidget(self.toolbarWidget_) #contentab self.setCentralWidget(self.contentTab) def helpFunc(self): """ Call the help tab To be added """ print("called helpFunc") def quit(self): """ Quit the application """ self.application.quit()
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
def _on_select(): global edd it = ed.scene().selectedItems() if len(it) > 0: it = it[0] else: it = None if isinstance(it, Element): edd = QDockWidget() edd.setWidget(it.editor()) edd.setWindowTitle('Element properties') edd.setFeatures(QDockWidget.DockWidgetMovable | QDockWidget.DockWidgetFloatable) w.addDockWidget(Qt.LeftDockWidgetArea, edd) elif edd is not None: w.removeDockWidget(edd) edd = None
class Window(QMainWindow): def __init__(self): super(Window, self).__init__() interactiveMap = InteractiveMap(self) self.setCentralWidget(interactiveMap) self.countryInfo = QDockWidget("Country Information", self) self.countryInfo.setFeatures(QDockWidget.DockWidgetClosable) self.countryInfo.setFixedWidth(300) self.countryInfo.hide() self.addDockWidget(Qt.RightDockWidgetArea, self.countryInfo) self.setWindowTitle("Project Fiscal") def showCountryDock(self, country): if self.countryInfo.isHidden(): self.countryInfo.show() self.countryInfo.setWidget(country)
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()
def initUI(self): self.scene = QGraphicsScene() self.pixmap = QGraphicsPixmapItem() self.scene.addItem(self.pixmap) self.boxRect = None # option to draw box around overlay spots if False: outlineBox = self.imageFile.box( self.imageFile.findCenterOfMass(self.frameIdx)) self.scene.addRect(QRect(*outlineBox), pen=QtGui.QPen(QtCore.Qt.blue, 1)) self.boxRect = self.scene.items()[ 0] # not the best way to get rect self.view = MyGraphicsView(self) self.view.setDragMode(QGraphicsView.ScrollHandDrag) self.view.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff) self.view.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff) self.view.setScene(self.scene) self.setCentralWidget(self.view) controlDock = QDockWidget() controlDock.setFeatures(QDockWidget.NoDockWidgetFeatures) self.addDockWidget(Qt.TopDockWidgetArea, controlDock) main_layout = QHBoxLayout() main_layout.setContentsMargins(0, 0, 0, 0) # first frame button firstButton = QPushButton('<<', self) firstButton.clicked.connect(self.firstFrame) main_layout.addWidget(firstButton) # previous frame button prevButton = QPushButton('<', self) prevButton.clicked.connect(self.prevFrame) main_layout.addWidget(prevButton) # next frame button nextButton = QPushButton('>', self) nextButton.clicked.connect(self.nextFrame) main_layout.addWidget(nextButton) # last frame button lastButton = QPushButton('>>', self) lastButton.clicked.connect(self.lastFrame) main_layout.addWidget(lastButton) # zoom button zoomButton = QPushButton('zoom', self) zoomButton.clicked.connect(self.toggleZoom) main_layout.addWidget(zoomButton) # zoom button overlayButton = QPushButton('color', self) overlayButton.clicked.connect(self.toggleOverlay) main_layout.addWidget(overlayButton) # add dock widget dockContainerWidget = QWidget(controlDock) dockContainerWidget.setLayout(main_layout) dockContainerWidget.setGeometry(0, 0, self.imageFile.imgW, 20) self.setFixedSize(self.imageFile.imgW, self.imageFile.imgH + dockContainerWidget.height()) self.show()
class TelemetryViewerGUI(QMainWindow): def __init__(self): super().__init__() title = "Telemetry Viewer" self.setWindowTitle(title) if sys.platform == "win32": import ctypes try: ctypes.windll.shell32.SetCurrentProcessExplicitAppUserModelID( title.replace(" ", "_")) except BaseException: pass self.icon = QIcon() iconPath = Path(__file__).parent / "icon.ico" self.icon.addFile(str(iconPath), QSize(64, 64)) self.setWindowIcon(self.icon) self.setAcceptDrops(True) self.fileDlg = QFileDialog(self, Qt.WindowFlags(0)) try: parentDir = str( Path(os.environ["ProgramData"]) / "Microsoft" / "Diagnosis") except BaseException: parentDir = "." self.fileDlg.setDirectory(parentDir) def callback(_, rowNo): #self.ar.setText(json.dumps(self.indexed[rowNo], indent="\t")) self.model.loadDict(self.indexed[rowNo]) self.table = TableWidget( ("#", "subIdx", "idx", "unkn3", "unkn4", "name", "time"), self, callback) #self.ar = QScrollArea(self) #self.ar = QTextEdit(self) self.ar = QTreeView() self.header = self.ar.header() self.model = QJsonModel() self.ar.setModel(self.model) for i in range(3): self.header.setSectionResizeMode(QHeaderView.ResizeToContents) #self.ar.setFontFamily("monospace") #self.ar.setReadOnly(True) #self.ar.acceptRichText #.dropEvent #wordWrapMode dockableWindowsFeatures = QDockWidget.DockWidgetFeatures( QDockWidget.AllDockWidgetFeatures & ~QDockWidget.DockWidgetClosable) self.JSONWindow = QDockWidget("JSON", self) self.JSONWindow.setFeatures(dockableWindowsFeatures) self.addDockWidget(Qt.RightDockWidgetArea, self.JSONWindow) # l self.JSONWindow.setWidget(self.ar) self.tableWindow = QDockWidget("Table", self) self.tableWindow.setFeatures(dockableWindowsFeatures) self.addDockWidget(Qt.LeftDockWidgetArea, self.tableWindow) self.tableWindow.setWidget(self.table) #self.openTelemetryBtn = QPushButton("Open telemetry", self) #self.openTelemetryBtn.clicked.connect(self.openTelemetry) self.indexed = {} telemetryFileMask = "Windows Diagnostics Framework (*.rbs)" def dragEnterEvent(self, event): if event.mimeData().hasUrls(): event.accept() else: event.ignore() def dropEvent(self, evt): urls = evt.mimeData().urls() if len(urls) != 1: errMsg = QErrorMessage() errMsg.showMessage("Drop exactly single rbs file!") else: self.loadTelemetry(urls[0].toLocalFile()) def openTelemetry(self) -> None: fns = self.fileDlg.getOpenFileName(self, "Load telemetry data file", None, self.__class__.telemetryFileMask) if fns[0]: self.loadTelemetry(fns[0]) def loadTelemetry(self, f: str) -> None: self.table.clear() parsed = SelectedRBS(f) self.indexed = {} i = 0 for j, item in enumerate(parsed): #repr(item.header.unkn3) # bluec0re suspects it is the type for subIndex, subItemText in enumerate(item.json): jso = parseJSON(subItemText) self.indexed[i] = jso self.table.appendRow( i - 1, (j, subIndex, item.index, item.unkn3, item.unkn4, jso["name"], transformTimeIntoLocal(jso["time"]))) i += 1 del parsed self.table.table.resizeColumnsToContents() if i: self.table.table.setCurrentIndex(self.table.table.model().index( 0, 0))
self.addTopLevelItem(category_item) if __name__ == "__main__": app = QApplication() w = QMainWindow() w.setMinimumSize(640, 480) elems = ElementTree() elems.create_from_dict(ELEMENTS) elems.expandAll() dw = QDockWidget() dw.setWidget(elems) dw.setWindowTitle('Elements') dw.setFeatures(QDockWidget.DockWidgetMovable | QDockWidget.DockWidgetFloatable) w.addDockWidget(Qt.LeftDockWidgetArea, dw) sim = Simulator() schem = Schematic() sim.root = schem.root g = Gate('or', 1, 2, False) g.name = 'gate' el = GateElement(sim, g) schem.add_element(el) ed = SchematicEditor(schem) t = QTimer(w) edd = None def _on_select():
class PuppetMaster(QMainWindow): requestEditMode = Signal(bool) def __init__(self, parent=None, editMode=bool(False)): super(PuppetMaster, self).__init__(parent) self._parent = parent self._model = list() self.editMode = editMode self.setMouseTracking(True) self.setFocusPolicy(Qt.StrongFocus) self.setWindowTitle('PuppetMaster') self.setMinimumHeight(1) self.setMinimumWidth(1) self.setContextMenuPolicy(Qt.PreventContextMenu) # Main tabs self.tab = CanvasGraphicsViewTab(parent=self) self.tab.requestEditMode.connect(self.set_edit) self.setCentralWidget(self.tab) # Parameter Widget self.parameter = Parameters(parent=self) self.parameterDock = QDockWidget(": Parameters ::", self) self.parameterDock.setObjectName('Parameters') self.parameterDock.setFeatures(QDockWidget.DockWidgetClosable) self.parameterDock.setWidget(self.parameter) self.parameterDock.setTitleBarWidget(QWidget(self.parameterDock)) self.parameterDock.setVisible(self.editMode) self.addDockWidget(Qt.BottomDockWidgetArea, self.parameterDock) self.tab.onSelection.connect(self.update_parameters) self.parameter.onChangeBGColor.connect(self.tab.update_bg_color) self.parameter.onChangeFontColor.connect(self.tab.update_font_color) self.parameter.onChangeFontSize.connect(self.tab.update_font_size) self.parameter.onChangeText.connect(self.tab.update_text) self.parameter.onChangeShape.connect(self.tab.update_shape) # maya Signal on selection items self.idx = OpenMaya.MEventMessage.addEventCallback( "SelectionChanged", self.tab.maya_selection) # Menu self.setMenuBar(self.create_menu()) self.set_edit(self.editMode) def update_parameters(self, node=PickNode): shape = node.Shape if isinstance(node, PickNode) else str() self.parameter.update_param(text=node.toPlainText(), fontSize=node.font().pointSize(), fontColor=node.defaultTextColor(), bgColor=node.Background, shapeName=shape) def create_menu(self): window_menu = QMenuBar(self) file_menu = window_menu.addMenu("&File") new_action = QAction("&New...", self) new_action.setShortcut('Ctrl+N') new_action.setShortcutContext(Qt.WidgetShortcut) new_action.setStatusTip('Create a new Set') new_action.triggered.connect(self.tab.new_tab) file_menu.addAction(new_action) open_action = QAction("&Open...", self) open_action.setShortcut('Ctrl+O') open_action.setShortcutContext(Qt.WidgetShortcut) open_action.setStatusTip('Open a new set') open_action.triggered.connect(self.tab.open_set) file_menu.addAction(open_action) refresh_action = QAction("&Refresh...", self) refresh_action.setStatusTip('Refresh the current sets') refresh_action.triggered.connect(self.tab.refresh_set) file_menu.addAction(refresh_action) file_menu.addSeparator() save_action = QAction("&Save...", self) save_action.setShortcut('Ctrl+S') save_action.setShortcutContext(Qt.WidgetShortcut) save_action.setStatusTip('Save the current tab') save_action.triggered.connect(self.tab.save_set) file_menu.addAction(save_action) saveAs_action = QAction("&Save As...", self) saveAs_action.setShortcut('Ctrl+Shift+S') saveAs_action.setShortcutContext(Qt.WidgetShortcut) saveAs_action.setStatusTip('Save the current tab as a new Set') saveAs_action.triggered.connect(self.tab.saveAs_set) file_menu.addAction(saveAs_action) saveAsTemp_action = QAction("&Save As Template...", self) saveAsTemp_action.setStatusTip( 'Save the current tab as a new Template') saveAsTemp_action.triggered.connect(self.tab.saveAsTemplate_set) file_menu.addAction(saveAsTemp_action) file_menu.addSeparator() rename_action = QAction("&Rename Tab...", self) rename_action.setStatusTip('Rename the current Tab') rename_action.triggered.connect(self.tab.rename_set) file_menu.addAction(rename_action) close_action = QAction("&Close Tab...", self) close_action.setShortcut('Ctrl+Shift+W') close_action.setShortcutContext(Qt.WidgetShortcut) close_action.setStatusTip('Close the current Tab') close_action.triggered.connect(self.tab.closeCurrentTab) file_menu.addAction(close_action) picker_menu = window_menu.addMenu("&Picker") self.edit_action = QAction("&Edit Mode", self) self.edit_action.setStatusTip('Toggle between view and edit mode.') self.edit_action.triggered.connect(self.edit_toggle) self.edit_action.setCheckable(True) self.edit_action.setChecked(self.editMode) picker_menu.addAction(self.edit_action) picker_menu.addSeparator() self.background_action = QAction("&Change Background...", self) self.background_action.setEnabled(self.editMode) self.background_action.setStatusTip('Change the background.') self.background_action.triggered.connect(self.tab.set_background) picker_menu.addAction(self.background_action) self.namespace_action = QAction("&Change Namespace...", self) self.namespace_action.setEnabled(self.editMode) self.namespace_action.setStatusTip('Change the namespace.') self.namespace_action.triggered.connect(self.tab.set_namespace) picker_menu.addAction(self.namespace_action) help_menu = window_menu.addMenu("&Help") wiki_action = QAction("&About PuppetMaster...", self) wiki_action.setStatusTip('Open Wiki page') wiki_action.triggered.connect(self.wiki_open) help_menu.addAction(wiki_action) return window_menu def edit_toggle(self): self.set_edit(not self.editMode) def wiki_open(self): webbrowser.open_new_tab( 'https://github.com/Bernardrouhi/PuppetMaster/wiki') def force_load(self, paths=list): self.tab.force_load(paths) def findAndLoad(self, names=list()): ''' Find the names in PuppetMaster folder and load them Parameters ---------- names: (list) List of dictionaries of name and namespace. ''' if names: self.tab.findAndLoad(names) def destroyMayaSignals(self): # destroy the connection OpenMaya.MMessage.removeCallback(self.idx) def get_edit(self): return self.editMode def set_edit(self, value=bool): self.editMode = value self.background_action.setEnabled(self.editMode) self.namespace_action.setEnabled(self.editMode) self.parameterDock.setVisible(self.editMode) self.edit_action.setChecked(self.editMode) self.tab.Edit = self.editMode # Notification if not self.editMode: warningMes( "PUPPETMASTER-INFO: Out of 'Edit Mode' you won't be able to create/edit/move or delete any node." ) else: warningMes( "PUPPETMASTER-INFO: In 'Edit Mode' command buttons won't run any commands." ) Edit = property(get_edit, set_edit) def keyPressEvent(self, event): if event.modifiers() == (Qt.ControlModifier | Qt.ShiftModifier): if event.key() == Qt.Key_W: self.tab.closeCurrentTab() elif event.key() == Qt.Key_S: self.tab.saveAs_set() elif event.modifiers() == Qt.ControlModifier: if event.key() == Qt.Key_S: self.tab.save_set() elif event.key() == Qt.Key_N: self.tab.new_tab() elif event.key() == Qt.Key_O: self.tab.open_set() else: super(PuppetMaster, self).keyPressEvent(event)
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()
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 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
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()
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)
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())
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_())
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)
class MainWidget(QWidget): def __init__(self, parent=None): super().__init__(parent=parent) self.username = parent.user self.left = 10 self.top = 10 self.width = 1200 self.height = 800 self.leftPanelWidth = 250 self.setContentsMargins(QMargins(0, 0, 0, 0)) self.initUI() def initUI(self): self.pacs_ctrl: PACS_ServerCtrl = PACS_OrthancServerCtrl() self.setGeometry(self.left, self.top, self.width, self.height) self.pacs_widget = PACS_MainWidget() self.dicom_widget = Dicom_Browser() # self.liver_support_widget = LiverSupportWidget() self.button_bar = MenuLeftPane() topLayout = QVBoxLayout() topLayout.setAlignment(Qt.AlignTop) topLayout.addWidget(self.button_bar) self.mainWidget = QDockWidget() self.mainWidget.setFeatures(QDockWidget.NoDockWidgetFeatures) self.mainWidget.setTitleBarWidget(QWidget()) self.mainWidget.setWidget(self.dicom_widget) mainLayout = QHBoxLayout(self) mainLayout.setContentsMargins(QMargins(0, 0, 0, 0)) mainLayout.addLayout(topLayout) mainLayout.addWidget(self.mainWidget) self.button_bar.pacs_btn.clicked.connect(self.show_PACSWidget) self.button_bar.dcm_btn.clicked.connect(self.show_DICOMBrowser) # self.button_bar.liv_support_btn.clicked.connect(self.show_liverSupport) self.pacs_widget.dcm_to_browser.connect(self.send_file_to_browser) self.show_PACSWidget() def show_PACSWidget(self): connect = self.pacs_ctrl.isConnect() if connect: self.mainWidget.setWidget(self.pacs_widget) self.button_bar.pacs_btn.setDisabled(1) self.button_bar.dcm_btn.setDisabled(0) # self.button_bar.liv_support_btn.setDisabled(0) else: QMessageBox.warning( self, 'Connect Error', '''Application is not able to connect with PACS Server Check Klient and Archive data ''', QMessageBox.Ok) def show_DICOMBrowser(self): self.mainWidget.setWidget(self.dicom_widget) self.button_bar.dcm_btn.setDisabled(1) self.button_bar.pacs_btn.setDisabled(0) # self.button_bar.liv_support_btn.setDisabled(0) # def show_liverSupport(self): # self.mainWidget.setWidget(self.liver_support_widget) # self.button_bar.pacs_btn.setDisabled(0) # self.button_bar.dcm_btn.setDisabled(0) # self.button_bar.liv_support_btn.setDisabled(1) def send_file_to_browser(self, path): self.mainWidget.setWidget(self.dicom_widget) self.dicom_widget.load_series(path) self.dicom_widget.series_id = self.pacs_widget.stage_chain['series'] self.button_bar.dcm_btn.setDisabled(1) self.button_bar.pacs_btn.setDisabled(0)
class Main(QMainWindow): """ MainWindow which contains all widgets of Osmapy. """ def __init__(self, parent=None): super(Main, self).__init__(parent) self.setWindowTitle("Osmapy") path_base = pathlib.Path(__file__).parent icon_path = str(path_base / pathlib.Path("assets/appicon.png")) self.setWindowIcon(QIcon(str(icon_path))) # All widgets should be destroyed when the main window is closed. This the widgets can use the destroyed widget # to allow clean up. E.g. save the database of the TileLoader. self.setAttribute(QtCore.Qt.WA_DeleteOnClose, True) self.resize(config.config.window_size[0], config.config.window_size[1]) self.elements_loader = ElementsLoader() # Element Viewer as DockWidget self.element_viewer = ElementViewer(self) self.dock_element_viewer = QDockWidget() self.dock_element_viewer.setWindowTitle("Element Viewer") self.dock_element_viewer.setFeatures(QDockWidget.DockWidgetFloatable | QDockWidget.DockWidgetMovable) self.dock_element_viewer.setWidget(self.element_viewer) self.addDockWidget(QtCore.Qt.RightDockWidgetArea, self.dock_element_viewer) # LayerManger as DockWidget self.layer_manager = LayerManager(self) self.dock_layer_manager = QDockWidget() self.dock_layer_manager.setWindowTitle("Layer Manager") self.dock_layer_manager.setFeatures(QDockWidget.DockWidgetFloatable | QDockWidget.DockWidgetMovable) self.dock_layer_manager.setWidget(self.layer_manager) self.addDockWidget(QtCore.Qt.RightDockWidgetArea, self.dock_layer_manager) self.viewer = Viewer.Viewer(self) self.setCentralWidget(self.viewer) self.viewer.setFocus() self.viewer.setFocusPolicy(QtCore.Qt.StrongFocus) self.changeset = Changeset(self) self.changset_form = ChangesetForm(self) self.toolbar = QToolBar() self.toolbar.addAction("Load Elements", self.viewer.load_elements) self.toolbar.addAction("Undo Changes", self.viewer.undo_changes) self.toolbar.addAction("Create Node", partial(self.viewer.change_mode, "new_node")) self.toolbar.addAction("Upload Changes", self.changset_form.show) if os.name == "nt": self.toolbar.addAction( "Open Configuration", partial(os.startfile, str(config.path_config))) elif sys.platform == "darwin": self.toolbar.addAction( "Open Configuration", partial(call, ["open", str(config.path_config)])) else: self.toolbar.addAction( "Open Configuration", partial(call, ["xdg-open", str(config.path_config)])) self.addToolBar(self.toolbar) self.statusBar().showMessage("Welcome to Osmapy!")